Skip to content

Commit 576228d

Browse files
feat: use appslug for default data directory in install wizard (#2380)
* use appslug for default data directory * set default data dir and update tests * make tests consistent * simplify how data dir is set in installation config * revert buildtools changes * refactor setconfigdefaults to have access to runtimeconfig * readd enable_v3 env var * update test * make sure existing data dir in config isn't overwritten
1 parent f9c2521 commit 576228d

File tree

10 files changed

+127
-49
lines changed

10 files changed

+127
-49
lines changed

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,14 +343,24 @@ create-node%: DISTRO = debian-bookworm
343343
create-node%: NODE_PORT = 30000
344344
create-node%: MANAGER_NODE_PORT = 30080
345345
create-node%: K0S_DATA_DIR = /var/lib/embedded-cluster/k0s
346+
create-node%: K0S_DATA_DIR_V3 = $(shell \
347+
if [ -n "$(REPLICATED_APP)" ]; then \
348+
echo "/var/lib/$(shell echo '$(REPLICATED_APP)' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g')/k0s"; \
349+
else \
350+
echo "/var/lib/embedded-cluster-smoke-test-staging-app/k0s"; \
351+
fi)
346352
create-node%: ENABLE_V3 = 0
347353
create-node%:
354+
@echo "Mounting data directories:"
355+
@echo " v2: $(K0S_DATA_DIR)"
356+
@echo " v3: $(K0S_DATA_DIR_V3)"
348357
@docker run -d \
349358
--name node$* \
350359
--hostname node$* \
351360
--privileged \
352361
--restart=unless-stopped \
353362
-v $(K0S_DATA_DIR) \
363+
-v $(K0S_DATA_DIR_V3) \
354364
-v $(shell pwd):/replicatedhq/embedded-cluster \
355365
-v $(shell dirname $(shell pwd))/kots:/replicatedhq/kots \
356366
$(if $(filter node0,node$*),-p $(NODE_PORT):$(NODE_PORT)) \

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ Additionally, it includes a Registry when deployed in air gap mode, and SeaweedF
9191
make list-distros
9292
```
9393
94+
**Note:** The development environment automatically mounts both data directories to support v2 and v3:
95+
- **v2 mode:** Uses `/var/lib/embedded-cluster/k0s`
96+
- **v3 mode:** Uses `/var/lib/{app-slug}/k0s` (determined from `REPLICATED_APP`)
97+
98+
Both directories are mounted automatically, so the embedded cluster binary can use whichever one it needs without any manual configuration.
99+
94100
1. In the Vendor Portal, create and download a license that is assigned to the channel.
95101
We recommend storing this license in the `local-dev/` directory, as it is gitignored and not otherwise used by the CI.
96102

api/controllers/linux/install/controller_test.go

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -52,73 +52,96 @@ var warnPreflightOutput = &types.HostPreflightsOutput{
5252
func TestGetInstallationConfig(t *testing.T) {
5353
tests := []struct {
5454
name string
55-
setupMock func(*installation.MockInstallationManager)
55+
setupMock func(*installation.MockInstallationManager, string)
5656
expectedErr bool
57-
expectedValue types.InstallationConfig
57+
expectedValue func(string) types.InstallationConfig
5858
}{
5959
{
60-
name: "successful get",
61-
setupMock: func(m *installation.MockInstallationManager) {
60+
name: "successful read and defaults",
61+
setupMock: func(m *installation.MockInstallationManager, tempDir string) {
6262
config := types.InstallationConfig{
6363
AdminConsolePort: 9000,
6464
GlobalCIDR: "10.0.0.1/16",
6565
}
6666

6767
mock.InOrder(
6868
m.On("GetConfig").Return(config, nil),
69-
m.On("SetConfigDefaults", &config).Return(nil),
70-
m.On("ValidateConfig", config, 9001).Return(nil),
69+
m.On("SetConfigDefaults", &config, mock.AnythingOfType("*runtimeconfig.runtimeConfig")).Run(func(args mock.Arguments) {
70+
cfg := args.Get(0).(*types.InstallationConfig)
71+
cfg.DataDirectory = tempDir
72+
}).Return(nil),
73+
m.On("ValidateConfig", mock.MatchedBy(func(cfg types.InstallationConfig) bool {
74+
return cfg.AdminConsolePort == 9000 &&
75+
cfg.GlobalCIDR == "10.0.0.1/16" &&
76+
cfg.DataDirectory == tempDir
77+
}), 9001).Return(nil),
7178
)
7279
},
7380
expectedErr: false,
74-
expectedValue: types.InstallationConfig{
75-
AdminConsolePort: 9000,
76-
GlobalCIDR: "10.0.0.1/16",
81+
expectedValue: func(tempDir string) types.InstallationConfig {
82+
return types.InstallationConfig{
83+
AdminConsolePort: 9000,
84+
GlobalCIDR: "10.0.0.1/16",
85+
DataDirectory: tempDir,
86+
}
7787
},
7888
},
7989
{
8090
name: "read config error",
81-
setupMock: func(m *installation.MockInstallationManager) {
91+
setupMock: func(m *installation.MockInstallationManager, tempDir string) {
8292
m.On("GetConfig").Return(nil, errors.New("read error"))
8393
},
84-
expectedErr: true,
85-
expectedValue: types.InstallationConfig{},
94+
expectedErr: true,
95+
expectedValue: func(tempDir string) types.InstallationConfig {
96+
return types.InstallationConfig{}
97+
},
8698
},
8799
{
88100
name: "set defaults error",
89-
setupMock: func(m *installation.MockInstallationManager) {
101+
setupMock: func(m *installation.MockInstallationManager, tempDir string) {
90102
config := types.InstallationConfig{}
91103
mock.InOrder(
92104
m.On("GetConfig").Return(config, nil),
93-
m.On("SetConfigDefaults", &config).Return(errors.New("defaults error")),
105+
m.On("SetConfigDefaults", &config, mock.AnythingOfType("*runtimeconfig.runtimeConfig")).Return(errors.New("defaults error")),
94106
)
95107
},
96-
expectedErr: true,
97-
expectedValue: types.InstallationConfig{},
108+
expectedErr: true,
109+
expectedValue: func(tempDir string) types.InstallationConfig {
110+
return types.InstallationConfig{}
111+
},
98112
},
99113
{
100114
name: "validate error",
101-
setupMock: func(m *installation.MockInstallationManager) {
115+
setupMock: func(m *installation.MockInstallationManager, tempDir string) {
102116
config := types.InstallationConfig{}
117+
103118
mock.InOrder(
104119
m.On("GetConfig").Return(config, nil),
105-
m.On("SetConfigDefaults", &config).Return(nil),
106-
m.On("ValidateConfig", config, 9001).Return(errors.New("validation error")),
120+
m.On("SetConfigDefaults", &config, mock.AnythingOfType("*runtimeconfig.runtimeConfig")).Run(func(args mock.Arguments) {
121+
cfg := args.Get(0).(*types.InstallationConfig)
122+
cfg.DataDirectory = tempDir
123+
}).Return(nil),
124+
m.On("ValidateConfig", mock.MatchedBy(func(cfg types.InstallationConfig) bool {
125+
return cfg.DataDirectory == tempDir
126+
}), 9001).Return(errors.New("validation error")),
107127
)
108128
},
109-
expectedErr: true,
110-
expectedValue: types.InstallationConfig{},
129+
expectedErr: true,
130+
expectedValue: func(tempDir string) types.InstallationConfig {
131+
return types.InstallationConfig{}
132+
},
111133
},
112134
}
113135

114136
for _, tt := range tests {
115137
t.Run(tt.name, func(t *testing.T) {
138+
tempDir := t.TempDir()
116139
rc := runtimeconfig.New(nil, runtimeconfig.WithEnvSetter(&testEnvSetter{}))
117-
rc.SetDataDir(t.TempDir())
140+
rc.SetDataDir(tempDir)
118141
rc.SetManagerPort(9001)
119142

120143
mockManager := &installation.MockInstallationManager{}
121-
tt.setupMock(mockManager)
144+
tt.setupMock(mockManager, rc.EmbeddedClusterHomeDirectory())
122145

123146
controller, err := NewInstallController(
124147
WithRuntimeConfig(rc),
@@ -133,7 +156,7 @@ func TestGetInstallationConfig(t *testing.T) {
133156
assert.Equal(t, types.InstallationConfig{}, result)
134157
} else {
135158
assert.NoError(t, err)
136-
assert.Equal(t, tt.expectedValue, result)
159+
assert.Equal(t, tt.expectedValue(rc.EmbeddedClusterHomeDirectory()), result)
137160
}
138161

139162
mockManager.AssertExpectations(t)

api/controllers/linux/install/installation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func (c *InstallController) GetInstallationConfig(ctx context.Context) (types.In
1717
return types.InstallationConfig{}, err
1818
}
1919

20-
if err := c.installationManager.SetConfigDefaults(&config); err != nil {
20+
if err := c.installationManager.SetConfigDefaults(&config, c.rc); err != nil {
2121
return types.InstallationConfig{}, fmt.Errorf("set defaults: %w", err)
2222
}
2323

api/integration/install_test.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ func TestConfigureInstallation(t *testing.T) {
287287
t.Run(tc.name, func(t *testing.T) {
288288
// Create a runtime config
289289
rc := runtimeconfig.New(nil, runtimeconfig.WithEnvSetter(&testEnvSetter{}))
290+
// Set the expected data directory to match the test case
291+
if tc.config.DataDirectory != "" {
292+
rc.SetDataDir(tc.config.DataDirectory)
293+
}
290294

291295
// Create an install controller with the config manager
292296
installController, err := linuxinstall.NewInstallController(
@@ -342,7 +346,7 @@ func TestConfigureInstallation(t *testing.T) {
342346
err = json.NewDecoder(rec.Body).Decode(&status)
343347
require.NoError(t, err)
344348

345-
// Verify that the status is not pending. We cannot check for an end state here because the hots config is async
349+
// Verify that the status is not pending. We cannot check for an end state here because the host config is async
346350
// so the state might have moved from running to a final state before we get the response.
347351
assert.NotEqual(t, types.StatePending, status.State)
348352
}
@@ -363,7 +367,7 @@ func TestConfigureInstallation(t *testing.T) {
363367
// Verify that the config is in the store
364368
storedConfig, err := installController.GetInstallationConfig(t.Context())
365369
require.NoError(t, err)
366-
assert.Equal(t, tc.config.DataDirectory, storedConfig.DataDirectory)
370+
assert.Equal(t, rc.EmbeddedClusterHomeDirectory(), storedConfig.DataDirectory)
367371
assert.Equal(t, tc.config.AdminConsolePort, storedConfig.AdminConsolePort)
368372

369373
// Verify that the runtime config is updated
@@ -372,7 +376,7 @@ func TestConfigureInstallation(t *testing.T) {
372376
assert.Equal(t, tc.config.LocalArtifactMirrorPort, rc.LocalArtifactMirrorPort())
373377
}
374378

375-
// Verify host confiuration was performed for successful tests
379+
// Verify host configuration was performed for successful tests
376380
tc.mockHostUtils.AssertExpectations(t)
377381
tc.mockNetUtils.AssertExpectations(t)
378382

@@ -534,7 +538,8 @@ func TestConfigureInstallationControllerError(t *testing.T) {
534538
// Test the getInstall endpoint returns installation data correctly
535539
func TestGetInstallationConfig(t *testing.T) {
536540
rc := runtimeconfig.New(nil, runtimeconfig.WithEnvSetter(&testEnvSetter{}))
537-
rc.SetDataDir(t.TempDir())
541+
tempDir := t.TempDir()
542+
rc.SetDataDir(tempDir)
538543

539544
// Create a config manager
540545
installationManager := installation.NewInstallationManager()
@@ -548,7 +553,7 @@ func TestGetInstallationConfig(t *testing.T) {
548553

549554
// Set some initial config
550555
initialConfig := types.InstallationConfig{
551-
DataDirectory: "/tmp/test-data",
556+
DataDirectory: rc.EmbeddedClusterHomeDirectory(),
552557
AdminConsolePort: 8080,
553558
LocalArtifactMirrorPort: 8081,
554559
GlobalCIDR: "10.0.0.0/16",
@@ -592,7 +597,7 @@ func TestGetInstallationConfig(t *testing.T) {
592597
require.NoError(t, err)
593598

594599
// Verify the installation data matches what we expect
595-
assert.Equal(t, initialConfig.DataDirectory, config.DataDirectory)
600+
assert.Equal(t, rc.EmbeddedClusterHomeDirectory(), config.DataDirectory)
596601
assert.Equal(t, initialConfig.AdminConsolePort, config.AdminConsolePort)
597602
assert.Equal(t, initialConfig.LocalArtifactMirrorPort, config.LocalArtifactMirrorPort)
598603
assert.Equal(t, initialConfig.GlobalCIDR, config.GlobalCIDR)
@@ -606,7 +611,8 @@ func TestGetInstallationConfig(t *testing.T) {
606611
netUtils.On("DetermineBestNetworkInterface").Return("eth0", nil).Once()
607612

608613
rc := runtimeconfig.New(nil, runtimeconfig.WithEnvSetter(&testEnvSetter{}))
609-
rc.SetDataDir(t.TempDir())
614+
defaultTempDir := t.TempDir()
615+
rc.SetDataDir(defaultTempDir)
610616

611617
// Create a fresh config manager without writing anything
612618
emptyInstallationManager := installation.NewInstallationManager(
@@ -653,7 +659,8 @@ func TestGetInstallationConfig(t *testing.T) {
653659
require.NoError(t, err)
654660

655661
// Verify the installation data contains defaults or empty values
656-
assert.Equal(t, "/var/lib/embedded-cluster", config.DataDirectory)
662+
// Note: DataDirectory gets overridden with the temp directory from RuntimeConfig
663+
assert.Equal(t, rc.EmbeddedClusterHomeDirectory(), config.DataDirectory)
657664
assert.Equal(t, 30000, config.AdminConsolePort)
658665
assert.Equal(t, 50000, config.LocalArtifactMirrorPort)
659666
assert.Equal(t, "10.244.0.0/16", config.GlobalCIDR)
@@ -992,7 +999,8 @@ func TestInstallWithAPIClient(t *testing.T) {
992999

9931000
// Create a runtimeconfig to be used in the install process
9941001
rc := runtimeconfig.New(nil, runtimeconfig.WithEnvSetter(&testEnvSetter{}))
995-
rc.SetDataDir(t.TempDir())
1002+
tempDir := t.TempDir()
1003+
rc.SetDataDir(tempDir)
9961004

9971005
// Create a mock hostutils
9981006
mockHostUtils := &hostutils.MockHostUtils{}
@@ -1012,7 +1020,7 @@ func TestInstallWithAPIClient(t *testing.T) {
10121020

10131021
// Set some initial config
10141022
initialConfig := types.InstallationConfig{
1015-
DataDirectory: "/tmp/test-data-for-client",
1023+
DataDirectory: rc.EmbeddedClusterHomeDirectory(),
10161024
AdminConsolePort: 9080,
10171025
LocalArtifactMirrorPort: 9081,
10181026
GlobalCIDR: "192.168.0.0/16",
@@ -1058,7 +1066,8 @@ func TestInstallWithAPIClient(t *testing.T) {
10581066
require.NoError(t, err, "GetInstallationConfig should succeed")
10591067

10601068
// Verify values
1061-
assert.Equal(t, "/tmp/test-data-for-client", config.DataDirectory)
1069+
// Note: DataDirectory gets overridden with the temp directory from RuntimeConfig
1070+
assert.Equal(t, rc.EmbeddedClusterHomeDirectory(), config.DataDirectory)
10621071
assert.Equal(t, 9080, config.AdminConsolePort)
10631072
assert.Equal(t, 9081, config.LocalArtifactMirrorPort)
10641073
assert.Equal(t, "192.168.0.0/16", config.GlobalCIDR)
@@ -1084,6 +1093,9 @@ func TestInstallWithAPIClient(t *testing.T) {
10841093
NetworkInterface: "eth0",
10851094
}
10861095

1096+
// Update runtime config to match expected data directory for this test
1097+
rc.SetDataDir(config.DataDirectory)
1098+
10871099
// Configure the installation using the client
10881100
_, err = c.ConfigureInstallation(config)
10891101
require.NoError(t, err, "ConfigureInstallation should succeed with valid config")
@@ -1102,7 +1114,7 @@ func TestInstallWithAPIClient(t *testing.T) {
11021114
// Get the config to verify it persisted
11031115
newConfig, err := c.GetInstallationConfig()
11041116
require.NoError(t, err, "GetInstallationConfig should succeed after setting config")
1105-
assert.Equal(t, config.DataDirectory, newConfig.DataDirectory)
1117+
assert.Equal(t, rc.EmbeddedClusterHomeDirectory(), newConfig.DataDirectory)
11061118
assert.Equal(t, config.AdminConsolePort, newConfig.AdminConsolePort)
11071119
assert.Equal(t, config.NetworkInterface, newConfig.NetworkInterface)
11081120

api/internal/managers/installation/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,13 @@ func (m *installationManager) validateDataDirectory(config types.InstallationCon
149149
}
150150

151151
// SetConfigDefaults sets default values for the installation configuration
152-
func (m *installationManager) SetConfigDefaults(config *types.InstallationConfig) error {
152+
func (m *installationManager) SetConfigDefaults(config *types.InstallationConfig, rc runtimeconfig.RuntimeConfig) error {
153153
if config.AdminConsolePort == 0 {
154154
config.AdminConsolePort = ecv1beta1.DefaultAdminConsolePort
155155
}
156156

157157
if config.DataDirectory == "" {
158-
config.DataDirectory = ecv1beta1.DefaultDataDir
158+
config.DataDirectory = rc.EmbeddedClusterHomeDirectory()
159159
}
160160

161161
if config.LocalArtifactMirrorPort == 0 {

0 commit comments

Comments
 (0)