Merge pull request #3465 from xyz-li/app-fix
Fix nil pointer and delete helmRelease
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
78
pkg/controller/openpitrix/helmrelease/get_chart_data.go
Normal file
78
pkg/controller/openpitrix/helmrelease/get_chart_data.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// TODO modify credential
|
||||
if request.Name != nil {
|
||||
repoCopy.Labels[constants.NameLabelKey] = *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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user