Skip to content

Commit 54ecfd7

Browse files
committed
Changes to add dev settings to speed up dev reload
1 parent 0db2ef5 commit 54ecfd7

14 files changed

Lines changed: 1320 additions & 47 deletions

internal/app/app.go

Lines changed: 130 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"html/template"
1414
"io"
1515
"io/fs"
16+
"maps"
1617
"net"
1718
"net/http"
1819
"os"
@@ -322,8 +323,12 @@ func (a *App) Reload(ctx context.Context, force, immediate bool, dryRun types.Dr
322323
return false, nil
323324
}
324325

325-
if requestTime.Compare(a.reloadStartTime) == -1 {
326-
// Current request is older than the last reloaded request, ignore
326+
if a.initialized && requestTime.Compare(a.reloadStartTime) == -1 {
327+
// Current request is older than the last reloaded request, ignore.
328+
// Applies to initialized apps only (coalescing file-change reloads):
329+
// while the app is uninitialized, a request queued behind a reload
330+
// that then FAILED would otherwise be reported as success here, and
331+
// the caller would serve the app with no router built (nil panic)
327332
a.Info().Msg("Ignoring reload request since it is older than the last reload request")
328333
return false, nil
329334
}
@@ -582,6 +587,15 @@ func (a *App) loadContainerManager(ctx context.Context, stripAppPath bool) error
582587
return fmt.Errorf("error reading cargs: %w", err)
583588
}
584589

590+
devSettingsMap, err := apptype.GetDictAttr(configAttr, "dev_settings", true)
591+
if err != nil {
592+
return fmt.Errorf("error reading dev_settings: %w", err)
593+
}
594+
devSettings, err := parseDevSettings(devSettingsMap)
595+
if err != nil {
596+
return fmt.Errorf("error parsing dev_settings: %w", err)
597+
}
598+
585599
// Parse the source file specification
586600
var fileName string
587601
switch src {
@@ -631,14 +645,127 @@ func (a *App) loadContainerManager(ctx context.Context, stripAppPath bool) error
631645
a.containerHandler, err = NewContainerHandler(a.Logger, a,
632646
fileName, a.serverConfig, portInt, lifetime, scheme, health, buildDir,
633647
a.sourceFS, a.paramValuesStr, a.AppConfig.Container, stripAppPath, volumes,
634-
a.getSecretsAllowed("container.in", "config"), cargs, a.bindings)
648+
a.getSecretsAllowed("container.in", "config"), cargs, a.bindings, devSettings)
635649
if err != nil {
636650
return fmt.Errorf("error creating container handler: %w", err)
637651
}
638652

639653
return nil
640654
}
641655

656+
// parseDevSettings converts the dev_settings dict from container.config into a
657+
// DevSettings struct. Returns nil when no dev_settings were specified, which
658+
// keeps the original dev reload behavior (full image rebuild on every change).
659+
func parseDevSettings(m map[string]any) (*types.DevSettings, error) {
660+
if len(m) == 0 {
661+
return nil, nil
662+
}
663+
664+
// Every key must be consumed by one of the getters below; a leftover key
665+
// (allowed by the plugin's validation but not parsed here) fails the load
666+
// instead of being silently dropped
667+
consumed := map[string]bool{}
668+
getString := func(key string) (string, error) {
669+
consumed[key] = true
670+
v, ok := m[key]
671+
if !ok {
672+
return "", nil
673+
}
674+
s, ok := v.(string)
675+
if !ok {
676+
return "", fmt.Errorf("dev_settings %s must be a string", key)
677+
}
678+
return s, nil
679+
}
680+
getStringList := func(key string) ([]string, error) {
681+
consumed[key] = true
682+
v, ok := m[key]
683+
if !ok {
684+
return nil, nil
685+
}
686+
switch list := v.(type) {
687+
case []string:
688+
return list, nil
689+
case []any:
690+
ret := make([]string, 0, len(list))
691+
for _, entry := range list {
692+
s, ok := entry.(string)
693+
if !ok {
694+
return nil, fmt.Errorf("dev_settings %s must be a list of strings", key)
695+
}
696+
ret = append(ret, s)
697+
}
698+
return ret, nil
699+
default:
700+
return nil, fmt.Errorf("dev_settings %s must be a list of strings", key)
701+
}
702+
}
703+
704+
ds := &types.DevSettings{}
705+
var err error
706+
if ds.Target, err = getString("target"); err != nil {
707+
return nil, err
708+
}
709+
if ds.Command, err = getString("command"); err != nil {
710+
return nil, err
711+
}
712+
if ds.Dir, err = getString("dir"); err != nil {
713+
return nil, err
714+
}
715+
if ds.Reload, err = getString("reload"); err != nil {
716+
return nil, err
717+
}
718+
if ds.EnvFiles, err = getStringList("env_files"); err != nil {
719+
return nil, err
720+
}
721+
if ds.AdditionalMounts, err = getStringList("additional_mounts"); err != nil {
722+
return nil, err
723+
}
724+
consumed["port"] = true
725+
if v, ok := m["port"]; ok {
726+
var port int64
727+
switch p := v.(type) {
728+
case int:
729+
port = int64(p)
730+
case int64:
731+
port = p
732+
default:
733+
return nil, fmt.Errorf("dev_settings port must be an integer higher than or equal to zero")
734+
}
735+
if port < 0 {
736+
return nil, fmt.Errorf("dev_settings port must be an integer higher than or equal to zero")
737+
}
738+
port32, err := types.Int64ToInt32(port)
739+
if err != nil {
740+
return nil, fmt.Errorf("dev_settings port: %w", err)
741+
}
742+
ds.Port = port32
743+
}
744+
745+
for _, key := range slices.Sorted(maps.Keys(m)) {
746+
if !consumed[key] {
747+
return nil, fmt.Errorf("unsupported dev_settings key %q", key)
748+
}
749+
}
750+
751+
if ds.Reload == "" {
752+
ds.Reload = types.DEV_RELOAD_RESTART
753+
}
754+
switch ds.Reload {
755+
case types.DEV_RELOAD_NONE, types.DEV_RELOAD_RESTART, types.DEV_RELOAD_RECREATE:
756+
default:
757+
return nil, fmt.Errorf("dev_settings reload must be one of none, restart, recreate; got %q", ds.Reload)
758+
}
759+
if ds.Dir == "" {
760+
return nil, fmt.Errorf("dev_settings dir must be set, it is the directory where the app source is mounted in the container")
761+
}
762+
if !strings.HasPrefix(ds.Dir, "/") {
763+
return nil, fmt.Errorf("dev_settings dir must be an absolute path inside the container")
764+
}
765+
766+
return ds, nil
767+
}
768+
642769
func (a *App) executeTemplate(w io.Writer, template, partial string, data any) error {
643770
var err error
644771
if a.template != nil {

0 commit comments

Comments
 (0)