Skip to content

Commit f774465

Browse files
committed
Added app level config update apis
1 parent 5db6df0 commit f774465

File tree

16 files changed

+243
-91
lines changed

16 files changed

+243
-91
lines changed

cmd/clace/app_cmds.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,21 +69,21 @@ func appCreateCommand(commonFlags []cli.Flag, clientConfig *types.ClientConfig)
6969
})
7070
flags = append(flags,
7171
&cli.StringSliceFlag{
72-
Name: "container-options",
72+
Name: "container-option",
7373
Aliases: []string{"copt"},
7474
Usage: "Set a container option. Format is opt[=optValue]",
7575
})
7676
flags = append(flags,
7777
&cli.StringSliceFlag{
78-
Name: "container-args",
78+
Name: "container-arg",
7979
Aliases: []string{"carg"},
8080
Usage: "Set an argument for building the container image. Format is argKey=argValue",
8181
})
8282

8383
flags = append(flags,
8484
&cli.StringSliceFlag{
8585
Name: "app-config",
86-
Aliases: []string{"config"},
86+
Aliases: []string{"conf"},
8787
Usage: "Set an default config option for the app. Format is configKey=configValue",
8888
})
8989

@@ -133,14 +133,14 @@ Examples:
133133
paramValues[key] = value
134134
}
135135

136-
containerOptions := cCtx.StringSlice("container-options")
136+
containerOptions := cCtx.StringSlice("container-option")
137137
coptMap := make(map[string]string)
138138
for _, param := range containerOptions {
139139
key, value, _ := strings.Cut(param, "=")
140140
coptMap[key] = value // value can be empty string
141141
}
142142

143-
containerArgs := cCtx.StringSlice("container-args")
143+
containerArgs := cCtx.StringSlice("container-arg")
144144
cargMap := make(map[string]string)
145145
for _, param := range containerArgs {
146146
key, value, ok := strings.Cut(param, "=")
@@ -150,14 +150,14 @@ Examples:
150150
cargMap[key] = value
151151
}
152152

