Skip to content

Commit d6968d8

Browse files
authored
Support the password config item type (#2463)
* Support the password config item type * unit tests * don't mask empty password values * apply to child items as well * mask child item password values * fix cmx test
1 parent 12e1317 commit d6968d8

File tree

30 files changed

+1593
-217
lines changed

30 files changed

+1593
-217
lines changed

api/client/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type Client interface {
1818
GetLinuxInfraStatus() (types.Infra, error)
1919
GetLinuxAppConfig() (types.AppConfig, error)
2020
GetLinuxAppConfigValues() (map[string]string, error)
21+
PatchLinuxAppConfigValues(values map[string]string) (types.AppConfig, error)
2122

2223
GetKubernetesInstallationConfig() (types.KubernetesInstallationConfig, error)
2324
ConfigureKubernetesInstallation(config types.KubernetesInstallationConfig) (types.Status, error)
@@ -26,6 +27,7 @@ type Client interface {
2627
GetKubernetesInfraStatus() (types.Infra, error)
2728
GetKubernetesAppConfig() (types.AppConfig, error)
2829
GetKubernetesAppConfigValues() (map[string]string, error)
30+
PatchKubernetesAppConfigValues(values map[string]string) (types.AppConfig, error)
2931
}
3032

3133
type client struct {

api/client/client_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,3 +858,143 @@ func TestKubernetesGetAppConfigValues(t *testing.T) {
858858
assert.Equal(t, http.StatusInternalServerError, apiErr.StatusCode)
859859
assert.Equal(t, "Internal Server Error", apiErr.Message)
860860
}
861+
862+
func TestLinuxPatchAppConfigValues(t *testing.T) {
863+
// Define expected config once
864+
expectedConfig := types.AppConfig{
865+
Groups: []kotsv1beta1.ConfigGroup{
866+
{
867+
Name: "test-group",
868+
Title: "Test Group",
869+
Items: []kotsv1beta1.ConfigItem{
870+
{
871+
Name: "test-item",
872+
Type: "text",
873+
Title: "Test Item",
874+
Default: multitype.BoolOrString{StrVal: "default"},
875+
Value: multitype.BoolOrString{StrVal: "value"},
876+
},
877+
},
878+
},
879+
},
880+
}
881+
882+
// Create a test server
883+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
884+
assert.Equal(t, "PATCH", r.Method)
885+
assert.Equal(t, "/api/linux/install/app/config/values", r.URL.Path)
886+
887+
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
888+
assert.Equal(t, "Bearer test-token", r.Header.Get("Authorization"))
889+
890+
// Decode and verify request body
891+
var req types.PatchAppConfigValuesRequest
892+
err := json.NewDecoder(r.Body).Decode(&req)
893+
require.NoError(t, err)
894+
assert.Equal(t, "new-value", req.Values["test-item"])
895+
896+
// Return successful response
897+
w.WriteHeader(http.StatusOK)
898+
json.NewEncoder(w).Encode(expectedConfig)
899+
}))
900+
defer server.Close()
901+
902+
// Test successful set
903+
c := New(server.URL, WithToken("test-token"))
904+
values := map[string]string{
905+
"test-item": "new-value",
906+
}
907+
config, err := c.PatchLinuxAppConfigValues(values)
908+
assert.NoError(t, err)
909+
assert.Equal(t, expectedConfig, config)
910+
911+
// Test error response
912+
errorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
913+
w.WriteHeader(http.StatusInternalServerError)
914+
json.NewEncoder(w).Encode(types.APIError{
915+
StatusCode: http.StatusInternalServerError,
916+
Message: "Internal Server Error",
917+
})
918+
}))
919+
defer errorServer.Close()
920+
921+
c = New(errorServer.URL, WithToken("test-token"))
922+
config, err = c.PatchLinuxAppConfigValues(values)
923+
assert.Error(t, err)
924+
assert.Equal(t, types.AppConfig{}, config)
925+
926+
apiErr, ok := err.(*types.APIError)
927+
require.True(t, ok, "Expected err to be of type *types.APIError")
928+
assert.Equal(t, http.StatusInternalServerError, apiErr.StatusCode)
929+
assert.Equal(t, "Internal Server Error", apiErr.Message)
930+
}
931+
932+
func TestKubernetesPatchAppConfigValues(t *testing.T) {
933+
// Define expected config once
934+
expectedConfig := types.AppConfig{
935+
Groups: []kotsv1beta1.ConfigGroup{
936+
{
937+
Name: "test-group",
938+
Title: "Test Group",
939+
Items: []kotsv1beta1.ConfigItem{
940+
{
941+
Name: "test-item",
942+
Type: "text",
943+
Title: "Test Item",
944+
Default: multitype.BoolOrString{StrVal: "default"},
945+
Value: multitype.BoolOrString{StrVal: "value"},
946+
},
947+
},
948+
},
949+
},
950+
}
951+
952+
// Create a test server
953+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
954+
assert.Equal(t, "PATCH", r.Method)
955+
assert.Equal(t, "/api/kubernetes/install/app/config/values", r.URL.Path)
956+
957+
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
958+
assert.Equal(t, "Bearer test-token", r.Header.Get("Authorization"))
959+
960+
// Decode and verify request body
961+
var req types.PatchAppConfigValuesRequest
962+
err := json.NewDecoder(r.Body).Decode(&req)
963+
require.NoError(t, err)
964+
assert.Equal(t, "new-value", req.Values["test-item"])
965+
966+
// Return successful response
967+
w.WriteHeader(http.StatusOK)
968+
json.NewEncoder(w).Encode(expectedConfig)
969+
}))
970+
defer server.Close()
971+
972+
// Test successful set
973+
c := New(server.URL, WithToken("test-token"))
974+
values := map[string]string{
975+
"test-item": "new-value",
976+
}
977+
config, err := c.PatchKubernetesAppConfigValues(values)
978+
assert.NoError(t, err)
979+
assert.Equal(t, expectedConfig, config)
980+
981+
// Test error response
982+
errorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
983+
w.WriteHeader(http.StatusInternalServerError)
984+
json.NewEncoder(w).Encode(types.APIError{
985+
StatusCode: http.StatusInternalServerError,
986+
Message: "Internal Server Error",
987+
})
988+
}))
989+
defer errorServer.Close()
990+
991+
c = New(errorServer.URL, WithToken("test-token"))
992+
config, err = c.PatchKubernetesAppConfigValues(values)
993+
assert.Error(t, err)
994+
assert.Equal(t, types.AppConfig{}, config)
995+
996+
apiErr, ok := err.(*types.APIError)
997+
require.True(t, ok, "Expected err to be of type *types.APIError")
998+
assert.Equal(t, http.StatusInternalServerError, apiErr.StatusCode)
999+
assert.Equal(t, "Internal Server Error", apiErr.Message)
1000+
}

