openpitrix crd
Signed-off-by: LiHui <andrewli@yunify.com> delete helm repo, release and app Signed-off-by: LiHui <andrewli@yunify.com> Fix Dockerfile Signed-off-by: LiHui <andrewli@yunify.com> add unit test for category controller Signed-off-by: LiHui <andrewli@yunify.com> resource api Signed-off-by: LiHui <andrewli@yunify.com> miscellaneous Signed-off-by: LiHui <andrewli@yunify.com> resource api Signed-off-by: LiHui <andrewli@yunify.com> add s3 repo indx Signed-off-by: LiHui <andrewli@yunify.com> attachment api Signed-off-by: LiHui <andrewli@yunify.com> repo controller test Signed-off-by: LiHui <andrewli@yunify.com> application controller test Signed-off-by: LiHui <andrewli@yunify.com> release metric Signed-off-by: LiHui <andrewli@yunify.com> helm release controller test Signed-off-by: LiHui <andrewli@yunify.com> move constants to /pkg/apis/application Signed-off-by: LiHui <andrewli@yunify.com> remove unused code Signed-off-by: LiHui <andrewli@yunify.com> add license header Signed-off-by: LiHui <andrewli@yunify.com> Fix bugs Signed-off-by: LiHui <andrewli@yunify.com> cluster cluent Signed-off-by: LiHui <andrewli@yunify.com> format code Signed-off-by: LiHui <andrewli@yunify.com> move workspace,cluster from spec to labels Signed-off-by: LiHui <andrewli@yunify.com> add license header Signed-off-by: LiHui <andrewli@yunify.com> openpitrix test Signed-off-by: LiHui <andrewli@yunify.com> add worksapce labels for app in appstore Signed-off-by: LiHui <andrewli@yunify.com>
This commit is contained in:
@@ -14,408 +14,614 @@ limitations under the License.
|
||||
package openpitrix
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
v1alpha13 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
listers_v1alpha1 "kubesphere.io/kubesphere/pkg/client/listers/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
|
||||
"openpitrix.io/openpitrix/pkg/pb"
|
||||
"openpitrix.io/openpitrix/pkg/util/pbutil"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix/helmrepoindex"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/s3"
|
||||
"kubesphere.io/kubesphere/pkg/utils/idutils"
|
||||
"kubesphere.io/kubesphere/pkg/utils/reposcache"
|
||||
"kubesphere.io/kubesphere/pkg/utils/stringutils"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ApplicationInterface interface {
|
||||
ListApplications(conditions *params.Conditions, limit, offset int, orderBy string, reverse bool) (*models.PageableResponse, error)
|
||||
DescribeApplication(namespace, applicationId, clusterName string) (*Application, error)
|
||||
CreateApplication(clusterName, namespace string, request CreateClusterRequest) error
|
||||
ModifyApplication(request ModifyClusterAttributesRequest) error
|
||||
DeleteApplication(id string) error
|
||||
UpgradeApplication(request UpgradeClusterRequest) error
|
||||
ListApps(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
||||
DescribeApp(id string) (*App, error)
|
||||
DeleteApp(id string) error
|
||||
CreateApp(req *CreateAppRequest) (*CreateAppResponse, error)
|
||||
ModifyApp(appId string, request *ModifyAppRequest) error
|
||||
DeleteAppVersion(id string) error
|
||||
ModifyAppVersion(id string, request *ModifyAppVersionRequest) error
|
||||
DescribeAppVersion(id string) (*AppVersion, error)
|
||||
CreateAppVersion(request *CreateAppVersionRequest) (*CreateAppVersionResponse, error)
|
||||
ValidatePackage(request *ValidatePackageRequest) (*ValidatePackageResponse, error)
|
||||
GetAppVersionPackage(appId, versionId string) (*GetAppVersionPackageResponse, error)
|
||||
DoAppAction(appId string, request *ActionRequest) error
|
||||
DoAppVersionAction(versionId string, request *ActionRequest) error
|
||||
ListAppVersionAudits(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
||||
GetAppVersionFiles(versionId string, request *GetAppVersionFilesRequest) (*GetAppVersionPackageFilesResponse, error)
|
||||
ListAppVersionReviews(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
||||
ListAppVersions(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
||||
}
|
||||
|
||||
type applicationOperator struct {
|
||||
informers informers.SharedInformerFactory
|
||||
opClient openpitrix.Client
|
||||
backingStoreClient s3.Interface
|
||||
informers externalversions.SharedInformerFactory
|
||||
|
||||
appClient v1alpha13.HelmApplicationInterface
|
||||
appVersionClient v1alpha13.HelmApplicationVersionInterface
|
||||
|
||||
appLister listers_v1alpha1.HelmApplicationLister
|
||||
versionLister listers_v1alpha1.HelmApplicationVersionLister
|
||||
|
||||
repoLister listers_v1alpha1.HelmRepoLister
|
||||
ctgLister listers_v1alpha1.HelmCategoryLister
|
||||
rlsLister listers_v1alpha1.HelmReleaseLister
|
||||
|
||||
cachedRepos reposcache.ReposCache
|
||||
}
|
||||
|
||||
func newApplicationOperator(informers informers.SharedInformerFactory, opClient openpitrix.Client) ApplicationInterface {
|
||||
return &applicationOperator{informers: informers, opClient: opClient}
|
||||
func newApplicationOperator(cached reposcache.ReposCache, informers externalversions.SharedInformerFactory, ksClient versioned.Interface, storeClient s3.Interface) ApplicationInterface {
|
||||
op := &applicationOperator{
|
||||
backingStoreClient: storeClient,
|
||||
informers: informers,
|
||||
repoLister: informers.Application().V1alpha1().HelmRepos().Lister(),
|
||||
|
||||
appClient: ksClient.ApplicationV1alpha1().HelmApplications(),
|
||||
appVersionClient: ksClient.ApplicationV1alpha1().HelmApplicationVersions(),
|
||||
|
||||
appLister: informers.Application().V1alpha1().HelmApplications().Lister(),
|
||||
versionLister: informers.Application().V1alpha1().HelmApplicationVersions().Lister(),
|
||||
|
||||
ctgLister: informers.Application().V1alpha1().HelmCategories().Lister(),
|
||||
rlsLister: informers.Application().V1alpha1().HelmReleases().Lister(),
|
||||
cachedRepos: cached,
|
||||
}
|
||||
|
||||
return op
|
||||
}
|
||||
|
||||
type Application struct {
|
||||
Name string `json:"name" description:"application name"`
|
||||
Cluster *Cluster `json:"cluster,omitempty" description:"application cluster info"`
|
||||
Version *AppVersion `json:"version,omitempty" description:"application template version info"`
|
||||
App *App `json:"app,omitempty" description:"application template info"`
|
||||
WorkLoads *workLoads `json:"workloads,omitempty" description:"application workloads"`
|
||||
Services []v1.Service `json:"services,omitempty" description:"application services"`
|
||||
Ingresses []v1beta1.Ingress `json:"ingresses,omitempty" description:"application ingresses"`
|
||||
}
|
||||
|
||||
type workLoads struct {
|
||||
Deployments []appsv1.Deployment `json:"deployments,omitempty" description:"deployment list"`
|
||||
Statefulsets []appsv1.StatefulSet `json:"statefulsets,omitempty" description:"statefulset list"`
|
||||
Daemonsets []appsv1.DaemonSet `json:"daemonsets,omitempty" description:"daemonset list"`
|
||||
}
|
||||
|
||||
type resourceInfo struct {
|
||||
Deployments []appsv1.Deployment `json:"deployments,omitempty" description:"deployment list"`
|
||||
Statefulsets []appsv1.StatefulSet `json:"statefulsets,omitempty" description:"statefulset list"`
|
||||
Daemonsets []appsv1.DaemonSet `json:"daemonsets,omitempty" description:"daemonset list"`
|
||||
Services []v1.Service `json:"services,omitempty" description:"application services"`
|
||||
Ingresses []v1beta1.Ingress `json:"ingresses,omitempty" description:"application ingresses"`
|
||||
}
|
||||
|
||||
func (c *applicationOperator) ListApplications(conditions *params.Conditions, limit, offset int, orderBy string, reverse bool) (*models.PageableResponse, error) {
|
||||
describeClustersRequest := &pb.DescribeClustersRequest{
|
||||
Limit: uint32(limit),
|
||||
Offset: uint32(offset)}
|
||||
if keyword := conditions.Match[Keyword]; keyword != "" {
|
||||
describeClustersRequest.SearchWord = &wrappers.StringValue{Value: keyword}
|
||||
}
|
||||
if runtimeId := conditions.Match[RuntimeId]; runtimeId != "" {
|
||||
describeClustersRequest.RuntimeId = []string{runtimeId}
|
||||
}
|
||||
if appId := conditions.Match[AppId]; appId != "" {
|
||||
describeClustersRequest.AppId = []string{appId}
|
||||
}
|
||||
if versionId := conditions.Match[VersionId]; versionId != "" {
|
||||
describeClustersRequest.VersionId = []string{versionId}
|
||||
}
|
||||
if status := conditions.Match[Status]; status != "" {
|
||||
describeClustersRequest.Status = strings.Split(status, "|")
|
||||
}
|
||||
if zone := conditions.Match[Zone]; zone != "" {
|
||||
describeClustersRequest.Zone = []string{zone}
|
||||
}
|
||||
if orderBy != "" {
|
||||
describeClustersRequest.SortKey = &wrappers.StringValue{Value: orderBy}
|
||||
}
|
||||
describeClustersRequest.Reverse = &wrappers.BoolValue{Value: reverse}
|
||||
resp, err := c.opClient.DescribeClusters(openpitrix.SystemContext(), describeClustersRequest)
|
||||
// save icon data and helm application
|
||||
func (c *applicationOperator) createApp(app *v1alpha1.HelmApplication, iconData []byte) (*v1alpha1.HelmApplication, error) {
|
||||
exists, err := c.getHelmAppByName(app.GetWorkspace(), app.GetTrueName())
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
result := models.PageableResponse{TotalCount: int(resp.TotalCount)}
|
||||
result.Items = make([]interface{}, 0)
|
||||
for _, cluster := range resp.ClusterSet {
|
||||
app, err := c.describeApplication(cluster)
|
||||
if exists != nil {
|
||||
return nil, appItemExists
|
||||
}
|
||||
|
||||
if len(iconData) != 0 {
|
||||
// save icon attachment
|
||||
iconId := idutils.GetUuid(v1alpha1.HelmAttachmentPrefix)
|
||||
err = c.backingStoreClient.Upload(iconId, iconId, bytes.NewBuffer(iconData))
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
klog.Errorf("save icon attachment failed, error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
result.Items = append(result.Items, app)
|
||||
app.Spec.Icon = iconId
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
app, err = c.appClient.Create(context.TODO(), app, metav1.CreateOptions{})
|
||||
return app, err
|
||||
}
|
||||
|
||||
func (c *applicationOperator) describeApplication(cluster *pb.Cluster) (*Application, error) {
|
||||
var app Application
|
||||
app.Name = cluster.Name.Value
|
||||
app.Cluster = convertCluster(cluster)
|
||||
versionInfo, err := c.opClient.DescribeAppVersions(openpitrix.SystemContext(), &pb.DescribeAppVersionsRequest{VersionId: []string{cluster.GetVersionId().GetValue()}})
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
// get helm app by name in workspace
|
||||
func (c *applicationOperator) getHelmAppByName(workspace, name string) (*v1alpha1.HelmApplication, error) {
|
||||
ls := map[string]string{
|
||||
constants.WorkspaceLabelKey: workspace,
|
||||
}
|
||||
|
||||
list, err := c.appLister.List(labels.SelectorFromSet(ls))
|
||||
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
if len(versionInfo.AppVersionSet) > 0 {
|
||||
app.Version = convertAppVersion(versionInfo.AppVersionSet[0])
|
||||
|
||||
if len(list) > 0 {
|
||||
for _, a := range list {
|
||||
if a.GetTrueName() == name {
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
appInfo, err := c.opClient.DescribeApps(openpitrix.SystemContext(), &pb.DescribeAppsRequest{AppId: []string{cluster.GetAppId().GetValue()}, Limit: 1})
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
if len(appInfo.AppSet) > 0 {
|
||||
app.App = convertApp(appInfo.GetAppSet()[0])
|
||||
}
|
||||
return &app, nil
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) DescribeApplication(namespace string, applicationId string, clusterName string) (*Application, error) {
|
||||
describeClusterRequest := &pb.DescribeClustersRequest{
|
||||
ClusterId: []string{applicationId},
|
||||
RuntimeId: []string{clusterName},
|
||||
Zone: []string{namespace},
|
||||
WithDetail: pbutil.ToProtoBool(true),
|
||||
Limit: 1,
|
||||
}
|
||||
clusters, err := c.opClient.DescribeClusters(openpitrix.SystemContext(), describeClusterRequest)
|
||||
func (c *applicationOperator) ValidatePackage(request *ValidatePackageRequest) (*ValidatePackageResponse, error) {
|
||||
|
||||
chrt, err := helmrepoindex.LoadPackage(request.VersionPackage)
|
||||
|
||||
result := &ValidatePackageResponse{}
|
||||
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cluster *pb.Cluster
|
||||
if len(clusters.ClusterSet) > 0 {
|
||||
cluster = clusters.GetClusterSet()[0]
|
||||
matchPackageFailedError(err, result)
|
||||
if (result.Error == "EOF" || result.Error == "") && len(result.ErrorDetails) == 0 {
|
||||
klog.Errorf("package parse failed, error: %s", err.Error())
|
||||
return nil, errors.New("package parse failed")
|
||||
}
|
||||
} else {
|
||||
err := status.New(codes.NotFound, "resource not found").Err()
|
||||
klog.Errorln(err)
|
||||
return nil, err
|
||||
result.Name = chrt.GetName()
|
||||
result.VersionName = chrt.GetVersionName()
|
||||
result.Description = chrt.GetDescription()
|
||||
result.URL = chrt.GetUrls()
|
||||
}
|
||||
app, err := c.describeApplication(cluster)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) DoAppAction(appId string, request *ActionRequest) error {
|
||||
|
||||
app, err := c.appLister.Get(appId)
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var filterState string
|
||||
switch request.Action {
|
||||
case ActionSuspend:
|
||||
if app.Status.State != v1alpha1.StateActive {
|
||||
err = actionNotSupport
|
||||
}
|
||||
filterState = v1alpha1.StateActive
|
||||
case ActionRecover:
|
||||
if app.Status.State != v1alpha1.StateSuspended {
|
||||
err = actionNotSupport
|
||||
}
|
||||
filterState = v1alpha1.StateSuspended
|
||||
default:
|
||||
err = actionNotSupport
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var versions []*v1alpha1.HelmApplicationVersion
|
||||
ls := map[string]string{
|
||||
constants.ChartApplicationIdLabelKey: appId,
|
||||
}
|
||||
versions, err = c.versionLister.List(labels.SelectorFromSet(ls))
|
||||
if err != nil {
|
||||
klog.Errorf("get helm app %s version failed, error: %s", appId, err)
|
||||
return err
|
||||
}
|
||||
|
||||
versions = filterAppVersionByState(versions, []string{filterState})
|
||||
for _, version := range versions {
|
||||
err = c.DoAppVersionAction(version.GetHelmApplicationVersionId(), request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) CreateApp(req *CreateAppRequest) (*CreateAppResponse, error) {
|
||||
if c.backingStoreClient == nil {
|
||||
return nil, invalidS3Config
|
||||
}
|
||||
chrt, err := helmrepoindex.LoadPackage(req.VersionPackage)
|
||||
if err != nil {
|
||||
klog.Errorf("load package %s/%s failed, error: %s", req.Isv, req.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resource := new(resourceInfo)
|
||||
workloads := cluster.AdditionalInfo.GetValue()
|
||||
if workloads == "" {
|
||||
err := status.New(codes.NotFound, "cannot get workload").Err()
|
||||
klog.Errorln(err)
|
||||
// create helm application
|
||||
name := idutils.GetUuid36(v1alpha1.HelmApplicationIdPrefix)
|
||||
helmApp := &v1alpha1.HelmApplication{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Annotations: map[string]string{
|
||||
constants.CreatorAnnotationKey: req.Username,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
constants.WorkspaceLabelKey: req.Isv,
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.HelmApplicationSpec{
|
||||
Name: req.Name,
|
||||
Description: stringutils.ShortenString(chrt.GetDescription(), v1alpha1.MsgLen),
|
||||
},
|
||||
}
|
||||
app, err := c.createApp(helmApp, req.Icon)
|
||||
if err != nil {
|
||||
klog.Errorf("create helm application %s/%s failed, error: %s", req.Isv, req.Name, err)
|
||||
if helmApp.Spec.Icon != "" {
|
||||
c.backingStoreClient.Delete(helmApp.Spec.Icon)
|
||||
}
|
||||
return nil, err
|
||||
} else {
|
||||
klog.V(4).Infof("helm application %s/%s created, app id: %s", req.Isv, req.Name, app.Name)
|
||||
}
|
||||
|
||||
// create app version
|
||||
chartPackage := req.VersionPackage.String()
|
||||
ver := buildApplicationVersion(app, chrt, &chartPackage, req.Username)
|
||||
ver, err = c.createApplicationVersion(ver)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("create helm application %s/%s versions failed, error: %s", req.Isv, req.Name, err)
|
||||
return nil, err
|
||||
} else {
|
||||
klog.V(4).Infof("helm application version %s/%s created, app version id: %s", req.Isv, req.Name, ver.Name)
|
||||
}
|
||||
|
||||
return &CreateAppResponse{
|
||||
AppID: app.GetHelmApplicationId(),
|
||||
VersionID: ver.GetHelmApplicationVersionId(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildLabelSelector(conditions *params.Conditions) map[string]string {
|
||||
ls := make(map[string]string)
|
||||
|
||||
repoId := conditions.Match[RepoId]
|
||||
// app store come first
|
||||
if repoId != "" {
|
||||
ls[constants.ChartRepoIdLabelKey] = repoId
|
||||
} else {
|
||||
if conditions.Match[WorkspaceLabel] != "" {
|
||||
ls[constants.WorkspaceLabelKey] = conditions.Match[WorkspaceLabel]
|
||||
}
|
||||
}
|
||||
if conditions.Match[CategoryId] != "" {
|
||||
ls[constants.CategoryIdLabelKey] = conditions.Match[CategoryId]
|
||||
}
|
||||
|
||||
return ls
|
||||
}
|
||||
|
||||
func (c *applicationOperator) ListApps(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
||||
apps, err := c.listApps(conditions)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal([]byte(workloads), resource)
|
||||
apps = filterApps(apps, conditions)
|
||||
|
||||
if reverse {
|
||||
sort.Sort(sort.Reverse(HelmApplicationList(apps)))
|
||||
} else {
|
||||
sort.Sort(HelmApplicationList(apps))
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0, limit)
|
||||
|
||||
for i, j := offset, 0; i < len(apps) && j < limit; {
|
||||
versions, err := c.getAppVersionsByAppId(apps[i].GetHelmApplicationId())
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctg, _ := c.ctgLister.Get(apps[i].GetHelmCategoryId())
|
||||
|
||||
items = append(items, convertApp(apps[i], versions, ctg, 0))
|
||||
i++
|
||||
j++
|
||||
}
|
||||
return &models.PageableResponse{Items: items, TotalCount: len(apps)}, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) DeleteApp(id string) error {
|
||||
app, err := c.appLister.Get(id)
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
if apierrors.IsNotFound(err) {
|
||||
klog.V(4).Infof("app %s has been deleted", id)
|
||||
return nil
|
||||
} else {
|
||||
klog.Error(err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ls := map[string]string{
|
||||
constants.ChartApplicationIdLabelKey: app.GetHelmApplicationId(),
|
||||
}
|
||||
releases, err := c.rlsLister.List(labels.SelectorFromSet(ls))
|
||||
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Error(err)
|
||||
return err
|
||||
} else if len(releases) > 0 {
|
||||
return fmt.Errorf("app %s has releases not deleted", id)
|
||||
}
|
||||
|
||||
list, err := c.versionLister.List(labels.SelectorFromSet(ls))
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
klog.V(4).Infof("versions of app %s has been deleted", id)
|
||||
} else {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
} else if len(list) > 0 {
|
||||
return fmt.Errorf("app %s has some versions not deleted", id)
|
||||
}
|
||||
|
||||
err = c.appClient.Delete(context.TODO(), id, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
klog.Errorf("delete app %s failed, error: %s", id, err)
|
||||
return err
|
||||
} else {
|
||||
c.deleteAppAttachment(app)
|
||||
klog.V(4).Infof("app %s deleted", app.Name)
|
||||
}
|
||||
|
||||
// delete application in app store
|
||||
id = fmt.Sprintf("%s%s", id, v1alpha1.HelmApplicationAppStoreSuffix)
|
||||
app, err = c.appClient.Get(context.TODO(), id, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
} else {
|
||||
klog.Errorf("get app %s failed, error: %s", id, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// delete application in app store
|
||||
err = c.appClient.Delete(context.TODO(), id, metav1.DeleteOptions{})
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Errorf("delete app %s failed, error: %s", id, err)
|
||||
return err
|
||||
} else {
|
||||
c.deleteAppAttachment(app)
|
||||
klog.V(4).Infof("app %s deleted", app.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) ModifyApp(appId string, request *ModifyAppRequest) error {
|
||||
if c.backingStoreClient == nil {
|
||||
return invalidS3Config
|
||||
}
|
||||
|
||||
app, err := c.appLister.Get(appId)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
appCopy := app.DeepCopy()
|
||||
// modify category
|
||||
if request.CategoryID != nil {
|
||||
if *request.CategoryID == "" {
|
||||
delete(appCopy.Labels, constants.CategoryIdLabelKey)
|
||||
klog.V(4).Infof("delete app %s category", app.Name)
|
||||
} else {
|
||||
appCopy.Labels[constants.CategoryIdLabelKey] = *request.CategoryID
|
||||
klog.V(4).Infof("set app %s category to %s", app.Name, *request.CategoryID)
|
||||
}
|
||||
}
|
||||
|
||||
// modify app name
|
||||
if request.Name != nil && len(*request.Name) > 0 && app.GetTrueName() != *request.Name {
|
||||
existsApp, err := c.getHelmAppByName(app.GetWorkspace(), *request.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existsApp != nil {
|
||||
return appItemExists
|
||||
}
|
||||
klog.V(4).Infof("change app %s name from %s to %s", app.Name, app.GetTrueName(), *request.Name)
|
||||
appCopy.Spec.Name = *request.Name
|
||||
}
|
||||
|
||||
// save app attachment and icon
|
||||
add, err := c.modifyAppAttachment(appCopy, request)
|
||||
if err != nil {
|
||||
klog.Errorf("add app attachment %s failed, error: %s", appCopy.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if request.Description != nil {
|
||||
appCopy.Spec.Description = *request.Description
|
||||
}
|
||||
if request.Abstraction != nil {
|
||||
appCopy.Spec.Abstraction = *request.Abstraction
|
||||
}
|
||||
|
||||
if request.Home != nil {
|
||||
appCopy.Spec.AppHome = *request.Home
|
||||
}
|
||||
appCopy.Status.UpdateTime = &metav1.Time{Time: time.Now()}
|
||||
|
||||
patch := client.MergeFrom(app)
|
||||
data, err := patch.Data(appCopy)
|
||||
if err != nil {
|
||||
klog.Errorf("create patch failed, error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.appClient.Patch(context.TODO(), appId, patch.Type(), data, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
klog.Errorf("patch helm application: %s failed, error: %s", appId, err)
|
||||
if add != "" {
|
||||
// if patch failed, delete saved icon or attachment
|
||||
c.backingStoreClient.Delete(add)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) deleteAppAttachment(app *v1alpha1.HelmApplication) {
|
||||
if app.Spec.Icon != "" {
|
||||
c.backingStoreClient.Delete(app.Spec.Icon)
|
||||
}
|
||||
|
||||
for _, id := range app.Spec.Attachments {
|
||||
c.backingStoreClient.Delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *applicationOperator) modifyAppAttachment(app *v1alpha1.HelmApplication, request *ModifyAppRequest) (add string, err error) {
|
||||
if request.Type == nil {
|
||||
return "", nil
|
||||
}
|
||||
switch *request.Type {
|
||||
case v1alpha1.AttachmentTypeScreenshot:
|
||||
if request.Sequence == nil {
|
||||
return "", nil
|
||||
}
|
||||
seq := *request.Sequence
|
||||
attachments := &app.Spec.Attachments
|
||||
if len(request.AttachmentContent) == 0 {
|
||||
// delete old attachments
|
||||
if len(*attachments) > int(seq) {
|
||||
del := (*attachments)[seq]
|
||||
err = c.backingStoreClient.Delete(del)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
*attachments = append((*attachments)[:seq], (*attachments)[seq+1:]...)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(*attachments) < 6 {
|
||||
// add attachment to app
|
||||
add := idutils.GetUuid("att-")
|
||||
*attachments = append(*attachments, add)
|
||||
err = c.backingStoreClient.Upload(add, add, bytes.NewBuffer(request.AttachmentContent))
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return add, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
case v1alpha1.AttachmentTypeIcon: // modify app icon
|
||||
// delete old icon
|
||||
if app.Spec.Icon != "" {
|
||||
err = c.backingStoreClient.Delete(app.Spec.Icon)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(request.AttachmentContent) != 0 {
|
||||
add := idutils.GetUuid("att-")
|
||||
err = c.backingStoreClient.Upload(add, add, bytes.NewBuffer(request.AttachmentContent))
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
app.Spec.Icon = add
|
||||
return add, nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// modify icon or attachment of the app
|
||||
// added: new attachments have been saved to store
|
||||
// deleted: attachments should be deleted
|
||||
func (c *applicationOperator) appAttachmentDiff(old, newApp *v1alpha1.HelmApplication) (added, deleted []string) {
|
||||
|
||||
added = make([]string, 0, 7)
|
||||
deleted = make([]string, 0, 7)
|
||||
|
||||
if old.Spec.Icon != newApp.Spec.Icon {
|
||||
if old.Spec.Icon != "" && !strings.HasPrefix(old.Spec.Icon, "http://") {
|
||||
deleted = append(deleted, old.Spec.Icon)
|
||||
}
|
||||
added = append(added, newApp.Spec.Icon)
|
||||
}
|
||||
|
||||
existsAtt := make(map[string]string, 6)
|
||||
newAtt := make(map[string]string, 6)
|
||||
|
||||
for _, id := range newApp.Spec.Attachments {
|
||||
newAtt[id] = ""
|
||||
}
|
||||
|
||||
for _, id := range old.Spec.Attachments {
|
||||
existsAtt[id] = ""
|
||||
}
|
||||
|
||||
for _, id := range newApp.Spec.Attachments {
|
||||
if _, exists := existsAtt[id]; !exists {
|
||||
added = append(added, id)
|
||||
}
|
||||
}
|
||||
|
||||
for _, id := range old.Spec.Attachments {
|
||||
if _, exists := newAtt[id]; !exists {
|
||||
deleted = append(deleted, id)
|
||||
}
|
||||
}
|
||||
|
||||
return added, deleted
|
||||
}
|
||||
|
||||
func (c *applicationOperator) DescribeApp(id string) (*App, error) {
|
||||
var helmApp *v1alpha1.HelmApplication
|
||||
var ctg *v1alpha1.HelmCategory
|
||||
var err error
|
||||
|
||||
helmApp, err = c.getHelmApplication(id)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
app.WorkLoads = &workLoads{
|
||||
Deployments: resource.Deployments,
|
||||
Statefulsets: resource.Statefulsets,
|
||||
Daemonsets: resource.Daemonsets,
|
||||
versions, err := c.getAppVersionsByAppId(helmApp.GetHelmApplicationId())
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
app.Services = resource.Services
|
||||
app.Ingresses = resource.Ingresses
|
||||
|
||||
ctg, err = c.ctgLister.Get(helmApp.GetHelmCategoryId())
|
||||
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
app := convertApp(helmApp, versions, ctg, 0)
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) getWorkLoads(namespace string, clusterRoles []*pb.ClusterRole) (*workLoads, error) {
|
||||
|
||||
var works workLoads
|
||||
for _, clusterRole := range clusterRoles {
|
||||
workLoadName := clusterRole.Role.Value
|
||||
if len(workLoadName) > 0 {
|
||||
if strings.HasSuffix(workLoadName, openpitrix.DeploySuffix) {
|
||||
name := strings.Split(workLoadName, openpitrix.DeploySuffix)[0]
|
||||
|
||||
item, err := c.informers.Apps().V1().Deployments().Lister().Deployments(namespace).Get(name)
|
||||
|
||||
if err != nil {
|
||||
// app not ready
|
||||
if errors.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
klog.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
works.Deployments = append(works.Deployments, *item)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasSuffix(workLoadName, openpitrix.DaemonSuffix) {
|
||||
name := strings.Split(workLoadName, openpitrix.DaemonSuffix)[0]
|
||||
item, err := c.informers.Apps().V1().DaemonSets().Lister().DaemonSets(namespace).Get(name)
|
||||
if err != nil {
|
||||
// app not ready
|
||||
if errors.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
klog.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
works.Daemonsets = append(works.Daemonsets, *item)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasSuffix(workLoadName, openpitrix.StateSuffix) {
|
||||
name := strings.Split(workLoadName, openpitrix.StateSuffix)[0]
|
||||
item, err := c.informers.Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(name)
|
||||
if err != nil {
|
||||
// app not ready
|
||||
if errors.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
klog.Errorln(err)
|
||||
return nil, err
|
||||
}
|
||||
works.Statefulsets = append(works.Statefulsets, *item)
|
||||
continue
|
||||
}
|
||||
func (c *applicationOperator) listApps(conditions *params.Conditions) (ret []*v1alpha1.HelmApplication, err error) {
|
||||
repoId := conditions.Match[RepoId]
|
||||
if repoId != "" && repoId != v1alpha1.AppStoreRepoId {
|
||||
// get helm application from helm repo
|
||||
if ret, exists := c.cachedRepos.ListApplicationsByRepoId(repoId); !exists {
|
||||
klog.Warningf("load repo failed, repo id: %s", repoId)
|
||||
return nil, loadRepoInfoFailed
|
||||
} else {
|
||||
return ret, nil
|
||||
}
|
||||
} else {
|
||||
ret, err = c.appLister.List(labels.SelectorFromSet(buildLabelSelector(conditions)))
|
||||
}
|
||||
return &works, nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *applicationOperator) getLabels(namespace string, workloads *workLoads) *[]map[string]string {
|
||||
|
||||
var workloadLabels []map[string]string
|
||||
if workloads == nil {
|
||||
return nil
|
||||
func (c *applicationOperator) getHelmApplication(appId string) (*v1alpha1.HelmApplication, error) {
|
||||
if app, exists := c.cachedRepos.GetApplication(appId); exists {
|
||||
return app, nil
|
||||
} else {
|
||||
return c.appLister.Get(appId)
|
||||
}
|
||||
|
||||
for _, workload := range workloads.Deployments {
|
||||
deploy, err := c.informers.Apps().V1().Deployments().Lister().Deployments(namespace).Get(workload.Name)
|
||||
if errors.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
workloadLabels = append(workloadLabels, deploy.Labels)
|
||||
}
|
||||
|
||||
for _, workload := range workloads.Daemonsets {
|
||||
daemonset, err := c.informers.Apps().V1().DaemonSets().Lister().DaemonSets(namespace).Get(workload.Name)
|
||||
if errors.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
workloadLabels = append(workloadLabels, daemonset.Labels)
|
||||
}
|
||||
|
||||
for _, workload := range workloads.Statefulsets {
|
||||
statefulset, err := c.informers.Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(workload.Name)
|
||||
if errors.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
workloadLabels = append(workloadLabels, statefulset.Labels)
|
||||
}
|
||||
|
||||
return &workloadLabels
|
||||
}
|
||||
|
||||
func (c *applicationOperator) isExist(svcs []v1.Service, svc *v1.Service) bool {
|
||||
for _, item := range svcs {
|
||||
if item.Name == svc.Name && item.Namespace == svc.Namespace {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *applicationOperator) getSvcs(namespace string, workLoadLabels *[]map[string]string) []v1.Service {
|
||||
if len(*workLoadLabels) == 0 {
|
||||
return nil
|
||||
}
|
||||
var services []v1.Service
|
||||
for _, label := range *workLoadLabels {
|
||||
labelSelector := labels.Set(label).AsSelector()
|
||||
svcs, err := c.informers.Core().V1().Services().Lister().Services(namespace).List(labelSelector)
|
||||
if err != nil {
|
||||
klog.Errorf("get app's svc failed, reason: %v", err)
|
||||
}
|
||||
for _, item := range svcs {
|
||||
if !c.isExist(services, item) {
|
||||
services = append(services, *item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return services
|
||||
}
|
||||
|
||||
func (c *applicationOperator) getIng(namespace string, services []v1.Service) []v1beta1.Ingress {
|
||||
if services == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ings []v1beta1.Ingress
|
||||
for _, svc := range services {
|
||||
ingresses, err := c.informers.Extensions().V1beta1().Ingresses().Lister().Ingresses(namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return ings
|
||||
}
|
||||
|
||||
for _, ingress := range ingresses {
|
||||
if ingress.Spec.Backend.ServiceName != svc.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
exist := false
|
||||
var tmpRules []v1beta1.IngressRule
|
||||
for _, rule := range ingress.Spec.Rules {
|
||||
for _, p := range rule.HTTP.Paths {
|
||||
if p.Backend.ServiceName == svc.Name {
|
||||
exist = true
|
||||
tmpRules = append(tmpRules, rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if exist {
|
||||
ing := v1beta1.Ingress{}
|
||||
ing.Name = ingress.Name
|
||||
ing.Spec.Rules = tmpRules
|
||||
ings = append(ings, ing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ings
|
||||
}
|
||||
|
||||
func (c *applicationOperator) CreateApplication(clusterName, namespace string, request CreateClusterRequest) error {
|
||||
_, err := c.opClient.CreateCluster(openpitrix.ContextWithUsername(request.Username), &pb.CreateClusterRequest{
|
||||
AppId: &wrappers.StringValue{Value: request.AppId},
|
||||
VersionId: &wrappers.StringValue{Value: request.VersionId},
|
||||
RuntimeId: &wrappers.StringValue{Value: clusterName},
|
||||
Conf: &wrappers.StringValue{Value: request.Conf},
|
||||
Zone: &wrappers.StringValue{Value: namespace},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) ModifyApplication(request ModifyClusterAttributesRequest) error {
|
||||
|
||||
modifyClusterAttributesRequest := &pb.ModifyClusterAttributesRequest{ClusterId: &wrappers.StringValue{Value: request.ClusterID}}
|
||||
if request.Name != nil {
|
||||
modifyClusterAttributesRequest.Name = &wrappers.StringValue{Value: *request.Name}
|
||||
}
|
||||
if request.Description != nil {
|
||||
modifyClusterAttributesRequest.Description = &wrappers.StringValue{Value: *request.Description}
|
||||
}
|
||||
|
||||
_, err := c.opClient.ModifyClusterAttributes(openpitrix.SystemContext(), modifyClusterAttributesRequest)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) DeleteApplication(applicationId string) error {
|
||||
_, err := c.opClient.DeleteClusters(openpitrix.SystemContext(), &pb.DeleteClustersRequest{ClusterId: []string{applicationId}, Force: &wrappers.BoolValue{Value: true}})
|
||||
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) UpgradeApplication(request UpgradeClusterRequest) error {
|
||||
_, err := c.opClient.UpgradeCluster(openpitrix.ContextWithUsername(request.Username), &pb.UpgradeClusterRequest{
|
||||
ClusterId: &wrappers.StringValue{Value: request.ClusterId},
|
||||
VersionId: &wrappers.StringValue{Value: request.VersionId},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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.
|
||||
@@ -14,96 +17,158 @@ limitations under the License.
|
||||
package openpitrix
|
||||
|
||||
import (
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"github.com/go-openapi/strfmt"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
|
||||
"openpitrix.io/openpitrix/pkg/pb"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
fakek8s "k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
|
||||
"kubesphere.io/kubesphere/pkg/informers"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/s3/fake"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func namespacesToRuntimeObjects(namespaces ...*v1.Namespace) []runtime.Object {
|
||||
var objs []runtime.Object
|
||||
for _, deploy := range namespaces {
|
||||
objs = append(objs, deploy)
|
||||
var rawChartData = "H4sIFAAAAAAA/ykAK2FIUjBjSE02THk5NWIzVjBkUzVpWlM5Nk9WVjZNV2xqYW5keVRRbz1IZWxtAOxYUW/bNhDOM3/FTVmBNltoubEdQEAfirTAim1pMA/ZwzAUtHSS2FAkS1JOvLT77QNJ2XGUZEmxJukw34NEkcfj3fG+41EOrRsc1Mw4umCN2LoPStM0nYxG4Z2maf+dDif7W8NROhyP0v3R/t5WOnw+nAy3IL0XbXrUWsfMVvqv1+ob9x8hpvkxGsuVzGD+nDCtV59DOpzQlBRoc8O1C30v4QcUDeQ+YKBUBn5sZ2gkOrREsgYz8AFF3EJjBkxrwXPmZ5L5UmpKhzQlj232hjoK+J8z0aK9twRwG/6fD8d9/I+GG/w/CBkMGD1QrXQZDAnhDaswIwAGtbLcKbPIQFZcnhEA3QpxpATPFxm8KQ+VOzJoUToC4FiVQZJ0Ao5aIaaYG3Q2g9//CLnh7RyN4QUGtrIV4krnYzvjf0gB/w4bLZhDO3hXo9BoLHX6y6WCW/C/t7c/6eF/NN6c/w9D5+eDHZjzJgOLDkou0J/dLxrvlrzGDHYGnz4Rz0Ven2kmC3A1gkcuqDK0Qy1ASce3CwWWXCIkPrKoZ0xg92KItcIBjQXnoZdCj+Phs54M4CM408ocJnuhyZtpW5b8DJLdBDpZKAvfjKodGGQOga1W8OllAR9aJnjJsfClSFCakt8wyg78zq/gDbAww5y1FsGqBteqmmhqyVEUFphBELzhDgtwClzNLTydLYIbXh1OPS+XFViN+TNK3pRgUCCznb9yJR3j0nbVU+jjDk65EDBDaK3X0wILynfaXu/VZfK88CwvV47sZ9alw24cv4uzhV3J+TYonr24+25e6LhyQRRCf4n+iXOXel7q/EzltOHSlZA8sbtPbNKTFRe9e2xd37wUcWtb6bHRVbl+G8N2drERuQSbobhpSwPLxX727Vh3cWx3ZTp89Ae1YDlC8l0Cybvk88GjmkbJqJ69Qb04GPWrUTTU1oOgcgbn58BlLtqiZwqNi/UGLQrMnTI/dQLpWnR0lr1c3UH8GNOanqzgSLkarK4S5+fXTPkIH1rlsGfpVSkNk6zCYne2iIKWkTJFM+d5f3701LRT/p991Tdx99r1423pin8irOn1OnNpHZM5XtZ4HTzXxWg/YdvOQpbnvurzmay1eKMxgfll5D28KelcZqN5XLmX9p9eNvUii9FnNwmS67at4XwpMukayZ0EXMHyY5++j0+9+i9XsuRVw/SXvAze+v9nnPbqv3E63tR/D0InXBYZHIRt/5lp0qBjBXPM3wBXKWoZH1eBG/PU2i+kIVnO9qwZ+C8CsEHaV0oB/9Qf6bySyuB9rHEb/sd7V/7/7E3GG/w/BG3DEXMOjbS+DogxAKc1Spi1XBT+OqNZfsIqtJRsw6/+ymNbrZVxFmyNQkAl1Awa5vKay+p7f+dhjs8RNHP1Wj+TBdkGiVX4IQxPtcGSn2EBp9zV8M0zCm+lWICSYaZXCTQaEFwiJfTV9N3UKYNkG7p69fhgCgU3ltCKu0F4RvUJnf1pBuG57KirgX8sP+1cDi4EzVh+0upw97Vkh9pTTXbojJ2QHeoa31aGV2TnL7INx8xw1Vp48+q1JVQb9R5zRygvkA0iu1HvCZ3bXBU42CS9DW1oQ18z/R0AAP//GfF7tgAeAAA="
|
||||
|
||||
func TestOpenPitrixApp(t *testing.T) {
|
||||
appOperator := prepareAppOperator()
|
||||
|
||||
chartData, _ := base64.RawStdEncoding.DecodeString(rawChartData)
|
||||
|
||||
validateReq := &ValidatePackageRequest{
|
||||
VersionPackage: chartData,
|
||||
}
|
||||
// validate package
|
||||
validateResp, err := appOperator.ValidatePackage(validateReq)
|
||||
if err != nil || validateResp.Error != "" {
|
||||
klog.Errorf("validate pacakge failed, error: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
validateReq = &ValidatePackageRequest{
|
||||
VersionPackage: strfmt.Base64(""),
|
||||
}
|
||||
|
||||
// validate corrupted package
|
||||
validateResp, err = appOperator.ValidatePackage(validateReq)
|
||||
if err == nil {
|
||||
klog.Errorf("validate pacakge failed, error: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
appReq := &CreateAppRequest{
|
||||
Isv: testWorkspace,
|
||||
Name: "test-chart",
|
||||
VersionName: "0.1.0",
|
||||
VersionPackage: strfmt.Base64(chartData),
|
||||
}
|
||||
|
||||
// create app
|
||||
createAppResp, err := appOperator.CreateApp(appReq)
|
||||
if err != nil {
|
||||
klog.Errorf("create app failed")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// add app to indexer
|
||||
apps, err := ksClient.ApplicationV1alpha1().HelmApplications().List(context.TODO(), metav1.ListOptions{})
|
||||
for _, app := range apps.Items {
|
||||
err := fakeInformerFactory.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmApplications().
|
||||
Informer().GetIndexer().Add(&app)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to add app to indexer")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// add app version to indexer
|
||||
appvers, err := ksClient.ApplicationV1alpha1().HelmApplicationVersions().List(context.TODO(), metav1.ListOptions{})
|
||||
for _, ver := range appvers.Items {
|
||||
err := fakeInformerFactory.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmApplicationVersions().
|
||||
Informer().GetIndexer().Add(&ver)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to add app version to indexer")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// describe app
|
||||
app, err := appOperator.DescribeApp(createAppResp.AppID)
|
||||
if err != nil {
|
||||
klog.Errorf("describe app failed, err: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
_ = app
|
||||
|
||||
cond := ¶ms.Conditions{Match: map[string]string{
|
||||
WorkspaceLabel: testWorkspace,
|
||||
}}
|
||||
// list apps
|
||||
listApps, err := appOperator.ListApps(cond, "", false, 10, 0)
|
||||
if err != nil {
|
||||
klog.Errorf("list app failed")
|
||||
t.FailNow()
|
||||
}
|
||||
_ = listApps
|
||||
|
||||
// describe app
|
||||
describeAppVersion, err := appOperator.DescribeAppVersion(createAppResp.VersionID)
|
||||
if err != nil {
|
||||
klog.Errorf("describe app version failed, error: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
_ = describeAppVersion
|
||||
|
||||
cond.Match[AppId] = createAppResp.AppID
|
||||
// list app version
|
||||
_, err = appOperator.ListAppVersions(cond, "", false, 10, 0)
|
||||
if err != nil {
|
||||
klog.Errorf("list app version failed")
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// get app version file
|
||||
getAppVersionFilesRequest := &GetAppVersionFilesRequest{}
|
||||
_, err = appOperator.GetAppVersionFiles(createAppResp.VersionID, getAppVersionFilesRequest)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("get app version files failed")
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
//delete app
|
||||
err = appOperator.DeleteApp(createAppResp.AppID)
|
||||
|
||||
if err == nil {
|
||||
klog.Errorf("we should delete application version first")
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
//delete app
|
||||
err = appOperator.DeleteAppVersion(createAppResp.VersionID)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("delete application version failed, err: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
return objs
|
||||
}
|
||||
|
||||
func TestApplicationOperator_CreateApplication(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
existNamespaces []*v1.Namespace
|
||||
targetNamespace string
|
||||
createClusterRequest CreateClusterRequest
|
||||
expected error
|
||||
}{
|
||||
{
|
||||
description: "create application test",
|
||||
existNamespaces: []*v1.Namespace{{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Annotations: map[string]string{openpitrix.RuntimeAnnotationKey: "runtime-ncafface"}},
|
||||
}},
|
||||
targetNamespace: "test",
|
||||
createClusterRequest: CreateClusterRequest{
|
||||
Conf: "app-agwerl",
|
||||
RuntimeId: "runtime-ncafface",
|
||||
VersionId: "version-acklmalkds",
|
||||
Username: "system",
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
description: "create application test2",
|
||||
existNamespaces: []*v1.Namespace{},
|
||||
targetNamespace: "test2",
|
||||
createClusterRequest: CreateClusterRequest{
|
||||
Conf: "app-agwerl",
|
||||
RuntimeId: "runtime-ncafface",
|
||||
VersionId: "version-acklmalkds",
|
||||
Username: "system",
|
||||
},
|
||||
expected: errors.NewNotFound(schema.GroupResource{Group: "", Resource: "namespace"}, "test2"),
|
||||
},
|
||||
}
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
var (
|
||||
ksClient versioned.Interface
|
||||
k8sClient kubernetes.Interface
|
||||
fakeInformerFactory informers.InformerFactory
|
||||
testWorkspace = "test-workspace"
|
||||
)
|
||||
|
||||
for _, test := range tests {
|
||||
op := openpitrix.NewMockClient(ctrl)
|
||||
objs := namespacesToRuntimeObjects(test.existNamespaces...)
|
||||
k8s := fake.NewSimpleClientset(objs...)
|
||||
informer := informers.NewSharedInformerFactory(k8s, 0)
|
||||
stopChan := make(chan struct{}, 0)
|
||||
informer.Core().V1().Namespaces().Lister()
|
||||
informer.Start(stopChan)
|
||||
informer.WaitForCacheSync(stopChan)
|
||||
func prepareAppOperator() ApplicationInterface {
|
||||
ksClient = fakeks.NewSimpleClientset()
|
||||
k8sClient = fakek8s.NewSimpleClientset()
|
||||
fakeInformerFactory = informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil, nil)
|
||||
|
||||
applicationOperator := newApplicationOperator(informer, op)
|
||||
|
||||
// setup expect response
|
||||
// op.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).Return(&pb.CreateClusterResponse{}, nil).AnyTimes()
|
||||
op.EXPECT().CreateCluster(openpitrix.ContextWithUsername(test.createClusterRequest.Username), &pb.CreateClusterRequest{
|
||||
AppId: &wrappers.StringValue{Value: test.createClusterRequest.AppId},
|
||||
VersionId: &wrappers.StringValue{Value: test.createClusterRequest.VersionId},
|
||||
RuntimeId: &wrappers.StringValue{Value: test.createClusterRequest.RuntimeId},
|
||||
Conf: &wrappers.StringValue{Value: test.createClusterRequest.Conf},
|
||||
Zone: &wrappers.StringValue{Value: test.targetNamespace},
|
||||
}).Return(&pb.CreateClusterResponse{}, nil).AnyTimes()
|
||||
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
|
||||
err := applicationOperator.CreateApplication(test.createClusterRequest.RuntimeId, test.targetNamespace, test.createClusterRequest)
|
||||
|
||||
if err != nil && err.Error() != test.expected.Error() {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
return newApplicationOperator(cachedReposData, fakeInformerFactory.KubeSphereSharedInformerFactory(), ksClient, fake.NewFakeS3())
|
||||
}
|
||||
|
||||
572
pkg/models/openpitrix/applicationversions.go
Normal file
572
pkg/models/openpitrix/applicationversions.go
Normal file
@@ -0,0 +1,572 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 openpitrix
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix/helmrepoindex"
|
||||
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
||||
"kubesphere.io/kubesphere/pkg/utils/stringutils"
|
||||
"math"
|
||||
"reflect"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *applicationOperator) GetAppVersionPackage(appId, versionId string) (*GetAppVersionPackageResponse, error) {
|
||||
var version *v1alpha1.HelmApplicationVersion
|
||||
var err error
|
||||
|
||||
version, err = c.getAppVersionByVersionIdWithData(versionId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GetAppVersionPackageResponse{
|
||||
AppId: appId,
|
||||
VersionId: versionId,
|
||||
Package: version.Spec.Data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// check helm package and create helm app version if not exist
|
||||
func (c *applicationOperator) CreateAppVersion(request *CreateAppVersionRequest) (*CreateAppVersionResponse, error) {
|
||||
if c.backingStoreClient == nil {
|
||||
return nil, invalidS3Config
|
||||
}
|
||||
|
||||
chrt, err := helmrepoindex.LoadPackage(request.Package)
|
||||
if err != nil {
|
||||
klog.Errorf("load package failed, error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
app, err := c.appLister.Get(request.AppId)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("get app %s failed, error: %s", request.AppId, err)
|
||||
return nil, err
|
||||
}
|
||||
chartPackage := request.Package.String()
|
||||
version := buildApplicationVersion(app, chrt, &chartPackage, request.Username)
|
||||
version, err = c.createApplicationVersion(version)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("create helm app version failed, error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
klog.V(4).Infof("create helm app version %s success", request.Name)
|
||||
|
||||
return &CreateAppVersionResponse{
|
||||
VersionId: version.GetHelmApplicationVersionId(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) DeleteAppVersion(id string) error {
|
||||
appVersion, err := c.versionLister.Get(id)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
} else {
|
||||
klog.Infof("get app version %s failed, error: %s", id, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch appVersion.Status.State {
|
||||
case v1alpha1.StateActive:
|
||||
klog.Warningf("delete app version %s/%s not permitted, current state:%s", appVersion.GetWorkspace(),
|
||||
appVersion.GetTrueName(), appVersion.Status.State)
|
||||
return actionNotPermitted
|
||||
}
|
||||
|
||||
// check release
|
||||
rls, err := c.rlsLister.List(labels.SelectorFromSet(map[string]string{constants.ChartApplicationVersionIdLabelKey: id}))
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if len(rls) > 0 {
|
||||
klog.V(4).Infof("There are releases use data from app version %s", id)
|
||||
infoMap := make(map[string]string)
|
||||
allString := &bytes.Buffer{}
|
||||
for _, r := range rls {
|
||||
info := fmt.Sprintf("%s/%s", r.GetWorkspace(), r.GetRlsNamespace())
|
||||
if _, exists := infoMap[info]; !exists {
|
||||
infoMap[info] = ""
|
||||
allString.WriteString(info)
|
||||
if len(infoMap) > 1 {
|
||||
allString.WriteString(",")
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("release exists: %s", allString.String())
|
||||
}
|
||||
|
||||
// Delete data in storage
|
||||
err = c.backingStoreClient.Delete(dataKeyInStorage(appVersion.GetWorkspace(), id))
|
||||
if err != nil {
|
||||
if aerr, ok := err.(awserr.Error); ok && aerr.Code() != s3.ErrCodeNoSuchKey {
|
||||
klog.Errorf("delete app version %s/%s data failed, error: %s", appVersion.GetWorkspace(), appVersion.Name, err)
|
||||
return deleteDataInStorageFailed
|
||||
}
|
||||
}
|
||||
|
||||
// delete app version in etcd
|
||||
err = c.appVersionClient.Delete(context.TODO(), id, metav1.DeleteOptions{})
|
||||
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
klog.Errorf("delete app version %s failed", err)
|
||||
return err
|
||||
} else {
|
||||
klog.Infof("app version %s deleted", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) DescribeAppVersion(id string) (*AppVersion, error) {
|
||||
version, err := c.versionLister.Get(id)
|
||||
if err != nil {
|
||||
klog.Errorf("get app version [%s] failed, error: %s", id, err)
|
||||
return nil, err
|
||||
}
|
||||
app := convertAppVersion(version)
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) ModifyAppVersion(id string, request *ModifyAppVersionRequest) error {
|
||||
|
||||
version, err := c.versionLister.Get(id)
|
||||
if err != nil {
|
||||
klog.Errorf("get app version [%s] failed, error: %s", id, err)
|
||||
return err
|
||||
}
|
||||
|
||||
versionCopy := version.DeepCopy()
|
||||
spec := &versionCopy.Spec
|
||||
if request.Name != nil && *request.Name != "" {
|
||||
spec.Version, spec.AppVersion = parseChartVersionName(*request.Name)
|
||||
}
|
||||
|
||||
if request.Description != nil && *request.Description != "" {
|
||||
spec.Description = stringutils.ShortenString(*request.Description, v1alpha1.MsgLen)
|
||||
}
|
||||
patch := client.MergeFrom(version)
|
||||
data, err := patch.Data(versionCopy)
|
||||
if err != nil {
|
||||
klog.Error("create patch failed", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// data == "{}", need not to patch
|
||||
if len(data) == 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = c.appVersionClient.Patch(context.TODO(), id, patch.Type(), data, metav1.PatchOptions{})
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) ListAppVersions(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
||||
versions, err := c.getAppVersionsByAppId(conditions.Match[AppId])
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var status []string
|
||||
if len(conditions.Match[Status]) > 0 {
|
||||
status = strings.Split(conditions.Match[Status], "|")
|
||||
}
|
||||
versions = filterAppVersionByState(versions, status)
|
||||
if reverse {
|
||||
sort.Sort(sort.Reverse(AppVersions(versions)))
|
||||
} else {
|
||||
sort.Sort(AppVersions(versions))
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0, int(math.Min(float64(limit), float64(len(versions)))))
|
||||
|
||||
for i, j := offset, 0; i < len(versions) && j < limit; {
|
||||
items = append(items, convertAppVersion(versions[i]))
|
||||
i++
|
||||
j++
|
||||
}
|
||||
return &models.PageableResponse{Items: items, TotalCount: len(versions)}, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) ListAppVersionReviews(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
||||
|
||||
var allStatus []string
|
||||
if status := conditions.Match[Status]; status != "" {
|
||||
allStatus = strings.Split(status, "|")
|
||||
}
|
||||
|
||||
appVersions, err := c.versionLister.List(labels.Everything())
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filtered := make([]*v1alpha1.HelmApplicationVersion, 0, len(appVersions)/2)
|
||||
for _, version := range appVersions {
|
||||
if sliceutil.HasString(allStatus, version.Status.State) {
|
||||
filtered = append(filtered, version)
|
||||
}
|
||||
}
|
||||
|
||||
if reverse {
|
||||
sort.Sort(sort.Reverse(AppVersions(filtered)))
|
||||
} else {
|
||||
sort.Sort(AppVersions(filtered))
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0, len(filtered))
|
||||
|
||||
for i, j := offset, 0; i < len(filtered) && j < limit; {
|
||||
review := convertAppVersionReview(filtered[i])
|
||||
items = append(items, review)
|
||||
i++
|
||||
j++
|
||||
}
|
||||
|
||||
return &models.PageableResponse{Items: items, TotalCount: len(filtered)}, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) ListAppVersionAudits(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
||||
appId := conditions.Match[AppId]
|
||||
versionId := conditions.Match[VersionId]
|
||||
|
||||
var versions []*v1alpha1.HelmApplicationVersion
|
||||
var err error
|
||||
if versionId == "" {
|
||||
ls := map[string]string{
|
||||
constants.ChartApplicationIdLabelKey: appId,
|
||||
}
|
||||
versions, err = c.versionLister.List(labels.SelectorFromSet(ls))
|
||||
if err != nil {
|
||||
klog.Errorf("get app %s failed, error: %s", appId, err)
|
||||
}
|
||||
} else {
|
||||
version, err := c.versionLister.Get(versionId)
|
||||
if err != nil {
|
||||
klog.Errorf("get app version %s failed, error: %s", versionId, err)
|
||||
}
|
||||
versions = []*v1alpha1.HelmApplicationVersion{version}
|
||||
}
|
||||
|
||||
var allAudits []*AppVersionAudit
|
||||
for _, item := range versions {
|
||||
audits := convertAppVersionAudit(item)
|
||||
allAudits = append(allAudits, audits...)
|
||||
}
|
||||
|
||||
sort.Sort(AppVersionAuditList(allAudits))
|
||||
|
||||
items := make([]interface{}, 0, limit)
|
||||
|
||||
for i, j := offset, 0; i < len(allAudits) && j < limit; {
|
||||
items = append(items, allAudits[i])
|
||||
i++
|
||||
j++
|
||||
}
|
||||
|
||||
return &models.PageableResponse{Items: items, TotalCount: len(allAudits)}, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) DoAppVersionAction(versionId string, request *ActionRequest) error {
|
||||
var err error
|
||||
t := metav1.Now()
|
||||
var audit = v1alpha1.Audit{
|
||||
Message: request.Message,
|
||||
Operator: request.Username,
|
||||
Time: t,
|
||||
}
|
||||
state := v1alpha1.StateDraft
|
||||
|
||||
version, err := c.versionLister.Get(versionId)
|
||||
if err != nil {
|
||||
klog.Errorf("get app version %s failed, error: %s", versionId, err)
|
||||
return err
|
||||
}
|
||||
|
||||
switch request.Action {
|
||||
case ActionCancel:
|
||||
if version.Status.State != v1alpha1.StateSubmitted {
|
||||
}
|
||||
state = v1alpha1.StateDraft
|
||||
audit.State = v1alpha1.StateDraft
|
||||
case ActionPass:
|
||||
if version.Status.State != v1alpha1.StateSubmitted {
|
||||
|
||||
}
|
||||
state = v1alpha1.StatePassed
|
||||
audit.State = v1alpha1.StatePassed
|
||||
case ActionRecover:
|
||||
if version.Status.State != v1alpha1.StateSuspended {
|
||||
|
||||
}
|
||||
state = v1alpha1.StateActive
|
||||
audit.State = v1alpha1.StateActive
|
||||
case ActionReject:
|
||||
if version.Status.State != v1alpha1.StateSubmitted {
|
||||
// todo check status
|
||||
}
|
||||
state = v1alpha1.StateRejected
|
||||
audit.State = v1alpha1.StateRejected
|
||||
case ActionSubmit:
|
||||
if version.Status.State != v1alpha1.StateDraft {
|
||||
// todo check status
|
||||
}
|
||||
state = v1alpha1.StateSubmitted
|
||||
audit.State = v1alpha1.StateSubmitted
|
||||
case ActionSuspend:
|
||||
if version.Status.State != v1alpha1.StateActive {
|
||||
// todo check status
|
||||
}
|
||||
state = v1alpha1.StateSuspended
|
||||
audit.State = v1alpha1.StateSuspended
|
||||
case ActionRelease:
|
||||
// release to app store
|
||||
if version.Status.State != v1alpha1.StatePassed {
|
||||
// todo check status
|
||||
}
|
||||
state = v1alpha1.StateActive
|
||||
audit.State = v1alpha1.StateActive
|
||||
default:
|
||||
err = errors.New("action not support")
|
||||
}
|
||||
|
||||
_ = state
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
version, err = c.updateAppVersionStatus(version, state, &audit)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("update app version audit [%s] failed, error: %s", versionId, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if request.Action == ActionRelease || request.Action == ActionRecover {
|
||||
// if we release a new helm application version, we need update the spec in helm application copy
|
||||
app, err := c.appLister.Get(version.GetHelmApplicationId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appInStore, err := c.appLister.Get(fmt.Sprintf("%s%s", version.GetHelmApplicationId(), v1alpha1.HelmApplicationAppStoreSuffix))
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
// controller-manager will create application in app store
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(&app.Spec, &appInStore.Spec) {
|
||||
appCopy := appInStore.DeepCopy()
|
||||
appCopy.Spec = app.Spec
|
||||
patch := client.MergeFrom(appInStore)
|
||||
data, _ := patch.Data(appCopy)
|
||||
_, err = c.appClient.Patch(context.TODO(), appCopy.Name, patch.Type(), data, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create helmApplicationVersion and helmAudit
|
||||
func (c *applicationOperator) createApplicationVersion(ver *v1alpha1.HelmApplicationVersion) (*v1alpha1.HelmApplicationVersion, error) {
|
||||
ls := map[string]string{
|
||||
constants.ChartApplicationIdLabelKey: ver.GetHelmApplicationId(),
|
||||
}
|
||||
|
||||
list, err := c.versionLister.List(labels.SelectorFromSet(ls))
|
||||
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(list) > 0 {
|
||||
verName := ver.GetVersionName()
|
||||
for _, v := range list {
|
||||
if verName == v.GetVersionName() {
|
||||
klog.V(2).Infof("helm application version: %s exist", verName)
|
||||
return nil, appVersionItemExists
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save chart data to s3 storage
|
||||
_, err = base64.StdEncoding.Decode(ver.Spec.Data, ver.Spec.Data)
|
||||
if err != nil {
|
||||
klog.Errorf("decode error: %s", err)
|
||||
return nil, err
|
||||
} else {
|
||||
err = c.backingStoreClient.Upload(dataKeyInStorage(ver.GetWorkspace(), ver.Name), ver.Name, bytes.NewReader(ver.Spec.Data))
|
||||
if err != nil {
|
||||
klog.Errorf("upload chart for app version: %s/%s failed, error: %s", ver.GetWorkspace(),
|
||||
ver.GetTrueName(), err)
|
||||
return nil, uploadChartDataFailed
|
||||
} else {
|
||||
klog.V(4).Infof("chart data uploaded for app version: %s/%s", ver.GetWorkspace(), ver.GetTrueName())
|
||||
}
|
||||
}
|
||||
|
||||
// data will not save to etcd
|
||||
ver.Spec.Data = nil
|
||||
ver.Spec.DataKey = ver.Name
|
||||
version, err := c.appVersionClient.Create(context.TODO(), ver, metav1.CreateOptions{})
|
||||
if err == nil {
|
||||
klog.V(4).Infof("create helm application %s version success", version.Name)
|
||||
}
|
||||
|
||||
return version, err
|
||||
}
|
||||
|
||||
func (c *applicationOperator) updateAppVersionStatus(version *v1alpha1.HelmApplicationVersion, state string, status *v1alpha1.Audit) (*v1alpha1.HelmApplicationVersion, error) {
|
||||
version.Status.State = state
|
||||
|
||||
states := append([]v1alpha1.Audit{*status}, version.Status.Audit...)
|
||||
if len(version.Status.Audit) >= v1alpha1.HelmRepoSyncStateLen {
|
||||
// strip the last item
|
||||
states = states[:v1alpha1.HelmRepoSyncStateLen:v1alpha1.HelmRepoSyncStateLen]
|
||||
}
|
||||
|
||||
version.Status.Audit = states
|
||||
version, err := c.appVersionClient.UpdateStatus(context.TODO(), version, metav1.UpdateOptions{})
|
||||
|
||||
return version, err
|
||||
}
|
||||
|
||||
func (c *applicationOperator) GetAppVersionFiles(versionId string, request *GetAppVersionFilesRequest) (*GetAppVersionPackageFilesResponse, error) {
|
||||
var version *v1alpha1.HelmApplicationVersion
|
||||
var err error
|
||||
|
||||
version, err = c.getAppVersionByVersionIdWithData(versionId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gzReader, err := gzip.NewReader(bytes.NewReader(version.Spec.Data))
|
||||
if err != nil {
|
||||
klog.Errorf("read app version %s failed, error: %s", versionId, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tarReader := tar.NewReader(gzReader)
|
||||
|
||||
res := &GetAppVersionPackageFilesResponse{Files: map[string]strfmt.Base64{}, VersionId: versionId}
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("ExtractTarGz: Next() failed: %s", err.Error())
|
||||
return res, err
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeReg:
|
||||
curData, _ := ioutil.ReadAll(tarReader)
|
||||
name := strings.TrimLeft(header.Name, fmt.Sprintf("%s/", version.GetTrueName()))
|
||||
res.Files[name] = curData
|
||||
default:
|
||||
klog.Errorf(
|
||||
"ExtractTarGz: unknown type: %v in %s",
|
||||
header.Typeflag,
|
||||
header.Name)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) getAppVersionByVersionIdWithData(versionId string) (*v1alpha1.HelmApplicationVersion, error) {
|
||||
if version, exists, err := c.cachedRepos.GetAppVersionWithData(versionId); exists {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
version, err := c.versionLister.Get(versionId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := c.backingStoreClient.Read(dataKeyInStorage(version.GetWorkspace(), versionId))
|
||||
if err != nil {
|
||||
klog.Errorf("load chart data for app version: %s/%s failed, error : %s", version.GetTrueName(),
|
||||
version.GetTrueName(), err)
|
||||
return nil, downloadFileFailed
|
||||
}
|
||||
version.Spec.Data = data
|
||||
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) getAppVersionsByAppId(appId string) (ret []*v1alpha1.HelmApplicationVersion, err error) {
|
||||
if ret, exists := c.cachedRepos.ListAppVersionsByAppId(appId); exists {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// list app version from client-go
|
||||
ret, err = c.versionLister.List(labels.SelectorFromSet(map[string]string{constants.ChartApplicationIdLabelKey: appId}))
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,614 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 openpitrix
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/models"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
|
||||
"openpitrix.io/openpitrix/pkg/pb"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AppTemplateInterface interface {
|
||||
ListApps(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
||||
DescribeApp(id string) (*App, error)
|
||||
DeleteApp(id string) error
|
||||
CreateApp(request *CreateAppRequest) (*CreateAppResponse, error)
|
||||
ModifyApp(appId string, request *ModifyAppRequest) error
|
||||
DeleteAppVersion(id string) error
|
||||
ModifyAppVersion(id string, request *ModifyAppVersionRequest) error
|
||||
DescribeAppVersion(id string) (*AppVersion, error)
|
||||
CreateAppVersion(request *CreateAppVersionRequest) (*CreateAppVersionResponse, error)
|
||||
ValidatePackage(request *ValidatePackageRequest) (*ValidatePackageResponse, error)
|
||||
GetAppVersionPackage(appId, versionId string) (*GetAppVersionPackageResponse, error)
|
||||
DoAppAction(appId string, request *ActionRequest) error
|
||||
DoAppVersionAction(versionId string, request *ActionRequest) error
|
||||
GetAppVersionFiles(versionId string, request *GetAppVersionFilesRequest) (*GetAppVersionPackageFilesResponse, error)
|
||||
ListAppVersionAudits(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
||||
ListAppVersionReviews(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
||||
ListAppVersions(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
||||
}
|
||||
|
||||
type appTemplateOperator struct {
|
||||
opClient openpitrix.Client
|
||||
}
|
||||
|
||||
func newAppTemplateOperator(opClient openpitrix.Client) AppTemplateInterface {
|
||||
return &appTemplateOperator{
|
||||
opClient: opClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) ListApps(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
||||
|
||||
describeAppsRequest := &pb.DescribeAppsRequest{}
|
||||
if keyword := conditions.Match[Keyword]; keyword != "" {
|
||||
describeAppsRequest.SearchWord = &wrappers.StringValue{Value: keyword}
|
||||
}
|
||||
if appId := conditions.Match[AppId]; appId != "" {
|
||||
describeAppsRequest.AppId = strings.Split(appId, "|")
|
||||
}
|
||||
if isv := conditions.Match[ISV]; isv != "" {
|
||||
describeAppsRequest.Isv = strings.Split(isv, "|")
|
||||
}
|
||||
if categoryId := conditions.Match[CategoryId]; categoryId != "" {
|
||||
describeAppsRequest.CategoryId = strings.Split(categoryId, "|")
|
||||
}
|
||||
if repoId := conditions.Match[RepoId]; repoId != "" {
|
||||
// hard code, app template in built-in repo has no repo_id attribute
|
||||
if repoId == BuiltinRepoId {
|
||||
describeAppsRequest.RepoId = []string{"\u0000"}
|
||||
} else {
|
||||
describeAppsRequest.RepoId = strings.Split(repoId, "|")
|
||||
}
|
||||
}
|
||||
if status := conditions.Match[Status]; status != "" {
|
||||
describeAppsRequest.Status = strings.Split(status, "|")
|
||||
}
|
||||
if orderBy != "" {
|
||||
describeAppsRequest.SortKey = &wrappers.StringValue{Value: orderBy}
|
||||
}
|
||||
describeAppsRequest.Reverse = &wrappers.BoolValue{Value: reverse}
|
||||
describeAppsRequest.Limit = uint32(limit)
|
||||
describeAppsRequest.Offset = uint32(offset)
|
||||
resp, err := c.opClient.DescribeApps(openpitrix.SystemContext(), describeAppsRequest)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0)
|
||||
|
||||
for _, item := range resp.AppSet {
|
||||
items = append(items, convertApp(item))
|
||||
}
|
||||
|
||||
return &models.PageableResponse{Items: items, TotalCount: int(resp.TotalCount)}, nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) DescribeApp(id string) (*App, error) {
|
||||
resp, err := c.opClient.DescribeApps(openpitrix.SystemContext(), &pb.DescribeAppsRequest{
|
||||
AppId: []string{id},
|
||||
Limit: 1,
|
||||
})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var app *App
|
||||
|
||||
if len(resp.AppSet) > 0 {
|
||||
app = convertApp(resp.AppSet[0])
|
||||
return app, nil
|
||||
} else {
|
||||
err := status.New(codes.NotFound, "resource not found").Err()
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) DeleteApp(id string) error {
|
||||
_, err := c.opClient.DeleteApps(openpitrix.SystemContext(), &pb.DeleteAppsRequest{
|
||||
AppId: []string{id},
|
||||
})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) CreateApp(request *CreateAppRequest) (*CreateAppResponse, error) {
|
||||
createAppRequest := &pb.CreateAppRequest{
|
||||
Name: &wrappers.StringValue{Value: request.Name},
|
||||
VersionType: &wrappers.StringValue{Value: request.VersionType},
|
||||
VersionName: &wrappers.StringValue{Value: request.VersionName},
|
||||
}
|
||||
if request.VersionPackage != nil {
|
||||
createAppRequest.VersionPackage = &wrappers.BytesValue{Value: request.VersionPackage}
|
||||
}
|
||||
if request.Icon != nil {
|
||||
createAppRequest.Icon = &wrappers.BytesValue{Value: request.Icon}
|
||||
}
|
||||
if request.Isv != "" {
|
||||
createAppRequest.Isv = &wrappers.StringValue{Value: request.Isv}
|
||||
}
|
||||
resp, err := c.opClient.CreateApp(openpitrix.ContextWithUsername(request.Username), createAppRequest)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
appTemplateCreationCounter.WithLabelValues(request.Isv, request.Name, "failed").Inc()
|
||||
return nil, err
|
||||
} else {
|
||||
appTemplateCreationCounter.WithLabelValues(request.Isv, request.Name, "success").Inc()
|
||||
}
|
||||
return &CreateAppResponse{
|
||||
AppID: resp.GetAppId().GetValue(),
|
||||
VersionID: resp.GetVersionId().GetValue(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) ModifyApp(appId string, request *ModifyAppRequest) error {
|
||||
// upload app attachment
|
||||
if request.AttachmentContent != nil {
|
||||
uploadAttachmentRequest := &pb.UploadAppAttachmentRequest{
|
||||
AppId: &wrappers.StringValue{Value: appId},
|
||||
AttachmentContent: &wrappers.BytesValue{Value: request.AttachmentContent},
|
||||
}
|
||||
if request.Type != nil {
|
||||
uploadAttachmentRequest.Type = pb.UploadAppAttachmentRequest_Type(pb.UploadAppAttachmentRequest_Type_value[*request.Type])
|
||||
}
|
||||
if request.Sequence != nil {
|
||||
uploadAttachmentRequest.Sequence = &wrappers.UInt32Value{Value: uint32(*request.Sequence)}
|
||||
}
|
||||
|
||||
_, err := c.opClient.UploadAppAttachment(openpitrix.SystemContext(), uploadAttachmentRequest)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
// patch app
|
||||
} else {
|
||||
patchAppRequest := &pb.ModifyAppRequest{
|
||||
AppId: &wrappers.StringValue{Value: appId},
|
||||
}
|
||||
|
||||
if request.Abstraction != nil {
|
||||
patchAppRequest.Abstraction = &wrappers.StringValue{Value: *request.Abstraction}
|
||||
}
|
||||
if request.CategoryID != nil {
|
||||
patchAppRequest.CategoryId = &wrappers.StringValue{Value: *request.CategoryID}
|
||||
}
|
||||
if request.Description != nil {
|
||||
patchAppRequest.Description = &wrappers.StringValue{Value: *request.Description}
|
||||
}
|
||||
if request.Home != nil {
|
||||
patchAppRequest.Home = &wrappers.StringValue{Value: *request.Home}
|
||||
}
|
||||
if request.Keywords != nil {
|
||||
patchAppRequest.Keywords = &wrappers.StringValue{Value: *request.Keywords}
|
||||
}
|
||||
if request.Maintainers != nil {
|
||||
patchAppRequest.Maintainers = &wrappers.StringValue{Value: *request.Maintainers}
|
||||
}
|
||||
if request.Name != nil {
|
||||
patchAppRequest.Name = &wrappers.StringValue{Value: *request.Name}
|
||||
}
|
||||
if request.Readme != nil {
|
||||
patchAppRequest.Readme = &wrappers.StringValue{Value: *request.Readme}
|
||||
}
|
||||
if request.Sources != nil {
|
||||
patchAppRequest.Sources = &wrappers.StringValue{Value: *request.Sources}
|
||||
}
|
||||
if request.Tos != nil {
|
||||
patchAppRequest.Tos = &wrappers.StringValue{Value: *request.Tos}
|
||||
}
|
||||
|
||||
_, err := c.opClient.ModifyApp(openpitrix.SystemContext(), patchAppRequest)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) CreateAppVersion(request *CreateAppVersionRequest) (*CreateAppVersionResponse, error) {
|
||||
createAppVersionRequest := &pb.CreateAppVersionRequest{
|
||||
AppId: &wrappers.StringValue{Value: request.AppId},
|
||||
Name: &wrappers.StringValue{Value: request.Name},
|
||||
Description: &wrappers.StringValue{Value: request.Description},
|
||||
Type: &wrappers.StringValue{Value: request.Type},
|
||||
}
|
||||
|
||||
if request.Package != nil {
|
||||
createAppVersionRequest.Package = &wrappers.BytesValue{Value: request.Package}
|
||||
}
|
||||
|
||||
resp, err := c.opClient.CreateAppVersion(openpitrix.ContextWithUsername(request.Username), createAppVersionRequest)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
return &CreateAppVersionResponse{
|
||||
VersionId: resp.GetVersionId().GetValue(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) ValidatePackage(request *ValidatePackageRequest) (*ValidatePackageResponse, error) {
|
||||
r := &pb.ValidatePackageRequest{}
|
||||
|
||||
if request.VersionPackage != nil {
|
||||
r.VersionPackage = request.VersionPackage
|
||||
}
|
||||
if request.VersionType != "" {
|
||||
r.VersionType = request.VersionType
|
||||
}
|
||||
|
||||
resp, err := c.opClient.ValidatePackage(openpitrix.SystemContext(), r)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &ValidatePackageResponse{}
|
||||
|
||||
if resp.Error != nil {
|
||||
result.Error = resp.Error.Value
|
||||
}
|
||||
if resp.Description != nil {
|
||||
result.Description = resp.Description.Value
|
||||
}
|
||||
if resp.Error != nil {
|
||||
result.Error = resp.Error.Value
|
||||
}
|
||||
if resp.ErrorDetails != nil {
|
||||
result.ErrorDetails = resp.ErrorDetails
|
||||
}
|
||||
if resp.Name != nil {
|
||||
result.Name = resp.Name.Value
|
||||
}
|
||||
if resp.Url != nil {
|
||||
result.URL = resp.Url.Value
|
||||
}
|
||||
if resp.VersionName != nil {
|
||||
result.VersionName = resp.VersionName.Value
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) DeleteAppVersion(id string) error {
|
||||
_, err := c.opClient.DeleteAppVersion(openpitrix.SystemContext(), &pb.DeleteAppVersionRequest{
|
||||
VersionId: &wrappers.StringValue{Value: id},
|
||||
})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) ModifyAppVersion(id string, request *ModifyAppVersionRequest) error {
|
||||
modifyAppVersionRequest := &pb.ModifyAppVersionRequest{
|
||||
VersionId: &wrappers.StringValue{Value: id},
|
||||
}
|
||||
|
||||
if request.Name != nil {
|
||||
modifyAppVersionRequest.Name = &wrappers.StringValue{Value: *request.Name}
|
||||
}
|
||||
if request.Description != nil {
|
||||
modifyAppVersionRequest.Description = &wrappers.StringValue{Value: *request.Description}
|
||||
}
|
||||
if request.Package != nil {
|
||||
modifyAppVersionRequest.Package = &wrappers.BytesValue{Value: request.Package}
|
||||
}
|
||||
if request.PackageFiles != nil {
|
||||
modifyAppVersionRequest.PackageFiles = request.PackageFiles
|
||||
}
|
||||
|
||||
_, err := c.opClient.ModifyAppVersion(openpitrix.SystemContext(), modifyAppVersionRequest)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) DescribeAppVersion(id string) (*AppVersion, error) {
|
||||
resp, err := c.opClient.DescribeAppVersions(openpitrix.SystemContext(), &pb.DescribeAppVersionsRequest{
|
||||
VersionId: []string{id},
|
||||
Limit: 1,
|
||||
})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var app *AppVersion
|
||||
|
||||
if len(resp.AppVersionSet) > 0 {
|
||||
app = convertAppVersion(resp.AppVersionSet[0])
|
||||
return app, nil
|
||||
} else {
|
||||
err := status.New(codes.NotFound, "resource not found").Err()
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) GetAppVersionPackage(appId, versionId string) (*GetAppVersionPackageResponse, error) {
|
||||
resp, err := c.opClient.GetAppVersionPackage(openpitrix.SystemContext(), &pb.GetAppVersionPackageRequest{
|
||||
VersionId: &wrappers.StringValue{Value: versionId},
|
||||
})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
app := &GetAppVersionPackageResponse{
|
||||
AppId: appId,
|
||||
VersionId: versionId,
|
||||
}
|
||||
|
||||
if resp.Package != nil {
|
||||
app.Package = resp.Package
|
||||
}
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) DoAppAction(appId string, request *ActionRequest) error {
|
||||
switch request.Action {
|
||||
|
||||
case ActionRecover:
|
||||
// TODO openpitrix need to implement app recover interface
|
||||
resp, err := c.opClient.DescribeAppVersions(openpitrix.SystemContext(), &pb.DescribeAppVersionsRequest{
|
||||
AppId: []string{appId},
|
||||
Status: []string{StatusSuspended},
|
||||
Limit: 200,
|
||||
Offset: 0,
|
||||
})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
for _, version := range resp.AppVersionSet {
|
||||
|
||||
_, err = c.opClient.RecoverAppVersion(openpitrix.SystemContext(), &pb.RecoverAppVersionRequest{
|
||||
VersionId: version.VersionId,
|
||||
})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case ActionSuspend:
|
||||
// TODO openpitrix need to implement app suspend interface
|
||||
resp, err := c.opClient.DescribeAppVersions(openpitrix.SystemContext(), &pb.DescribeAppVersionsRequest{
|
||||
AppId: []string{appId},
|
||||
Status: []string{StatusActive},
|
||||
Limit: 200,
|
||||
Offset: 0,
|
||||
})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
for _, version := range resp.AppVersionSet {
|
||||
_, err = c.opClient.SuspendAppVersion(openpitrix.SystemContext(), &pb.SuspendAppVersionRequest{
|
||||
VersionId: version.VersionId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
err := status.New(codes.InvalidArgument, "action not support").Err()
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) DoAppVersionAction(versionId string, request *ActionRequest) error {
|
||||
var err error
|
||||
switch request.Action {
|
||||
case ActionCancel:
|
||||
_, err = c.opClient.CancelAppVersion(openpitrix.ContextWithUsername(request.Username), &pb.CancelAppVersionRequest{
|
||||
VersionId: &wrappers.StringValue{Value: versionId},
|
||||
})
|
||||
case ActionPass:
|
||||
_, err = c.opClient.AdminPassAppVersion(openpitrix.ContextWithUsername(request.Username), &pb.PassAppVersionRequest{
|
||||
VersionId: &wrappers.StringValue{Value: versionId},
|
||||
})
|
||||
case ActionRecover:
|
||||
_, err = c.opClient.RecoverAppVersion(openpitrix.ContextWithUsername(request.Username), &pb.RecoverAppVersionRequest{
|
||||
VersionId: &wrappers.StringValue{Value: versionId},
|
||||
})
|
||||
case ActionReject:
|
||||
_, err = c.opClient.AdminRejectAppVersion(openpitrix.ContextWithUsername(request.Username), &pb.RejectAppVersionRequest{
|
||||
VersionId: &wrappers.StringValue{Value: versionId},
|
||||
Message: &wrappers.StringValue{Value: request.Message},
|
||||
})
|
||||
case ActionSubmit:
|
||||
_, err = c.opClient.SubmitAppVersion(openpitrix.ContextWithUsername(request.Username), &pb.SubmitAppVersionRequest{
|
||||
VersionId: &wrappers.StringValue{Value: versionId},
|
||||
})
|
||||
case ActionSuspend:
|
||||
_, err = c.opClient.SuspendAppVersion(openpitrix.ContextWithUsername(request.Username), &pb.SuspendAppVersionRequest{
|
||||
VersionId: &wrappers.StringValue{Value: versionId},
|
||||
})
|
||||
case ActionRelease:
|
||||
_, err = c.opClient.ReleaseAppVersion(openpitrix.ContextWithUsername(request.Username), &pb.ReleaseAppVersionRequest{
|
||||
VersionId: &wrappers.StringValue{Value: versionId},
|
||||
})
|
||||
default:
|
||||
err = status.New(codes.InvalidArgument, "action not support").Err()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) GetAppVersionFiles(versionId string, request *GetAppVersionFilesRequest) (*GetAppVersionPackageFilesResponse, error) {
|
||||
getAppVersionPackageFilesRequest := &pb.GetAppVersionPackageFilesRequest{
|
||||
VersionId: &wrappers.StringValue{Value: versionId},
|
||||
}
|
||||
if request.Files != nil {
|
||||
getAppVersionPackageFilesRequest.Files = request.Files
|
||||
}
|
||||
|
||||
resp, err := c.opClient.GetAppVersionPackageFiles(openpitrix.SystemContext(), getAppVersionPackageFilesRequest)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version := &GetAppVersionPackageFilesResponse{
|
||||
VersionId: versionId,
|
||||
}
|
||||
|
||||
if resp.Files != nil {
|
||||
version.Files = make(map[string]strfmt.Base64)
|
||||
for k, v := range resp.Files {
|
||||
version.Files[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) ListAppVersionAudits(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
||||
describeAppVersionAudits := &pb.DescribeAppVersionAuditsRequest{}
|
||||
|
||||
if keyword := conditions.Match[Keyword]; keyword != "" {
|
||||
describeAppVersionAudits.SearchWord = &wrappers.StringValue{Value: keyword}
|
||||
}
|
||||
if appId := conditions.Match[AppId]; appId != "" {
|
||||
describeAppVersionAudits.AppId = []string{appId}
|
||||
}
|
||||
if versionId := conditions.Match[VersionId]; versionId != "" {
|
||||
describeAppVersionAudits.VersionId = []string{versionId}
|
||||
}
|
||||
if status := conditions.Match[Status]; status != "" {
|
||||
describeAppVersionAudits.Status = strings.Split(status, "|")
|
||||
}
|
||||
if orderBy != "" {
|
||||
describeAppVersionAudits.SortKey = &wrappers.StringValue{Value: orderBy}
|
||||
}
|
||||
describeAppVersionAudits.Reverse = &wrappers.BoolValue{Value: reverse}
|
||||
describeAppVersionAudits.Limit = uint32(limit)
|
||||
describeAppVersionAudits.Offset = uint32(offset)
|
||||
resp, err := c.opClient.DescribeAppVersionAudits(openpitrix.SystemContext(), describeAppVersionAudits)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0)
|
||||
|
||||
for _, item := range resp.AppVersionAuditSet {
|
||||
appVersion := convertAppVersionAudit(item)
|
||||
items = append(items, appVersion)
|
||||
}
|
||||
|
||||
return &models.PageableResponse{Items: items, TotalCount: int(resp.TotalCount)}, nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) ListAppVersionReviews(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
||||
describeAppVersionReviews := &pb.DescribeAppVersionReviewsRequest{}
|
||||
|
||||
if keyword := conditions.Match[Keyword]; keyword != "" {
|
||||
describeAppVersionReviews.SearchWord = &wrappers.StringValue{Value: keyword}
|
||||
}
|
||||
if status := conditions.Match[Status]; status != "" {
|
||||
describeAppVersionReviews.Status = strings.Split(status, "|")
|
||||
}
|
||||
if orderBy != "" {
|
||||
describeAppVersionReviews.SortKey = &wrappers.StringValue{Value: orderBy}
|
||||
}
|
||||
describeAppVersionReviews.Reverse = &wrappers.BoolValue{Value: reverse}
|
||||
describeAppVersionReviews.Limit = uint32(limit)
|
||||
describeAppVersionReviews.Offset = uint32(offset)
|
||||
// TODO icon is needed
|
||||
resp, err := c.opClient.DescribeAppVersionReviews(openpitrix.SystemContext(), describeAppVersionReviews)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0)
|
||||
|
||||
for _, item := range resp.AppVersionReviewSet {
|
||||
appVersion := convertAppVersionReview(item)
|
||||
items = append(items, appVersion)
|
||||
}
|
||||
|
||||
return &models.PageableResponse{Items: items, TotalCount: int(resp.TotalCount)}, nil
|
||||
}
|
||||
|
||||
func (c *appTemplateOperator) ListAppVersions(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
||||
describeAppVersionsRequest := &pb.DescribeAppVersionsRequest{}
|
||||
|
||||
if keyword := conditions.Match[Keyword]; keyword != "" {
|
||||
describeAppVersionsRequest.SearchWord = &wrappers.StringValue{Value: keyword}
|
||||
}
|
||||
if appId := conditions.Match[AppId]; appId != "" {
|
||||
describeAppVersionsRequest.AppId = []string{appId}
|
||||
}
|
||||
if status := conditions.Match[Status]; status != "" {
|
||||
describeAppVersionsRequest.Status = strings.Split(status, "|")
|
||||
}
|
||||
if orderBy != "" {
|
||||
describeAppVersionsRequest.SortKey = &wrappers.StringValue{Value: orderBy}
|
||||
}
|
||||
describeAppVersionsRequest.Reverse = &wrappers.BoolValue{Value: reverse}
|
||||
describeAppVersionsRequest.Limit = uint32(limit)
|
||||
describeAppVersionsRequest.Offset = uint32(offset)
|
||||
resp, err := c.opClient.DescribeAppVersions(openpitrix.SystemContext(), describeAppVersionsRequest)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0)
|
||||
|
||||
for _, item := range resp.AppVersionSet {
|
||||
appVersion := convertAppVersion(item)
|
||||
items = append(items, appVersion)
|
||||
}
|
||||
|
||||
return &models.PageableResponse{Items: items, TotalCount: int(resp.TotalCount)}, nil
|
||||
}
|
||||
@@ -1,53 +1,82 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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.
|
||||
*/
|
||||
|
||||
///*
|
||||
//Copyright 2020 The KubeSphere 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 openpitrix
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"bytes"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
|
||||
"openpitrix.io/openpitrix/pkg/pb"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/s3"
|
||||
"kubesphere.io/kubesphere/pkg/utils/idutils"
|
||||
)
|
||||
|
||||
type AttachmentInterface interface {
|
||||
DescribeAttachment(id string) (*Attachment, error)
|
||||
CreateAttachment(data []byte) (*Attachment, error)
|
||||
DeleteAttachments(ids []string) error
|
||||
}
|
||||
|
||||
type attachmentOperator struct {
|
||||
opClient openpitrix.Client
|
||||
backingStoreClient s3.Interface
|
||||
}
|
||||
|
||||
func newAttachmentOperator(opClient openpitrix.Client) AttachmentInterface {
|
||||
func newAttachmentOperator(storeClient s3.Interface) AttachmentInterface {
|
||||
return &attachmentOperator{
|
||||
opClient: opClient,
|
||||
backingStoreClient: storeClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *attachmentOperator) DescribeAttachment(id string) (*Attachment, error) {
|
||||
resp, err := c.opClient.GetAttachments(openpitrix.SystemContext(), &pb.GetAttachmentsRequest{
|
||||
AttachmentId: []string{id},
|
||||
})
|
||||
data, err := c.backingStoreClient.Read(id)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
klog.Errorf("read attachment %s failed, error: %s", id, err)
|
||||
return nil, downloadFileFailed
|
||||
}
|
||||
if len(resp.Attachments) > 0 {
|
||||
return convertAttachment(resp.Attachments[id]), nil
|
||||
att := &Attachment{AttachmentID: id}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
err := status.New(codes.NotFound, "resource not found").Err()
|
||||
klog.Error(err)
|
||||
att.AttachmentContent = map[string]strfmt.Base64{
|
||||
"raw": data,
|
||||
}
|
||||
}
|
||||
|
||||
return att, nil
|
||||
}
|
||||
func (c *attachmentOperator) CreateAttachment(data []byte) (*Attachment, error) {
|
||||
id := idutils.GetUuid36(v1alpha1.HelmAttachmentPrefix)
|
||||
|
||||
err := c.backingStoreClient.Upload(id, id, bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
klog.Errorf("upload attachment failed, err: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
klog.V(4).Infof("upload attachment success")
|
||||
|
||||
att := &Attachment{AttachmentID: id}
|
||||
return att, nil
|
||||
}
|
||||
|
||||
func (c *attachmentOperator) DeleteAttachments(ids []string) error {
|
||||
for _, id := range ids {
|
||||
err := c.backingStoreClient.Delete(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,14 +14,22 @@ limitations under the License.
|
||||
package openpitrix
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"context"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
typed_v1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
listers_v1alpha1 "kubesphere.io/kubesphere/pkg/client/listers/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/models"
|
||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
|
||||
"openpitrix.io/openpitrix/pkg/pb"
|
||||
"kubesphere.io/kubesphere/pkg/utils/idutils"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type CategoryInterface interface {
|
||||
@@ -33,39 +41,85 @@ type CategoryInterface interface {
|
||||
}
|
||||
|
||||
type categoryOperator struct {
|
||||
opClient openpitrix.Client
|
||||
ctgClient typed_v1alpha1.ApplicationV1alpha1Interface
|
||||
ctgLister listers_v1alpha1.HelmCategoryLister
|
||||
}
|
||||
|
||||
func newCategoryOperator(opClient openpitrix.Client) CategoryInterface {
|
||||
return &categoryOperator{
|
||||
opClient: opClient,
|
||||
func newCategoryOperator(ksFactory externalversions.SharedInformerFactory, ksClient versioned.Interface) CategoryInterface {
|
||||
c := &categoryOperator{
|
||||
ctgClient: ksClient.ApplicationV1alpha1(),
|
||||
ctgLister: ksFactory.Application().V1alpha1().HelmCategories().Lister(),
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *categoryOperator) getCategoryByName(name string) (*v1alpha1.HelmCategory, error) {
|
||||
ctgs, err := c.ctgLister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ctg := range ctgs {
|
||||
if name == ctg.Spec.Name {
|
||||
return ctg, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *categoryOperator) createCategory(name, desc string) (*v1alpha1.HelmCategory, error) {
|
||||
ctg := &v1alpha1.HelmCategory{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: idutils.GetUuid36(v1alpha1.HelmCategoryIdPrefix),
|
||||
},
|
||||
Spec: v1alpha1.HelmCategorySpec{
|
||||
Description: desc,
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
|
||||
return c.ctgClient.HelmCategories().Create(context.TODO(), ctg, metav1.CreateOptions{})
|
||||
}
|
||||
|
||||
func (c *categoryOperator) CreateCategory(request *CreateCategoryRequest) (*CreateCategoryResponse, error) {
|
||||
r := &pb.CreateCategoryRequest{
|
||||
Name: &wrappers.StringValue{Value: request.Name},
|
||||
Locale: &wrappers.StringValue{Value: request.Locale},
|
||||
Description: &wrappers.StringValue{Value: request.Description},
|
||||
}
|
||||
if request.Icon != nil {
|
||||
r.Icon = &wrappers.BytesValue{Value: request.Icon}
|
||||
|
||||
ctg, err := c.getCategoryByName(request.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.opClient.CreateCategory(openpitrix.SystemContext(), r)
|
||||
if ctg != nil {
|
||||
return nil, errors.New("category %s exists", ctg.Spec.Name)
|
||||
}
|
||||
|
||||
ctg, err = c.createCategory(request.Name, request.Description)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CreateCategoryResponse{
|
||||
CategoryId: resp.GetCategoryId().GetValue(),
|
||||
CategoryId: ctg.Name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *categoryOperator) DeleteCategory(id string) error {
|
||||
_, err := c.opClient.DeleteCategories(openpitrix.SystemContext(), &pb.DeleteCategoriesRequest{
|
||||
CategoryId: []string{id},
|
||||
})
|
||||
ctg, err := c.ctgClient.HelmCategories().Get(context.TODO(), id, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if ctg.Status.Total > 0 {
|
||||
return errors.New("category %s owns application", ctg.Spec.Name)
|
||||
}
|
||||
|
||||
err = c.ctgClient.HelmCategories().Delete(context.TODO(), id, metav1.DeleteOptions{})
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
@@ -74,23 +128,30 @@ func (c *categoryOperator) DeleteCategory(id string) error {
|
||||
}
|
||||
|
||||
func (c *categoryOperator) ModifyCategory(id string, request *ModifyCategoryRequest) error {
|
||||
modifyCategoryRequest := &pb.ModifyCategoryRequest{
|
||||
CategoryId: &wrappers.StringValue{Value: id},
|
||||
|
||||
ctg, err := c.ctgClient.HelmCategories().Get(context.TODO(), id, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return errors.New("category %s not found", id)
|
||||
}
|
||||
ctgCopy := ctg.DeepCopy()
|
||||
|
||||
if request.Name != nil {
|
||||
modifyCategoryRequest.Name = &wrappers.StringValue{Value: *request.Name}
|
||||
}
|
||||
if request.Locale != nil {
|
||||
modifyCategoryRequest.Locale = &wrappers.StringValue{Value: *request.Locale}
|
||||
}
|
||||
if request.Description != nil {
|
||||
modifyCategoryRequest.Description = &wrappers.StringValue{Value: *request.Description}
|
||||
}
|
||||
if request.Icon != nil {
|
||||
modifyCategoryRequest.Icon = &wrappers.BytesValue{Value: request.Icon}
|
||||
ctgCopy.Spec.Name = *request.Name
|
||||
}
|
||||
|
||||
_, err := c.opClient.ModifyCategory(openpitrix.SystemContext(), modifyCategoryRequest)
|
||||
if request.Description != nil {
|
||||
ctgCopy.Spec.Description = *request.Description
|
||||
}
|
||||
|
||||
patch := client.MergeFrom(ctg)
|
||||
data, err := patch.Data(ctgCopy)
|
||||
if err != nil {
|
||||
klog.Error("create patch failed", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.ctgClient.HelmCategories().Patch(context.TODO(), id, patch.Type(), data, metav1.PatchOptions{})
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
@@ -99,50 +160,33 @@ func (c *categoryOperator) ModifyCategory(id string, request *ModifyCategoryRequ
|
||||
}
|
||||
|
||||
func (c *categoryOperator) DescribeCategory(id string) (*Category, error) {
|
||||
resp, err := c.opClient.DescribeCategories(openpitrix.SystemContext(), &pb.DescribeCategoriesRequest{
|
||||
CategoryId: []string{id},
|
||||
Limit: 1,
|
||||
})
|
||||
var err error
|
||||
ctg := &v1alpha1.HelmCategory{}
|
||||
ctg, err = c.ctgClient.HelmCategories().Get(context.TODO(), id, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var category *Category
|
||||
|
||||
if len(resp.CategorySet) > 0 {
|
||||
category = convertCategory(resp.CategorySet[0])
|
||||
return category, nil
|
||||
} else {
|
||||
err := status.New(codes.NotFound, "resource not found").Err()
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
return convertCategory(ctg), nil
|
||||
}
|
||||
|
||||
func (c *categoryOperator) ListCategories(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
||||
req := &pb.DescribeCategoriesRequest{}
|
||||
|
||||
if keyword := conditions.Match[Keyword]; keyword != "" {
|
||||
req.SearchWord = &wrappers.StringValue{Value: keyword}
|
||||
}
|
||||
if orderBy != "" {
|
||||
req.SortKey = &wrappers.StringValue{Value: orderBy}
|
||||
}
|
||||
req.Reverse = &wrappers.BoolValue{Value: reverse}
|
||||
req.Limit = uint32(limit)
|
||||
req.Offset = uint32(offset)
|
||||
resp, err := c.opClient.DescribeCategories(openpitrix.SystemContext(), req)
|
||||
ctgs, err := c.ctgLister.List(labels.Everything())
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0)
|
||||
sort.Sort(HelmCategoryList(ctgs))
|
||||
|
||||
for _, item := range resp.CategorySet {
|
||||
items = append(items, convertCategory(item))
|
||||
items := make([]interface{}, 0, limit)
|
||||
for i, j := offset, 0; i < len(ctgs) && j < limit; {
|
||||
items = append(items, convertCategory(ctgs[i]))
|
||||
i++
|
||||
j++
|
||||
}
|
||||
|
||||
return &models.PageableResponse{Items: items, TotalCount: int(resp.TotalCount)}, nil
|
||||
return &models.PageableResponse{Items: items, TotalCount: len(ctgs)}, nil
|
||||
}
|
||||
|
||||
84
pkg/models/openpitrix/category_test.go
Normal file
84
pkg/models/openpitrix/category_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 openpitrix
|
||||
|
||||
import (
|
||||
"context"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
fakek8s "k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/klog"
|
||||
fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
|
||||
"kubesphere.io/kubesphere/pkg/informers"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOpenPitrixCategory(t *testing.T) {
|
||||
ctgOperator := prepareCategoryOperator()
|
||||
|
||||
ctgReq := &CreateCategoryRequest{
|
||||
Name: "test-ctg",
|
||||
}
|
||||
|
||||
// create category
|
||||
ctgResp, err := ctgOperator.CreateCategory(ctgReq)
|
||||
if err != nil {
|
||||
klog.Errorf("create category failed")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// add category to indexer
|
||||
ctgs, err := ksClient.ApplicationV1alpha1().HelmCategories().List(context.TODO(), metav1.ListOptions{})
|
||||
for _, ctg := range ctgs.Items {
|
||||
err := fakeInformerFactory.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmCategories().
|
||||
Informer().GetIndexer().Add(&ctg)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to add category to indexer")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// describe category
|
||||
cond := ¶ms.Conditions{}
|
||||
ctgList, err := ctgOperator.ListCategories(cond, "", false, 10, 0)
|
||||
if err != nil {
|
||||
klog.Errorf("list app failed, err: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if len(ctgList.Items) != 1 {
|
||||
klog.Errorf("list app failed")
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// describe category
|
||||
ctg, err := ctgOperator.DescribeCategory(ctgResp.CategoryId)
|
||||
if err != nil {
|
||||
klog.Errorf("describe app failed, err: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
_ = ctg
|
||||
|
||||
}
|
||||
|
||||
func prepareCategoryOperator() CategoryInterface {
|
||||
ksClient = fakeks.NewSimpleClientset()
|
||||
k8sClient = fakek8s.NewSimpleClientset()
|
||||
fakeInformerFactory = informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil, nil)
|
||||
|
||||
return newCategoryOperator(fakeInformerFactory.KubeSphereSharedInformerFactory(), ksClient)
|
||||
}
|
||||
35
pkg/models/openpitrix/errors.go
Normal file
35
pkg/models/openpitrix/errors.go
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 openpitrix
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
uploadChartDataFailed = errors.New("upload chart data failed")
|
||||
invalidS3Config = errors.New("invalid storage config")
|
||||
deleteDataInStorageFailed = errors.New("delete data in storage failed")
|
||||
repoItemExists = errors.New("repo exists")
|
||||
appItemExists = errors.New("application exists")
|
||||
appVersionItemExists = errors.New("application version exists")
|
||||
actionNotSupport = errors.New("action not support")
|
||||
actionNotPermitted = errors.New("action not permitted")
|
||||
|
||||
loadRepoInfoFailed = errors.New("load repo info failed")
|
||||
downloadFileFailed = errors.New("download file failed")
|
||||
readFileFailed = errors.New("read file failed")
|
||||
releaseExists = errors.New("release exists")
|
||||
)
|
||||
@@ -1,9 +1,12 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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.
|
||||
@@ -14,35 +17,70 @@ limitations under the License.
|
||||
package openpitrix
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/informers"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
ks_informers "kubesphere.io/kubesphere/pkg/informers"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/s3"
|
||||
"kubesphere.io/kubesphere/pkg/utils/reposcache"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
ApplicationInterface
|
||||
AppTemplateInterface
|
||||
AttachmentInterface
|
||||
CategoryInterface
|
||||
RepoInterface
|
||||
}
|
||||
type openpitrixOperator struct {
|
||||
ApplicationInterface
|
||||
AppTemplateInterface
|
||||
AttachmentInterface
|
||||
CategoryInterface
|
||||
RepoInterface
|
||||
ReleaseInterface
|
||||
CategoryInterface
|
||||
}
|
||||
|
||||
func NewOpenpitrixOperator(informers informers.SharedInformerFactory, opClient openpitrix.Client) Interface {
|
||||
if opClient == nil {
|
||||
return nil
|
||||
}
|
||||
type openpitrixOperator struct {
|
||||
AttachmentInterface
|
||||
ApplicationInterface
|
||||
RepoInterface
|
||||
ReleaseInterface
|
||||
CategoryInterface
|
||||
}
|
||||
|
||||
var cachedReposData reposcache.ReposCache
|
||||
var helmReposInformer cache.SharedIndexInformer
|
||||
var once sync.Once
|
||||
|
||||
func init() {
|
||||
cachedReposData = reposcache.NewReposCache()
|
||||
}
|
||||
|
||||
func NewOpenpitrixOperator(ksInformers ks_informers.InformerFactory, ksClient versioned.Interface, s3Client s3.Interface) Interface {
|
||||
|
||||
once.Do(func() {
|
||||
klog.Infof("start helm repo informer")
|
||||
helmReposInformer = ksInformers.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmRepos().Informer()
|
||||
helmReposInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
r := obj.(*v1alpha1.HelmRepo)
|
||||
cachedReposData.AddRepo(r)
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
oldR := oldObj.(*v1alpha1.HelmRepo)
|
||||
cachedReposData.DeleteRepo(oldR)
|
||||
r := newObj.(*v1alpha1.HelmRepo)
|
||||
cachedReposData.AddRepo(r)
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
r := obj.(*v1alpha1.HelmRepo)
|
||||
cachedReposData.DeleteRepo(r)
|
||||
},
|
||||
})
|
||||
go helmReposInformer.Run(wait.NeverStop)
|
||||
})
|
||||
|
||||
return &openpitrixOperator{
|
||||
ApplicationInterface: newApplicationOperator(informers, opClient),
|
||||
AppTemplateInterface: newAppTemplateOperator(opClient),
|
||||
AttachmentInterface: newAttachmentOperator(opClient),
|
||||
CategoryInterface: newCategoryOperator(opClient),
|
||||
RepoInterface: newRepoOperator(opClient),
|
||||
AttachmentInterface: newAttachmentOperator(s3Client),
|
||||
ApplicationInterface: newApplicationOperator(cachedReposData, ksInformers.KubeSphereSharedInformerFactory(), ksClient, s3Client),
|
||||
RepoInterface: newRepoOperator(cachedReposData, ksInformers.KubeSphereSharedInformerFactory(), ksClient),
|
||||
ReleaseInterface: newReleaseOperator(cachedReposData, ksInformers.KubernetesSharedInformerFactory(), ksInformers.KubeSphereSharedInformerFactory(), ksClient),
|
||||
CategoryInterface: newCategoryOperator(ksInformers.KubeSphereSharedInformerFactory(), ksClient),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 openpitrix
|
||||
|
||||
import (
|
||||
|
||||
417
pkg/models/openpitrix/release.go
Normal file
417
pkg/models/openpitrix/release.go
Normal file
@@ -0,0 +1,417 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 openpitrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-openapi/strfmt"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
typed_v1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
listers_v1alpha1 "kubesphere.io/kubesphere/pkg/client/listers/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix/helmwrapper"
|
||||
"kubesphere.io/kubesphere/pkg/utils/clusterclient"
|
||||
"kubesphere.io/kubesphere/pkg/utils/idutils"
|
||||
"kubesphere.io/kubesphere/pkg/utils/reposcache"
|
||||
"kubesphere.io/kubesphere/pkg/utils/resourceparse"
|
||||
"kubesphere.io/kubesphere/pkg/utils/stringutils"
|
||||
"math"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ReleaseInterface interface {
|
||||
ListApplications(workspace, clusterName, namespace string, conditions *params.Conditions, limit, offset int, orderBy string, reverse bool) (*models.PageableResponse, error)
|
||||
DescribeApplication(workspace, clusterName, namespace, applicationId string) (*Application, error)
|
||||
CreateApplication(workspace, clusterName, namespace string, request CreateClusterRequest) error
|
||||
ModifyApplication(request ModifyClusterAttributesRequest) error
|
||||
DeleteApplication(workspace, clusterName, namespace, id string) error
|
||||
UpgradeApplication(request UpgradeClusterRequest) error
|
||||
}
|
||||
|
||||
type releaseOperator struct {
|
||||
informers informers.SharedInformerFactory
|
||||
rlsClient typed_v1alpha1.HelmReleaseInterface
|
||||
rlsLister listers_v1alpha1.HelmReleaseLister
|
||||
appVersionLister listers_v1alpha1.HelmApplicationVersionLister
|
||||
cachedRepos reposcache.ReposCache
|
||||
clusterClients clusterclient.ClusterClients
|
||||
}
|
||||
|
||||
func newReleaseOperator(cached reposcache.ReposCache, k8sFactory informers.SharedInformerFactory, ksFactory externalversions.SharedInformerFactory, ksClient versioned.Interface) ReleaseInterface {
|
||||
c := &releaseOperator{
|
||||
informers: k8sFactory,
|
||||
rlsClient: ksClient.ApplicationV1alpha1().HelmReleases(),
|
||||
rlsLister: ksFactory.Application().V1alpha1().HelmReleases().Lister(),
|
||||
cachedRepos: cached,
|
||||
clusterClients: clusterclient.NewClusterClient(ksFactory.Cluster().V1alpha1().Clusters()),
|
||||
appVersionLister: ksFactory.Application().V1alpha1().HelmApplicationVersions().Lister(),
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
type Application struct {
|
||||
Name string `json:"name" description:"application name"`
|
||||
Cluster *Cluster `json:"cluster,omitempty" description:"application cluster info"`
|
||||
Version *AppVersion `json:"version,omitempty" description:"application template version info"`
|
||||
App *App `json:"app,omitempty" description:"application template info"`
|
||||
|
||||
ReleaseInfo []runtime.Object `json:"releaseInfo,omitempty" description:"release info"`
|
||||
}
|
||||
|
||||
func (c *releaseOperator) UpgradeApplication(request UpgradeClusterRequest) error {
|
||||
oldRls, err := c.rlsLister.Get(request.ClusterId)
|
||||
|
||||
// todo check namespace
|
||||
if err != nil {
|
||||
klog.Errorf("get release %s/%s failed, error: %s", request.Namespace, request.ClusterId, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if oldRls.Status.State != v1alpha1.HelmStatusActive {
|
||||
return errors.New("application is not active now")
|
||||
}
|
||||
|
||||
version, err := c.getAppVersion("", request.VersionId)
|
||||
if err != nil {
|
||||
klog.Errorf("get helm application version %s/%s failed, error: %s", request.AppId, request.VersionId, err)
|
||||
return err
|
||||
}
|
||||
|
||||
newRls := oldRls.DeepCopy()
|
||||
newRls.Spec.ApplicationId = request.AppId
|
||||
newRls.Spec.ApplicationVersionId = request.VersionId
|
||||
|
||||
newRls.Spec.Version += 1
|
||||
newRls.Spec.RepoId = version.GetHelmRepoId()
|
||||
newRls.Spec.ChartVersion = version.GetChartVersion()
|
||||
newRls.Spec.ChartAppVersion = version.GetChartAppVersion()
|
||||
// Use the new conf if the client has one, or server will just use the old conf.
|
||||
if request.Conf != "" {
|
||||
newRls.Spec.Values = strfmt.Base64(request.Conf)
|
||||
}
|
||||
|
||||
patch := client.MergeFrom(oldRls)
|
||||
data, err := patch.Data(newRls)
|
||||
|
||||
newRls, err = c.rlsClient.Patch(context.TODO(), request.ClusterId, patch.Type(), data, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
klog.Errorf("patch release %s/%s failed, error: %s", request.Namespace, request.ClusterId, err)
|
||||
return err
|
||||
} else {
|
||||
klog.V(2).Infof("patch release %s/%s success", request.Namespace, request.ClusterId)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// create all helm release in host cluster
|
||||
func (c *releaseOperator) CreateApplication(workspace, clusterName, namespace string, request CreateClusterRequest) error {
|
||||
version, err := c.getAppVersion("", request.VersionId)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("get helm application version %s failed, error: %v", request.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
exists, err := c.releaseExists(workspace, clusterName, namespace, request.Name)
|
||||
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Errorf("get helm release %s failed, error: %v", request.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if exists {
|
||||
err = fmt.Errorf("release %s exists", request.Name)
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
rls := &v1alpha1.HelmRelease{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: idutils.GetUuid36(v1alpha1.HelmReleasePrefix),
|
||||
Annotations: map[string]string{
|
||||
constants.CreatorAnnotationKey: request.Username,
|
||||
},
|
||||
Labels: map[string]string{
|
||||
constants.ChartApplicationVersionIdLabelKey: request.VersionId,
|
||||
constants.ChartApplicationIdLabelKey: strings.TrimSuffix(request.AppId, v1alpha1.HelmApplicationAppStoreSuffix),
|
||||
constants.WorkspaceLabelKey: request.Workspace,
|
||||
constants.NamespaceLabelKey: namespace,
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.HelmReleaseSpec{
|
||||
Name: request.Name,
|
||||
Version: 1,
|
||||
Values: strfmt.Base64(request.Conf),
|
||||
ApplicationId: strings.TrimSuffix(request.AppId, v1alpha1.HelmApplicationAppStoreSuffix),
|
||||
ApplicationVersionId: request.VersionId,
|
||||
ChartName: version.GetTrueName(),
|
||||
RepoId: version.GetHelmRepoId(),
|
||||
ChartVersion: version.GetChartVersion(),
|
||||
ChartAppVersion: version.GetChartAppVersion(),
|
||||
},
|
||||
}
|
||||
|
||||
if clusterName != "" {
|
||||
rls.Labels[constants.ClusterNameLabelKey] = clusterName
|
||||
}
|
||||
|
||||
if repoId := version.GetHelmRepoId(); repoId != "" {
|
||||
rls.Labels[constants.ChartRepoIdLabelKey] = repoId
|
||||
}
|
||||
|
||||
rls, err = c.rlsClient.Create(context.TODO(), rls, metav1.CreateOptions{})
|
||||
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
return err
|
||||
} else {
|
||||
klog.Infof("create helm release %s success in %s", request.Name, namespace)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *releaseOperator) releaseExists(workspace, clusterName, namespace, name string) (bool, error) {
|
||||
set := map[string]string{
|
||||
constants.WorkspaceLabelKey: workspace,
|
||||
constants.NamespaceLabelKey: namespace,
|
||||
}
|
||||
if clusterName != "" {
|
||||
set[constants.ClusterNameLabelKey] = clusterName
|
||||
}
|
||||
|
||||
list, err := c.rlsLister.List(labels.SelectorFromSet(set))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, rls := range list {
|
||||
if rls.Spec.Name == name {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *releaseOperator) ModifyApplication(request ModifyClusterAttributesRequest) error {
|
||||
|
||||
if request.Description == nil || len(*request.Description) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rls, err := c.rlsLister.Get(request.ClusterID)
|
||||
if err != nil {
|
||||
klog.Errorf("get release failed, error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
rlsCopy := rls.DeepCopy()
|
||||
rlsCopy.Spec.Description = stringutils.ShortenString(strings.TrimSpace(*request.Description), v1alpha1.MsgLen)
|
||||
|
||||
pt := client.MergeFrom(rls)
|
||||
|
||||
data, err := pt.Data(rlsCopy)
|
||||
if err != nil {
|
||||
klog.Errorf("create patch failed, error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.rlsClient.Patch(context.TODO(), request.ClusterID, pt.Type(), data, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
klog.Errorln(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *releaseOperator) ListApplications(workspace, clusterName, namespace string, conditions *params.Conditions, limit, offset int, orderBy string, reverse bool) (*models.PageableResponse, error) {
|
||||
appId := conditions.Match[AppId]
|
||||
versionId := conditions.Match[VersionId]
|
||||
ls := map[string]string{}
|
||||
if appId != "" {
|
||||
ls[constants.ChartApplicationIdLabelKey] = strings.TrimSuffix(appId, v1alpha1.HelmApplicationAppStoreSuffix)
|
||||
}
|
||||
|
||||
if versionId != "" {
|
||||
ls[constants.ChartApplicationVersionIdLabelKey] = versionId
|
||||
}
|
||||
|
||||
if workspace != "" {
|
||||
ls[constants.WorkspaceLabelKey] = workspace
|
||||
}
|
||||
if namespace != "" {
|
||||
ls[constants.NamespaceLabelKey] = namespace
|
||||
}
|
||||
if clusterName != "" {
|
||||
ls[constants.ClusterNameLabelKey] = clusterName
|
||||
}
|
||||
|
||||
releases, err := c.rlsLister.List(labels.SelectorFromSet(ls))
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Errorf("list app release failed, error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
releases = filterReleases(releases, conditions)
|
||||
|
||||
// only show release whose app versions are active or suspended
|
||||
if versionId == "" && strings.HasSuffix(appId, v1alpha1.HelmApplicationAppStoreSuffix) {
|
||||
stripId := strings.TrimSuffix(appId, v1alpha1.HelmApplicationAppStoreSuffix)
|
||||
versions, err := c.appVersionLister.List(labels.SelectorFromSet(map[string]string{constants.ChartApplicationIdLabelKey: stripId}))
|
||||
if err != nil {
|
||||
klog.Errorf("list app version failed, error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
versions = filterAppVersionByState(versions, []string{v1alpha1.StateActive, v1alpha1.StateSuspended})
|
||||
versionMap := make(map[string]*v1alpha1.HelmApplicationVersion)
|
||||
for _, version := range versions {
|
||||
versionMap[version.Name] = version
|
||||
}
|
||||
releases = filterReleasesWithAppVersions(releases, versionMap)
|
||||
}
|
||||
|
||||
if reverse {
|
||||
sort.Sort(sort.Reverse(HelmReleaseList(releases)))
|
||||
} else {
|
||||
sort.Sort(HelmReleaseList(releases))
|
||||
}
|
||||
|
||||
result := models.PageableResponse{TotalCount: len(releases)}
|
||||
result.Items = make([]interface{}, 0, int(math.Min(float64(limit), float64(len(releases)))))
|
||||
|
||||
for i, j := offset, 0; i < len(releases) && j < limit; {
|
||||
app := convertApplication(releases[i], nil)
|
||||
result.Items = append(result.Items, app)
|
||||
i++
|
||||
j++
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (c *releaseOperator) DescribeApplication(workspace, clusterName, namespace, applicationId string) (*Application, error) {
|
||||
|
||||
rls, err := c.rlsLister.Get(applicationId)
|
||||
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Errorf("list helm release failed, error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
app := &Application{}
|
||||
if rls != nil && rls.GetRlsCluster() != "" {
|
||||
// TODO check clusterName, workspace, namespace
|
||||
clusterConfig, err := c.clusterClients.GetClusterKubeconfig(rls.GetRlsCluster())
|
||||
if err != nil {
|
||||
klog.Errorf("get cluster config failed, error: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
// If clusterConfig is empty, this application will be installed in current host.
|
||||
hw := helmwrapper.NewHelmWrapper(clusterConfig, namespace, rls.Spec.Name)
|
||||
manifest, err := hw.Manifest()
|
||||
if err != nil {
|
||||
klog.Errorf("get manifest failed, error: %s", err)
|
||||
}
|
||||
infos, err := resourceparse.Parse(bytes.NewBufferString(manifest), namespace, rls.Spec.Name, true)
|
||||
if err != nil {
|
||||
klog.Errorf("parse resource failed, error: %s", err)
|
||||
}
|
||||
app = convertApplication(rls, infos)
|
||||
}
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func (c *releaseOperator) DeleteApplication(workspace, clusterName, namespace, id string) error {
|
||||
|
||||
rls, err := c.rlsLister.Get(id)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
klog.Errorf("get release %s/%s failed, err: %s", namespace, id, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO, check workspace, cluster and namespace
|
||||
if rls.GetWorkspace() != workspace || rls.GetRlsCluster() != clusterName || rls.GetRlsNamespace() != namespace {
|
||||
}
|
||||
|
||||
err = c.rlsClient.Delete(context.TODO(), id, metav1.DeleteOptions{})
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("delete release %s/%s failed, error: %s", namespace, id, err)
|
||||
return err
|
||||
} else {
|
||||
klog.V(2).Infof("delete release %s/%s", namespace, id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// get app version from repo and helm application
|
||||
func (c *releaseOperator) getAppVersion(repoId, id string) (ret *v1alpha1.HelmApplicationVersion, err error) {
|
||||
if ver, exists, _ := c.cachedRepos.GetAppVersion(id); exists {
|
||||
return ver, nil
|
||||
}
|
||||
|
||||
if repoId != "" && repoId != v1alpha1.AppStoreRepoId {
|
||||
return nil, fmt.Errorf("app version not found")
|
||||
}
|
||||
ret, err = c.appVersionLister.Get(id)
|
||||
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// get app version from repo and helm application
|
||||
func (c *releaseOperator) getAppVersionWithData(repoId, id string) (ret *v1alpha1.HelmApplicationVersion, err error) {
|
||||
if ver, exists, _ := c.cachedRepos.GetAppVersionWithData(id); exists {
|
||||
return ver, nil
|
||||
}
|
||||
|
||||
if repoId != "" && repoId != v1alpha1.AppStoreRepoId {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
ret, err = c.appVersionLister.Get(id)
|
||||
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
127
pkg/models/openpitrix/release_test.go
Normal file
127
pkg/models/openpitrix/release_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 openpitrix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"github.com/go-openapi/strfmt"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOpenPitrixRelease(t *testing.T) {
|
||||
appOperator := prepareAppOperator()
|
||||
|
||||
chartData, _ := base64.RawStdEncoding.DecodeString(rawChartData)
|
||||
|
||||
appReq := &CreateAppRequest{
|
||||
Isv: testWorkspace,
|
||||
Name: "test-chart",
|
||||
VersionName: "0.1.0",
|
||||
VersionPackage: strfmt.Base64(chartData),
|
||||
}
|
||||
|
||||
// create app
|
||||
createAppResp, err := appOperator.CreateApp(appReq)
|
||||
if err != nil {
|
||||
klog.Errorf("create app failed")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// add app to indexer
|
||||
apps, err := ksClient.ApplicationV1alpha1().HelmApplications().List(context.TODO(), metav1.ListOptions{})
|
||||
for _, app := range apps.Items {
|
||||
err := fakeInformerFactory.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmApplications().
|
||||
Informer().GetIndexer().Add(&app)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to add app to indexer")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// add app version to indexer
|
||||
appvers, err := ksClient.ApplicationV1alpha1().HelmApplicationVersions().List(context.TODO(), metav1.ListOptions{})
|
||||
for _, ver := range appvers.Items {
|
||||
err := fakeInformerFactory.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmApplicationVersions().
|
||||
Informer().GetIndexer().Add(&ver)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to add app version to indexer")
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
rlsOperator := newReleaseOperator(cachedReposData, fakeInformerFactory.KubernetesSharedInformerFactory(), fakeInformerFactory.KubeSphereSharedInformerFactory(), ksClient)
|
||||
|
||||
req := CreateClusterRequest{
|
||||
Name: "test-rls",
|
||||
AppId: createAppResp.AppID,
|
||||
VersionId: createAppResp.VersionID,
|
||||
Workspace: testWorkspace,
|
||||
}
|
||||
err = rlsOperator.CreateApplication(testWorkspace, "", "default", req)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("create release failed, error: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// add app version to indexer
|
||||
rls, err := ksClient.ApplicationV1alpha1().HelmReleases().List(context.TODO(), metav1.ListOptions{})
|
||||
for _, item := range rls.Items {
|
||||
err := fakeInformerFactory.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmReleases().
|
||||
Informer().GetIndexer().Add(&item)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to add release to indexer")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
cond := ¶ms.Conditions{Match: map[string]string{
|
||||
WorkspaceLabel: testWorkspace,
|
||||
}}
|
||||
rlsList, err := rlsOperator.ListApplications(testWorkspace, "", "default", cond, 10, 0, "", false)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("failed to list release, error: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
var rlsId string
|
||||
for _, item := range rlsList.Items {
|
||||
app := item.(*Application)
|
||||
rlsId = app.Cluster.ClusterId
|
||||
break
|
||||
}
|
||||
|
||||
//describe release
|
||||
describeRls, err := rlsOperator.DescribeApplication(testWorkspace, "", "default", rlsId)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to describe release, error: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
_ = describeRls
|
||||
|
||||
//delete release
|
||||
err = rlsOperator.DeleteApplication(testWorkspace, "", "default", rlsId)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to delete release, error: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
112
pkg/models/openpitrix/repo_test.go
Normal file
112
pkg/models/openpitrix/repo_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 openpitrix
|
||||
|
||||
import (
|
||||
"context"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
fakek8s "k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
fakeks "kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/informers"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/utils/idutils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOpenPitrixRepo(t *testing.T) {
|
||||
repoOperator := prepareRepoOperator()
|
||||
|
||||
repo := v1alpha1.HelmRepo{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: idutils.GetUuid36(v1alpha1.HelmRepoIdPrefix),
|
||||
|
||||
Labels: map[string]string{
|
||||
constants.WorkspaceLabelKey: testWorkspace,
|
||||
},
|
||||
},
|
||||
Spec: v1alpha1.HelmRepoSpec{
|
||||
Name: "test-repo",
|
||||
Url: "https://charts.kubesphere.io/main",
|
||||
SyncPeriod: 0,
|
||||
},
|
||||
}
|
||||
|
||||
// validate repo
|
||||
validateRes, err := repoOperator.ValidateRepo(repo.Spec.Url, &repo.Spec.Credential)
|
||||
if err != nil || validateRes.Ok == false {
|
||||
klog.Errorf("validate category failed, error: %s", err)
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// validate the corrupt repo
|
||||
validateRes, err = repoOperator.ValidateRepo("http://www.baidu.com", &repo.Spec.Credential)
|
||||
if err == nil {
|
||||
klog.Errorf("validate category failed")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// create repo
|
||||
repoResp, err := repoOperator.CreateRepo(&repo)
|
||||
if err != nil {
|
||||
klog.Errorf("create category failed")
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// add category to indexer
|
||||
repos, err := ksClient.ApplicationV1alpha1().HelmRepos().List(context.TODO(), metav1.ListOptions{})
|
||||
for _, repo := range repos.Items {
|
||||
err := fakeInformerFactory.KubeSphereSharedInformerFactory().Application().V1alpha1().HelmRepos().
|
||||
Informer().GetIndexer().Add(&repo)
|
||||
if err != nil {
|
||||
klog.Errorf("failed to add repo to indexer")
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// list repo
|
||||
cond := ¶ms.Conditions{Match: map[string]string{WorkspaceLabel: testWorkspace}}
|
||||
repoList, err := repoOperator.ListRepos(cond, "", false, 10, 0)
|
||||
if err != nil {
|
||||
klog.Errorf("list repo failed, err: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if len(repoList.Items) != 1 {
|
||||
klog.Errorf("list repo failed")
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// describe repo
|
||||
describeRepo, err := repoOperator.DescribeRepo(repoResp.RepoID)
|
||||
if err != nil {
|
||||
klog.Errorf("describe app failed, err: %s", err)
|
||||
t.FailNow()
|
||||
}
|
||||
_ = describeRepo
|
||||
|
||||
}
|
||||
|
||||
func prepareRepoOperator() RepoInterface {
|
||||
ksClient = fakeks.NewSimpleClientset()
|
||||
k8sClient = fakek8s.NewSimpleClientset()
|
||||
fakeInformerFactory = informers.NewInformerFactories(k8sClient, ksClient, nil, nil, nil, nil)
|
||||
|
||||
return newRepoOperator(cachedReposData, fakeInformerFactory.KubeSphereSharedInformerFactory(), ksClient)
|
||||
}
|
||||
@@ -14,118 +14,185 @@ limitations under the License.
|
||||
package openpitrix
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/golang/protobuf/ptypes/wrappers"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"github.com/go-openapi/strfmt"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned"
|
||||
typed_v1alpha1 "kubesphere.io/kubesphere/pkg/client/clientset/versioned/typed/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
listers_v1alpha1 "kubesphere.io/kubesphere/pkg/client/listers/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
"kubesphere.io/kubesphere/pkg/models"
|
||||
"kubesphere.io/kubesphere/pkg/server/params"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
|
||||
"openpitrix.io/openpitrix/pkg/pb"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix/helmrepoindex"
|
||||
"kubesphere.io/kubesphere/pkg/utils/reposcache"
|
||||
"kubesphere.io/kubesphere/pkg/utils/stringutils"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const DescriptionLen = 512
|
||||
|
||||
type RepoInterface interface {
|
||||
CreateRepo(request *CreateRepoRequest) (*CreateRepoResponse, error)
|
||||
CreateRepo(repo *v1alpha1.HelmRepo) (*CreateRepoResponse, error)
|
||||
DeleteRepo(id string) error
|
||||
ValidateRepo(u string, request *v1alpha1.HelmRepoCredential) (*ValidateRepoResponse, error)
|
||||
ModifyRepo(id string, request *ModifyRepoRequest) error
|
||||
DescribeRepo(id string) (*Repo, error)
|
||||
ListRepos(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
||||
ValidateRepo(request *ValidateRepoRequest) (*ValidateRepoResponse, error)
|
||||
DoRepoAction(repoId string, request *RepoActionRequest) error
|
||||
ListEvents(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
|
||||
ListRepoEvents(repoId string, conditions *params.Conditions, limit, offset int) (*models.PageableResponse, error)
|
||||
}
|
||||
|
||||
type repoOperator struct {
|
||||
opClient openpitrix.Client
|
||||
cachedRepos reposcache.ReposCache
|
||||
informers externalversions.SharedInformerFactory
|
||||
repoClient typed_v1alpha1.ApplicationV1alpha1Interface
|
||||
repoLister listers_v1alpha1.HelmRepoLister
|
||||
rlsLister listers_v1alpha1.HelmReleaseLister
|
||||
}
|
||||
|
||||
func newRepoOperator(opClient openpitrix.Client) RepoInterface {
|
||||
func newRepoOperator(cachedRepos reposcache.ReposCache, informers externalversions.SharedInformerFactory, ksClient versioned.Interface) RepoInterface {
|
||||
return &repoOperator{
|
||||
opClient: opClient,
|
||||
cachedRepos: cachedRepos,
|
||||
informers: informers,
|
||||
repoClient: ksClient.ApplicationV1alpha1(),
|
||||
repoLister: informers.Application().V1alpha1().HelmRepos().Lister(),
|
||||
rlsLister: informers.Application().V1alpha1().HelmReleases().Lister(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *repoOperator) CreateRepo(request *CreateRepoRequest) (*CreateRepoResponse, error) {
|
||||
createRepoRequest := &pb.CreateRepoRequest{
|
||||
Name: &wrappers.StringValue{Value: request.Name},
|
||||
Description: &wrappers.StringValue{Value: request.Description},
|
||||
Type: &wrappers.StringValue{Value: request.Type},
|
||||
Url: &wrappers.StringValue{Value: request.URL},
|
||||
Credential: &wrappers.StringValue{Value: request.Credential},
|
||||
Visibility: &wrappers.StringValue{Value: request.Visibility},
|
||||
CategoryId: &wrappers.StringValue{Value: request.CategoryId},
|
||||
AppDefaultStatus: &wrappers.StringValue{Value: request.AppDefaultStatus},
|
||||
}
|
||||
|
||||
if request.Providers != nil {
|
||||
createRepoRequest.Providers = request.Providers
|
||||
}
|
||||
if request.Workspace != nil {
|
||||
createRepoRequest.Labels = &wrappers.StringValue{Value: fmt.Sprintf("workspace=%s", *request.Workspace)}
|
||||
}
|
||||
|
||||
resp, err := c.opClient.CreateRepo(openpitrix.SystemContext(), createRepoRequest)
|
||||
// TODO implement DoRepoAction
|
||||
func (c *repoOperator) DoRepoAction(repoId string, request *RepoActionRequest) error {
|
||||
repo, err := c.repoLister.Get(repoId)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if request.Workspace != repo.GetWorkspace() {
|
||||
return nil
|
||||
}
|
||||
return &CreateRepoResponse{
|
||||
RepoID: resp.GetRepoId().GetValue(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *repoOperator) DeleteRepo(id string) error {
|
||||
_, err := c.opClient.DeleteRepos(openpitrix.SystemContext(), &pb.DeleteReposRequest{
|
||||
RepoId: []string{id},
|
||||
})
|
||||
patch := client.MergeFrom(repo)
|
||||
copyRepo := repo.DeepCopy()
|
||||
copyRepo.Spec.Version += 1
|
||||
data, err := patch.Data(copyRepo)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
klog.Errorf("create patch [%s] failed, error: %s", repoId, err)
|
||||
return err
|
||||
}
|
||||
repo, err = c.repoClient.HelmRepos().Patch(context.TODO(), repoId, types.MergePatchType, data, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
klog.Errorf("patch repo [%s] failed, error: %s", repoId, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *repoOperator) ModifyRepo(id string, request *ModifyRepoRequest) error {
|
||||
modifyRepoRequest := &pb.ModifyRepoRequest{
|
||||
RepoId: &wrappers.StringValue{Value: id},
|
||||
func (c *repoOperator) ValidateRepo(u string, cred *v1alpha1.HelmRepoCredential) (*ValidateRepoResponse, error) {
|
||||
_, err := helmrepoindex.LoadRepoIndex(context.TODO(), u, cred)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ValidateRepoResponse{Ok: true}, nil
|
||||
}
|
||||
|
||||
func (c *repoOperator) CreateRepo(repo *v1alpha1.HelmRepo) (*CreateRepoResponse, error) {
|
||||
name := repo.GetTrueName()
|
||||
|
||||
items, err := c.repoLister.List(labels.SelectorFromSet(map[string]string{constants.WorkspaceLabelKey: repo.GetWorkspace()}))
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Errorf("list helm repo failed: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if request.Name != nil {
|
||||
modifyRepoRequest.Name = &wrappers.StringValue{Value: *request.Name}
|
||||
for _, exists := range items {
|
||||
if exists.GetTrueName() == name {
|
||||
klog.Error(repoItemExists, "name: ", name)
|
||||
return nil, repoItemExists
|
||||
}
|
||||
}
|
||||
|
||||
repo.Spec.Description = stringutils.ShortenString(repo.Spec.Description, DescriptionLen)
|
||||
_, err = c.repoClient.HelmRepos().Create(context.TODO(), repo, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
klog.Errorf("create helm repo failed, repod_id: %s, error: %s", repo.GetHelmRepoId(), err)
|
||||
return nil, err
|
||||
} else {
|
||||
klog.V(4).Infof("create helm repo success, repo_id: %s", repo.GetHelmRepoId())
|
||||
}
|
||||
|
||||
return &CreateRepoResponse{repo.GetHelmRepoId()}, nil
|
||||
}
|
||||
|
||||
func (c *repoOperator) DeleteRepo(id string) error {
|
||||
ls := map[string]string{
|
||||
constants.ChartRepoIdLabelKey: id,
|
||||
}
|
||||
releases, err := c.rlsLister.List(labels.SelectorFromSet(ls))
|
||||
|
||||
if err != nil && apierrors.IsNotFound(err) {
|
||||
return err
|
||||
} else if len(releases) > 0 {
|
||||
return fmt.Errorf("repo %s has releases not deleted", id)
|
||||
}
|
||||
|
||||
err = c.repoClient.HelmRepos().Delete(context.TODO(), id, metav1.DeleteOptions{})
|
||||
if err != nil && apierrors.IsNotFound(err) {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
klog.V(4).Infof("repo %s deleted", id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *repoOperator) ModifyRepo(id string, request *ModifyRepoRequest) error {
|
||||
repo, err := c.repoClient.HelmRepos().Get(context.TODO(), id, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
klog.Error("get repo failed", err)
|
||||
return err
|
||||
}
|
||||
|
||||
repoCopy := repo.DeepCopy()
|
||||
if request.Description != nil {
|
||||
modifyRepoRequest.Description = &wrappers.StringValue{Value: *request.Description}
|
||||
}
|
||||
if request.Type != nil {
|
||||
modifyRepoRequest.Type = &wrappers.StringValue{Value: *request.Type}
|
||||
repoCopy.Spec.Description = stringutils.ShortenString(*request.Description, DescriptionLen)
|
||||
}
|
||||
if request.URL != nil {
|
||||
modifyRepoRequest.Url = &wrappers.StringValue{Value: *request.URL}
|
||||
}
|
||||
if request.Credential != nil {
|
||||
modifyRepoRequest.Credential = &wrappers.StringValue{Value: *request.Credential}
|
||||
}
|
||||
if request.Visibility != nil {
|
||||
modifyRepoRequest.Visibility = &wrappers.StringValue{Value: *request.Visibility}
|
||||
repoCopy.Spec.Url = *request.URL
|
||||
}
|
||||
|
||||
if request.CategoryID != nil {
|
||||
modifyRepoRequest.CategoryId = &wrappers.StringValue{Value: *request.CategoryID}
|
||||
}
|
||||
if request.AppDefaultStatus != nil {
|
||||
modifyRepoRequest.AppDefaultStatus = &wrappers.StringValue{Value: *request.AppDefaultStatus}
|
||||
}
|
||||
if request.Providers != nil {
|
||||
modifyRepoRequest.Providers = request.Providers
|
||||
// TODO modify credential
|
||||
if request.Name != nil {
|
||||
repoCopy.Labels[constants.NameLabelKey] = *request.Name
|
||||
}
|
||||
if request.Workspace != nil {
|
||||
modifyRepoRequest.Labels = &wrappers.StringValue{Value: fmt.Sprintf("workspace=%s", *request.Workspace)}
|
||||
repoCopy.Labels[constants.WorkspaceLabelKey] = *request.Workspace
|
||||
}
|
||||
|
||||
_, err := c.opClient.ModifyRepo(openpitrix.SystemContext(), modifyRepoRequest)
|
||||
patch := client.MergeFrom(repo)
|
||||
repoCopy.Spec.Version += 1
|
||||
data, err := patch.Data(repoCopy)
|
||||
if err != nil {
|
||||
klog.Error("create patch failed", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// data == "{}", need not to patch
|
||||
if len(data) == 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
repo, err = c.repoClient.HelmRepos().Patch(context.TODO(), id, patch.Type(), data, metav1.PatchOptions{})
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
@@ -134,164 +201,98 @@ func (c *repoOperator) ModifyRepo(id string, request *ModifyRepoRequest) error {
|
||||
}
|
||||
|
||||
func (c *repoOperator) DescribeRepo(id string) (*Repo, error) {
|
||||
resp, err := c.opClient.DescribeRepos(openpitrix.SystemContext(), &pb.DescribeReposRequest{
|
||||
RepoId: []string{id},
|
||||
Limit: 1,
|
||||
})
|
||||
repo, err := c.repoClient.HelmRepos().Get(context.TODO(), id, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var repo *Repo
|
||||
var desRepo Repo
|
||||
|
||||
if len(resp.RepoSet) > 0 {
|
||||
repo = convertRepo(resp.RepoSet[0])
|
||||
return repo, nil
|
||||
} else {
|
||||
err := status.New(codes.NotFound, "resource not found").Err()
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
desRepo.URL = repo.Spec.Url
|
||||
desRepo.Description = repo.Spec.Description
|
||||
desRepo.Name = repo.GetTrueName()
|
||||
desRepo.RepoId = repo.Name
|
||||
dt, _ := strfmt.ParseDateTime(repo.CreationTimestamp.String())
|
||||
desRepo.CreateTime = &dt
|
||||
|
||||
return &desRepo, nil
|
||||
}
|
||||
|
||||
func (c *repoOperator) ListRepos(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
||||
req := &pb.DescribeReposRequest{}
|
||||
|
||||
if keyword := conditions.Match[Keyword]; keyword != "" {
|
||||
req.SearchWord = &wrappers.StringValue{Value: keyword}
|
||||
}
|
||||
if status := conditions.Match[Status]; status != "" {
|
||||
req.Status = strings.Split(status, "|")
|
||||
}
|
||||
if typeStr := conditions.Match[Type]; typeStr != "" {
|
||||
req.Type = strings.Split(typeStr, "|")
|
||||
}
|
||||
if visibility := conditions.Match[Visibility]; visibility != "" {
|
||||
req.Visibility = strings.Split(visibility, "|")
|
||||
}
|
||||
if status := conditions.Match[Status]; status != "" {
|
||||
req.Status = strings.Split(status, "|")
|
||||
}
|
||||
if workspace := conditions.Match[WorkspaceLabel]; workspace != "" {
|
||||
req.Label = &wrappers.StringValue{Value: fmt.Sprintf("workspace=%s", workspace)}
|
||||
}
|
||||
if orderBy != "" {
|
||||
req.SortKey = &wrappers.StringValue{Value: orderBy}
|
||||
}
|
||||
req.Reverse = &wrappers.BoolValue{Value: reverse}
|
||||
req.Limit = uint32(limit)
|
||||
req.Offset = uint32(offset)
|
||||
resp, err := c.opClient.DescribeRepos(openpitrix.SystemContext(), req)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
ls := labels.NewSelector()
|
||||
r, _ := labels.NewRequirement(constants.WorkspaceLabelKey, selection.Equals, []string{conditions.Match[WorkspaceLabel]})
|
||||
ls = ls.Add([]labels.Requirement{*r}...)
|
||||
|
||||
items := make([]interface{}, 0)
|
||||
|
||||
for _, item := range resp.RepoSet {
|
||||
items = append(items, convertRepo(item))
|
||||
}
|
||||
|
||||
return &models.PageableResponse{Items: items, TotalCount: int(resp.TotalCount)}, nil
|
||||
}
|
||||
|
||||
func (c *repoOperator) ValidateRepo(request *ValidateRepoRequest) (*ValidateRepoResponse, error) {
|
||||
resp, err := c.opClient.ValidateRepo(openpitrix.SystemContext(), &pb.ValidateRepoRequest{
|
||||
Type: &wrappers.StringValue{Value: request.Type},
|
||||
Credential: &wrappers.StringValue{Value: request.Credential},
|
||||
Url: &wrappers.StringValue{Value: request.Url},
|
||||
})
|
||||
repos, err := c.repoLister.List(ls)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if conditions.Match[Keyword] != "" {
|
||||
repos = helmRepoFilter(conditions.Match[Keyword], repos)
|
||||
}
|
||||
|
||||
return &ValidateRepoResponse{
|
||||
ErrorCode: int64(resp.ErrorCode),
|
||||
Ok: resp.Ok.Value,
|
||||
}, nil
|
||||
if reverse {
|
||||
sort.Sort(sort.Reverse(HelmRepoList(repos)))
|
||||
} else {
|
||||
sort.Sort(HelmRepoList(repos))
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0, limit)
|
||||
for i, j := offset, 0; i < len(repos) && j < limit; {
|
||||
items = append(items, convertRepo(repos[i]))
|
||||
i++
|
||||
j++
|
||||
}
|
||||
return &models.PageableResponse{Items: items, TotalCount: len(repos)}, nil
|
||||
}
|
||||
|
||||
func (c *repoOperator) DoRepoAction(repoId string, request *RepoActionRequest) error {
|
||||
var err error
|
||||
switch request.Action {
|
||||
case ActionIndex:
|
||||
indexRepoRequest := &pb.IndexRepoRequest{
|
||||
RepoId: &wrappers.StringValue{Value: repoId},
|
||||
func helmRepoFilter(namePrefix string, list []*v1alpha1.HelmRepo) (res []*v1alpha1.HelmRepo) {
|
||||
for _, repo := range list {
|
||||
name := repo.GetTrueName()
|
||||
if strings.HasPrefix(name, namePrefix) {
|
||||
res = append(res, repo)
|
||||
}
|
||||
_, err := c.opClient.IndexRepo(openpitrix.SystemContext(), indexRepoRequest)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return err
|
||||
}
|
||||
type HelmRepoList []*v1alpha1.HelmRepo
|
||||
|
||||
return nil
|
||||
default:
|
||||
err = status.New(codes.InvalidArgument, "action not support").Err()
|
||||
klog.Error(err)
|
||||
return err
|
||||
func (l HelmRepoList) Len() int { return len(l) }
|
||||
func (l HelmRepoList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
func (l HelmRepoList) Less(i, j int) bool {
|
||||
t1 := l[i].CreationTimestamp.UnixNano()
|
||||
t2 := l[j].CreationTimestamp.UnixNano()
|
||||
if t1 < t2 {
|
||||
return true
|
||||
} else if t1 > t2 {
|
||||
return false
|
||||
} else {
|
||||
n1 := l[i].GetTrueName()
|
||||
n2 := l[j].GetTrueName()
|
||||
return n1 < n2
|
||||
}
|
||||
}
|
||||
|
||||
func (c *repoOperator) ListRepoEvents(repoId string, conditions *params.Conditions, limit, offset int) (*models.PageableResponse, error) {
|
||||
describeRepoEventsRequest := &pb.DescribeRepoEventsRequest{
|
||||
RepoId: []string{repoId},
|
||||
}
|
||||
if eventId := conditions.Match["repo_event_id"]; eventId != "" {
|
||||
describeRepoEventsRequest.RepoEventId = strings.Split(eventId, "|")
|
||||
}
|
||||
if status := conditions.Match["status"]; status != "" {
|
||||
describeRepoEventsRequest.Status = strings.Split(status, "|")
|
||||
}
|
||||
describeRepoEventsRequest.Limit = uint32(limit)
|
||||
describeRepoEventsRequest.Offset = uint32(offset)
|
||||
|
||||
resp, err := c.opClient.DescribeRepoEvents(openpitrix.SystemContext(), describeRepoEventsRequest)
|
||||
repo, err := c.repoClient.HelmRepos().Get(context.TODO(), repoId, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0)
|
||||
|
||||
for _, item := range resp.RepoEventSet {
|
||||
items = append(items, convertRepoEvent(item))
|
||||
items := make([]interface{}, 0, limit)
|
||||
for i, j := offset, 0; i < len(repo.Status.SyncState) && j < limit; {
|
||||
items = append(items, convertRepoEvent(&repo.ObjectMeta, &repo.Status.SyncState[j]))
|
||||
i++
|
||||
j++
|
||||
}
|
||||
|
||||
return &models.PageableResponse{Items: items, TotalCount: int(resp.TotalCount)}, nil
|
||||
}
|
||||
|
||||
func (c *repoOperator) ListEvents(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
|
||||
describeRepoEventsRequest := &pb.DescribeRepoEventsRequest{}
|
||||
if repoId := conditions.Match["repo_id"]; repoId != "" {
|
||||
describeRepoEventsRequest.RepoId = strings.Split(repoId, "|")
|
||||
}
|
||||
if eventId := conditions.Match["repo_event_id"]; eventId != "" {
|
||||
describeRepoEventsRequest.RepoEventId = strings.Split(eventId, "|")
|
||||
}
|
||||
if status := conditions.Match["status"]; status != "" {
|
||||
describeRepoEventsRequest.Status = strings.Split(status, "|")
|
||||
}
|
||||
describeRepoEventsRequest.Limit = uint32(limit)
|
||||
describeRepoEventsRequest.Offset = uint32(offset)
|
||||
|
||||
resp, err := c.opClient.DescribeRepoEvents(openpitrix.SystemContext(), describeRepoEventsRequest)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0)
|
||||
|
||||
for _, item := range resp.RepoEventSet {
|
||||
items = append(items, convertRepoEvent(item))
|
||||
}
|
||||
|
||||
return &models.PageableResponse{Items: items, TotalCount: int(resp.TotalCount)}, nil
|
||||
return &models.PageableResponse{Items: items, TotalCount: len(repo.Status.SyncState)}, nil
|
||||
}
|
||||
|
||||
@@ -576,7 +576,7 @@ type CreateRepoRequest struct {
|
||||
// repository description
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
// workspace
|
||||
// If workspace is empty, then it's a global repo
|
||||
Workspace *string `json:"workspace,omitempty"`
|
||||
|
||||
// required, repository name
|
||||
@@ -629,7 +629,8 @@ type ModifyRepoRequest struct {
|
||||
}
|
||||
|
||||
type RepoActionRequest struct {
|
||||
Action string `json:"action"`
|
||||
Action string `json:"action"`
|
||||
Workspace string `json:"workspace"`
|
||||
}
|
||||
|
||||
type ValidateRepoRequest struct {
|
||||
@@ -663,6 +664,8 @@ type RepoLabels []*RepoLabel
|
||||
type RepoSelectors []*RepoSelector
|
||||
|
||||
type Repo struct {
|
||||
ChartCount int `json:"chart_count,omitempty"`
|
||||
|
||||
// app default status eg[active|draft]
|
||||
AppDefaultStatus string `json:"app_default_status,omitempty"`
|
||||
|
||||
@@ -732,6 +735,9 @@ type ValidateRepoResponse struct {
|
||||
|
||||
type CreateClusterRequest struct {
|
||||
|
||||
// release name
|
||||
Name string `json:"name"`
|
||||
|
||||
// advanced param
|
||||
AdvancedParam []string `json:"advanced_param"`
|
||||
|
||||
@@ -748,19 +754,28 @@ type CreateClusterRequest struct {
|
||||
VersionId string `json:"version_id,omitempty"`
|
||||
|
||||
Username string `json:"-"`
|
||||
|
||||
// current workspace
|
||||
Workspace string `json:"workspace,omitempty"`
|
||||
}
|
||||
|
||||
type UpgradeClusterRequest struct {
|
||||
// release namespace
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
|
||||
// cluster id
|
||||
ClusterId string `json:"cluster_id"`
|
||||
|
||||
// helm app id
|
||||
AppId string `json:"app_id"`
|
||||
|
||||
// advanced param
|
||||
AdvancedParam []string `json:"advanced_param"`
|
||||
|
||||
// required, conf a json string, include cpu, memory info of cluster
|
||||
Conf string `json:"conf,omitempty"`
|
||||
|
||||
// required, id of runtime
|
||||
// Deprecated: required, id of runtime
|
||||
RuntimeId string `json:"runtime_id,omitempty"`
|
||||
|
||||
// required, id of app version
|
||||
@@ -777,9 +792,6 @@ type Cluster struct {
|
||||
// id of app run in cluster
|
||||
AppId string `json:"app_id,omitempty"`
|
||||
|
||||
//// cluster common set
|
||||
//ClusterCommonSet OpenpitrixClusterClusterCommonSet `json:"cluster_common_set"`
|
||||
|
||||
// cluster id
|
||||
ClusterId string `json:"cluster_id,omitempty"`
|
||||
|
||||
@@ -857,9 +869,11 @@ type Runtime struct {
|
||||
}
|
||||
|
||||
type ModifyClusterAttributesRequest struct {
|
||||
ClusterName string `json:"clusterName,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
|
||||
// required, id of cluster to modify
|
||||
ClusterID string `json:"cluster_id,omitempty"`
|
||||
ClusterID string `json:"cluster_id"`
|
||||
|
||||
// cluster description
|
||||
Description *string `json:"description,omitempty"`
|
||||
@@ -869,10 +883,9 @@ type ModifyClusterAttributesRequest struct {
|
||||
}
|
||||
|
||||
const (
|
||||
CreateTime = "create_time"
|
||||
StatusTime = "status_time"
|
||||
RuntimeId = "runtime_id"
|
||||
Zone = "zone"
|
||||
CreateTime = "create_time"
|
||||
StatusTime = "status_time"
|
||||
|
||||
VersionId = "version_id"
|
||||
RepoId = "repo_id"
|
||||
CategoryId = "category_id"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
86
pkg/models/openpitrix/v2alpha1/applications.go
Normal file
86
pkg/models/openpitrix/v2alpha1/applications.go
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 v2alpha1
|
||||
|
||||
import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
resources "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
|
||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/openpitrix/application"
|
||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/openpitrix/applicationversion"
|
||||
)
|
||||
|
||||
type ApplicationInterface interface {
|
||||
DescribeAppVersion(id string) (*v1alpha1.HelmApplicationVersion, error)
|
||||
DescribeApp(id string) (*v1alpha1.HelmApplication, error)
|
||||
|
||||
ListApps(workspace string, q *query.Query) (*api.ListResult, error)
|
||||
ListAppVersions(workspace, appId string, q *query.Query) (*api.ListResult, error)
|
||||
}
|
||||
|
||||
type applicationOperator struct {
|
||||
appsGetter resources.Interface
|
||||
appVersionGetter resources.Interface
|
||||
}
|
||||
|
||||
func newApplicationOperator(informers externalversions.SharedInformerFactory) ApplicationInterface {
|
||||
op := &applicationOperator{
|
||||
appsGetter: application.New(informers),
|
||||
appVersionGetter: applicationversion.New(informers),
|
||||
}
|
||||
|
||||
return op
|
||||
}
|
||||
|
||||
func (c *applicationOperator) ListApps(workspace string, q *query.Query) (*api.ListResult, error) {
|
||||
|
||||
labelSelector, err := labels.ConvertSelectorToLabelsMap(q.LabelSelector)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extra := labels.Set{}
|
||||
if workspace != "" {
|
||||
extra[constants.WorkspaceLabelKey] = workspace
|
||||
}
|
||||
|
||||
if len(extra) > 0 {
|
||||
q.LabelSelector = labels.Merge(labelSelector, extra).String()
|
||||
}
|
||||
|
||||
releases, err := c.appsGetter.List("", q)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Errorf("list app failed, error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) DescribeApp(verId string) (*v1alpha1.HelmApplication, error) {
|
||||
ret, err := c.appsGetter.Get("", verId)
|
||||
if err != nil {
|
||||
klog.Errorf("get app failed, error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret.(*v1alpha1.HelmApplication), nil
|
||||
}
|
||||
64
pkg/models/openpitrix/v2alpha1/applicationsversions.go
Normal file
64
pkg/models/openpitrix/v2alpha1/applicationsversions.go
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 v2alpha1
|
||||
|
||||
import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
)
|
||||
|
||||
func (c *applicationOperator) ListAppVersions(workspace, appId string, q *query.Query) (*api.ListResult, error) {
|
||||
|
||||
labelSelector, err := labels.ConvertSelectorToLabelsMap(q.LabelSelector)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extra := labels.Set{}
|
||||
if workspace != "" {
|
||||
extra[constants.WorkspaceLabelKey] = workspace
|
||||
}
|
||||
|
||||
if appId != "" {
|
||||
extra[constants.ChartApplicationIdLabelKey] = appId
|
||||
}
|
||||
|
||||
if len(extra) > 0 {
|
||||
q.LabelSelector = labels.Merge(labelSelector, extra).String()
|
||||
}
|
||||
|
||||
releases, err := c.appVersionGetter.List("", q)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Errorf("list app version failed, error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return releases, nil
|
||||
}
|
||||
|
||||
func (c *applicationOperator) DescribeAppVersion(verId string) (*v1alpha1.HelmApplicationVersion, error) {
|
||||
ret, err := c.appVersionGetter.Get("", verId)
|
||||
if err != nil {
|
||||
klog.Errorf("get app version failed, error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ret.(*v1alpha1.HelmApplicationVersion), nil
|
||||
}
|
||||
64
pkg/models/openpitrix/v2alpha1/categories.go
Normal file
64
pkg/models/openpitrix/v2alpha1/categories.go
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 v2alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
resources "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
|
||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/openpitrix/category"
|
||||
)
|
||||
|
||||
type CategoryInterface interface {
|
||||
ListCategories(q *query.Query) (*api.ListResult, error)
|
||||
DescribeCategory(id string) (*v1alpha1.HelmCategory, error)
|
||||
}
|
||||
|
||||
type categoryOperator struct {
|
||||
ctgGetter resources.Interface
|
||||
}
|
||||
|
||||
func newCategoryOperator(ksFactory externalversions.SharedInformerFactory) CategoryInterface {
|
||||
c := &categoryOperator{
|
||||
ctgGetter: category.New(ksFactory),
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *categoryOperator) DescribeCategory(id string) (*v1alpha1.HelmCategory, error) {
|
||||
ret, err := c.ctgGetter.Get("", id)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctg := ret.(*v1alpha1.HelmCategory)
|
||||
return ctg, nil
|
||||
}
|
||||
|
||||
func (c *categoryOperator) ListCategories(q *query.Query) (*api.ListResult, error) {
|
||||
|
||||
result, err := c.ctgGetter.List("", q)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
44
pkg/models/openpitrix/v2alpha1/interface.go
Normal file
44
pkg/models/openpitrix/v2alpha1/interface.go
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 v2alpha1
|
||||
|
||||
import (
|
||||
"kubesphere.io/kubesphere/pkg/informers"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
ApplicationInterface
|
||||
RepoInterface
|
||||
HelmReleaseInterface
|
||||
CategoryInterface
|
||||
}
|
||||
|
||||
type openpitrixOperator struct {
|
||||
ApplicationInterface
|
||||
RepoInterface
|
||||
HelmReleaseInterface
|
||||
CategoryInterface
|
||||
}
|
||||
|
||||
func NewOpenPitrixOperator(ksInformers informers.InformerFactory) Interface {
|
||||
return &openpitrixOperator{
|
||||
ApplicationInterface: newApplicationOperator(ksInformers.KubeSphereSharedInformerFactory()),
|
||||
RepoInterface: newRepoOperator(ksInformers.KubeSphereSharedInformerFactory()),
|
||||
HelmReleaseInterface: newReleaseOperator(ksInformers.KubeSphereSharedInformerFactory()),
|
||||
CategoryInterface: newCategoryOperator(ksInformers.KubeSphereSharedInformerFactory()),
|
||||
}
|
||||
}
|
||||
90
pkg/models/openpitrix/v2alpha1/release.go
Normal file
90
pkg/models/openpitrix/v2alpha1/release.go
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 v2alpha1
|
||||
|
||||
import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
resources "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
|
||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/openpitrix/helmrelease"
|
||||
)
|
||||
|
||||
type HelmReleaseInterface interface {
|
||||
DescribeApplication(workspace, clusterName, namespace, applicationId string) (*v1alpha1.HelmRelease, error)
|
||||
ListApplications(workspace, cluster, namespace string, q *query.Query) (*api.ListResult, error)
|
||||
}
|
||||
type releaseOperator struct {
|
||||
rlsGetter resources.Interface
|
||||
}
|
||||
|
||||
func newReleaseOperator(ksFactory externalversions.SharedInformerFactory) HelmReleaseInterface {
|
||||
c := &releaseOperator{
|
||||
rlsGetter: helmrelease.New(ksFactory),
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
func (c *releaseOperator) DescribeApplication(workspace, clusterName, namespace, applicationId string) (*v1alpha1.HelmRelease, error) {
|
||||
ret, err := c.rlsGetter.Get("", applicationId)
|
||||
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rls := ret.(*v1alpha1.HelmRelease)
|
||||
return rls, nil
|
||||
}
|
||||
|
||||
func (c *releaseOperator) ListApplications(workspace, cluster, namespace string, q *query.Query) (*api.ListResult, error) {
|
||||
|
||||
labelSelector, err := labels.ConvertSelectorToLabelsMap(q.LabelSelector)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extra := labels.Set{}
|
||||
if workspace != "" {
|
||||
extra[constants.WorkspaceLabelKey] = workspace
|
||||
}
|
||||
|
||||
// cluster must used with namespace
|
||||
if cluster != "" {
|
||||
extra[constants.ClusterNameLabelKey] = cluster
|
||||
}
|
||||
if namespace != "" {
|
||||
extra[constants.NamespaceLabelKey] = namespace
|
||||
}
|
||||
if len(extra) > 0 {
|
||||
q.LabelSelector = labels.Merge(labelSelector, extra).String()
|
||||
}
|
||||
|
||||
releases, err := c.rlsGetter.List("", q)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
klog.Errorf("list app release failed, error: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return releases, nil
|
||||
}
|
||||
82
pkg/models/openpitrix/v2alpha1/repos.go
Normal file
82
pkg/models/openpitrix/v2alpha1/repos.go
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright 2020 The KubeSphere 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 v2alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/klog"
|
||||
"kubesphere.io/kubesphere/pkg/api"
|
||||
"kubesphere.io/kubesphere/pkg/apis/application/v1alpha1"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/query"
|
||||
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
"kubesphere.io/kubesphere/pkg/constants"
|
||||
resources "kubesphere.io/kubesphere/pkg/models/resources/v1alpha3"
|
||||
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha3/openpitrix/repo"
|
||||
)
|
||||
|
||||
type RepoInterface interface {
|
||||
ListRepos(workspace string, q *query.Query) (*api.ListResult, error)
|
||||
DescribeRepo(id string) (*v1alpha1.HelmRepo, error)
|
||||
}
|
||||
|
||||
type repoOperator struct {
|
||||
reposGetter resources.Interface
|
||||
}
|
||||
|
||||
func newRepoOperator(factory externalversions.SharedInformerFactory) RepoInterface {
|
||||
return &repoOperator{
|
||||
reposGetter: repo.New(factory),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *repoOperator) DescribeRepo(id string) (*v1alpha1.HelmRepo, error) {
|
||||
result, err := c.reposGetter.Get("", id)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo := result.(*v1alpha1.HelmRepo)
|
||||
repo.Status.Data = ""
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
func (c *repoOperator) ListRepos(workspace string, qry *query.Query) (result *api.ListResult, err error) {
|
||||
if workspace != "" {
|
||||
labelSelector, err := labels.ConvertSelectorToLabelsMap(qry.LabelSelector)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
qry.LabelSelector = labels.Merge(labelSelector, labels.Set{constants.WorkspaceLabelKey: workspace}).String()
|
||||
}
|
||||
result, err = c.reposGetter.List("", qry)
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// remove status data and credential
|
||||
for i := range result.Items {
|
||||
d := result.Items[i].(*v1alpha1.HelmRepo)
|
||||
d.Status.Data = ""
|
||||
d.Spec.Credential = v1alpha1.HelmRepoCredential{}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user