153-
appDefaults := cCtx.StringSlice("app-config")
154-
defMap := make(map[string]string)
155-
for _, def := range appDefaults {
153+
appConfig := cCtx.StringSlice("app-config")
154+
confMap := make(map[string]string)
155+
for _, def := range appConfig {
156156
key, value, ok := strings.Cut(def, "=")
157157
if !ok {
158-
return fmt.Errorf("invalid app default format: %s", def)
158+
return fmt.Errorf("invalid app config format: %s", def)
159159
}
160-
defMap[key] = value
160+
confMap[key] = value
161161
}
162162

163163
body := types.CreateAppRequest{
@@ -171,7 +171,7 @@ Examples:
171171
ParamValues: paramValues,
172172
ContainerOptions: coptMap,
173173
ContainerArgs: cargMap,
174-
AppDefaults: defMap,
174+
AppConfig: confMap,
175175
}
176176
var createResult types.AppCreateResponse
177177
err := client.Post("/_clace/app", values, body, &createResult)

cmd/clace/app_updates.go renamed to cmd/clace/app_update_cmds.go

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ func appUpdateMetadataCommand(commonFlags []cli.Flag, clientConfig *types.Client
264264
Usage: `Update Clace app metadata. Metadata updates are staged and have to be promoted to prod. Use "clace param" to update app parameter metadata.`,
265265
Subcommands: []*cli.Command{
266266
appUpdateAppSpec(commonFlags, clientConfig),
267+
appUpdateConfig(commonFlags, clientConfig, "container-option", "copt", types.AppMetadataContainerOptions),
268+
appUpdateConfig(commonFlags, clientConfig, "container-arg", "carg", types.AppMetadataContainerArgs),
269+
appUpdateConfig(commonFlags, clientConfig, "app-config", "conf", types.AppMetadataAppConfig),
267270
},
268271
}
269272
}
@@ -284,7 +287,7 @@ func appUpdateAppSpec(commonFlags []cli.Flag, clientConfig *types.ClientConfig)
284287
UsageText: `args: <value:spec_name|none> <appPathGlob>
285288
286289
The first required argument <value> is a string, a valid app spec name or - (to unset spec).
287-
The second required argument is <appPathGlob>. ` + PATH_SPEC_HELP + `
290+
The last required argument is <appPathGlob>. ` + PATH_SPEC_HELP + `
288291
289292
Examples:
290293
Update all apps, across domains: clace app update-metadata spec - all
@@ -310,9 +313,78 @@ The second required argument is <appPathGlob>. ` + PATH_SPEC_HELP + `
310313
}
311314

312315
for _, updateResult := range updateResponse.StagedUpdateResults {
313-
fmt.Printf("Updating %s\n", updateResult)
316+
fmt.Printf("Updated %s\n", updateResult)
317+
}
318+
319+
if len(updateResponse.PromoteResults) > 0 {
320+
fmt.Fprintf(cCtx.App.Writer, "Promoted apps: ")
321+
for i, promoteResult := range updateResponse.PromoteResults {
322+
if i > 0 {
323+
fmt.Fprintf(cCtx.App.Writer, ", ")
324+
}
325+
fmt.Fprintf(cCtx.App.Writer, "%s", promoteResult)
326+
}
327+
fmt.Fprintln(cCtx.App.Writer)
328+
}
329+
330+
fmt.Fprintf(cCtx.App.Writer, "%d app(s) updated, %d app(s) promoted.\n", len(updateResponse.StagedUpdateResults), len(updateResponse.PromoteResults))
331+
332+
if updateResponse.DryRun {
333+
fmt.Print(DRY_RUN_MESSAGE)
334+
}
335+
336+
return nil
337+
},
338+
}
339+
}
340+
341+
// appUpdateConfig creates a command to update app metadata config
342+
func appUpdateConfig(commonFlags []cli.Flag, clientConfig *types.ClientConfig, arg string, shortFlag string, configType types.AppMetadataConfigType) *cli.Command {
343+
flags := make([]cli.Flag, 0, len(commonFlags)+2)
344+
flags = append(flags, commonFlags...)
345+
flags = append(flags, dryRunFlag())
346+
flags = append(flags, newBoolFlag(PROMOTE_FLAG, "p", "Promote the change from stage to prod", false))
347+
348+
return &cli.Command{
349+
Name: arg,
350+
Aliases: []string{shortFlag},
351+
Usage: fmt.Sprintf("Update %s metadata for apps", arg),
352+
Flags: flags,
353+
Before: altsrc.InitInputSourceWithContext(flags, altsrc.NewTomlSourceFromFlagFunc(configFileFlagName)),
354+
ArgsUsage: "key=value <appPathGlob>",
355+
356+
UsageText: fmt.Sprintf(`args: key=value [key=value ...] <appPathGlob>
357+
The initial arguments key=value are strings, the key to set and the value to use delimited by =. The value is optional for
358+
container options. The last argument is <appPathGlob>. `+PATH_SPEC_HELP+`
359+
360+
Examples:
361+
Update all apps, across domains: clace app update-metadata %s key=value all
362+
Update apps in the example.com domain: clace app update-metadata %s key=value "example.com:**"`, arg, arg),
363+
364+
Action: func(cCtx *cli.Context) error {
365+
if cCtx.NArg() < 2 {
366+
return fmt.Errorf("requires at least two arguments: key=value [key=value ...] <appPathGlob>")
367+
}
368+
369+
client := system.NewHttpClient(clientConfig.ServerUri, clientConfig.AdminUser, clientConfig.Client.AdminPassword, clientConfig.Client.SkipCertCheck)
370+
values := url.Values{}
371+
372+
values.Add("appPathGlob", cCtx.Args().Get(cCtx.NArg()-1))
373+
values.Add(DRY_RUN_ARG, strconv.FormatBool(cCtx.Bool(DRY_RUN_FLAG)))
374+
values.Add(PROMOTE_ARG, strconv.FormatBool(cCtx.Bool(PROMOTE_FLAG)))
375+
376+
body := types.CreateUpdateAppMetadataRequest()
377+
body.ConfigType = configType
378+
body.ConfigEntries = cCtx.Args().Slice()[:cCtx.NArg()-1]
379+
380+
var updateResponse types.AppUpdateMetadataResponse
381+
if err := client.Post("/_clace/app_metadata", values, body, &updateResponse); err != nil {
382+
return err
383+
}
384+
385+
for _, updateResult := range updateResponse.StagedUpdateResults {
386+
fmt.Printf("Updated %s\n", updateResult)
314387
}
315-
fmt.Fprintf(cCtx.App.Writer, "%d app(s) updated.\n", len(updateResponse.StagedUpdateResults))
316388

317389
if len(updateResponse.PromoteResults) > 0 {
318390
fmt.Fprintf(cCtx.App.Writer, "Promoted apps: ")

internal/app/app.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type App struct {
4646
Name string
4747
CustomLayout bool
4848

49-
Config *apptype.AppConfig
49+
codeConfig *apptype.CodeConfig
5050
sourceFS *appfs.SourceFs
5151
initMutex sync.Mutex
5252
initialized bool
@@ -73,7 +73,7 @@ type App struct {
7373
sseListeners []chan SSEMessage
7474
funcMap template.FuncMap
7575
starlarkCache map[string]*starlarkCacheEntry
76-
appDefaults types.AppDefaults
76+
appConfig types.AppConfig
7777
}
7878

7979
type starlarkCacheEntry struct {
@@ -88,7 +88,7 @@ type SSEMessage struct {
8888

8989
func NewApp(sourceFS *appfs.SourceFs, workFS *appfs.WorkFs, logger *types.Logger,
9090
appEntry *types.AppEntry, systemConfig *types.SystemConfig,
91-
plugins map[string]types.PluginSettings, appDefaults types.AppDefaults) (*App, error) {
91+
plugins map[string]types.PluginSettings, appConfig types.AppConfig) (*App, error) {
9292
newApp := &App{
9393
sourceFS: sourceFS,
9494
Logger: logger,
@@ -97,8 +97,8 @@ func NewApp(sourceFS *appfs.SourceFs, workFS *appfs.WorkFs, logger *types.Logger
9797
starlarkCache: map[string]*starlarkCacheEntry{},
9898
}
9999
newApp.plugins = NewAppPlugins(newApp, plugins, appEntry.Metadata.Accounts)
100-
newApp.appDefaults = appDefaults
101-
if err := newApp.updateAppDefaults(); err != nil {
100+
newApp.appConfig = appConfig
101+
if err := newApp.updateAppConfig(); err != nil {
102102
return nil, err
103103
}
104104

@@ -213,16 +213,16 @@ func (a *App) Reload(force, immediate bool, dryRun DryRun) (bool, error) {
213213

214214
// Config lock is not present, use default config
215215
a.Debug().Msg("No config lock file found, using default config")
216-
a.Config = apptype.NewAppConfig()
216+
a.codeConfig = apptype.NewCodeConfig()
217217
if a.IsDev {
218-
a.appDev.Config = a.Config
218+
a.appDev.Config = a.codeConfig
219219
a.appDev.SaveConfigLockFile()
220220
}
221221
} else {
222222
// Config lock file is present, read defaults from that
223223
a.Debug().Msg("Config lock file found, using config from lock file")
224-
a.Config = apptype.NewCompatibleAppConfig()
225-
if err := json.Unmarshal(configData, a.Config); err != nil {
224+
a.codeConfig = apptype.NewCompatibleCodeConfig()
225+
if err := json.Unmarshal(configData, a.codeConfig); err != nil {
226226
return false, err
227227
}
228228
}
@@ -234,7 +234,7 @@ func (a *App) Reload(force, immediate bool, dryRun DryRun) (bool, error) {
234234

235235
if a.IsDev {
236236
// Copy settings into appdev
237-
a.appDev.Config = a.Config
237+
a.appDev.Config = a.codeConfig
238238
a.appDev.CustomLayout = a.CustomLayout
239239

240240
// Initialize style configuration
@@ -273,14 +273,14 @@ func (a *App) Reload(force, immediate bool, dryRun DryRun) (bool, error) {
273273

274274
// Parse HTML templates if there are HTML routes
275275
if a.usesHtmlTemplate {
276-
baseFiles, err := a.sourceFS.Glob(path.Join(a.Config.Routing.BaseTemplates, "*.go.html"))
276+
baseFiles, err := a.sourceFS.Glob(path.Join(a.codeConfig.Routing.BaseTemplates, "*.go.html"))
277277
if err != nil {
278278
return false, err
279279
}
280280

281281
if len(baseFiles) == 0 {
282282
// No base templates found, use the default unstructured templates
283-
if a.template, err = a.sourceFS.ParseFS(a.funcMap, a.Config.Routing.TemplateLocations...); err != nil {
283+
if a.template, err = a.sourceFS.ParseFS(a.funcMap, a.codeConfig.Routing.TemplateLocations...); err != nil {
284284
return false, err
285285
}
286286
} else {
@@ -291,7 +291,7 @@ func (a *App) Reload(force, immediate bool, dryRun DryRun) (bool, error) {
291291
}
292292

293293
a.templateMap = make(map[string]*template.Template)
294-
for _, paths := range a.Config.Routing.TemplateLocations {
294+
for _, paths := range a.codeConfig.Routing.TemplateLocations {
295295
files, err := a.sourceFS.Glob(paths)
296296
if err != nil {
297297
return false, err
@@ -702,18 +702,18 @@ func (a *App) loadStarlark(thread *starlark.Thread, module string, cache map[str
702702
return cacheEntry.globals, cacheEntry.err
703703
}
704704

705-
// updateAppDefaults updates the app defaults from the metadata
705+
// updateAppConfig updates the app defaults from the metadata
706706
// It creates a TOML intermediate string so that the TOML parsing can be used
707-
func (a *App) updateAppDefaults() error {
708-
if len(a.Metadata.AppDefaults) == 0 {
707+
func (a *App) updateAppConfig() error {
708+
if len(a.Metadata.AppConfig) == 0 {
709709
return nil
710710
}
711711

712712
buf := strings.Builder{}
713-
for key, value := range a.Metadata.AppDefaults {
713+
for key, value := range a.Metadata.AppConfig {
714714
buf.WriteString(fmt.Sprintf("%s=\"%s\"\n", key, value))
715715
}
716716

717-
_, err := toml.Decode(buf.String(), &a.appDefaults)
717+
_, err := toml.Decode(buf.String(), &a.appConfig)
718718
return err
719719
}

internal/app/apptype/appconfig.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
package apptype
55

6-
type AppConfig struct {
6+
type CodeConfig struct {
77
Routing RouteConfig `json:"routing"`
88
Htmx HtmxConfig `json:"htmx"`
99
}
@@ -22,12 +22,12 @@ type HtmxConfig struct {
2222
Version string `json:"version"`
2323
}
2424

25-
// NewAppConfig creates an AppConfig with default values. This config is used when lock
25+
// NewCodeConfig creates an CodeConfig with default values. This config is used when lock
2626
// file is not present. The config file load order is
2727
//
28-
// DefaultAppConfig -> StarlarkAppConfig
29-
func NewAppConfig() *AppConfig {
30-
return &AppConfig{
28+
// DefaultCodeConfig -> StarlarkCodeConfig
29+
func NewCodeConfig() *CodeConfig {
30+
return &CodeConfig{
3131
Routing: RouteConfig{
3232
TemplateLocations: []string{"*.go.html"},
3333
BaseTemplates: "base_templates",
@@ -41,19 +41,19 @@ func NewAppConfig() *AppConfig {
4141
}
4242
}
4343

44-
// NewCompatibleAppConfig creates an AppConfig focused on maintaining backward compatibility.
44+
// NewCompatibleCodeConfig creates an CodeConfig focused on maintaining backward compatibility.
4545
// This is used when the app is created from a source url where the source has the config lock file
4646
// present. The configs are read in the order
4747
//
48-
// CompatibleAppConfig -> LockFile -> StarlarkAppConfig
48+
// CompatibleCodeConfig -> LockFile -> StarlarkCodeConfig
4949
//
5050
// The goal is that if the application has a lock file, then all settings will attempt to be locked
5151
// such that there should not be any change in behavior when the Clace version is updated.
5252
// Removing the lock file will result in new config defaults getting applied, which can be
5353
// done when the app developer wants to do an application refresh. Refresh will require additional
5454
// testing to ensure that UI functionality is not changed..
55-
func NewCompatibleAppConfig() *AppConfig {
56-
config := NewAppConfig()
55+
func NewCompatibleCodeConfig() *CodeConfig {
56+
config := NewCodeConfig()
5757
config.Htmx.Version = "1.9.1"
5858
return config
5959
}

internal/app/container/manager.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,21 +114,11 @@ func extractVolumes(node *parser.Node) []string {
114114
ret := []string{}
115115
for node.Next != nil {
116116
node = node.Next
117-
ret = append(ret, stripQuotes(node.Value))
117+
ret = append(ret, types.StripQuotes(node.Value))
118118
}
119119
return ret
120120
}
121121

122-
func stripQuotes(s string) string {
123-
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
124-
return s[1 : len(s)-1]
125-
}
126-
if len(s) >= 2 && s[0] == '\'' && s[len(s)-1] == '\'' {
127-
return s[1 : len(s)-1]
128-
}
129-
return s
130-
}
131-
132122
func (m *Manager) GetProxyUrl() string {
133123
return fmt.Sprintf("%s://127.0.0.1:%d", m.scheme, m.hostPort)
134124
}

internal/app/dev/appdev.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type AppDev struct {
3838
*types.Logger
3939

4040
CustomLayout bool
41-
Config *apptype.AppConfig
41+
Config *apptype.CodeConfig
4242
systemConfig *types.SystemConfig
4343
sourceFS *appfs.WritableSourceFs
4444
workFS *appfs.WorkFs

internal/app/handler.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func (a *App) createHandlerFunc(fullHtml, fragment string, handler starlark.Call
6060

6161
isHtmxRequest := r.Header.Get("HX-Request") == "true" && !(r.Header.Get("HX-Boosted") == "true")
6262

63-
if a.Config.Routing.EarlyHints && !a.IsDev && r.Method == http.MethodGet &&
63+
if a.codeConfig.Routing.EarlyHints && !a.IsDev && r.Method == http.MethodGet &&
6464
r.Header.Get("sec-fetch-mode") == "navigate" &&
6565
rtype == apptype.HTML_TYPE && !(isHtmxRequest && fragment != "") {
6666
// Prod mode, for a GET request from newer browsers on a top level HTML page, send http early hints
@@ -85,8 +85,8 @@ func (a *App) createHandlerFunc(fullHtml, fragment string, handler starlark.Call
8585
Method: r.Method,
8686
IsDev: a.IsDev,
8787
IsPartial: isHtmxRequest,
88-
PushEvents: a.Config.Routing.PushEvents,
89-
HtmxVersion: a.Config.Htmx.Version,
88+
PushEvents: a.codeConfig.Routing.PushEvents,
89+
HtmxVersion: a.codeConfig.Htmx.Version,
9090
Headers: r.Header,
9191
RemoteIP: getRemoteIP(r),
9292
}

0 commit comments

Comments
 (0)