diff --git a/pkg/appcfg/updater.go b/pkg/appcfg/updater.go index 97c16a6..3155dcb 100644 --- a/pkg/appcfg/updater.go +++ b/pkg/appcfg/updater.go @@ -10,47 +10,139 @@ import ( // UpdateMetadataField updates one field under metadata: by in-place line replacement to preserve templating and comments. func UpdateMetadataField(appDir string, field string, value string) error { - appCfgPath := filepath.Join(appDir, constants.AppCfgFileName) - data, err := os.ReadFile(appCfgPath) - if err != nil { - return err - } - content := string(data) - lines := strings.Split(content, "\n") - inMetadata := false - for i := 0; i < len(lines); i++ { - line := lines[i] - if !inMetadata { - if strings.TrimSpace(line) == "metadata:" { - inMetadata = true - } - continue - } - if len(line) > 0 && (line[0] != ' ' && line[0] != '\t') { - break - } - trimmed := strings.TrimSpace(line) - if strings.HasPrefix(trimmed, field+":") { - colonIdx := strings.Index(line, ":") - if colonIdx == -1 { - continue - } - prefix := line[:colonIdx+1] - after := line[colonIdx+1:] - commentIdx := strings.Index(after, "#") - var comment string - if commentIdx >= 0 { - comment = after[commentIdx:] - } - lines[i] = strings.TrimRight(prefix, " ") + " " + value - if commentIdx >= 0 { - lines[i] += " " + strings.TrimRight(comment, "\r\n") - } - break - } - } - newContent := strings.Join(lines, "\n") - return os.WriteFile(appCfgPath, []byte(newContent), 0644) + appCfgPath := filepath.Join(appDir, constants.AppCfgFileName) + data, err := os.ReadFile(appCfgPath) + if err != nil { + return err + } + content := string(data) + lines := strings.Split(content, "\n") + inMetadata := false + for i := 0; i < len(lines); i++ { + line := lines[i] + if !inMetadata { + if strings.TrimSpace(line) == "metadata:" { + inMetadata = true + } + continue + } + if len(line) > 0 && (line[0] != ' ' && line[0] != '\t') { + break + } + trimmed := strings.TrimSpace(line) + if strings.HasPrefix(trimmed, field+":") { + colonIdx := strings.Index(line, ":") + if colonIdx == -1 { + continue + } + prefix := line[:colonIdx+1] + after := line[colonIdx+1:] + commentIdx := strings.Index(after, "#") + var comment string + if commentIdx >= 0 { + comment = after[commentIdx:] + } + lines[i] = strings.TrimRight(prefix, " ") + " " + value + if commentIdx >= 0 { + lines[i] += " " + strings.TrimRight(comment, "\r\n") + } + break + } + } + newContent := strings.Join(lines, "\n") + return os.WriteFile(appCfgPath, []byte(newContent), 0644) } +// UpdateEntrancesTitleAddDev appends "-dev" to every title under entrances list +// It performs in-place line updates to preserve existing comments and templating. +func UpdateEntrancesTitleAddDev(appDir string) error { + appCfgPath := filepath.Join(appDir, constants.AppCfgFileName) + data, err := os.ReadFile(appCfgPath) + if err != nil { + return err + } + lines := strings.Split(string(data), "\n") + inEntrances := false + entrancesIndent := "" + + // get leading whitespace (spaces/tabs) of a line + leadingWS := func(s string) string { + for i := 0; i < len(s); i++ { + if s[i] != ' ' && s[i] != '\t' { + return s[:i] + } + } + return s + } + + for i := 0; i < len(lines); i++ { + line := lines[i] + trimmed := strings.TrimSpace(line) + + if !inEntrances { + if trimmed == "entrances:" { + inEntrances = true + entrancesIndent = leadingWS(line) + } + continue + } + + // leave entrances block when indentation returns, but ignore helm template lines like {{- end }} + if len(line) > 0 { + ws := leadingWS(line) + if len(ws) <= len(entrancesIndent) && strings.TrimSpace(line) != "" && !strings.HasPrefix(trimmed, "#") && !strings.HasPrefix(trimmed, "{{") && !strings.HasPrefix(trimmed, "-") { + inEntrances = false + i-- // re-process this line outside entrances + continue + } + } + + if strings.HasPrefix(trimmed, "title:") { + colonIdx := strings.Index(line, ":") + if colonIdx == -1 { + continue + } + prefix := line[:colonIdx+1] + after := line[colonIdx+1:] + commentIdx := strings.Index(after, "#") + var valuePart string + var comment string + if commentIdx >= 0 { + valuePart = after[:commentIdx] + comment = after[commentIdx:] + } else { + valuePart = after + } + + v := strings.TrimSpace(valuePart) + newV := v + if len(v) >= 2 && ((v[0] == '"' && v[len(v)-1] == '"') || (v[0] == '\'' && v[len(v)-1] == '\'')) { + quote := v[0] + content := v[1 : len(v)-1] + if strings.HasSuffix(content, "-dev") { + newV = v + } else { + newV = string(quote) + content + "-dev" + string(quote) + } + } else if v != "" { + if strings.HasSuffix(v, "-dev") { + newV = v + } else { + newV = v + "-dev" + } + } else { + newV = "-dev" + } + + newLine := strings.TrimRight(prefix, " ") + " " + newV + if commentIdx >= 0 { + newLine += " " + strings.TrimRight(comment, "\r\n") + } + lines[i] = newLine + } + } + + newContent := strings.Join(lines, "\n") + return os.WriteFile(appCfgPath, []byte(newContent), 0644) +} diff --git a/pkg/development/helm/chart.go b/pkg/development/helm/chart.go index 8bcce27..c96a700 100644 --- a/pkg/development/helm/chart.go +++ b/pkg/development/helm/chart.go @@ -14,8 +14,6 @@ import ( "github.com/Masterminds/semver/v3" "github.com/beclab/devbox/pkg/appcfg" - "github.com/beclab/devbox/pkg/constants" - "github.com/beclab/devbox/pkg/utils" "gopkg.in/yaml.v2" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" @@ -118,75 +116,37 @@ func UpdateChartName(chart *chart.Chart, name, path string) error { } func UpdateAppCfgVersion(owner, path string, version *semver.Version) error { - appCfgYaml := filepath.Join(path, constants.AppCfgFileName) - data, err := os.ReadFile(appCfgYaml) + err := appcfg.UpdateMetadataField(path, "version", version.String()) if err != nil { - klog.Error("read app cfg error, ", err, ", ", appCfgYaml) + klog.Infof("failed to update metadata version %v", err) return err } - //var appCfg application.AppConfiguration - //err = yaml.Unmarshal(data, &appCfg) - - appCfg, err := utils.GetAppConfig(owner, data) - if err != nil { - klog.Error("parse appcfg error, ", err) - return err - } - if version != nil { - appCfg.Metadata.Version = version.String() - } - data, err = yaml.Marshal(&appCfg) - if err != nil { - klog.Error("encode appcfg error, ", err) - return err - } - err = os.WriteFile(appCfgYaml, data, 0644) - if err != nil { - klog.Error("write file OlaresManifest.yaml error, ", err) - return err - } - return nil } func UpdateAppCfgName(owner, name, path string) error { appDevName := name + "-dev" - appCfgYaml := filepath.Join(path, constants.AppCfgFileName) - data, err := os.ReadFile(appCfgYaml) - if err != nil { - klog.Error("read app cfg error, ", err, ", ", appCfgYaml) - return err - } - //var appCfg application.AppConfiguration - //err = yaml.Unmarshal(data, &appCfg) - appCfg, err := utils.GetAppConfig(owner, data) - if err != nil { - klog.Error("parse OlaresManifest.yaml error, ", err) + // update metadata fields directly using in-place updater + if err := appcfg.UpdateMetadataField(path, "name", appDevName); err != nil { + klog.Infof("failed to update metadata name %v", err) + return err } + if err := appcfg.UpdateMetadataField(path, "appid", appDevName); err != nil { + klog.Infof("failed to update metadata appid %v", err) - appCfg.Metadata.Name = appDevName - appCfg.Metadata.AppID = appDevName - appCfg.Metadata.Title = appDevName - - //if version != nil { - // appCfg.Metadata.Version = version.String() - //} - - for i, e := range appCfg.Entrances { - appCfg.Entrances[i].Title = e.Title + "-dev" + return err } + if err := appcfg.UpdateMetadataField(path, "title", appDevName); err != nil { + klog.Infof("failed to update metadata title %v", err) - data, err = yaml.Marshal(&appCfg) - if err != nil { - klog.Error("encode appcfg error, ", err) return err } - err = os.WriteFile(appCfgYaml, data, 0644) - if err != nil { - klog.Error("write file OlaresManifest.yaml error, ", err) + if err := appcfg.UpdateEntrancesTitleAddDev(path); err != nil { + klog.Infof("failed to update entrances title %v", err) + return err } @@ -197,7 +157,7 @@ func UpgradeChartVersion(chart *chart.Chart, name, path string, version *semver. return UpdateChartVersion(chart, name, path, version) } -// try to render the helm chart, and return the rendered manifest or error +// DryRun try to render the helm chart, and return the rendered manifest or error func DryRun(ctx context.Context, kubeConfig *rest.Config, namespace, app, path string, vals map[string]interface{}) (string, error) { settings := cli.New() settings.KubeAPIServer = kubeConfig.Host