Skip to content

Commit 5db6df0

Browse files
committed
Added initial support for CORS properties
1 parent 6037be9 commit 5db6df0

File tree

10 files changed

+105
-8
lines changed

10 files changed

+105
-8
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ logs/
1010
.DS_Store
1111
run/
1212
config/
13+
internal/server/appspecs/

cmd/clace/app_cmds.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ func appCreateCommand(commonFlags []cli.Flag, clientConfig *types.ClientConfig)
8080
Usage: "Set an argument for building the container image. Format is argKey=argValue",
8181
})
8282

83+
flags = append(flags,
84+
&cli.StringSliceFlag{
85+
Name: "app-config",
86+
Aliases: []string{"config"},
87+
Usage: "Set an default config option for the app. Format is configKey=configValue",
88+
})
89+
8390
flags = append(flags, dryRunFlag())
8491

8592
return &cli.Command{
@@ -143,6 +150,16 @@ Examples:
143150
cargMap[key] = value
144151
}
145152

153+
appDefaults := cCtx.StringSlice("app-config")
154+
defMap := make(map[string]string)
155+
for _, def := range appDefaults {
156+
key, value, ok := strings.Cut(def, "=")
157+
if !ok {
158+
return fmt.Errorf("invalid app default format: %s", def)
159+
}
160+
defMap[key] = value
161+
}
162+
146163
body := types.CreateAppRequest{
147164
SourceUrl: cCtx.Args().Get(0),
148165
IsDev: cCtx.Bool("dev"),
@@ -154,6 +171,7 @@ Examples:
154171
ParamValues: paramValues,
155172
ContainerOptions: coptMap,
156173
ContainerArgs: cargMap,
174+
AppDefaults: defMap,
157175
}
158176
var createResult types.AppCreateResponse
159177
err := client.Post("/_clace/app", values, body, &createResult)

