package host import ( "errors" "os" "path/filepath" "testing" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" ) // flatClaimEnvFile is a test's env file resolver: every host's env file // flat under dir, as the fixtures that predate the row's layout place it. func flatClaimEnvFile(dir string) func(host, dbPath string) (claimEnvFile, error) { return func(host, _ string) (claimEnvFile, error) { return claimEnvFile{Dir: dir, Name: host + ".env"}, nil } } // envFileOf adapts a moves FilesDir to the provider's resolver: the env // path it names, flat. func envFileOf(filesDir func(string) (string, string)) func(host, dbPath string) (claimEnvFile, error) { return func(host, _ string) (claimEnvFile, error) { _, env := filesDir(host) return claimEnvFile{Dir: filepath.Dir(env), Name: filepath.Base(env)}, nil } } // testClaimRoutes is a registry's rows by host; err answers every read. type testClaimRoutes struct { rows map[string]registry.Route err error } func (r testClaimRoutes) Get(host string) (*registry.Route, error) { if r.err != nil { return nil, r.err } rt, ok := r.rows[host] if !ok { return nil, nil } return &rt, nil } func (r testClaimRoutes) List() ([]registry.Route, error) { if r.err != nil { return nil, r.err } var out []registry.Route for _, rt := range r.rows { out = append(out, rt) } return out, nil } // Merge round 6, finding 2: the provider finds a host's env file through // its row, as the launch source does. A nested row's is in its instance // directory, a flat or foreign row's is flat; no row, a row that cannot be // read, a row that is not the wake's, and a layout migration in flight // each refuse, never a guess at the flat path. func TestClaimEnvFileFollowsTheRowsLayout(t *testing.T) { dataDir := t.TempDir() const host = "shop.example.com" nested := registry.Route{Host: host, Kind: "instance", Hibernate: true, DBPath: provision.NestedDBPath(dataDir, host), Socket: provision.NestedSocket(host)} flat := registry.Route{Host: host, Kind: "instance", Hibernate: true, DBPath: provision.DefaultDBPath(dataDir, host), Socket: filepath.Join(provision.SocketDir, host+".sock")} foreign := registry.Route{Host: host, Kind: "instance", Hibernate: true, DBPath: filepath.Join(dataDir, "elsewhere", host+".db"), Socket: "/run/other.sock"} resolve := func(rt registry.Route) func(string, string) (claimEnvFile, error) { return claimEnvFileFor(dataDir, testClaimRoutes{rows: map[string]registry.Route{host: rt}}) } got, err := resolve(nested)(host, nested.DBPath) if err != nil || got != (claimEnvFile{Dir: provision.InstanceDir(dataDir, host), Name: host + ".env", Nested: true}) { t.Fatalf("nested: %+v %v", got, err) } for name, rt := range map[string]registry.Route{"flat": flat, "foreign": foreign} { got, err := resolve(rt)(host, rt.DBPath) if err != nil || got != (claimEnvFile{Dir: dataDir, Name: host + ".env"}) { t.Fatalf("%s: %+v %v", name, got, err) } } // The wake's row must be the row: another database path is a row that // moved since the wake selected it. if _, err := resolve(nested)(host, flat.DBPath); !errors.Is(err, hostbox.ErrChanged) { t.Fatalf("a row that moved: %v", err) } if _, err := resolve(nested)(host, ""); !errors.Is(err, hostbox.ErrChanged) { t.Fatalf("no database path: %v", err) } if _, err := claimEnvFileFor(dataDir, testClaimRoutes{})(host, nested.DBPath); !errors.Is(err, hostbox.ErrChanged) { t.Fatalf("no row: %v", err) } unreadable := errors.New("registry: disk I/O error") if _, err := claimEnvFileFor(dataDir, testClaimRoutes{err: unreadable})(host, nested.DBPath); !errors.Is(err, unreadable) { t.Fatalf("an unreadable registry: %v", err) } // A layout migration in flight: the env file may be at either path. journal := provision.LayoutJournalPath(dataDir, host) if err := os.MkdirAll(filepath.Dir(journal), 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(journal, []byte("{}"), 0o600); err != nil { t.Fatal(err) } for name, rt := range map[string]registry.Route{"nested": nested, "flat": flat} { if _, err := resolve(rt)(host, rt.DBPath); !errors.Is(err, hostbox.ErrChanged) { t.Fatalf("%s under a layout journal: %v", name, err) } } }