diff --git a/cmd/controller-manager/app/server.go b/cmd/controller-manager/app/server.go index da93bf1311e442a7a803a235615d572720b5abbf..c4f004e2b86d34b07a99516dd4a58b7c1cfb2d6d 100644 --- a/cmd/controller-manager/app/server.go +++ b/cmd/controller-manager/app/server.go @@ -228,8 +228,9 @@ func run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) klog.Fatal("Unable to create helm category controller") } + var opS3Client s3.Interface if !s.OpenPitrixOptions.AppStoreConfIsEmpty() { - storageClient, err := s3.NewS3Client(s.OpenPitrixOptions.S3Options) + opS3Client, err = s3.NewS3Client(s.OpenPitrixOptions.S3Options) if err != nil { klog.Fatalf("failed to connect to s3, please check openpitrix s3 service status, error: %v", err) } @@ -242,15 +243,17 @@ func run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) if err != nil { klog.Fatalf("Unable to create helm application version controller, error: %s ", err) } + } - err = (&helmrelease.ReconcileHelmRelease{ - StorageClient: storageClient, - KsFactory: informerFactory.KubeSphereSharedInformerFactory(), - }).SetupWithManager(mgr) + err = (&helmrelease.ReconcileHelmRelease{ + // nil interface is valid value. + StorageClient: opS3Client, + KsFactory: informerFactory.KubeSphereSharedInformerFactory(), + MultiClusterEnable: s.MultiClusterOptions.Enable, + }).SetupWithManager(mgr) - if err != nil { - klog.Fatalf("Unable to create helm release controller, error: %s", err) - } + if err != nil { + klog.Fatalf("Unable to create helm release controller, error: %s", err) } selector, _ := labels.Parse(s.ApplicationSelector) diff --git a/config/crds/application.kubesphere.io_helmrepos.yaml b/config/crds/application.kubesphere.io_helmrepos.yaml index 6b0e01c2217c2dd2d5970f1e175e5d874e8f88c2..6b7586981d649ea533957729d1456438e3a8b953 100644 --- a/config/crds/application.kubesphere.io_helmrepos.yaml +++ b/config/crds/application.kubesphere.io_helmrepos.yaml @@ -22,7 +22,7 @@ spec: - jsonPath: .spec.name name: name type: string - - jsonPath: .metadata.labels.kubesphere\\.io/workspace + - jsonPath: .metadata.labels.kubesphere\.io/workspace name: Workspace type: string - jsonPath: .spec.url diff --git a/pkg/apis/application/v1alpha1/helmrepo_types.go b/pkg/apis/application/v1alpha1/helmrepo_types.go index 281da80eab7bc928b2bc119ba89dfd7e9cfe864c..f24762c4440734a47e2aaa542f4b31cfdf33e615 100644 --- a/pkg/apis/application/v1alpha1/helmrepo_types.go +++ b/pkg/apis/application/v1alpha1/helmrepo_types.go @@ -92,7 +92,7 @@ type HelmRepoStatus struct { // +kubebuilder:resource:scope=Cluster,path=helmrepos,shortName=hrepo // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="name",type=string,JSONPath=`.spec.name` -// +kubebuilder:printcolumn:name="Workspace",type=string,JSONPath=`.metadata.labels.kubesphere\\.io/workspace` +// +kubebuilder:printcolumn:name="Workspace",type="string",JSONPath=".metadata.labels.kubesphere\\.io/workspace" // +kubebuilder:printcolumn:name="url",type=string,JSONPath=`.spec.url` // +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state" // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" diff --git a/pkg/apiserver/config/config.go b/pkg/apiserver/config/config.go index 4954270c6be72ae71adb0a7d11ea4ca9b4cbfc6b..93307a3325cea32fcebce9de247590567d19c01d 100644 --- a/pkg/apiserver/config/config.go +++ b/pkg/apiserver/config/config.go @@ -205,7 +205,7 @@ func (conf *Config) ToMap() map[string]bool { if conf.OpenPitrixOptions == nil { result["openpitrix.appstore"] = false } else { - result["openpitrix.appstore"] = conf.OpenPitrixOptions.AppStoreConfIsEmpty() + result["openpitrix.appstore"] = !conf.OpenPitrixOptions.AppStoreConfIsEmpty() } continue } diff --git a/pkg/controller/openpitrix/helmrelease/get_chart_data.go b/pkg/controller/openpitrix/helmrelease/get_chart_data.go new file mode 100644 index 0000000000000000000000000000000000000000..6a8d558c8083d988e994e1dbcf0a9e80d1fe390c --- /dev/null +++ b/pkg/controller/openpitrix/helmrelease/get_chart_data.go @@ -0,0 +1,78 @@ +/* +Copyright 2019 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 helmrelease + +import ( + "context" + "k8s.io/apimachinery/pkg/types" + "k8s.io/klog" + "kubesphere.io/kubesphere/pkg/apis/application/v1alpha1" + "kubesphere.io/kubesphere/pkg/simple/client/openpitrix/helmrepoindex" + "path" + "strings" +) + +func (r *ReconcileHelmRelease) GetChartData(rls *v1alpha1.HelmRelease) (chartName string, chartData []byte, err error) { + if rls.Spec.RepoId != "" && rls.Spec.RepoId != v1alpha1.AppStoreRepoId { + // load chart data from helm repo + repo := v1alpha1.HelmRepo{} + err := r.Get(context.TODO(), types.NamespacedName{Name: rls.Spec.RepoId}, &repo) + if err != nil { + klog.Errorf("get helm repo %s failed, error: %v", rls.Spec.RepoId, err) + return chartName, chartData, ErrGetRepoFailed + } + + index, err := helmrepoindex.ByteArrayToSavedIndex([]byte(repo.Status.Data)) + + if version := index.GetApplicationVersion(rls.Spec.ApplicationId, rls.Spec.ApplicationVersionId); version != nil { + url := version.Spec.URLs[0] + if !(strings.HasPrefix(url, "https://") || strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "s3://")) { + url = repo.Spec.Url + "/" + url + } + buf, err := helmrepoindex.LoadChart(context.TODO(), url, &repo.Spec.Credential) + if err != nil { + klog.Infof("load chart failed, error: %s", err) + return chartName, chartData, ErrLoadChartFailed + } + chartData = buf.Bytes() + chartName = version.Name + } else { + klog.Errorf("get app version: %s failed", rls.Spec.ApplicationVersionId) + return chartName, chartData, ErrGetAppVersionFailed + } + } else { + // load chart data from helm application version + appVersion := &v1alpha1.HelmApplicationVersion{} + err = r.Get(context.TODO(), types.NamespacedName{Name: rls.Spec.ApplicationVersionId}, appVersion) + if err != nil { + klog.Errorf("get app version %s failed, error: %v", rls.Spec.ApplicationVersionId, err) + return chartName, chartData, ErrGetAppVersionFailed + } + + if r.StorageClient == nil { + return "", nil, ErrS3Config + } + chartData, err = r.StorageClient.Read(path.Join(appVersion.GetWorkspace(), appVersion.Name)) + if err != nil { + klog.Errorf("load chart from storage failed, error: %s", err) + return chartName, chartData, ErrLoadChartFromStorageFailed + } + + chartName = appVersion.GetTrueName() + } + return +} diff --git a/pkg/controller/openpitrix/helmrelease/helm_release_controller.go b/pkg/controller/openpitrix/helmrelease/helm_release_controller.go index 6e42ab2f7cc83b3e152daee24b000f393d965b7f..1501a27e232026808df590bb29e20239620cfa0c 100644 --- a/pkg/controller/openpitrix/helmrelease/helm_release_controller.go +++ b/pkg/controller/openpitrix/helmrelease/helm_release_controller.go @@ -19,37 +19,29 @@ package helmrelease import ( "context" "errors" - "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/record" "k8s.io/klog" "kubesphere.io/kubesphere/pkg/apis/application/v1alpha1" + clusterv1alpha1 "kubesphere.io/kubesphere/pkg/apis/cluster/v1alpha1" "kubesphere.io/kubesphere/pkg/client/informers/externalversions" "kubesphere.io/kubesphere/pkg/constants" - "kubesphere.io/kubesphere/pkg/simple/client/openpitrix/helmrepoindex" "kubesphere.io/kubesphere/pkg/simple/client/openpitrix/helmwrapper" "kubesphere.io/kubesphere/pkg/simple/client/s3" "kubesphere.io/kubesphere/pkg/utils/clusterclient" "kubesphere.io/kubesphere/pkg/utils/sliceutil" "kubesphere.io/kubesphere/pkg/utils/stringutils" "math" - "path" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "strings" "time" ) const ( HelmReleaseFinalizer = "helmrelease.application.kubesphere.io" - IndexerName = "clusterNamespace" ) var ( @@ -58,6 +50,7 @@ var ( ErrAppVersionDataIsEmpty = errors.New("app version data is empty") ErrGetAppVersionFailed = errors.New("get app version failed") ErrLoadChartFailed = errors.New("load chart failed") + ErrS3Config = errors.New("invalid s3 config") ErrLoadChartFromStorageFailed = errors.New("load chart from storage failed") ) @@ -65,14 +58,16 @@ var _ reconcile.Reconciler = &ReconcileHelmRelease{} // ReconcileWorkspace reconciles a Workspace object type ReconcileHelmRelease struct { - StorageClient s3.Interface - KsFactory externalversions.SharedInformerFactory - clusterClients clusterclient.ClusterClients + StorageClient s3.Interface + KsFactory externalversions.SharedInformerFactory client.Client recorder record.EventRecorder // mock helm install && uninstall helmMock bool informer cache.SharedIndexInformer + + clusterClients clusterclient.ClusterClients + MultiClusterEnable bool } // @@ -111,8 +106,29 @@ func (r *ReconcileHelmRelease) Reconcile(request reconcile.Request) (reconcile.R // The object is not being deleted, so if it does not have our finalizer, // then lets add the finalizer and update the object. if !sliceutil.HasString(instance.ObjectMeta.Finalizers, HelmReleaseFinalizer) { + clusterName := instance.GetRlsCluster() + if r.MultiClusterEnable && clusterName != "" { + clusterInfo, err := r.clusterClients.Get(clusterName) + if err != nil { + // cluster not exists, delete the crd + klog.Warningf("cluster %s not found, delete the helm release %s/%s", + clusterName, instance.GetRlsNamespace(), instance.GetTrueName()) + return reconcile.Result{}, r.Delete(context.TODO(), instance) + } + + // Host cluster will self-healing, delete host cluster won't cause deletion of helm release + if !r.clusterClients.IsHostCluster(clusterInfo) { + // add owner References + instance.OwnerReferences = append(instance.OwnerReferences, metav1.OwnerReference{ + APIVersion: clusterv1alpha1.SchemeGroupVersion.String(), + Kind: clusterv1alpha1.ResourceKindCluster, + Name: clusterInfo.Name, + UID: clusterInfo.UID, + }) + } + } + instance.ObjectMeta.Finalizers = append(instance.ObjectMeta.Finalizers, HelmReleaseFinalizer) - // add owner References if err := r.Update(context.Background(), instance); err != nil { return reconcile.Result{}, err } @@ -146,67 +162,17 @@ func (r *ReconcileHelmRelease) Reconcile(request reconcile.Request) (reconcile.R return r.reconcile(instance) } -func (r *ReconcileHelmRelease) GetChartData(rls *v1alpha1.HelmRelease) (chartName string, chartData []byte, err error) { - if rls.Spec.RepoId != "" && rls.Spec.RepoId != v1alpha1.AppStoreRepoId { - // load chart data from helm repo - repo := v1alpha1.HelmRepo{} - err := r.Get(context.TODO(), types.NamespacedName{Name: rls.Spec.RepoId}, &repo) - if err != nil { - klog.Errorf("get helm repo %s failed, error: %v", rls.Spec.RepoId, err) - return chartName, chartData, ErrGetRepoFailed - } - - index, err := helmrepoindex.ByteArrayToSavedIndex([]byte(repo.Status.Data)) - - if version := index.GetApplicationVersion(rls.Spec.ApplicationId, rls.Spec.ApplicationVersionId); version != nil { - url := version.Spec.URLs[0] - if !(strings.HasPrefix(url, "https://") || strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "s3://")) { - url = repo.Spec.Url + "/" + url - } - buf, err := helmrepoindex.LoadChart(context.TODO(), url, &repo.Spec.Credential) - if err != nil { - klog.Infof("load chart failed, error: %s", err) - return chartName, chartData, ErrLoadChartFailed - } - chartData = buf.Bytes() - chartName = version.Name - } else { - klog.Errorf("get app version: %s failed", rls.Spec.ApplicationVersionId) - return chartName, chartData, ErrGetAppVersionFailed - } - } else { - // load chart data from helm application version - appVersion := &v1alpha1.HelmApplicationVersion{} - err = r.Get(context.TODO(), types.NamespacedName{Name: rls.Spec.ApplicationVersionId}, appVersion) - if err != nil { - klog.Errorf("get app version %s failed, error: %v", rls.Spec.ApplicationVersionId, err) - return chartName, chartData, ErrGetAppVersionFailed - } - - chartData, err = r.StorageClient.Read(path.Join(appVersion.GetWorkspace(), appVersion.Name)) - if err != nil { - klog.Errorf("load chart from storage failed, error: %s", err) - return chartName, chartData, ErrLoadChartFromStorageFailed - } - - chartName = appVersion.GetTrueName() - } - return -} - +// Check the state of the instance then decide what to do. func (r *ReconcileHelmRelease) reconcile(instance *v1alpha1.HelmRelease) (reconcile.Result, error) { if instance.Status.State == v1alpha1.HelmStatusActive && instance.Status.Version == instance.Spec.Version { - // check release status - return reconcile.Result{ - // recheck release status after 10 minutes - RequeueAfter: 10 * time.Minute, - }, nil + // todo check release status + return reconcile.Result{}, nil } ft := failedTimes(instance.Status.DeployStatus) if v1alpha1.HelmStatusFailed == instance.Status.State && ft > 0 { - // exponential backoff, max delay 180s + // failed too much times, exponential backoff, max delay 180s retryAfter := time.Duration(math.Min(math.Exp2(float64(ft)), 180)) * time.Second var lastDeploy time.Time @@ -226,16 +192,18 @@ func (r *ReconcileHelmRelease) reconcile(instance *v1alpha1.HelmRelease) (reconc // no operation return reconcile.Result{}, nil case v1alpha1.HelmStatusActive: + // Release used to be active, but instance.Status.Version not equal to instance.Spec.Version instance.Status.State = v1alpha1.HelmStatusUpgrading + // Update the state first. err = r.Status().Update(context.TODO(), instance) return reconcile.Result{}, err case v1alpha1.HelmStatusCreating: // create new release err = r.createOrUpgradeHelmRelease(instance, false) case v1alpha1.HelmStatusFailed: - // check failed times err = r.createOrUpgradeHelmRelease(instance, false) case v1alpha1.HelmStatusUpgrading: + // We can update the release now. err = r.createOrUpgradeHelmRelease(instance, true) case v1alpha1.HelmStatusRollbacking: // TODO: rollback helm release @@ -260,6 +228,7 @@ func (r *ReconcileHelmRelease) reconcile(instance *v1alpha1.HelmRelease) (reconc instance.Status.LastDeployed = &now if len(instance.Status.DeployStatus) > 0 { instance.Status.DeployStatus = append([]v1alpha1.HelmReleaseDeployStatus{deployStatus}, instance.Status.DeployStatus...) + // At most ten records will be saved. if len(instance.Status.DeployStatus) >= 10 { instance.Status.DeployStatus = instance.Status.DeployStatus[:10:10] } @@ -301,7 +270,7 @@ func (r *ReconcileHelmRelease) createOrUpgradeHelmRelease(rls *v1alpha1.HelmRele clusterName := rls.GetRlsCluster() var clusterConfig string - if clusterName != "" && r.KsFactory != nil { + if r.MultiClusterEnable && clusterName != "" { clusterConfig, err = r.clusterClients.GetClusterKubeconfig(clusterName) if err != nil { klog.Errorf("get cluster %s config failed", clusterConfig) @@ -327,6 +296,7 @@ func (r *ReconcileHelmRelease) createOrUpgradeHelmRelease(rls *v1alpha1.HelmRele } func (r *ReconcileHelmRelease) uninstallHelmRelease(rls *v1alpha1.HelmRelease) error { + if rls.Status.State != v1alpha1.HelmStatusDeleting { rls.Status.State = v1alpha1.HelmStatusDeleting rls.Status.LastUpdate = metav1.Now() @@ -339,12 +309,20 @@ func (r *ReconcileHelmRelease) uninstallHelmRelease(rls *v1alpha1.HelmRelease) e clusterName := rls.GetRlsCluster() var clusterConfig string var err error - if clusterName != "" && r.KsFactory != nil { - clusterConfig, err = r.clusterClients.GetClusterKubeconfig(clusterName) + if r.MultiClusterEnable && clusterName != "" { + clusterInfo, err := r.clusterClients.Get(clusterName) if err != nil { - klog.Errorf("get cluster %s config failed", clusterConfig) - return err + klog.V(2).Infof("cluster %s was deleted, skip helm release uninstall", clusterName) + return nil + } + + // If user deletes helmRelease first and then delete cluster immediately, this may cause helm resources leak. + if clusterInfo.DeletionTimestamp != nil { + klog.V(2).Infof("cluster %s is deleting, skip helm release uninstall", clusterName) + return nil } + + clusterConfig = string(clusterInfo.Spec.Connection.KubeConfig) } hw := helmwrapper.NewHelmWrapper(clusterConfig, rls.GetRlsNamespace(), rls.Spec.Name, helmwrapper.SetMock(r.helmMock)) @@ -359,118 +337,11 @@ func (r *ReconcileHelmRelease) uninstallHelmRelease(rls *v1alpha1.HelmRelease) e func (r *ReconcileHelmRelease) SetupWithManager(mgr ctrl.Manager) error { r.Client = mgr.GetClient() - if r.KsFactory != nil { + if r.KsFactory != nil && r.MultiClusterEnable { r.clusterClients = clusterclient.NewClusterClient(r.KsFactory.Cluster().V1alpha1().Clusters()) - - r.informer = r.KsFactory.Application().V1alpha1().HelmReleases().Informer() - err := r.informer.AddIndexers(map[string]cache.IndexFunc{ - IndexerName: func(obj interface{}) ([]string, error) { - rls := obj.(*v1alpha1.HelmRelease) - return []string{fmt.Sprintf("%s/%s", rls.GetRlsCluster(), rls.GetRlsNamespace())}, nil - }, - }) - if err != nil { - return err - } - - go func() { - <-mgr.Elected() - go r.cleanHelmReleaseWhenNamespaceDeleted() - }() } return ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.HelmRelease{}). Complete(r) } - -func (r *ReconcileHelmRelease) getClusterConfig(cluster string) (string, error) { - if cluster == "" { - return "", nil - } - - clusterConfig, err := r.clusterClients.GetClusterKubeconfig(cluster) - if err != nil { - klog.Errorf("get cluster %s config failed", clusterConfig) - return "", err - } - - return clusterConfig, nil -} - -// When namespace have been removed from member cluster, we need clean all -// the helmRelease from the host cluster. -func (r *ReconcileHelmRelease) cleanHelmReleaseWhenNamespaceDeleted() { - - ticker := time.NewTicker(2 * time.Minute) - for _ = range ticker.C { - keys := r.informer.GetIndexer().ListIndexFuncValues(IndexerName) - for _, clusterNs := range keys { - klog.V(4).Infof("clean resource in %s", clusterNs) - parts := stringutils.Split(clusterNs, "/") - if len(parts) == 2 { - cluster, ns := parts[0], parts[1] - items, err := r.informer.GetIndexer().ByIndex(IndexerName, clusterNs) - if err != nil { - klog.Errorf("get items from index failed, error: %s", err) - continue - } - - kubeconfig, err := r.getClusterConfig(cluster) - if err != nil { - klog.Errorf("get cluster %s config failed, error: %s", cluster, err) - continue - } - - // connect to member or host cluster - var restConfig *restclient.Config - if kubeconfig == "" { - restConfig, err = restclient.InClusterConfig() - } else { - cc, err := clientcmd.NewClientConfigFromBytes([]byte(kubeconfig)) - if err != nil { - klog.Errorf("get client config for cluster %s failed, error: %s", cluster, err) - continue - } - restConfig, err = cc.ClientConfig() - } - - if err != nil { - klog.Errorf("build rest config for cluster %s failed, error: %s", cluster, err) - continue - } - - clientSet, err := kubernetes.NewForConfig(restConfig) - if err != nil { - klog.Errorf("create client set failed, error: %s", err) - continue - } - // check namespace exists or not - namespace, err := clientSet.CoreV1().Namespaces().Get(context.TODO(), ns, metav1.GetOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - klog.V(2).Infof("delete all helm release in %s", clusterNs) - for ind := range items { - rls := items[ind].(*v1alpha1.HelmRelease) - err := r.Client.Delete(context.TODO(), rls) - if err != nil && !apierrors.IsNotFound(err) { - klog.Errorf("delete release %s failed", rls.Name) - } - } - } else { - klog.Errorf("get namespace %s from cluster %s failed, error: %s", ns, cluster, err) - continue - } - } else { - for ind := range items { - rls := items[ind].(*v1alpha1.HelmRelease) - if namespace.CreationTimestamp.After(rls.CreationTimestamp.Time) { - klog.V(2).Infof("delete helm release %s in %s", rls.Namespace, clusterNs) - // todo, namespace is newer than helmRelease, should we delete the helmRelease - } - } - } - } - } - } -} diff --git a/pkg/kapis/openpitrix/v1/handler.go b/pkg/kapis/openpitrix/v1/handler.go index c0a7d791c5546a806ba16e6e8db442e579fecbcf..a3e2c1dddbda8d724916ff030dc1b5399f8627a5 100644 --- a/pkg/kapis/openpitrix/v1/handler.go +++ b/pkg/kapis/openpitrix/v1/handler.go @@ -82,6 +82,9 @@ func (h *openpitrixHandler) CreateRepo(req *restful.Request, resp *restful.Respo api.HandleBadRequest(resp, nil, err) return } + userInfo := parsedUrl.User + // trim credential from url + parsedUrl.User = nil repo := v1alpha1.HelmRepo{ ObjectMeta: metav1.ObjectMeta{ @@ -95,16 +98,16 @@ func (h *openpitrixHandler) CreateRepo(req *restful.Request, resp *restful.Respo }, Spec: v1alpha1.HelmRepoSpec{ Name: createRepoRequest.Name, - Url: createRepoRequest.URL, + Url: parsedUrl.String(), SyncPeriod: 0, Description: stringutils.ShortenString(createRepoRequest.Description, 512), }, } if strings.HasPrefix(createRepoRequest.URL, "https://") || strings.HasPrefix(createRepoRequest.URL, "http://") { - if parsedUrl.User != nil { - repo.Spec.Credential.Username = parsedUrl.User.Username() - repo.Spec.Credential.Password, _ = parsedUrl.User.Password() + if userInfo != nil { + repo.Spec.Credential.Username = userInfo.Username() + repo.Spec.Credential.Password, _ = userInfo.Password() } } else if strings.HasPrefix(createRepoRequest.URL, "s3://") { cfg := v1alpha1.S3Config{} diff --git a/pkg/models/openpitrix/applications.go b/pkg/models/openpitrix/applications.go index 9d18aeab2f90d1a67b05b11f6d6ddbe7f5362e3e..316093a29f76782d01956a0d185f5c1a4a522dc3 100644 --- a/pkg/models/openpitrix/applications.go +++ b/pkg/models/openpitrix/applications.go @@ -291,6 +291,7 @@ func buildLabelSelector(conditions *params.Conditions) map[string]string { } 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) @@ -306,7 +307,7 @@ func (c *applicationOperator) ListApps(conditions *params.Conditions, orderBy st items := make([]interface{}, 0, limit) - for i, j := offset, 0; i < len(apps) && j < limit; { + for i, j := offset, 0; i < len(apps) && j < limit; i, j = i+1, j+1 { versions, err := c.getAppVersionsByAppId(apps[i].GetHelmApplicationId()) if err != nil && !apierrors.IsNotFound(err) { return nil, err @@ -315,8 +316,6 @@ func (c *applicationOperator) ListApps(conditions *params.Conditions, orderBy st 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 } @@ -612,6 +611,9 @@ func (c *applicationOperator) listApps(conditions *params.Conditions) (ret []*v1 return ret, nil } } else { + if c.backingStoreClient == nil { + return []*v1alpha1.HelmApplication{}, nil + } ret, err = c.appLister.List(labels.SelectorFromSet(buildLabelSelector(conditions))) } diff --git a/pkg/models/openpitrix/applicationversions.go b/pkg/models/openpitrix/applicationversions.go index e0e2bd7d9ecf3e523005da4dce30d839f8764e87..f7d2f8929a54f38c41537e849141dc5d89abef42 100644 --- a/pkg/models/openpitrix/applicationversions.go +++ b/pkg/models/openpitrix/applicationversions.go @@ -38,7 +38,6 @@ import ( "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" @@ -218,11 +217,7 @@ func (c *applicationOperator) ListAppVersions(conditions *params.Conditions, ord return nil, err } - var status []string - if len(conditions.Match[Status]) > 0 { - status = strings.Split(conditions.Match[Status], "|") - } - versions = filterAppVersionByState(versions, status) + versions = filterAppVersions(versions, conditions) if reverse { sort.Sort(sort.Reverse(AppVersions(versions))) } else { @@ -231,47 +226,32 @@ func (c *applicationOperator) ListAppVersions(conditions *params.Conditions, ord items := make([]interface{}, 0, int(math.Min(float64(limit), float64(len(versions))))) - for i, j := offset, 0; i < len(versions) && j < limit; { + for i, j := offset, 0; i < len(versions) && j < limit; i, j = i+1, j+1 { 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) - } - } - + filtered := filterAppReviews(appVersions, conditions) if reverse { - sort.Sort(sort.Reverse(AppVersions(filtered))) + sort.Sort(sort.Reverse(AppVersionReviews(filtered))) } else { - sort.Sort(AppVersions(filtered)) + sort.Sort(AppVersionReviews(filtered)) } items := make([]interface{}, 0, len(filtered)) - for i, j := offset, 0; i < len(filtered) && j < limit; { + for i, j := offset, 0; i < len(filtered) && j < limit; i, j = i+1, j+1 { review := convertAppVersionReview(filtered[i]) items = append(items, review) - i++ - j++ } return &models.PageableResponse{Items: items, TotalCount: len(filtered)}, nil @@ -309,10 +289,8 @@ func (c *applicationOperator) ListAppVersionAudits(conditions *params.Conditions items := make([]interface{}, 0, limit) - for i, j := offset, 0; i < len(allAudits) && j < limit; { + for i, j := offset, 0; i < len(allAudits) && j < limit; i, j = i+1, j+1 { items = append(items, allAudits[i]) - i++ - j++ } return &models.PageableResponse{Items: items, TotalCount: len(allAudits)}, nil diff --git a/pkg/models/openpitrix/attachments.go b/pkg/models/openpitrix/attachments.go index 1a43cac221a25b45ae6e402974cc050c94a5bf0d..756e6145fc1964c6850cc2e930a160563e74ef9b 100644 --- a/pkg/models/openpitrix/attachments.go +++ b/pkg/models/openpitrix/attachments.go @@ -39,25 +39,28 @@ func newAttachmentOperator(storeClient s3.Interface) AttachmentInterface { } func (c *attachmentOperator) DescribeAttachment(id string) (*Attachment, error) { + if c.backingStoreClient == nil { + return nil, invalidS3Config + } data, err := c.backingStoreClient.Read(id) if err != nil { klog.Errorf("read attachment %s failed, error: %s", id, err) return nil, downloadFileFailed } - att := &Attachment{AttachmentID: id} - if err != nil { - return nil, err - } else { - att.AttachmentContent = map[string]strfmt.Base64{ + att := &Attachment{AttachmentID: id, + AttachmentContent: map[string]strfmt.Base64{ "raw": data, - } + }, } return att, nil } func (c *attachmentOperator) CreateAttachment(data []byte) (*Attachment, error) { + if c.backingStoreClient == nil { + return nil, invalidS3Config + } id := idutils.GetUuid36(v1alpha1.HelmAttachmentPrefix) err := c.backingStoreClient.Upload(id, id, bytes.NewBuffer(data)) @@ -72,6 +75,9 @@ func (c *attachmentOperator) CreateAttachment(data []byte) (*Attachment, error) } func (c *attachmentOperator) DeleteAttachments(ids []string) error { + if c.backingStoreClient == nil { + return invalidS3Config + } for _, id := range ids { err := c.backingStoreClient.Delete(id) if err != nil { diff --git a/pkg/models/openpitrix/categories.go b/pkg/models/openpitrix/categories.go index 0728c03708ded44607ab377dbf384bd0ec2c9355..1b91c6a97aac0d2144dc2a06b70b82adf3cf268e 100644 --- a/pkg/models/openpitrix/categories.go +++ b/pkg/models/openpitrix/categories.go @@ -182,10 +182,8 @@ func (c *categoryOperator) ListCategories(conditions *params.Conditions, orderBy sort.Sort(HelmCategoryList(ctgs)) items := make([]interface{}, 0, limit) - for i, j := offset, 0; i < len(ctgs) && j < limit; { + for i, j := offset, 0; i < len(ctgs) && j < limit; i, j = i+1, j+1 { items = append(items, convertCategory(ctgs[i])) - i++ - j++ } return &models.PageableResponse{Items: items, TotalCount: len(ctgs)}, nil diff --git a/pkg/models/openpitrix/release.go b/pkg/models/openpitrix/release.go index 491cc9b74f0ffe88709f4236eb3f672feb63e34b..a619c30140d00941fc9e175744a119a92511e563 100644 --- a/pkg/models/openpitrix/release.go +++ b/pkg/models/openpitrix/release.go @@ -310,11 +310,9 @@ func (c *releaseOperator) ListApplications(workspace, clusterName, namespace str 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; { + for i, j := offset, 0; i < len(releases) && j < limit; i, j = i+1, j+1 { app := convertApplication(releases[i], nil) result.Items = append(result.Items, app) - i++ - j++ } return &result, nil @@ -330,13 +328,25 @@ func (c *releaseOperator) DescribeApplication(workspace, clusterName, namespace, } app := &Application{} - if rls != nil && rls.GetRlsCluster() != "" { + + var clusterConfig string + if rls != nil { // 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 clusterName != "" { + cluster, err := c.clusterClients.Get(clusterName) + if err != nil { + klog.Errorf("get cluster config failed, error: %s", err) + return nil, err + } + if !c.clusterClients.IsHostCluster(cluster) { + 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() diff --git a/pkg/models/openpitrix/repos.go b/pkg/models/openpitrix/repos.go index 68339ec05c98323828aefcf9aa32cb438546b629..436b844ee4b958190843abee0956466b8dcd6750 100644 --- a/pkg/models/openpitrix/repos.go +++ b/pkg/models/openpitrix/repos.go @@ -15,6 +15,7 @@ package openpitrix import ( "context" + "encoding/json" "fmt" "github.com/go-openapi/strfmt" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -34,6 +35,7 @@ import ( "kubesphere.io/kubesphere/pkg/simple/client/openpitrix/helmrepoindex" "kubesphere.io/kubesphere/pkg/utils/reposcache" "kubesphere.io/kubesphere/pkg/utils/stringutils" + "net/url" "sigs.k8s.io/controller-runtime/pkg/client" "sort" "strings" @@ -166,16 +168,54 @@ func (c *repoOperator) ModifyRepo(id string, request *ModifyRepoRequest) error { if request.Description != nil { repoCopy.Spec.Description = stringutils.ShortenString(*request.Description, DescriptionLen) } - if request.URL != nil { - repoCopy.Spec.Url = *request.URL - } - // TODO modify credential - if request.Name != nil { - repoCopy.Labels[constants.NameLabelKey] = *request.Name + if request.Name != nil && len(*request.Name) > 0 && *request.Name != repoCopy.Name { + 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 err + } + + for _, exists := range items { + if exists.GetTrueName() == *request.Name { + klog.Error(repoItemExists, "name: ", *request.Name) + return repoItemExists + } + } + + repoCopy.Spec.Name = *request.Name } - if request.Workspace != nil { - repoCopy.Labels[constants.WorkspaceLabelKey] = *request.Workspace + + // modify credential + if request.URL != nil && len(*request.URL) > 0 { + parsedUrl, err := url.Parse(*request.URL) + if err != nil { + return err + } + userInfo := parsedUrl.User + // trim the credential from url + parsedUrl.User = nil + cred := &v1alpha1.HelmRepoCredential{} + if strings.HasPrefix(*request.URL, "https://") || strings.HasPrefix(*request.URL, "http://") { + if userInfo != nil { + cred.Password, _ = userInfo.Password() + cred.Username = userInfo.Username() + } else { + // trim the old credential + cred.Password, _ = userInfo.Password() + cred.Username = userInfo.Username() + } + } else if strings.HasPrefix(*request.URL, "s3://") { + cfg := v1alpha1.S3Config{} + err := json.Unmarshal([]byte(*request.Credential), &cfg) + if err != nil { + return err + } + cred.S3Config = cfg + } + + repoCopy.Spec.Credential = *cred + repoCopy.Spec.Url = parsedUrl.String() } patch := client.MergeFrom(repo) @@ -242,10 +282,8 @@ func (c *repoOperator) ListRepos(conditions *params.Conditions, orderBy string, } items := make([]interface{}, 0, limit) - for i, j := offset, 0; i < len(repos) && j < limit; { + for i, j := offset, 0; i < len(repos) && j < limit; i, j = i+1, j+1 { items = append(items, convertRepo(repos[i])) - i++ - j++ } return &models.PageableResponse{Items: items, TotalCount: len(repos)}, nil } @@ -253,7 +291,7 @@ func (c *repoOperator) ListRepos(conditions *params.Conditions, orderBy string, func helmRepoFilter(namePrefix string, list []*v1alpha1.HelmRepo) (res []*v1alpha1.HelmRepo) { for _, repo := range list { name := repo.GetTrueName() - if strings.HasPrefix(name, namePrefix) { + if strings.Contains(name, namePrefix) { res = append(res, repo) } } @@ -288,10 +326,8 @@ func (c *repoOperator) ListRepoEvents(repoId string, conditions *params.Conditio } items := make([]interface{}, 0, limit) - for i, j := offset, 0; i < len(repo.Status.SyncState) && j < limit; { + for i, j := offset, 0; i < len(repo.Status.SyncState) && j < limit; i, j = i+1, j+1 { items = append(items, convertRepoEvent(&repo.ObjectMeta, &repo.Status.SyncState[j])) - i++ - j++ } return &models.PageableResponse{Items: items, TotalCount: len(repo.Status.SyncState)}, nil diff --git a/pkg/models/openpitrix/utils.go b/pkg/models/openpitrix/utils.go index 2c442c2b3c39ce60e266620ad8e62527e4f257a1..9ca7c4cfece963ee52548d73f1e652545d942dee 100644 --- a/pkg/models/openpitrix/utils.go +++ b/pkg/models/openpitrix/utils.go @@ -322,7 +322,7 @@ func convertApp(app *v1alpha1.HelmApplication, versions []*v1alpha1.HelmApplicat } if versions != nil && len(versions) > 0 { sort.Sort(AppVersions(versions)) - out.LatestAppVersion = convertAppVersion(versions[0]) + out.LatestAppVersion = convertAppVersion(versions[len(versions)-1]) } else { out.LatestAppVersion = &AppVersion{} } @@ -393,7 +393,7 @@ func convertRepo(in *v1alpha1.HelmRepo) *Repo { out.RepoId = in.GetHelmRepoId() out.Name = in.GetTrueName() - out.Status = "active" + out.Status = in.Status.State date := strfmt.DateTime(time.Unix(in.CreationTimestamp.Unix(), 0)) out.CreateTime = &date @@ -439,6 +439,43 @@ func (l HelmApplicationList) Less(i, j int) bool { } } +type AppVersionReviews []*v1alpha1.HelmApplicationVersion + +// Len returns the length. +func (c AppVersionReviews) Len() int { return len(c) } + +// Swap swaps the position of two items in the versions slice. +func (c AppVersionReviews) Swap(i, j int) { c[i], c[j] = c[j], c[i] } + +// Less returns true if the version of entry a is less than the version of entry b. +func (c AppVersionReviews) Less(a, b int) bool { + aVersion := c[a] + bVersion := c[b] + + if len(aVersion.Status.Audit) > 0 && len(bVersion.Status.Audit) > 0 { + t1 := aVersion.Status.Audit[0].Time + t2 := bVersion.Status.Audit[0].Time + if t1.Before(&t2) { + return true + } else if t2.Before(&t1) { + return false + } + } + + i, err := semver.NewVersion(aVersion.GetSemver()) + if err != nil { + return true + } + j, err := semver.NewVersion(bVersion.GetSemver()) + if err != nil { + return false + } + if i.Equal(j) { + return aVersion.CreationTimestamp.Before(&bVersion.CreationTimestamp) + } + return j.LessThan(i) +} + type AppVersions []*v1alpha1.HelmApplicationVersion // Len returns the length. @@ -463,7 +500,7 @@ func (c AppVersions) Less(a, b int) bool { if i.Equal(j) { return aVersion.CreationTimestamp.Before(&bVersion.CreationTimestamp) } - return j.LessThan(i) + return i.LessThan(j) } // buildApplicationVersion build an application version @@ -524,7 +561,7 @@ func filterAppByName(app *v1alpha1.HelmApplication, namePart string) bool { } name := app.GetTrueName() - if strings.HasSuffix(name, namePart) || strings.HasPrefix(name, namePart) { + if strings.Contains(name, namePart) { return true } return false @@ -545,6 +582,67 @@ func filterAppByStates(app *v1alpha1.HelmApplication, state []string) bool { return false } +func filterAppReviews(versions []*v1alpha1.HelmApplicationVersion, conditions *params.Conditions) []*v1alpha1.HelmApplicationVersion { + if conditions == nil || len(conditions.Match) == 0 || len(versions) == 0 { + return versions + } + + curr := 0 + for i := 0; i < len(versions); i++ { + if conditions.Match[Keyword] != "" { + if !(strings.Contains(versions[i].Spec.Name, conditions.Match[Keyword])) { + continue + } + } + + if conditions.Match[Status] != "" { + states := strings.Split(conditions.Match[Status], "|") + state := versions[i].State() + if !sliceutil.HasString(states, state) { + continue + } + } + + if curr != i { + versions[curr] = versions[i] + } + curr++ + } + + return versions[:curr:curr] +} + +func filterAppVersions(versions []*v1alpha1.HelmApplicationVersion, conditions *params.Conditions) []*v1alpha1.HelmApplicationVersion { + if conditions == nil || len(conditions.Match) == 0 || len(versions) == 0 { + return versions + } + + curr := 0 + for i := 0; i < len(versions); i++ { + if conditions.Match[Keyword] != "" { + if !(strings.Contains(versions[i].Spec.Version, conditions.Match[Keyword]) || + strings.Contains(versions[i].Spec.AppVersion, conditions.Match[Keyword])) { + continue + } + } + + if conditions.Match[Status] != "" { + states := strings.Split(conditions.Match[Status], "|") + state := versions[i].State() + if !sliceutil.HasString(states, state) { + continue + } + } + + if curr != i { + versions[curr] = versions[i] + } + curr++ + } + + return versions[:curr:curr] +} + func filterApps(apps []*v1alpha1.HelmApplication, conditions *params.Conditions) []*v1alpha1.HelmApplication { if conditions == nil || len(conditions.Match) == 0 || len(apps) == 0 { return apps @@ -575,18 +673,6 @@ func filterApps(apps []*v1alpha1.HelmApplication, conditions *params.Conditions) return apps[:curr:curr] } -func filterReleaseByName(rls *v1alpha1.HelmRelease, namePart string) bool { - if len(namePart) == 0 { - return true - } - - name := rls.GetTrueName() - if strings.HasSuffix(name, namePart) || strings.HasPrefix(name, namePart) { - return true - } - return false -} - func filterReleaseByStates(rls *v1alpha1.HelmRelease, state []string) bool { if len(state) == 0 { return true @@ -626,8 +712,11 @@ func filterReleases(releases []*v1alpha1.HelmRelease, conditions *params.Conditi curr := 0 for i := 0; i < len(releases); i++ { - if conditions.Match[Keyword] != "" { - fv := filterReleaseByName(releases[i], conditions.Match[Keyword]) + keyword := conditions.Match[Keyword] + if keyword != "" { + fv := strings.Contains(releases[i].GetTrueName(), keyword) || + strings.Contains(releases[i].Spec.ChartVersion, keyword) || + strings.Contains(releases[i].Spec.ChartAppVersion, keyword) if !fv { continue } diff --git a/pkg/simple/client/openpitrix/helmrepoindex/repo_index.go b/pkg/simple/client/openpitrix/helmrepoindex/repo_index.go index 43d796bae0fb37a02bbd75910ebf67e26af5324f..81163f2d16693d378d0dc6eb8237570c6851b165 100644 --- a/pkg/simple/client/openpitrix/helmrepoindex/repo_index.go +++ b/pkg/simple/client/openpitrix/helmrepoindex/repo_index.go @@ -63,7 +63,7 @@ func LoadRepoIndex(ctx context.Context, u string, cred *v1alpha1.HelmRepoCredent // This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails. func loadIndex(data []byte) (*helmrepo.IndexFile, error) { i := &helmrepo.IndexFile{} - if err := yaml.UnmarshalStrict(data, i); err != nil { + if err := yaml.Unmarshal(data, i); err != nil { return i, err } i.SortEntries() @@ -73,6 +73,8 @@ func loadIndex(data []byte) (*helmrepo.IndexFile, error) { return i, nil } +var empty = struct{}{} + // merge new index with index from crd func MergeRepoIndex(index *helmrepo.IndexFile, existsSavedIndex *SavedIndex) *SavedIndex { saved := &SavedIndex{} @@ -92,7 +94,7 @@ func MergeRepoIndex(index *helmrepo.IndexFile, existsSavedIndex *SavedIndex) *Sa saved.Generated = index.Generated saved.PublicKeys = index.PublicKeys - allNames := make(map[string]bool, len(index.Entries)) + allAppNames := make(map[string]struct{}, len(index.Entries)) for name, versions := range index.Entries { // add new applications if application, exists := saved.Applications[name]; !exists { @@ -112,6 +114,7 @@ func MergeRepoIndex(index *helmrepo.IndexFile, existsSavedIndex *SavedIndex) *Sa } charts = append(charts, chart) } + application.Charts = charts saved.Applications[name] = application } else { @@ -121,6 +124,7 @@ func MergeRepoIndex(index *helmrepo.IndexFile, existsSavedIndex *SavedIndex) *Sa savedChartVersion[ver.Version] = struct{}{} } charts := application.Charts + var newVersion = make(map[string]struct{}, len(versions)) for _, ver := range versions { // add new chart version if _, exists := savedChartVersion[ver.Version]; !exists { @@ -131,15 +135,34 @@ func MergeRepoIndex(index *helmrepo.IndexFile, existsSavedIndex *SavedIndex) *Sa } charts = append(charts, chart) } - application.Charts = charts - saved.Applications[name] = application + newVersion[ver.Version] = empty + } + + // delete not exists chart version + for last, curr := 0, 0; curr < len(charts); { + chart := charts[curr] + version := chart.Version + if _, exists := newVersion[version]; !exists { + // version not exists, check next one + curr++ + } else { + // If last and curr point to the same place, there is nothing to do, just move to next. + if last != curr { + charts[last] = charts[curr] + } + last++ + curr++ + } } + application.Charts = charts[:len(newVersion)] + saved.Applications[name] = application } - allNames[name] = true + + allAppNames[name] = empty } for name := range saved.Applications { - if _, exists := allNames[name]; !exists { + if _, exists := allAppNames[name]; !exists { delete(saved.Applications, name) } } diff --git a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper.go b/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper.go index d91f83b057922c74876944f360b963291281da20..66571d86baf57875dcb8e94dbc1a673a655c9efa 100644 --- a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper.go +++ b/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper.go @@ -21,8 +21,10 @@ import ( "encoding/json" "fmt" "gopkg.in/yaml.v3" + helmrelease "helm.sh/helm/v3/pkg/release" "k8s.io/klog" kpath "k8s.io/utils/path" + "kubesphere.io/kubesphere/pkg/server/errors" "kubesphere.io/kubesphere/pkg/utils/idutils" "os" "os/exec" @@ -39,6 +41,7 @@ const ( var ( UninstallNotFoundFormat = "Error: uninstall: Release not loaded: %s: release: not found" StatusNotFoundFormat = "Error: release: not found" + releaseExists = "release exists" kustomizationFile = "kustomization.yaml" postRenderExecFile = "helm-post-render.sh" @@ -54,37 +57,6 @@ type HelmRes struct { Message string } -type releaseStatus struct { - Name string `json:"name,omitempty"` - Info *Info `json:"info,omitempty"` -} - -// copy from helm -// Info describes release information. -type Info struct { - // FirstDeployed is when the release was first deployed. - FirstDeployed time.Time `json:"first_deployed,omitempty"` - // LastDeployed is when the release was last deployed. - LastDeployed time.Time `json:"last_deployed,omitempty"` - // Deleted tracks when this object was deleted. - Deleted time.Time `json:"deleted"` - // Description is human-friendly "log entry" about this release. - Description string `json:"description,omitempty"` - // Status is the current state of the release - Status string `json:"status,omitempty"` - // Contains the rendered templates/NOTES.txt if available - Notes string `json:"notes,omitempty"` -} - -type helmRlsStatus struct { - Name string `json:"name"` - Namespace string `json:"namespace"` - Revision int `json:"revision"` - Status string `json:"status"` - Chart string `json:"chart"` - AppVersion string `json:"app_version"` -} - var _ HelmWrapper = &helmWrapper{} type HelmWrapper interface { @@ -96,7 +68,7 @@ type HelmWrapper interface { Manifest() (string, error) } -func (c *helmWrapper) Status() (status *releaseStatus, err error) { +func (c *helmWrapper) Status() (status *helmrelease.Release, err error) { if err = c.ensureWorkspace(); err != nil { return nil, err } @@ -127,6 +99,11 @@ func (c *helmWrapper) Status() (status *releaseStatus, err error) { err = cmd.Run() if err != nil { + helmErr := strings.TrimSpace(stderr.String()) + if helmErr == StatusNotFoundFormat { + klog.V(2).Infof("namespace: %s, name: %s, run command failed, stderr: %s, error: %v", c.Namespace, c.ReleaseName, stderr, err) + return nil, errors.New(helmErr) + } klog.Errorf("namespace: %s, name: %s, run command failed, stderr: %s, error: %v", c.Namespace, c.ReleaseName, stderr, err) return } else { @@ -134,7 +111,7 @@ func (c *helmWrapper) Status() (status *releaseStatus, err error) { klog.V(8).Infof("namespace: %s, name: %s, run command success, stdout: %s", c.Namespace, c.ReleaseName, stdout) } - status = &releaseStatus{} + status = &helmrelease.Release{} err = json.Unmarshal(stdout.Bytes(), status) if err != nil { klog.Errorf("namespace: %s, name: %s, json unmarshal failed, error: %s", c.Namespace, c.ReleaseName, err) @@ -420,7 +397,20 @@ func (c *helmWrapper) Upgrade(chartName, chartData, values string) (res HelmRes, // helm install func (c *helmWrapper) Install(chartName, chartData, values string) (res HelmRes, err error) { - return c.install(chartName, chartData, values, false) + sts, err := c.Status() + if err == nil { + // helm release has been installed + if sts.Info != nil && sts.Info.Status == "deployed" { + return HelmRes{}, nil + } + return HelmRes{}, errors.New(releaseExists) + } else { + if err.Error() == StatusNotFoundFormat { + // continue to install + return c.install(chartName, chartData, values, false) + } + return HelmRes{}, err + } } func (c *helmWrapper) install(chartName, chartData, values string, upgrade bool) (res HelmRes, err error) { diff --git a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_test.go b/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_test.go index c431bf6b60a0db3b916e0225c564f2e953d963d9..60d734891cc4c031ef089266bb7505a12767771d 100644 --- a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_test.go +++ b/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_test.go @@ -28,7 +28,7 @@ func TestHelmInstall(t *testing.T) { SetAnnotations(map[string]string{constants.CreatorAnnotationKey: "1234"}), SetMock(true)) - res, err := wr.Install("dummy-chart", "", "dummy-value") + res, err := wr.install("dummy-chart", "", "dummy-value", false) if err != nil { t.Fail() }