internal/app/app.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import (
1313
"net/http"
1414
"path"
1515
"path/filepath"
16+
"strings"
1617
"sync"
1718
"sync/atomic"
1819
"time"
1920

21+
"github.com/BurntSushi/toml"
2022
"github.com/Masterminds/sprig/v3"
2123
"github.com/claceio/clace/internal/app/appfs"
2224
"github.com/claceio/clace/internal/app/apptype"
@@ -71,6 +73,7 @@ type App struct {
7173
sseListeners []chan SSEMessage
7274
funcMap template.FuncMap
7375
starlarkCache map[string]*starlarkCacheEntry
76+
appDefaults types.AppDefaults
7477
}
7578

7679
type starlarkCacheEntry struct {
@@ -85,7 +88,7 @@ type SSEMessage struct {
8588

8689
func NewApp(sourceFS *appfs.SourceFs, workFS *appfs.WorkFs, logger *types.Logger,
8790
appEntry *types.AppEntry, systemConfig *types.SystemConfig,
88-
plugins map[string]types.PluginSettings) *App {
91+
plugins map[string]types.PluginSettings, appDefaults types.AppDefaults) (*App, error) {
8992
newApp := &App{
9093
sourceFS: sourceFS,
9194
Logger: logger,
@@ -94,6 +97,10 @@ func NewApp(sourceFS *appfs.SourceFs, workFS *appfs.WorkFs, logger *types.Logger
9497
starlarkCache: map[string]*starlarkCacheEntry{},
9598
}
9699
newApp.plugins = NewAppPlugins(newApp, plugins, appEntry.Metadata.Accounts)
100+
newApp.appDefaults = appDefaults
101+
if err := newApp.updateAppDefaults(); err != nil {
102+
return nil, err
103+
}
97104

98105
if appEntry.IsDev {
99106
newApp.appDev = dev.NewAppDev(logger, &appfs.WritableSourceFs{SourceFs: sourceFS}, workFS, systemConfig)
@@ -119,7 +126,7 @@ func NewApp(sourceFS *appfs.SourceFs, workFS *appfs.WorkFs, logger *types.Logger
119126
delete(funcMap, "expandenv")
120127

121128
newApp.funcMap = funcMap
122-
return newApp
129+
return newApp, nil
123130
}
124131

125132
func (a *App) Initialize(dryRun DryRun) error {
@@ -694,3 +701,19 @@ func (a *App) loadStarlark(thread *starlark.Thread, module string, cache map[str
694701
}
695702
return cacheEntry.globals, cacheEntry.err
696703
}
704+
705+
// updateAppDefaults updates the app defaults from the metadata
706+
// 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 {
709+
return nil
710+
}
711+
712+
buf := strings.Builder{}
713+
for key, value := range a.Metadata.AppDefaults {
714+
buf.WriteString(fmt.Sprintf("%s=\"%s\"\n", key, value))
715+
}
716+
717+
_, err := toml.Decode(buf.String(), &a.appDefaults)
718+
return err
719+
}

internal/app/setup.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package app
55

66
import (
77
"bytes"
8+
"cmp"
89
"encoding/json"
910
"errors"
1011
"fmt"
@@ -600,11 +601,35 @@ func (a *App) addProxyConfig(count int, router *chi.Mux, proxyDef *starlarkstruc
600601
// disabled in proxy config
601602
req.Host = url.Host
602603
}
604+
603605
}
604606

605607
permsHandler := func(p *httputil.ReverseProxy) http.Handler {
606608
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
607-
// If write APi, check if preview/stage app is allowed access
609+
610+
if a.appDefaults.CORS.Setting == "strict" || a.appDefaults.CORS.Setting == "lax" {
611+
origin := "*"
612+
if a.appDefaults.CORS.Setting == "strict" {
613+
origin = getRequestUrl(r)
614+
}
615+
if r.Method == http.MethodOptions {
616+
w.Header().Set("Access-Control-Allow-Origin", cmp.Or(a.appDefaults.CORS.AllowOrigin, origin))
617+
w.Header().Set("Access-Control-Allow-Methods", a.appDefaults.CORS.AllowMethods)
618+
w.Header().Set("Access-Control-Allow-Headers", a.appDefaults.CORS.AllowHeaders)
619+
w.Header().Set("Access-Control-Allow-Credentials", a.appDefaults.CORS.AllowCredentials)
620+
w.Header().Set("Access-Control-Max-Age", a.appDefaults.CORS.MaxAge)
621+
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
622+
w.Header().Set("Content-Length", "0")
623+
w.WriteHeader(http.StatusNoContent)
624+
return
625+
} else {
626+
w.Header().Set("Access-Control-Allow-Origin", cmp.Or(a.appDefaults.CORS.AllowOrigin, origin))
627+
w.Header().Set("Access-Control-Allow-Methods", a.appDefaults.CORS.AllowMethods)
628+
w.Header().Set("Access-Control-Allow-Headers", a.appDefaults.CORS.AllowHeaders)
629+
}
630+
}
631+
632+
// If write API, check if preview/stage app is allowed access
608633
isWriteReques := r.Method == http.MethodPost || r.Method == http.MethodPut || r.Method == http.MethodDelete
609634
if isWriteReques {
610635
if strings.HasPrefix(string(a.Id), types.ID_PREFIX_APP_PREVIEW) && !a.Settings.PreviewWriteAccess {

internal/app/tests/app_test_helper.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,11 @@ func CreateTestAppInt(logger *types.Logger, path string, fileData map[string]str
9191
ParamValues: params,
9292
}
9393
workFS := appfs.NewWorkFs("", &TestWriteFS{TestReadFS: &TestReadFS{fileData: map[string]string{}}})
94-
a := app.NewApp(sourceFS, workFS, logger,
95-
createTestAppEntry(id, path, isDev, metadata), &systemConfig, pluginConfig)
94+
a, err := app.NewApp(sourceFS, workFS, logger,
95+
createTestAppEntry(id, path, isDev, metadata), &systemConfig, pluginConfig, types.AppDefaults{})
96+
if err != nil {
97+
return nil, nil, err
98+
}
9699
err = a.Initialize(app.DryRunFalse)
97100
return a, workFS, err
98101
}

internal/server/app_apis.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func (s *Server) CreateApp(ctx context.Context, appPath string, approve, dryRun
9494
appEntry.Metadata.ParamValues = appRequest.ParamValues
9595
appEntry.Metadata.ContainerOptions = appRequest.ContainerOptions
9696
appEntry.Metadata.ContainerArgs = appRequest.ContainerArgs
97+
appEntry.Metadata.AppDefaults = appRequest.AppDefaults
9798

9899
auditResult, err := s.createApp(ctx, &appEntry, approve, dryRun, appRequest.GitBranch, appRequest.GitCommit, appRequest.GitAuthName)
99100
if err != nil {
@@ -293,9 +294,7 @@ func (s *Server) setupApp(appEntry *types.AppEntry, tx types.Transaction) (*app.
293294
&appfs.DiskWriteFS{
294295
DiskReadFS: appfs.NewDiskReadFS(&appLogger, appPath, *appEntry.Metadata.SpecFiles),
295296
})
296-
application := app.NewApp(sourceFS, workFS, &appLogger, appEntry, &s.config.System, s.config.Plugins)
297-
298-
return application, nil
297+
return app.NewApp(sourceFS, workFS, &appLogger, appEntry, &s.config.System, s.config.Plugins, s.config.AppDefaults)
299298
}
300299

301300
func (s *Server) GetAppApi(ctx context.Context, appPath string) (*types.AppGetResponse, error) {

internal/system/clace.default.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,12 @@ container_command = "auto" # "auto" or "docker" or "podman"
5454

5555
[plugin."store.in"]
5656
db_connection = "sqlite:$CL_HOME/clace_app.db"
57+
58+
[appdefaults]
59+
cors.setting = "strict"
60+
cors.allow_origin = "" # if empty, set to * for lax and origin host for strict
61+
cors.allow_methods = "GET, POST, PUT, DELETE, PATCH, OPTIONS"
62+
cors.allow_headers = """DNT,User-Agent,X-Requested-With,If-Modified-Since,
63+
Cache-Control,Content-Type,Range,Authorization,X-Requested-With"""
64+
cors.allow_credentials = "true"
65+
cors.max_age = "2678400" # 31 days

internal/system/config_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ func TestServerConfig(t *testing.T) {
5757

5858
// Container Settings
5959
testutil.AssertEqualsString(t, "command", "auto", c.System.ContainerCommand)
60+
61+
// App default Settings
62+
testutil.AssertEqualsString(t, "cors setting", "strict", c.AppDefaults.CORS.Setting)
6063
}
6164

6265
func TestClientConfig(t *testing.T) {

internal/types/api.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type CreateAppRequest struct {
4040
ParamValues map[string]string `json:"param_values"`
4141
ContainerOptions map[string]string `json:"container_options"`
4242
ContainerArgs map[string]string `json:"container_args"`
43+
AppDefaults map[string]string `json:"appdefaults"`
4344
}
4445

4546
// UpdateAppRequest is the request body for updating an app settings

internal/types/types.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,24 @@ type ServerConfig struct {
5959
Plugins map[string]PluginSettings `toml:"plugin"`
6060
Auth map[string]AuthConfig `toml:"auth"`
6161
ProfileMode string `toml:"profile_mode"`
62+
AppDefaults AppDefaults `toml:"appdefaults"`
6263
}
6364

6465
type PluginSettings map[string]any
6566

67+
type AppDefaults struct {
68+
CORS CORS `toml:"cors"`
69+
}
70+
71+
type CORS struct {
72+
Setting string `toml:"setting"`
73+
AllowOrigin string `toml:"allow_origin"`
74+
AllowMethods string `toml:"allow_methods"`
75+
AllowHeaders string `toml:"allow_headers"`
76+
AllowCredentials string `toml:"allow_credentials"`
77+
MaxAge string `toml:"max_age"`
78+
}
79+
6680
type PluginContext struct {
6781
Logger *Logger
6882
AppId AppId
@@ -268,6 +282,7 @@ type AppMetadata struct {
268282
SpecFiles *SpecFiles `json:"spec_files"`
269283
ContainerOptions map[string]string `json:"container_options"`
270284
ContainerArgs map[string]string `json:"container_args"`
285+
AppDefaults map[string]string `json:"appdefaults"`
271286
}
272287

273288
// AppSettings contains the settings for an app. Settings are not version controlled.

0 commit comments

Comments
 (0)