/* * Please refer to the LICENSE file in the root directory of the project. * https://github.com/kubesphere/kubesphere/blob/master/LICENSE */ package core import ( "bytes" "encoding/base64" goerrors "errors" "fmt" "io" "os" "path/filepath" "sort" "strings" "text/template" "github.com/Masterminds/semver/v3" yaml3 "gopkg.in/yaml.v3" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/storage/driver" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/klog/v2" clusterv1alpha1 "kubesphere.io/api/cluster/v1alpha1" corev1alpha1 "kubesphere.io/api/core/v1alpha1" "kubesphere.io/utils/helm" "kubesphere.io/kubesphere/pkg/utils/hashutil" "kubesphere.io/kubesphere/pkg/version" ) func getRecommendedExtensionVersion(versions []corev1alpha1.ExtensionVersion, k8sVersion *semver.Version) (string, error) { if len(versions) == 0 { return "", nil } ksVersion, err := semver.NewVersion(version.Get().GitVersion) if err != nil { return "", fmt.Errorf("parse KubeSphere version failed: %v", err) } var matchedVersions []*semver.Version for _, v := range versions { kubeVersionMatched, ksVersionMatched := matchVersionConstraints(v, k8sVersion, ksVersion) if kubeVersionMatched && ksVersionMatched { targetVersion, err := semver.NewVersion(v.Spec.Version) if err != nil { klog.V(2).Infof("parse version failed, extension version: %s, err: %s", v.Spec.Version, err) continue } matchedVersions = append(matchedVersions, targetVersion) } } if len(matchedVersions) == 0 { return "", nil } sort.Slice(matchedVersions, func(i, j int) bool { return matchedVersions[i].Compare(matchedVersions[j]) >= 0 }) return matchedVersions[0].Original(), nil } func matchVersionConstraints(v corev1alpha1.ExtensionVersion, k8sVersion, ksVersion *semver.Version) (bool, bool) { kubeVersionMatched := v.Spec.KubeVersion == "" || checkVersionConstraint(v.Spec.KubeVersion, k8sVersion) ksVersionMatched := v.Spec.KSVersion == "" || checkVersionConstraint(v.Spec.KSVersion, ksVersion) return kubeVersionMatched, ksVersionMatched } func checkVersionConstraint(constraint string, version *semver.Version) bool { targetVersion, err := semver.NewConstraint(constraint) if err != nil { klog.Warningf("failed to parse version constraints: %s, err: %s", constraint, err) return false } return targetVersion.Check(version) } func getLatestExtensionVersion(versions []corev1alpha1.ExtensionVersion) *corev1alpha1.ExtensionVersion { if len(versions) == 0 { return nil } var latestVersion *corev1alpha1.ExtensionVersion var latestSemver *semver.Version for i := range versions { currSemver, err := semver.NewVersion(versions[i].Spec.Version) if err != nil { klog.Warningf("parse version failed, extension version: %s, err: %s", versions[i].Name, err) continue } if latestSemver == nil || latestSemver.LessThan(currSemver) { latestSemver = currSemver latestVersion = &versions[i] } } return latestVersion } func isReleaseNotFoundError(err error) bool { if err == nil { return false } return strings.Contains(err.Error(), driver.ErrReleaseNotFound.Error()) } func clusterConfig(sub *corev1alpha1.InstallPlan, clusterName string) []byte { if clusterName == "" { return []byte(sub.Spec.Config) } for cluster, config := range sub.Spec.ClusterScheduling.Overrides { if cluster == clusterName { return merge(sub.Spec.Config, config) } } return []byte(sub.Spec.Config) } func merge(config string, override string) []byte { config = strings.TrimSpace(config) override = strings.TrimSpace(override) if config == "" && override == "" { return []byte("") } if override == "" { return []byte(config) } if config == "" { return []byte(override) } baseConf := map[string]interface{}{} if err := yaml3.Unmarshal([]byte(config), &baseConf); err != nil { klog.Warningf("failed to unmarshal config: %v", err) } overrideConf := map[string]interface{}{} if err := yaml3.Unmarshal([]byte(override), overrideConf); err != nil { klog.Warningf("failed to unmarshal config: %v", err) } finalConf := mergeValues(baseConf, overrideConf) data, _ := yaml3.Marshal(finalConf) return data } // mergeValues will merge source and destination map, preferring values from the source map func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} { for k, v := range src { // If the key doesn't exist already, then just set the key to that value if _, exists := dest[k]; !exists { dest[k] = v continue } nextMap, ok := v.(map[string]interface{}) // If it isn't another map, overwrite the value if !ok { dest[k] = v continue } // Edge case: If the key exists in the destination, but isn't a map destMap, isMap := dest[k].(map[string]interface{}) // If the source map has a map for this key, prefer it if !isMap { dest[k] = v continue } // If we got to this point, it is a map in both, so merge them dest[k] = mergeValues(destMap, nextMap) } return dest } func usesPermissions(mainChart *chart.Chart) (rbacv1.ClusterRole, rbacv1.Role) { var clusterRole rbacv1.ClusterRole var role rbacv1.Role for _, file := range mainChart.Files { if file.Name == permissionDefinitionFile { // decoder := yaml.NewDecoder(bytes.NewReader(file.Data)) decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(file.Data), 1024) for { result := new(rbacv1.Role) // create new spec here // pass a reference to spec reference err := decoder.Decode(&result) // check it was parsed if result == nil { continue } // break the loop in case of EOF if goerrors.Is(err, io.EOF) { break } if err != nil { return clusterRole, role } if result.Kind == "ClusterRole" { clusterRole.Rules = append(clusterRole.Rules, result.Rules...) } if result.Kind == "Role" { role.Rules = append(role.Rules, result.Rules...) } } } } return clusterRole, role } func hasCluster(clusters []clusterv1alpha1.Cluster, clusterName string) bool { for _, cluster := range clusters { if cluster.Name == clusterName { return true } } return false } func versionChanged(plan *corev1alpha1.InstallPlan, cluster string) bool { var oldVersion string if cluster == "" { oldVersion = plan.Status.Version } else if plan.Status.ClusterSchedulingStatuses != nil { oldVersion = plan.Status.ClusterSchedulingStatuses[cluster].Version } newVersion := plan.Spec.Extension.Version if oldVersion == "" { return false } return newVersion != oldVersion } func configChanged(sub *corev1alpha1.InstallPlan, cluster string) bool { var oldConfigHash string if cluster == "" { oldConfigHash = sub.Status.InstallationStatus.ConfigHash } else { oldConfigHash = sub.Status.ClusterSchedulingStatuses[cluster].ConfigHash } newConfigHash := hashutil.FNVString(clusterConfig(sub, cluster)) if oldConfigHash == "" { return true } return newConfigHash != oldConfigHash } // newHelmCred from Repository func newHelmCred(repo *corev1alpha1.Repository) (helm.RepoCredential, error) { cred := helm.RepoCredential{ InsecureSkipTLSVerify: repo.Spec.Insecure, } if repo.Spec.CABundle != "" { caFile, err := storeCAFile(repo.Spec.CABundle, repo.Name) if err != nil { return cred, err } cred.CAFile = caFile } if repo.Spec.BasicAuth != nil { cred.Username = repo.Spec.BasicAuth.Username cred.Password = repo.Spec.BasicAuth.Password } return cred, nil } // storeCAFile in local file from caTemplate. func storeCAFile(caBundle string, repoName string) (string, error) { var buff = &bytes.Buffer{} tmpl, err := template.New("repositoryCABundle").Parse(caTemplate) if err != nil { return "", err } if err := tmpl.Execute(buff, map[string]string{ "TempDIR": os.TempDir(), "RepositoryName": repoName, }); err != nil { return "", err } caFile := buff.String() if _, err := os.Stat(filepath.Dir(caFile)); err != nil { if !os.IsNotExist(err) { return "", err } if err := os.MkdirAll(filepath.Dir(caFile), os.ModePerm); err != nil { return "", err } } data, err := base64.StdEncoding.DecodeString(caBundle) if err != nil { return "", err } if err := os.WriteFile(caFile, data, os.ModePerm); err != nil { return "", err } return caFile, nil }