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:
LiHui
2020-12-23 15:24:30 +08:00
parent 737639020b
commit 83e6221f3a
193 changed files with 19634 additions and 6039 deletions

View File

@@ -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
}

View File

@@ -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 := &params.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())
}

View 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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View 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 := &params.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)
}

View 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")
)

View File

@@ -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),
}
}

View File

@@ -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 (

View 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
}

View 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 := &params.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()
}
}

View 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 := &params.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)
}

View File

@@ -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
}

View File

@@ -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

View 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
}

View 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
}

View 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
}

View 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()),
}
}

View 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
}

View 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
}