api/client/install.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,41 @@ func (c *client) GetLinuxAppConfigValues() (map[string]string, error) {
349349
return response.Values, nil
350350
}
351351

352+
func (c *client) PatchLinuxAppConfigValues(values map[string]string) (types.AppConfig, error) {
353+
req := types.PatchAppConfigValuesRequest{
354+
Values: values,
355+
}
356+
b, err := json.Marshal(req)
357+
if err != nil {
358+
return types.AppConfig{}, err
359+
}
360+
361+
httpReq, err := http.NewRequest("PATCH", c.apiURL+"/api/linux/install/app/config/values", bytes.NewBuffer(b))
362+
if err != nil {
363+
return types.AppConfig{}, err
364+
}
365+
httpReq.Header.Set("Content-Type", "application/json")
366+
setAuthorizationHeader(httpReq, c.token)
367+
368+
resp, err := c.httpClient.Do(httpReq)
369+
if err != nil {
370+
return types.AppConfig{}, err
371+
}
372+
defer resp.Body.Close()
373+
374+
if resp.StatusCode != http.StatusOK {
375+
return types.AppConfig{}, errorFromResponse(resp)
376+
}
377+
378+
var config types.AppConfig
379+
err = json.NewDecoder(resp.Body).Decode(&config)
380+
if err != nil {
381+
return types.AppConfig{}, err
382+
}
383+
384+
return config, nil
385+
}
386+
352387
func (c *client) GetKubernetesAppConfig() (types.AppConfig, error) {
353388
req, err := http.NewRequest("GET", c.apiURL+"/api/kubernetes/install/app/config", nil)
354389
if err != nil {
@@ -402,3 +437,38 @@ func (c *client) GetKubernetesAppConfigValues() (map[string]string, error) {
402437

403438
return response.Values, nil
404439
}
440+
441+
func (c *client) PatchKubernetesAppConfigValues(values map[string]string) (types.AppConfig, error) {
442+
req := types.PatchAppConfigValuesRequest{
443+
Values: values,
444+
}
445+
b, err := json.Marshal(req)
446+
if err != nil {
447+
return types.AppConfig{}, err
448+
}
449+
450+
httpReq, err := http.NewRequest("PATCH", c.apiURL+"/api/kubernetes/install/app/config/values", bytes.NewBuffer(b))
451+
if err != nil {
452+
return types.AppConfig{}, err
453+
}
454+
httpReq.Header.Set("Content-Type", "application/json")
455+
setAuthorizationHeader(httpReq, c.token)
456+
457+
resp, err := c.httpClient.Do(httpReq)
458+
if err != nil {
459+
return types.AppConfig{}, err
460+
}
461+
defer resp.Body.Close()
462+
463+
if resp.StatusCode != http.StatusOK {
464+
return types.AppConfig{}, errorFromResponse(resp)
465+
}
466+
467+
var config types.AppConfig
468+
err = json.NewDecoder(resp.Body).Decode(&config)
469+
if err != nil {
470+
return types.AppConfig{}, err
471+
}
472+
473+
return config, nil
474+
}

api/controllers/kubernetes/install/app.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func (c *InstallController) GetAppConfig(ctx context.Context) (kotsv1beta1.Confi
1818
return c.appConfigManager.GetConfig(*c.releaseData.AppConfig)
1919
}
2020

21-
func (c *InstallController) SetAppConfigValues(ctx context.Context, values map[string]string) (finalErr error) {
21+
func (c *InstallController) PatchAppConfigValues(ctx context.Context, values map[string]string) (finalErr error) {
2222
if c.releaseData == nil || c.releaseData.AppConfig == nil {
2323
return errors.New("app config not found")
2424
}
@@ -51,9 +51,9 @@ func (c *InstallController) SetAppConfigValues(ctx context.Context, values map[s
5151
}
5252
}()
5353

54-
err = c.appConfigManager.SetConfigValues(*c.releaseData.AppConfig, values)
54+
err = c.appConfigManager.PatchConfigValues(ctx, *c.releaseData.AppConfig, values)
5555
if err != nil {
56-
return fmt.Errorf("set app config values: %w", err)
56+
return fmt.Errorf("patch app config values: %w", err)
5757
}
5858

5959
err = c.stateMachine.Transition(lock, StateApplicationConfigured)
@@ -64,6 +64,12 @@ func (c *InstallController) SetAppConfigValues(ctx context.Context, values map[s
6464
return nil
6565
}
6666

67-
func (c *InstallController) GetAppConfigValues(ctx context.Context) (map[string]string, error) {
68-
return c.appConfigManager.GetConfigValues()
67+
func (c *InstallController) GetAppConfigValues(ctx context.Context, maskPasswords bool) (map[string]string, error) {
68+
// Get the app config to determine which fields are password type
69+
appConfig, err := c.GetAppConfig(ctx)
70+
if err != nil {
71+
return nil, fmt.Errorf("get app config: %w", err)
72+
}
73+
74+
return c.appConfigManager.GetConfigValues(ctx, appConfig, maskPasswords)
6975
}

api/controllers/kubernetes/install/controller.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ type Controller interface {
2929
SetupInfra(ctx context.Context) error
3030
GetInfra(ctx context.Context) (types.Infra, error)
3131
GetAppConfig(ctx context.Context) (kotsv1beta1.Config, error)
32-
SetAppConfigValues(ctx context.Context, values map[string]string) error
33-
GetAppConfigValues(ctx context.Context) (map[string]string, error)
32+
PatchAppConfigValues(ctx context.Context, values map[string]string) error
33+
GetAppConfigValues(ctx context.Context, maskPasswords bool) (map[string]string, error)
3434
}
3535

3636
var _ Controller = (*InstallController)(nil)

api/controllers/kubernetes/install/controller_mock.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ func (m *MockController) GetAppConfig(ctx context.Context) (kotsv1beta1.Config,
6060
return args.Get(0).(kotsv1beta1.Config), args.Error(1)
6161
}
6262

63-
// SetAppConfigValues mocks the SetAppConfigValues method
64-
func (m *MockController) SetAppConfigValues(ctx context.Context, values map[string]string) error {
63+
// PatchAppConfigValues mocks the PatchAppConfigValues method
64+
func (m *MockController) PatchAppConfigValues(ctx context.Context, values map[string]string) error {
6565
args := m.Called(ctx, values)
6666
return args.Error(0)
6767
}
6868

6969
// GetAppConfigValues mocks the GetAppConfigValues method
70-
func (m *MockController) GetAppConfigValues(ctx context.Context) (map[string]string, error) {
71-
args := m.Called(ctx)
70+
func (m *MockController) GetAppConfigValues(ctx context.Context, maskPasswords bool) (map[string]string, error) {
71+
args := m.Called(ctx, maskPasswords)
7272
if args.Get(0) == nil {
7373
return nil, args.Error(1)
7474
}

api/controllers/kubernetes/install/controller_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ func TestSetupInfra(t *testing.T) {
299299
expectedState: StateSucceeded,
300300
setupMocks: func(ki kubernetesinstallation.Installation, im *installation.MockInstallationManager, fm *infra.MockInfraManager, mr *metrics.MockReporter, st *store.MockStore, am *appconfig.MockAppConfigManager) {
301301
mock.InOrder(
302-
am.On("GetKotsadmConfigValues", appConfig).Return(configValues, nil),
302+
am.On("GetKotsadmConfigValues", mock.Anything, appConfig).Return(configValues, nil),
303303
fm.On("Install", mock.Anything, ki, configValues).Return(nil),
304304
// TODO: we are not yet reporting
305305
// mr.On("ReportInstallationSucceeded", mock.Anything),
@@ -313,7 +313,7 @@ func TestSetupInfra(t *testing.T) {
313313
expectedState: StateInfrastructureInstallFailed,
314314
setupMocks: func(ki kubernetesinstallation.Installation, im *installation.MockInstallationManager, fm *infra.MockInfraManager, mr *metrics.MockReporter, st *store.MockStore, am *appconfig.MockAppConfigManager) {
315315
mock.InOrder(
316-
am.On("GetKotsadmConfigValues", appConfig).Return(configValues, nil),
316+
am.On("GetKotsadmConfigValues", mock.Anything, appConfig).Return(configValues, nil),
317317
fm.On("Install", mock.Anything, ki, configValues).Return(errors.New("install error")),
318318
st.LinuxInfraMockStore.On("GetStatus").Return(types.Status{Description: "install error"}, nil),
319319
// TODO: we are not yet reporting
@@ -328,7 +328,7 @@ func TestSetupInfra(t *testing.T) {
328328
expectedState: StateInfrastructureInstallFailed,
329329
setupMocks: func(ki kubernetesinstallation.Installation, im *installation.MockInstallationManager, fm *infra.MockInfraManager, mr *metrics.MockReporter, st *store.MockStore, am *appconfig.MockAppConfigManager) {
330330
mock.InOrder(
331-
am.On("GetKotsadmConfigValues", appConfig).Return(configValues, nil),
331+
am.On("GetKotsadmConfigValues", mock.Anything, appConfig).Return(configValues, nil),
332332
fm.On("Install", mock.Anything, ki, configValues).Return(errors.New("install error")),
333333
st.LinuxInfraMockStore.On("GetStatus").Return(nil, assert.AnError),
334334
)
@@ -341,7 +341,7 @@ func TestSetupInfra(t *testing.T) {
341341
expectedState: StateInfrastructureInstallFailed,
342342
setupMocks: func(ki kubernetesinstallation.Installation, im *installation.MockInstallationManager, fm *infra.MockInfraManager, mr *metrics.MockReporter, st *store.MockStore, am *appconfig.MockAppConfigManager) {
343343
mock.InOrder(
344-
am.On("GetKotsadmConfigValues", appConfig).Return(configValues, nil),
344+
am.On("GetKotsadmConfigValues", mock.Anything, appConfig).Return(configValues, nil),
345345
fm.On("Install", mock.Anything, ki, configValues).Panic("this is a panic"),
346346
st.LinuxInfraMockStore.On("GetStatus").Return(types.Status{Description: "this is a panic"}, nil),
347347
// TODO: we are not yet reporting
@@ -364,7 +364,7 @@ func TestSetupInfra(t *testing.T) {
364364
expectedState: StateInstallationConfigured,
365365
setupMocks: func(ki kubernetesinstallation.Installation, im *installation.MockInstallationManager, fm *infra.MockInfraManager, mr *metrics.MockReporter, st *store.MockStore, am *appconfig.MockAppConfigManager) {
366366
mock.InOrder(
367-
am.On("GetKotsadmConfigValues", appConfig).Return(kotsv1beta1.ConfigValues{}, assert.AnError),
367+
am.On("GetKotsadmConfigValues", mock.Anything, appConfig).Return(kotsv1beta1.ConfigValues{}, assert.AnError),
368368
)
369369
},
370370
expectedErr: assert.AnError,

api/controllers/kubernetes/install/infra.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func (c *InstallController) SetupInfra(ctx context.Context) (finalErr error) {
2828
}
2929
}()
3030

31-
configValues, err := c.appConfigManager.GetKotsadmConfigValues(*c.releaseData.AppConfig)
31+
configValues, err := c.appConfigManager.GetKotsadmConfigValues(ctx, *c.releaseData.AppConfig)
3232
if err != nil {
3333
return fmt.Errorf("failed to get kotsadm config values: %w", err)
3434
}

api/controllers/linux/install/app.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func (c *InstallController) GetAppConfig(ctx context.Context) (kotsv1beta1.Confi
1818
return c.appConfigManager.GetConfig(*c.releaseData.AppConfig)
1919
}
2020

21-
func (c *InstallController) SetAppConfigValues(ctx context.Context, values map[string]string) (finalErr error) {
21+
func (c *InstallController) PatchAppConfigValues(ctx context.Context, values map[string]string) (finalErr error) {
2222
if c.releaseData == nil || c.releaseData.AppConfig == nil {
2323
return errors.New("app config not found")
2424
}
@@ -51,9 +51,9 @@ func (c *InstallController) SetAppConfigValues(ctx context.Context, values map[s
5151
}
5252
}()
5353

54-
err = c.appConfigManager.SetConfigValues(*c.releaseData.AppConfig, values)
54+
err = c.appConfigManager.PatchConfigValues(ctx, *c.releaseData.AppConfig, values)
5555
if err != nil {
56-
return fmt.Errorf("set app config values: %w", err)
56+
return fmt.Errorf("patch app config values: %w", err)
5757
}
5858

5959
err = c.stateMachine.Transition(lock, StateApplicationConfigured)
@@ -64,6 +64,12 @@ func (c *InstallController) SetAppConfigValues(ctx context.Context, values map[s
6464
return nil
6565
}
6666

67-
func (c *InstallController) GetAppConfigValues(ctx context.Context) (map[string]string, error) {
68-
return c.appConfigManager.GetConfigValues()
67+
func (c *InstallController) GetAppConfigValues(ctx context.Context, maskPasswords bool) (map[string]string, error) {
68+
// Get the app config to determine which fields are password type
69+
appConfig, err := c.GetAppConfig(ctx)
70+
if err != nil {
71+
return nil, fmt.Errorf("get app config: %w", err)
72+
}
73+
74+
return c.appConfigManager.GetConfigValues(ctx, appConfig, maskPasswords)
6975
}

0 commit comments

Comments
 (0)