Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 133 additions & 41 deletions pkg/appcfg/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
70 changes: 15 additions & 55 deletions pkg/development/helm/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

Expand All @@ -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
Expand Down