diff --git a/cmd/vclusterctl/cmd/app/create/pro.go b/cmd/vclusterctl/cmd/app/create/pro.go index 1c1df9bf9f..364dac5fce 100644 --- a/cmd/vclusterctl/cmd/app/create/pro.go +++ b/cmd/vclusterctl/cmd/app/create/pro.go @@ -3,10 +3,15 @@ package create import ( "context" "fmt" + "os" "strconv" + "strings" "time" + "github.com/ghodss/yaml" + "github.com/go-logr/logr" clusterv1 "github.com/loft-sh/agentapi/v3/pkg/apis/loft/cluster/v1" + agentstoragev1 "github.com/loft-sh/agentapi/v3/pkg/apis/loft/storage/v1" managementv1 "github.com/loft-sh/api/v3/pkg/apis/management/v1" storagev1 "github.com/loft-sh/api/v3/pkg/apis/storage/v1" "github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/create" @@ -16,7 +21,14 @@ import ( "github.com/loft-sh/loftctl/v3/pkg/config" "github.com/loft-sh/loftctl/v3/pkg/vcluster" "github.com/loft-sh/log" + helmUtils "github.com/loft-sh/utils/pkg/helm" + "github.com/loft-sh/utils/pkg/helm/values" + "github.com/loft-sh/vcluster/pkg/strvals" + "github.com/loft-sh/vcluster/pkg/telemetry" "github.com/loft-sh/vcluster/pkg/upgrade" + "github.com/loft-sh/vcluster/pkg/util" + "github.com/loft-sh/vcluster/pkg/util/cliconfig" + "golang.org/x/mod/semver" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" @@ -25,7 +37,9 @@ import ( const LoftChartRepo = "https://charts.loft.sh" -func DeployProCluster(ctx context.Context, options *Options, proClient proclient.Client, virtualClusterName string, log log.Logger) error { +var AllowedDistros = []string{"k3s", "k0s", "k8s", "eks"} + +func DeployProCluster(ctx context.Context, options *Options, proClient proclient.Client, virtualClusterName, targetNamespace string, log log.Logger) error { // determine project & cluster name var err error options.Cluster, options.Project, err = helper.SelectProjectOrCluster(proClient, options.Cluster, options.Project, false, log) @@ -72,18 +86,40 @@ func DeployProCluster(ctx context.Context, options *Options, proClient proclient return fmt.Errorf("virtual cluster %s already exists in project %s", virtualClusterName, options.Project) } + // should create via template + useTemplate, err := shouldCreateWithTemplate(ctx, proClient, options, virtualClusterInstance) + if err != nil { + return fmt.Errorf("should use template: %w", err) + } + // create virtual cluster if necessary - if virtualClusterInstance == nil { - // create via template - virtualClusterInstance, err = createWithTemplate(ctx, proClient, options, virtualClusterName, log) - if err != nil { - return err + if useTemplate { + if virtualClusterInstance == nil { + // create via template + virtualClusterInstance, err = createWithTemplate(ctx, proClient, options, virtualClusterName, log) + if err != nil { + return err + } + } else { + // upgrade via template + virtualClusterInstance, err = upgradeWithTemplate(ctx, proClient, options, virtualClusterInstance, log) + if err != nil { + return err + } } - } else if options.Upgrade { - // upgrade via template - virtualClusterInstance, err = upgradeWithTemplate(ctx, proClient, options, virtualClusterInstance, log) - if err != nil { - return err + } else { + if virtualClusterInstance == nil { + // create without template + virtualClusterInstance, err = createWithoutTemplate(ctx, proClient, options, virtualClusterName, targetNamespace, log) + if err != nil { + return err + } + } else { + // upgrade via template + virtualClusterInstance, err = upgradeWithoutTemplate(ctx, proClient, options, virtualClusterInstance, log) + if err != nil { + return err + } } } @@ -97,6 +133,164 @@ func DeployProCluster(ctx context.Context, options *Options, proClient proclient return nil } +func createWithoutTemplate(ctx context.Context, proClient proclient.Client, options *Options, virtualClusterName, targetNamespace string, log log.Logger) (*managementv1.VirtualClusterInstance, error) { + err := validateNoTemplateOptions(options) + if err != nil { + return nil, err + } + + // merge values + helmValues, err := mergeValues(options, log) + if err != nil { + return nil, err + } + + // create virtual cluster instance + zone, offset := time.Now().Zone() + virtualClusterInstance := &managementv1.VirtualClusterInstance{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: naming.ProjectNamespace(options.Project), + Name: virtualClusterName, + Annotations: map[string]string{ + clusterv1.SleepModeTimezoneAnnotation: zone + "#" + strconv.Itoa(offset), + }, + }, + Spec: managementv1.VirtualClusterInstanceSpec{ + VirtualClusterInstanceSpec: storagev1.VirtualClusterInstanceSpec{ + Template: &storagev1.VirtualClusterTemplateDefinition{ + VirtualClusterCommonSpec: agentstoragev1.VirtualClusterCommonSpec{ + HelmRelease: agentstoragev1.VirtualClusterHelmRelease{ + Chart: agentstoragev1.VirtualClusterHelmChart{ + Name: options.ChartName, + Repo: options.ChartRepo, + Version: options.ChartVersion, + }, + Values: helmValues, + }, + // TODO: enable + // Pro: true + }, + }, + ClusterRef: storagev1.VirtualClusterClusterRef{ + ClusterRef: storagev1.ClusterRef{ + Cluster: options.Cluster, + Namespace: targetNamespace, + }, + }, + }, + }, + } + + // set links + create.SetCustomLinksAnnotation(virtualClusterInstance, options.Links) + + // get management client + managementClient, err := proClient.Management() + if err != nil { + return nil, err + } + + // create virtualclusterinstance + log.Infof("Creating virtual cluster %s in project %s...", virtualClusterName, options.Project) + virtualClusterInstance, err = managementClient.Loft().ManagementV1().VirtualClusterInstances(virtualClusterInstance.Namespace).Create(ctx, virtualClusterInstance, metav1.CreateOptions{}) + if err != nil { + return nil, fmt.Errorf("create virtual cluster: %w", err) + } + + return virtualClusterInstance, nil +} + +func upgradeWithoutTemplate(ctx context.Context, proClient proclient.Client, options *Options, virtualClusterInstance *managementv1.VirtualClusterInstance, log log.Logger) (*managementv1.VirtualClusterInstance, error) { + err := validateNoTemplateOptions(options) + if err != nil { + return nil, err + } + + // merge values + helmValues, err := mergeValues(options, log) + if err != nil { + return nil, err + } + + // update virtual cluster instance + if virtualClusterInstance.Spec.Template == nil { + return nil, fmt.Errorf("virtual cluster instance uses a template, cannot update virtual cluster") + } + + oldVirtualCluster := virtualClusterInstance.DeepCopy() + chartNameChanged := virtualClusterInstance.Spec.Template.HelmRelease.Chart.Name != options.ChartName + if chartNameChanged { + return nil, fmt.Errorf("cannot change chart name from '%s' to '%s', this operation is not allowed", virtualClusterInstance.Spec.Template.HelmRelease.Chart.Name, options.ChartName) + } + + chartRepoChanged := virtualClusterInstance.Spec.Template.HelmRelease.Chart.Repo != options.ChartRepo + chartVersionChanged := virtualClusterInstance.Spec.Template.HelmRelease.Chart.Version != options.ChartVersion + valuesChanged := virtualClusterInstance.Spec.Template.HelmRelease.Values != helmValues + linksChanged := create.SetCustomLinksAnnotation(virtualClusterInstance, options.Links) + + // check if update is needed + if chartRepoChanged || chartVersionChanged || valuesChanged || linksChanged { + virtualClusterInstance.Spec.Template.HelmRelease.Chart.Repo = options.ChartRepo + virtualClusterInstance.Spec.Template.HelmRelease.Chart.Version = options.ChartVersion + virtualClusterInstance.Spec.Template.HelmRelease.Values = helmValues + + // get management client + managementClient, err := proClient.Management() + if err != nil { + return nil, err + } + + patch := client.MergeFrom(oldVirtualCluster) + patchData, err := patch.Data(virtualClusterInstance) + if err != nil { + return nil, fmt.Errorf("calculate update patch: %w", err) + } + log.Infof("Updating virtual cluster %s in project %s...", virtualClusterInstance.Name, options.Project) + virtualClusterInstance, err = managementClient.Loft().ManagementV1().VirtualClusterInstances(virtualClusterInstance.Namespace).Patch(ctx, virtualClusterInstance.Name, patch.Type(), patchData, metav1.PatchOptions{}) + if err != nil { + return nil, fmt.Errorf("patch virtual cluster: %w", err) + } + } else { + log.Infof("Skip updating virtual cluster...") + } + + return virtualClusterInstance, nil +} + +func shouldCreateWithTemplate(ctx context.Context, proClient proclient.Client, options *Options, virtualClusterInstance *managementv1.VirtualClusterInstance) (bool, error) { + virtualClusterInstanceHasTemplate := virtualClusterInstance != nil && virtualClusterInstance.Spec.TemplateRef != nil + virtualClusterInstanceHasNoTemplate := virtualClusterInstance != nil && virtualClusterInstance.Spec.TemplateRef == nil + if virtualClusterInstanceHasTemplate || options.Template != "" { + return true, nil + } else if virtualClusterInstanceHasNoTemplate { + return false, nil + } + + managementClient, err := proClient.Management() + if err != nil { + return false, err + } + + project, err := managementClient.Loft().ManagementV1().Projects().Get(ctx, options.Project, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("get vCluster project: %w", err) + } + + // check if there is a default template + for _, template := range project.Spec.AllowedTemplates { + if template.Kind == "VirtualClusterTemplate" && template.IsDefault { + return true, nil + } + } + + // check if we can create without + if project.Spec.RequireTemplate.Disabled { + return false, nil + } + + return true, nil +} + func createWithTemplate(ctx context.Context, proClient proclient.Client, options *Options, virtualClusterName string, log log.Logger) (*managementv1.VirtualClusterInstance, error) { err := validateTemplateOptions(options) if err != nil { @@ -222,6 +416,23 @@ func upgradeWithTemplate(ctx context.Context, proClient proclient.Client, option return virtualClusterInstance, nil } +func validateNoTemplateOptions(options *Options) error { + if len(options.SetParams) > 0 { + return fmt.Errorf("cannot use --set-param because the vcluster is not using a template. Use --set instead") + } + if options.Params != "" { + return fmt.Errorf("cannot use --params because the vcluster is not using a template. Use --values instead") + } + if options.Template != "" { + return fmt.Errorf("cannot use --template because the vcluster is not using a template") + } + if options.TemplateVersion != "" { + return fmt.Errorf("cannot use --template-version because the vcluster is not using a template") + } + + return nil +} + func validateTemplateOptions(options *Options) error { if len(options.SetValues) > 0 { return fmt.Errorf("cannot use --set because the vcluster is using a template. Please use --set-param instead") @@ -250,3 +461,119 @@ func validateTemplateOptions(options *Options) error { return nil } + +func mergeValues(options *Options, log log.Logger) (string, error) { + // merge values + chartOptions, err := toChartOptions(options, log) + if err != nil { + return "", err + } + logger := logr.New(log.LogrLogSink()) + chartValues, err := values.GetDefaultReleaseValues(chartOptions, logger) + if err != nil { + return "", err + } + + // merge them with --values + outValues, err := parseString(chartValues) + if err != nil { + return "", err + } + + // merge values + for _, valuesFile := range options.Values { + out, err := os.ReadFile(valuesFile) + if err != nil { + return "", fmt.Errorf("reading values file %s: %w", valuesFile, err) + } + + extraValues, err := parseString(string(out)) + if err != nil { + return "", fmt.Errorf("parse values file %s: %w", valuesFile, err) + } + + strvals.MergeMaps(outValues, extraValues) + } + + // merge set + for _, set := range options.SetValues { + err = strvals.ParseIntoString(set, outValues) + if err != nil { + return "", fmt.Errorf("apply --set %s: %w", set, err) + } + } + + // out + out, err := yaml.Marshal(outValues) + if err != nil { + return "", err + } + + return string(out), nil +} + +func parseString(str string) (map[string]interface{}, error) { + out := map[string]interface{}{} + err := yaml.Unmarshal([]byte(str), &out) + if err != nil { + return nil, err + } + + return out, nil +} + +func toChartOptions(options *Options, log log.Logger) (*helmUtils.ChartOptions, error) { + if !util.Contains(options.Distro, AllowedDistros) { + return nil, fmt.Errorf("unsupported distro %s, please select one of: %s", options.Distro, strings.Join(AllowedDistros, ", ")) + } + + if options.ChartName == "vcluster" && options.Distro != "k3s" { + options.ChartName += "-" + options.Distro + } + + cliConf, err := cliconfig.GetConfig() + if err != nil { + log.Debugf("Failed to load local configuration file: %v", err.Error()) + } + instanceCreatorUID := "" + if !cliConf.TelemetryDisabled { + instanceCreatorUID = telemetry.GetInstanceCreatorUID() + } + + version := helmUtils.Version{} + if options.KubernetesVersion != "" { + if options.KubernetesVersion[0] != 'v' { + options.KubernetesVersion = "v" + options.KubernetesVersion + } + + if !semver.IsValid(options.KubernetesVersion) { + return nil, fmt.Errorf("please use valid semantic versioning format, e.g. vX.X") + } + + majorMinorVer := semver.MajorMinor(options.KubernetesVersion) + if splittedVersion := strings.Split(options.KubernetesVersion, "."); len(splittedVersion) > 2 { + log.Warnf("currently we only support major.minor version (%s) and not the patch version (%s)", majorMinorVer, options.KubernetesVersion) + } + + parsedVersion, err := values.ParseKubernetesVersionInfo(majorMinorVer) + if err != nil { + return nil, err + } + + version.Major = parsedVersion.Major + version.Minor = parsedVersion.Minor + } + + return &helmUtils.ChartOptions{ + ChartName: options.ChartName, + ChartRepo: options.ChartRepo, + ChartVersion: options.ChartVersion, + CreateClusterRole: true, + DisableIngressSync: options.DisableIngressSync, + Isolate: options.Isolate, + KubernetesVersion: version, + DisableTelemetry: cliConf.TelemetryDisabled, + InstanceCreatorType: "vclusterctl", + InstanceCreatorUID: instanceCreatorUID, + }, nil +} diff --git a/cmd/vclusterctl/cmd/create.go b/cmd/vclusterctl/cmd/create.go index db0f97d904..4005dd15f9 100644 --- a/cmd/vclusterctl/cmd/create.go +++ b/cmd/vclusterctl/cmd/create.go @@ -46,7 +46,6 @@ import ( ) var ( - AllowedDistros = []string{"k3s", "k0s", "k8s", "eks"} CreatedByVClusterAnnotation = "vcluster.loft.sh/created" ) @@ -97,7 +96,7 @@ vcluster create test --namespace test cobraCmd.Flags().StringVar(&cmd.ChartVersion, "chart-version", upgrade.GetVersion(), "The virtual cluster chart version to use (e.g. v0.9.1)") cobraCmd.Flags().StringVar(&cmd.ChartName, "chart-name", "vcluster", "The virtual cluster chart name to use") cobraCmd.Flags().StringVar(&cmd.ChartRepo, "chart-repo", create.LoftChartRepo, "The virtual cluster chart repo to use") - cobraCmd.Flags().StringVar(&cmd.Distro, "distro", "k3s", fmt.Sprintf("Kubernetes distro to use for the virtual cluster. Allowed distros: %s", strings.Join(AllowedDistros, ", "))) + cobraCmd.Flags().StringVar(&cmd.Distro, "distro", "k3s", fmt.Sprintf("Kubernetes distro to use for the virtual cluster. Allowed distros: %s", strings.Join(create.AllowedDistros, ", "))) cobraCmd.Flags().StringVar(&cmd.KubernetesVersion, "kubernetes-version", "", "The kubernetes version to use (e.g. v1.20). Patch versions are not supported") cobraCmd.Flags().StringArrayVarP(&cmd.Values, "values", "f", []string{}, "Path where to load extra helm values from") cobraCmd.Flags().StringArrayVar(&cmd.SetValues, "set", []string{}, "Set values for helm. E.g. --set 'persistence.enabled=true'") @@ -169,7 +168,7 @@ func (cmd *CreateCmd) Run(ctx context.Context, args []string) error { proClient, err := pro.CreateProClient() if err == nil { // deploy pro cluster - err = create.DeployProCluster(ctx, &cmd.Options, proClient, args[0], cmd.log) + err = create.DeployProCluster(ctx, &cmd.Options, proClient, args[0], cmd.Namespace, cmd.log) if err != nil { return err } @@ -400,8 +399,8 @@ func (cmd *CreateCmd) deployChart(ctx context.Context, vClusterName, chartValues } func (cmd *CreateCmd) ToChartOptions(kubernetesVersion *version.Info) (*helmUtils.ChartOptions, error) { - if !util.Contains(cmd.Distro, AllowedDistros) { - return nil, fmt.Errorf("unsupported distro %s, please select one of: %s", cmd.Distro, strings.Join(AllowedDistros, ", ")) + if !util.Contains(cmd.Distro, create.AllowedDistros) { + return nil, fmt.Errorf("unsupported distro %s, please select one of: %s", cmd.Distro, strings.Join(create.AllowedDistros, ", ")) } if cmd.ChartName == "vcluster" && cmd.Distro != "k3s" { diff --git a/go.mod b/go.mod index e5c22962bb..5864bc2196 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/loft-sh/agentapi/v3 v3.3.0-ci.1.0.20230921083523-e1d74f6f8fd1 github.com/loft-sh/api/v3 v3.0.0-20230922094800-6d0c1cbf0fa6 github.com/loft-sh/loftctl/v3 v3.0.0-20230922112452-20febcea05a7 - github.com/loft-sh/utils v0.0.25 + github.com/loft-sh/utils v0.0.28-0.20230922120533-1b39f079a09e github.com/mitchellh/go-homedir v1.1.0 github.com/moby/term v0.5.0 github.com/onsi/ginkgo/v2 v2.11.0 diff --git a/go.sum b/go.sum index 394f66c759..39eb93dfa9 100644 --- a/go.sum +++ b/go.sum @@ -562,8 +562,8 @@ github.com/loft-sh/loftctl/v3 v3.0.0-20230922112452-20febcea05a7 h1:lsBF4llFeHcD github.com/loft-sh/loftctl/v3 v3.0.0-20230922112452-20febcea05a7/go.mod h1:g2HZHmpkqtiQGqM8QVRuaCgNDI6IMW6DZQP0cOUgxp8= github.com/loft-sh/log v0.0.0-20230824104949-bd516c25712a h1:/gqqjKpcHEdFXIX41lx1Y/FBqT/72gbPpf7sa20tyM8= github.com/loft-sh/log v0.0.0-20230824104949-bd516c25712a/go.mod h1:YImeRjXH34Yf5E79T7UHBQpDZl9fIaaFRgyZ/bkY+UQ= -github.com/loft-sh/utils v0.0.25 h1:JbbRJfXO1Rd34fQcaoDSmwyPBEzsrLwBSR21C90hHuk= -github.com/loft-sh/utils v0.0.25/go.mod h1:9hlX9cGpWHg3mNi/oBlv3X4ePGDMK66k8MbOZGFMDTI= +github.com/loft-sh/utils v0.0.28-0.20230922120533-1b39f079a09e h1:oruuACHMZGg6wn3fASWd1pmpuj7CrRjyYG8dPDfrcdw= +github.com/loft-sh/utils v0.0.28-0.20230922120533-1b39f079a09e/go.mod h1:9hlX9cGpWHg3mNi/oBlv3X4ePGDMK66k8MbOZGFMDTI= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= diff --git a/pkg/strvals/strvals.go b/pkg/strvals/strvals.go new file mode 100644 index 0000000000..5a58b0bec3 --- /dev/null +++ b/pkg/strvals/strvals.go @@ -0,0 +1,480 @@ +/* +Copyright The Helm 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 strvals + +import ( + "bytes" + "fmt" + "io" + "reflect" + "strconv" + "strings" + + "github.com/pkg/errors" + "sigs.k8s.io/yaml" +) + +// ErrNotList indicates that a non-list was treated as a list. +var ErrNotList = errors.New("not a list") + +func MergeMaps(a, b map[string]interface{}) map[string]interface{} { + out := make(map[string]interface{}, len(a)) + for k, v := range a { + out[k] = v + } + for k, v := range b { + if v, ok := v.(map[string]interface{}); ok { + if bv, ok := out[k]; ok { + if bv, ok := bv.(map[string]interface{}); ok { + out[k] = MergeMaps(bv, v) + continue + } + } + } + out[k] = v + } + return out +} + +// ToYAML takes a string of arguments and converts to a YAML document. +func ToYAML(s string) (string, error) { + m, err := Parse(s) + if err != nil { + return "", err + } + d, err := yaml.Marshal(m) + return strings.TrimSuffix(string(d), "\n"), err +} + +// Parse parses a set line. +// +// A set line is of the form name1=value1,name2=value2 +func Parse(s string) (map[string]interface{}, error) { + vals := map[string]interface{}{} + scanner := bytes.NewBufferString(s) + t := newParser(scanner, vals, false) + err := t.parse() + return vals, err +} + +// ParseString parses a set line and forces a string value. +// +// A set line is of the form name1=value1,name2=value2 +func ParseString(s string) (map[string]interface{}, error) { + vals := map[string]interface{}{} + scanner := bytes.NewBufferString(s) + t := newParser(scanner, vals, true) + err := t.parse() + return vals, err +} + +// ParseInto parses a strvals line and merges the result into dest. +// +// If the strval string has a key that exists in dest, it overwrites the +// dest version. +func ParseInto(s string, dest map[string]interface{}) error { + scanner := bytes.NewBufferString(s) + t := newParser(scanner, dest, false) + return t.parse() +} + +// ParseFile parses a set line, but its final value is loaded from the file at the path specified by the original value. +// +// A set line is of the form name1=path1,name2=path2 +// +// When the files at path1 and path2 contained "val1" and "val2" respectively, the set line is consumed as +// name1=val1,name2=val2 +func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error) { + vals := map[string]interface{}{} + scanner := bytes.NewBufferString(s) + t := newFileParser(scanner, vals, reader) + err := t.parse() + return vals, err +} + +// ParseIntoString parses a strvals line and merges the result into dest. +// +// This method always returns a string as the value. +func ParseIntoString(s string, dest map[string]interface{}) error { + scanner := bytes.NewBufferString(s) + t := newParser(scanner, dest, true) + return t.parse() +} + +// ParseIntoFile parses a filevals line and merges the result into dest. +// +// This method always returns a string as the value. +func ParseIntoFile(s string, dest map[string]interface{}, reader RunesValueReader) error { + scanner := bytes.NewBufferString(s) + t := newFileParser(scanner, dest, reader) + return t.parse() +} + +// RunesValueReader is a function that takes the given value (a slice of runes) +// and returns the parsed value +type RunesValueReader func([]rune) (interface{}, error) + +// parser is a simple parser that takes a strvals line and parses it into a +// map representation. +// +// where sc is the source of the original data being parsed +// where data is the final parsed data from the parses with correct types +type parser struct { + sc *bytes.Buffer + data map[string]interface{} + reader RunesValueReader +} + +func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser { + stringConverter := func(rs []rune) (interface{}, error) { + return typedVal(rs, stringBool), nil + } + return &parser{sc: sc, data: data, reader: stringConverter} +} + +func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser { + return &parser{sc: sc, data: data, reader: reader} +} + +func (t *parser) parse() error { + for { + err := t.key(t.data) + if err == nil { + continue + } + if errors.Is(err, io.EOF) { + return nil + } + return err + } +} + +func runeSet(r []rune) map[rune]bool { + s := make(map[rune]bool, len(r)) + for _, rr := range r { + s[rr] = true + } + return s +} + +func (t *parser) key(data map[string]interface{}) (reterr error) { + defer func() { + if r := recover(); r != nil { + reterr = fmt.Errorf("unable to parse key: %s", r) + } + }() + stop := runeSet([]rune{'=', '[', ',', '.'}) + for { + switch k, last, err := runesUntil(t.sc, stop); { + case err != nil: + if len(k) == 0 { + return err + } + return errors.Errorf("key %q has no value", string(k)) + //set(data, string(k), "") + //return err + case last == '[': + // We are in a list index context, so we need to set an index. + i, err := t.keyIndex() + if err != nil { + return errors.Wrap(err, "error parsing index") + } + kk := string(k) + // Find or create target list + list := []interface{}{} + if _, ok := data[kk]; ok { + list = data[kk].([]interface{}) + } + + // Now we need to get the value after the ]. + list, err = t.listItem(list, i) + set(data, kk, list) + return err + case last == '=': + //End of key. Consume =, Get value. + // FIXME: Get value list first + vl, e := t.valList() + if e == nil { + set(data, string(k), vl) + return nil + } else if errors.Is(e, io.EOF) { + set(data, string(k), "") + return e + } else if errors.Is(e, ErrNotList) { + rs, e := t.val() + if e != nil && !errors.Is(e, io.EOF) { + return e + } + v, e := t.reader(rs) + set(data, string(k), v) + return e + } + + return e + case last == ',': + // No value given. Set the value to empty string. Return error. + set(data, string(k), "") + return errors.Errorf("key %q has no value (cannot end with ,)", string(k)) + case last == '.': + // First, create or find the target map. + inner := map[string]interface{}{} + if _, ok := data[string(k)]; ok { + inner = data[string(k)].(map[string]interface{}) + } + + // Recurse + e := t.key(inner) + if len(inner) == 0 { + return errors.Errorf("key map %q has no value", string(k)) + } + set(data, string(k), inner) + return e + } + } +} + +func set(data map[string]interface{}, key string, val interface{}) { + // If key is empty, don't set it. + if len(key) == 0 { + return + } + + rt := reflect.TypeOf(val) + switch rt.Kind() { + case reflect.String: + if len(val.(string)) < 1 { + data[key] = nil + } else { + data[key] = val + } + default: + data[key] = val + } +} + +func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{}, err error) { + // There are possible index values that are out of range on a target system + // causing a panic. This will catch the panic and return an error instead. + // The value of the index that causes a panic varies from system to system. + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("error processing index %d: %s", index, r) + } + }() + + if index < 0 { + return list, fmt.Errorf("negative %d index not allowed", index) + } + if len(list) <= index { + newlist := make([]interface{}, index+1) + copy(newlist, list) + list = newlist + } + switch reflect.TypeOf(val).Kind() { + case reflect.String: + if len(val.(string)) > 0 { + list[index] = val + } else { + list[index] = nil + } + default: + list[index] = val + } + return list, nil +} + +func (t *parser) keyIndex() (int, error) { + // First, get the key. + stop := runeSet([]rune{']'}) + v, _, err := runesUntil(t.sc, stop) + if err != nil { + return 0, err + } + // v should be the index + return strconv.Atoi(string(v)) +} + +func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { + if i < 0 { + return list, fmt.Errorf("negative %d index not allowed", i) + } + stop := runeSet([]rune{'[', '.', '='}) + switch k, last, err := runesUntil(t.sc, stop); { + case len(k) > 0: + return list, errors.Errorf("unexpected data at end of array index: %q", k) + case err != nil: + return list, err + case last == '=': + vl, e := t.valList() + if e == nil { + return setIndex(list, i, vl) + } else if errors.Is(e, io.EOF) { + return setIndex(list, i, "") + } else if errors.Is(e, ErrNotList) { + rs, e := t.val() + if e != nil && !errors.Is(e, io.EOF) { + return list, e + } + v, e := t.reader(rs) + if e != nil { + return list, e + } + return setIndex(list, i, v) + } + return list, e + case last == '[': + // now we have a nested list. Read the index and handle. + nextI, err := t.keyIndex() + if err != nil { + return list, errors.Wrap(err, "error parsing index") + } + var crtList []interface{} + if len(list) > i { + // If nested list already exists, take the value of list to next cycle. + existed := list[i] + if existed != nil { + crtList = list[i].([]interface{}) + } + } + // Now we need to get the value after the ]. + list2, err := t.listItem(crtList, nextI) + if err != nil { + return list, err + } + return setIndex(list, i, list2) + case last == '.': + // We have a nested object. Send to t.key + inner := map[string]interface{}{} + if len(list) > i { + var ok bool + inner, ok = list[i].(map[string]interface{}) + if !ok { + // We have indices out of order. Initialize empty value. + list[i] = map[string]interface{}{} + inner = list[i].(map[string]interface{}) + } + } + + // Recurse + e := t.key(inner) + if e != nil { + return list, e + } + return setIndex(list, i, inner) + default: + return nil, errors.Errorf("parse error: unexpected token %v", last) + } +} + +func (t *parser) val() ([]rune, error) { + stop := runeSet([]rune{','}) + v, _, err := runesUntil(t.sc, stop) + return v, err +} + +func (t *parser) valList() ([]interface{}, error) { + r, _, e := t.sc.ReadRune() + if e != nil { + return []interface{}{}, e + } + + if r != '{' { + _ = t.sc.UnreadRune() + return []interface{}{}, ErrNotList + } + + list := []interface{}{} + stop := runeSet([]rune{',', '}'}) + for { + switch rs, last, err := runesUntil(t.sc, stop); { + case err != nil: + if errors.Is(err, io.EOF) { + err = errors.New("list must terminate with '}'") + } + return list, err + case last == '}': + // If this is followed by ',', consume it. + if r, _, e := t.sc.ReadRune(); e == nil && r != ',' { + _ = t.sc.UnreadRune() + } + v, e := t.reader(rs) + list = append(list, v) + return list, e + case last == ',': + v, e := t.reader(rs) + if e != nil { + return list, e + } + list = append(list, v) + } + } +} + +func runesUntil(in io.RuneReader, stop map[rune]bool) ([]rune, rune, error) { + v := []rune{} + for { + switch r, _, e := in.ReadRune(); { + case e != nil: + return v, r, e + case inMap(r, stop): + return v, r, nil + case r == '\\': + next, _, e := in.ReadRune() + if e != nil { + return v, next, e + } + v = append(v, next) + default: + v = append(v, r) + } + } +} + +func inMap(k rune, m map[rune]bool) bool { + _, ok := m[k] + return ok +} + +func typedVal(v []rune, st bool) interface{} { + val := string(v) + + if st { + return val + } + + if strings.EqualFold(val, "true") { + return true + } + + if strings.EqualFold(val, "false") { + return false + } + + if strings.EqualFold(val, "null") { + return nil + } + + if strings.EqualFold(val, "0") { + return int64(0) + } + + // If this value does not start with zero, try parsing it to an int + if len(val) != 0 && val[0] != '0' { + if iv, err := strconv.ParseInt(val, 10, 64); err == nil { + return iv + } + } + + return val +} diff --git a/pkg/strvals/strvals_test.go b/pkg/strvals/strvals_test.go new file mode 100644 index 0000000000..7112e66c7a --- /dev/null +++ b/pkg/strvals/strvals_test.go @@ -0,0 +1,73 @@ +package strvals + +import ( + "encoding/json" + "fmt" + "log" + "testing" + + "github.com/pkg/errors" + "gotest.tools/assert" +) + +func TestSetStringFlag(t *testing.T) { + rawConfig := getActualRawConfig() + s := "deployments.dev.helm.values.containers[0]=" + err := ParseIntoString(s, rawConfig) + if err != nil { + fmt.Println(errors.Wrap(err, "parsing --set-string flag")) + log.Fatal(err) + } + b, err := json.Marshal(rawConfig) + if err != nil { + fmt.Println(err) + } + fmt.Println("output : " + string(b)) + assert.DeepEqual(t, rawConfig, getExpectedRawConfigForSetString()) +} + +func TestSetFlag(t *testing.T) { + s := "deployments.dev.helm.values.containers[1].image=" + rawConfig := getActualRawConfig() + err := ParseInto(s, rawConfig) + if err != nil { + fmt.Println(errors.Wrap(err, "parsing --set flag")) + log.Fatal(err) + } + b, err := json.Marshal(rawConfig) + if err != nil { + fmt.Println(err) + } + fmt.Println("output : " + string(b)) + assert.DeepEqual(t, rawConfig, getExpectedRawConfigForSet()) +} + +func getExpectedRawConfigForSet() map[string]interface{} { + jsonStr := "{\"deployments\":{\"dev\":{\"helm\":{\"values\":{\"containers\":[{\"image\":\"alpine\"},{\"image\":null}]}}}},\"name\":\"run-pipelines-demo\",\"pipelines\":{\"deploy\":\"create_deployments --all\",\"dev\":\"run_pipelines deploy --set deployments.dev.helm.values.containers[0].image=nginx --set-string deployments.dev.helm.values.containers[0].name=mynginx\"},\"version\":\"v2beta1\"}" + rawConfig := map[string]interface{}{} + err := json.Unmarshal([]byte(jsonStr), &rawConfig) + if err != nil { + fmt.Println(err) + } + return rawConfig +} + +func getExpectedRawConfigForSetString() map[string]interface{} { + jsonStr := "{\"deployments\":{\"dev\":{\"helm\":{\"values\":{\"containers\":[null,{\"image\":\"ns\"}]}}}},\"name\":\"run-pipelines-demo\",\"pipelines\":{\"deploy\":\"create_deployments --all\",\"dev\":\"run_pipelines deploy --set deployments.dev.helm.values.containers[0].image=nginx --set-string deployments.dev.helm.values.containers[0].name=mynginx\"},\"version\":\"v2beta1\"}" + rawConfig := map[string]interface{}{} + err := json.Unmarshal([]byte(jsonStr), &rawConfig) + if err != nil { + fmt.Println(err) + } + return rawConfig +} + +func getActualRawConfig() map[string]interface{} { + jsonStr := "{\n \"deployments\": {\n \"dev\": { \"helm\": { \"values\": { \"containers\": [{ \"image\": \"alpine\" },{ \"image\": \"ns\" }] } } }\n },\n \"name\": \"run-pipelines-demo\",\n \"pipelines\": {\n \"deploy\": \"create_deployments --all\",\n \"dev\": \"run_pipelines deploy --set deployments.dev.helm.values.containers[0].image=nginx --set-string deployments.dev.helm.values.containers[0].name=mynginx\"\n },\n \"version\": \"v2beta1\"\n}\n" + rawConfig := map[string]interface{}{} + err := json.Unmarshal([]byte(jsonStr), &rawConfig) + if err != nil { + fmt.Println(err) + } + return rawConfig +} diff --git a/vendor/github.com/loft-sh/utils/pkg/downloader/commands/helm_v3.go b/vendor/github.com/loft-sh/utils/pkg/downloader/commands/helm_v3.go index ad18b782a5..0ffc63146c 100644 --- a/vendor/github.com/loft-sh/utils/pkg/downloader/commands/helm_v3.go +++ b/vendor/github.com/loft-sh/utils/pkg/downloader/commands/helm_v3.go @@ -16,7 +16,7 @@ import ( ) var ( - helmVersion = "v3.11.1" + helmVersion = "v3.12.3" helmDownload = "https://get.helm.sh/helm-" + helmVersion + "-" + runtime.GOOS + "-" + runtime.GOARCH ) diff --git a/vendor/github.com/loft-sh/utils/pkg/helm/types.go b/vendor/github.com/loft-sh/utils/pkg/helm/types.go index 0c48db8dc0..77c24dea69 100644 --- a/vendor/github.com/loft-sh/utils/pkg/helm/types.go +++ b/vendor/github.com/loft-sh/utils/pkg/helm/types.go @@ -5,11 +5,6 @@ const ( K0SChart = "vcluster-k0s" K8SChart = "vcluster-k8s" EKSChart = "vcluster-eks" - - K3SProChart = "vcluster-pro" - K0SProChart = "vcluster-pro-k0s" - K8SProChart = "vcluster-pro-k8s" - EKSProChart = "vcluster-pro-eks" ) // ChartOptions holds the chart options @@ -29,13 +24,10 @@ type ChartOptions struct { DisableTelemetry bool InstanceCreatorType string InstanceCreatorUID string + Pro bool } type Version struct { Major string Minor string } - -func IsVclusterPro(chartName string) bool { - return chartName == K3SProChart || chartName == K0SProChart || chartName == K8SProChart || chartName == EKSProChart -} diff --git a/vendor/github.com/loft-sh/utils/pkg/helm/values/default.go b/vendor/github.com/loft-sh/utils/pkg/helm/values/default.go index a6d901381f..6b20dfc9ba 100644 --- a/vendor/github.com/loft-sh/utils/pkg/helm/values/default.go +++ b/vendor/github.com/loft-sh/utils/pkg/helm/values/default.go @@ -7,13 +7,13 @@ import ( func GetDefaultReleaseValues(chartOptions *helm.ChartOptions, log logr.Logger) (string, error) { switch chartOptions.ChartName { - case helm.K3SChart, helm.K3SProChart: + case helm.K3SChart: return getDefaultK3SReleaseValues(chartOptions, log) - case helm.K0SChart, helm.K0SProChart: + case helm.K0SChart: return getDefaultK0SReleaseValues(chartOptions, log) - case helm.K8SChart, helm.K8SProChart: + case helm.K8SChart: return getDefaultK8SReleaseValues(chartOptions, log) - case helm.EKSChart, helm.EKSProChart: + case helm.EKSChart: return getDefaultEKSReleaseValues(chartOptions, log) } diff --git a/vendor/github.com/loft-sh/utils/pkg/helm/values/eks.go b/vendor/github.com/loft-sh/utils/pkg/helm/values/eks.go index 4ea3391cb7..326dc399d5 100644 --- a/vendor/github.com/loft-sh/utils/pkg/helm/values/eks.go +++ b/vendor/github.com/loft-sh/utils/pkg/helm/values/eks.go @@ -8,66 +8,75 @@ import ( ) var EKSAPIVersionMap = map[string]string{ - "1.27": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.27.3-eks-1-27-7", - "1.26": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.26.6-eks-1-26-13", - "1.25": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.25.11-eks-1-25-17", - "1.24": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.24.15-eks-1-24-21", - "1.23": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.23.17-eks-1-23-26", + "1.27": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.27.4-eks-1-27-11", + "1.26": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.26.7-eks-1-26-17", + "1.25": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.25.12-eks-1-25-21", + "1.24": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.24.16-eks-1-24-25", + "1.23": "public.ecr.aws/eks-distro/kubernetes/kube-apiserver:v1.23.17-eks-1-23-30", } var EKSControllerVersionMap = map[string]string{ - "1.27": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.27.3-eks-1-27-7", - "1.26": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.26.6-eks-1-26-13", - "1.25": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.25.11-eks-1-25-17", - "1.24": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.24.15-eks-1-24-21", - "1.23": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.23.17-eks-1-23-26", + "1.27": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.27.4-eks-1-27-11", + "1.26": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.26.7-eks-1-26-17", + "1.25": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.25.12-eks-1-25-21", + "1.24": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.24.16-eks-1-24-25", + "1.23": "public.ecr.aws/eks-distro/kubernetes/kube-controller-manager:v1.23.17-eks-1-23-30", } var EKSEtcdVersionMap = map[string]string{ - "1.27": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.7-eks-1-27-7", - "1.26": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.7-eks-1-26-13", - "1.25": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.7-eks-1-25-17", - "1.24": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.7-eks-1-24-21", - "1.23": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.7-eks-1-23-26", + "1.27": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.8-eks-1-27-11", + "1.26": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.8-eks-1-26-17", + "1.25": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.8-eks-1-25-21", + "1.24": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.8-eks-1-24-25", + "1.23": "public.ecr.aws/eks-distro/etcd-io/etcd:v3.5.8-eks-1-23-30", } var EKSCoreDNSVersionMap = map[string]string{ - "1.27": "public.ecr.aws/eks-distro/coredns/coredns:v1.10.1-eks-1-27-7", - "1.26": "public.ecr.aws/eks-distro/coredns/coredns:v1.9.3-eks-1-26-13", - "1.25": "public.ecr.aws/eks-distro/coredns/coredns:v1.9.3-eks-1-25-17", - "1.24": "public.ecr.aws/eks-distro/coredns/coredns:v1.9.3-eks-1-24-21", - "1.23": "public.ecr.aws/eks-distro/coredns/coredns:v1.8.7-eks-1-23-26", + "1.27": "public.ecr.aws/eks-distro/coredns/coredns:v1.10.1-eks-1-27-11", + "1.26": "public.ecr.aws/eks-distro/coredns/coredns:v1.9.3-eks-1-26-17", + "1.25": "public.ecr.aws/eks-distro/coredns/coredns:v1.9.3-eks-1-25-21", + "1.24": "public.ecr.aws/eks-distro/coredns/coredns:v1.9.3-eks-1-24-25", + "1.23": "public.ecr.aws/eks-distro/coredns/coredns:v1.8.7-eks-1-23-30", } func getDefaultEKSReleaseValues(chartOptions *helm.ChartOptions, log logr.Logger) (string, error) { - serverVersionString := GetKubernetesVersion(chartOptions.KubernetesVersion) - serverMinorInt, err := GetKubernetesMinorVersion(chartOptions.KubernetesVersion) - if err != nil { - return "", err - } + apiImage := "" + controllerImage := "" + etcdImage := "" + corednsImage := "" + if chartOptions.KubernetesVersion.Major != "" && chartOptions.KubernetesVersion.Minor != "" { + serverVersionString := GetKubernetesVersion(chartOptions.KubernetesVersion) + serverMinorInt, err := GetKubernetesMinorVersion(chartOptions.KubernetesVersion) + if err != nil { + return "", err + } - apiImage := EKSAPIVersionMap[serverVersionString] - controllerImage := EKSControllerVersionMap[serverVersionString] - etcdImage := EKSEtcdVersionMap[serverVersionString] - corednsImage, ok := EKSCoreDNSVersionMap[serverVersionString] - if !ok { - if serverMinorInt > 27 { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.27", "serverVersion", serverVersionString) - apiImage = EKSAPIVersionMap["1.27"] - controllerImage = EKSControllerVersionMap["1.27"] - etcdImage = EKSEtcdVersionMap["1.27"] - corednsImage = EKSCoreDNSVersionMap["1.27"] - } else { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.23", "serverVersion", serverVersionString) - apiImage = EKSAPIVersionMap["1.23"] - controllerImage = EKSControllerVersionMap["1.23"] - etcdImage = EKSEtcdVersionMap["1.23"] - corednsImage = EKSCoreDNSVersionMap["1.23"] + var ok bool + apiImage = EKSAPIVersionMap[serverVersionString] + controllerImage = EKSControllerVersionMap[serverVersionString] + etcdImage = EKSEtcdVersionMap[serverVersionString] + corednsImage, ok = EKSCoreDNSVersionMap[serverVersionString] + if !ok { + if serverMinorInt > 27 { + log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.27", "serverVersion", serverVersionString) + apiImage = EKSAPIVersionMap["1.27"] + controllerImage = EKSControllerVersionMap["1.27"] + etcdImage = EKSEtcdVersionMap["1.27"] + corednsImage = EKSCoreDNSVersionMap["1.27"] + } else { + log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.23", "serverVersion", serverVersionString) + apiImage = EKSAPIVersionMap["1.23"] + controllerImage = EKSControllerVersionMap["1.23"] + etcdImage = EKSEtcdVersionMap["1.23"] + corednsImage = EKSCoreDNSVersionMap["1.23"] + } } } // build values - values := `api: + values := "" + if apiImage != "" { + values = `api: image: ##API_IMAGE## controller: image: ##CONTROLLER_IMAGE## @@ -76,9 +85,10 @@ etcd: coredns: image: ##COREDNS_IMAGE## ` - values = strings.ReplaceAll(values, "##API_IMAGE##", apiImage) - values = strings.ReplaceAll(values, "##CONTROLLER_IMAGE##", controllerImage) - values = strings.ReplaceAll(values, "##ETCD_IMAGE##", etcdImage) - values = strings.ReplaceAll(values, "##COREDNS_IMAGE##", corednsImage) + values = strings.ReplaceAll(values, "##API_IMAGE##", apiImage) + values = strings.ReplaceAll(values, "##CONTROLLER_IMAGE##", controllerImage) + values = strings.ReplaceAll(values, "##ETCD_IMAGE##", etcdImage) + values = strings.ReplaceAll(values, "##COREDNS_IMAGE##", corednsImage) + } return addCommonReleaseValues(values, chartOptions) } diff --git a/vendor/github.com/loft-sh/utils/pkg/helm/values/k0s.go b/vendor/github.com/loft-sh/utils/pkg/helm/values/k0s.go index 8d26e48d5d..2b12885569 100644 --- a/vendor/github.com/loft-sh/utils/pkg/helm/values/k0s.go +++ b/vendor/github.com/loft-sh/utils/pkg/helm/values/k0s.go @@ -8,34 +8,41 @@ import ( ) var K0SVersionMap = map[string]string{ - "1.27": "k0sproject/k0s:v1.27.3-k0s.0", - "1.26": "k0sproject/k0s:v1.26.6-k0s.0", - "1.25": "k0sproject/k0s:v1.25.11-k0s.0", - "1.24": "k0sproject/k0s:v1.24.15-k0s.0", + "1.27": "k0sproject/k0s:v1.27.5-k0s.0", + "1.26": "k0sproject/k0s:v1.26.8-k0s.0", + "1.25": "k0sproject/k0s:v1.25.13-k0s.0", + "1.24": "k0sproject/k0s:v1.24.17-k0s.0", } func getDefaultK0SReleaseValues(chartOptions *helm.ChartOptions, log logr.Logger) (string, error) { - serverVersionString := GetKubernetesVersion(chartOptions.KubernetesVersion) - serverMinorInt, err := GetKubernetesMinorVersion(chartOptions.KubernetesVersion) - if err != nil { - return "", err - } + image := "" + if chartOptions.KubernetesVersion.Major != "" && chartOptions.KubernetesVersion.Minor != "" { + serverVersionString := GetKubernetesVersion(chartOptions.KubernetesVersion) + serverMinorInt, err := GetKubernetesMinorVersion(chartOptions.KubernetesVersion) + if err != nil { + return "", err + } - image, ok := K0SVersionMap[serverVersionString] - if !ok { - if serverMinorInt > 27 { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.27", "serverVersion", serverVersionString) - image = K0SVersionMap["1.27"] - } else { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.24", "serverVersion", serverVersionString) - image = K0SVersionMap["1.24"] + var ok bool + image, ok = K0SVersionMap[serverVersionString] + if !ok { + if serverMinorInt > 27 { + log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.27", "serverVersion", serverVersionString) + image = K0SVersionMap["1.27"] + } else { + log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.24", "serverVersion", serverVersionString) + image = K0SVersionMap["1.24"] + } } } // build values - values := `vcluster: + values := "" + if image != "" { + values = `vcluster: image: ##IMAGE## ` - values = strings.ReplaceAll(values, "##IMAGE##", image) + values = strings.ReplaceAll(values, "##IMAGE##", image) + } return addCommonReleaseValues(values, chartOptions) } diff --git a/vendor/github.com/loft-sh/utils/pkg/helm/values/k3s.go b/vendor/github.com/loft-sh/utils/pkg/helm/values/k3s.go index 999339e507..acd5508191 100644 --- a/vendor/github.com/loft-sh/utils/pkg/helm/values/k3s.go +++ b/vendor/github.com/loft-sh/utils/pkg/helm/values/k3s.go @@ -11,10 +11,10 @@ import ( ) var K3SVersionMap = map[string]string{ - "1.27": "rancher/k3s:v1.27.3-k3s1", - "1.26": "rancher/k3s:v1.26.6-k3s1", - "1.25": "rancher/k3s:v1.25.11-k3s1", - "1.24": "rancher/k3s:v1.24.15-k3s1", + "1.27": "rancher/k3s:v1.27.5-k3s1", + "1.26": "rancher/k3s:v1.26.8-k3s1", + "1.25": "rancher/k3s:v1.25.13-k3s1", + "1.24": "rancher/k3s:v1.24.17-k3s1", "1.23": "rancher/k3s:v1.23.17-k3s1", } @@ -28,7 +28,7 @@ func getDefaultK3SReleaseValues(chartOptions *helm.ChartOptions, log logr.Logger err error ) - if image == "" { + if image == "" && chartOptions.KubernetesVersion.Major != "" && chartOptions.KubernetesVersion.Minor != "" { serverVersionString = GetKubernetesVersion(chartOptions.KubernetesVersion) serverMinorInt, err = GetKubernetesMinorVersion(chartOptions.KubernetesVersion) if err != nil { @@ -49,19 +49,19 @@ func getDefaultK3SReleaseValues(chartOptions *helm.ChartOptions, log logr.Logger } // build values - values := `vcluster: + values := "" + if image != "" { + values = `vcluster: image: ##IMAGE## -##BASEARGS## ` + values = strings.ReplaceAll(values, "##IMAGE##", image) + } if chartOptions.Isolate { values += ` securityContext: runAsUser: 12345 runAsNonRoot: true` } - - values = strings.ReplaceAll(values, "##IMAGE##", image) - return addCommonReleaseValues(values, chartOptions) } diff --git a/vendor/github.com/loft-sh/utils/pkg/helm/values/k8s.go b/vendor/github.com/loft-sh/utils/pkg/helm/values/k8s.go index 16382a704b..f758ff2879 100644 --- a/vendor/github.com/loft-sh/utils/pkg/helm/values/k8s.go +++ b/vendor/github.com/loft-sh/utils/pkg/helm/values/k8s.go @@ -8,27 +8,27 @@ import ( ) var K8SAPIVersionMap = map[string]string{ - "1.28": "registry.k8s.io/kube-apiserver:v1.28.0", - "1.27": "registry.k8s.io/kube-apiserver:v1.27.3", - "1.26": "registry.k8s.io/kube-apiserver:v1.26.6", - "1.25": "registry.k8s.io/kube-apiserver:v1.25.11", - "1.24": "registry.k8s.io/kube-apiserver:v1.24.15", + "1.28": "registry.k8s.io/kube-apiserver:v1.28.2", + "1.27": "registry.k8s.io/kube-apiserver:v1.27.6", + "1.26": "registry.k8s.io/kube-apiserver:v1.26.9", + "1.25": "registry.k8s.io/kube-apiserver:v1.25.14", + "1.24": "registry.k8s.io/kube-apiserver:v1.24.17", } var K8SControllerVersionMap = map[string]string{ - "1.28": "registry.k8s.io/kube-controller-manager:v1.28.0", - "1.27": "registry.k8s.io/kube-controller-manager:v1.27.3", - "1.26": "registry.k8s.io/kube-controller-manager:v1.26.6", - "1.25": "registry.k8s.io/kube-controller-manager:v1.25.11", - "1.24": "registry.k8s.io/kube-controller-manager:v1.24.15", + "1.28": "registry.k8s.io/kube-controller-manager:v1.28.2", + "1.27": "registry.k8s.io/kube-controller-manager:v1.27.6", + "1.26": "registry.k8s.io/kube-controller-manager:v1.26.9", + "1.25": "registry.k8s.io/kube-controller-manager:v1.25.14", + "1.24": "registry.k8s.io/kube-controller-manager:v1.24.17", } var K8SSchedulerVersionMap = map[string]string{ - "1.28": "registry.k8s.io/kube-scheduler:v1.28.0", - "1.27": "registry.k8s.io/kube-scheduler:v1.27.3", - "1.26": "registry.k8s.io/kube-scheduler:v1.26.6", - "1.25": "registry.k8s.io/kube-scheduler:v1.25.11", - "1.24": "registry.k8s.io/kube-scheduler:v1.24.15", + "1.28": "registry.k8s.io/kube-scheduler:v1.28.2", + "1.27": "registry.k8s.io/kube-scheduler:v1.27.6", + "1.26": "registry.k8s.io/kube-scheduler:v1.26.9", + "1.25": "registry.k8s.io/kube-scheduler:v1.25.14", + "1.24": "registry.k8s.io/kube-scheduler:v1.24.17", } var K8SEtcdVersionMap = map[string]string{ @@ -40,34 +40,43 @@ var K8SEtcdVersionMap = map[string]string{ } func getDefaultK8SReleaseValues(chartOptions *helm.ChartOptions, log logr.Logger) (string, error) { - serverVersionString := GetKubernetesVersion(chartOptions.KubernetesVersion) - serverMinorInt, err := GetKubernetesMinorVersion(chartOptions.KubernetesVersion) - if err != nil { - return "", err - } + apiImage := "" + controllerImage := "" + etcdImage := "" + schedulerImage := "" + if chartOptions.KubernetesVersion.Major != "" && chartOptions.KubernetesVersion.Minor != "" { + serverVersionString := GetKubernetesVersion(chartOptions.KubernetesVersion) + serverMinorInt, err := GetKubernetesMinorVersion(chartOptions.KubernetesVersion) + if err != nil { + return "", err + } - apiImage := K8SAPIVersionMap[serverVersionString] - controllerImage := K8SControllerVersionMap[serverVersionString] - schedulerImage := K8SSchedulerVersionMap[serverVersionString] - etcdImage, ok := K8SEtcdVersionMap[serverVersionString] - if !ok { - if serverMinorInt > 28 { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.28", "serverVersion", serverVersionString) - apiImage = K8SAPIVersionMap["1.28"] - controllerImage = K8SControllerVersionMap["1.28"] - schedulerImage = K8SSchedulerVersionMap["1.28"] - etcdImage = K8SEtcdVersionMap["1.28"] - } else { - log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.24", "serverVersion", serverVersionString) - apiImage = K8SAPIVersionMap["1.24"] - controllerImage = K8SControllerVersionMap["1.24"] - schedulerImage = K8SSchedulerVersionMap["1.24"] - etcdImage = K8SEtcdVersionMap["1.24"] + var ok bool + apiImage = K8SAPIVersionMap[serverVersionString] + controllerImage = K8SControllerVersionMap[serverVersionString] + schedulerImage = K8SSchedulerVersionMap[serverVersionString] + etcdImage, ok = K8SEtcdVersionMap[serverVersionString] + if !ok { + if serverMinorInt > 28 { + log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.28", "serverVersion", serverVersionString) + apiImage = K8SAPIVersionMap["1.28"] + controllerImage = K8SControllerVersionMap["1.28"] + schedulerImage = K8SSchedulerVersionMap["1.28"] + etcdImage = K8SEtcdVersionMap["1.28"] + } else { + log.Info("officially unsupported host server version, will fallback to virtual cluster version v1.24", "serverVersion", serverVersionString) + apiImage = K8SAPIVersionMap["1.24"] + controllerImage = K8SControllerVersionMap["1.24"] + schedulerImage = K8SSchedulerVersionMap["1.24"] + etcdImage = K8SEtcdVersionMap["1.24"] + } } } // build values - values := `api: + values := "" + if apiImage != "" { + values = `api: image: ##API_IMAGE## scheduler: image: ##SCHEDULER_IMAGE## @@ -76,9 +85,10 @@ controller: etcd: image: ##ETCD_IMAGE## ` - values = strings.ReplaceAll(values, "##API_IMAGE##", apiImage) - values = strings.ReplaceAll(values, "##CONTROLLER_IMAGE##", controllerImage) - values = strings.ReplaceAll(values, "##SCHEDULER_IMAGE##", schedulerImage) - values = strings.ReplaceAll(values, "##ETCD_IMAGE##", etcdImage) + values = strings.ReplaceAll(values, "##API_IMAGE##", apiImage) + values = strings.ReplaceAll(values, "##CONTROLLER_IMAGE##", controllerImage) + values = strings.ReplaceAll(values, "##SCHEDULER_IMAGE##", schedulerImage) + values = strings.ReplaceAll(values, "##ETCD_IMAGE##", etcdImage) + } return addCommonReleaseValues(values, chartOptions) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 514bfd095d..41ebcbdb78 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -391,7 +391,7 @@ github.com/loft-sh/log/scanner github.com/loft-sh/log/survey github.com/loft-sh/log/table github.com/loft-sh/log/terminal -# github.com/loft-sh/utils v0.0.25 +# github.com/loft-sh/utils v0.0.28-0.20230922120533-1b39f079a09e ## explicit; go 1.20 github.com/loft-sh/utils/pkg/command github.com/loft-sh/utils/pkg/downloader