Skip to content

Commit 3ec736e

Browse files
committed
backend, frontend, electron: Allow kubeconfig cluster removal via flag
Introduces a new backend configuration flag --allow-kubeconfig-removal to control whether users can delete clusters sourced from their kubeconfig via the UI. - backend: Add flag and expose it in client config. - frontend: Conditionally enable delete option based on flag. - electron: Enable flag by default as desktop app already supports kubeconfig management. - makefile: Enable flag in dev mode.
1 parent a51ad40 commit 3ec736e

File tree

10 files changed

+63
-32
lines changed

10 files changed

+63
-32
lines changed

Makefile

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,10 @@ run-backend:
234234
@echo "**** Warning: Running with Helm and dynamic-clusters endpoints enabled. ****"
235235

236236
ifeq ($(UNIXSHELL),true)
237-
HEADLAMP_BACKEND_TOKEN=headlamp HEADLAMP_CONFIG_ENABLE_HELM=true HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true ./backend/headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost
237+
HEADLAMP_BACKEND_TOKEN=headlamp HEADLAMP_CONFIG_ENABLE_HELM=true HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true ./backend/headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost
238238
else
239239
@echo "**** Running on Windows without bash or zsh. ****"
240-
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
240+
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& set HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
241241
endif
242242

243243
run-dev:
@@ -251,10 +251,11 @@ ifeq ($(UNIXSHELL),true)
251251
HEADLAMP_CONFIG_METRICS_ENABLED=true \
252252
HEADLAMP_CONFIG_ENABLE_HELM=true \
253253
HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true \
254+
HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true \
254255
./backend/headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost
255256
else
256257
@echo "**** Running on Windows without bash or zsh. ****"
257-
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_METRICS_ENABLED=true&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
258+
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_METRICS_ENABLED=true&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& set HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
258259
endif
259260

260261
run-backend-with-traces:
@@ -264,10 +265,11 @@ ifeq ($(UNIXSHELL),true)
264265
HEADLAMP_CONFIG_TRACING_ENABLED=true \
265266
HEADLAMP_CONFIG_ENABLE_HELM=true \
266267
HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true \
268+
HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true \
267269
./backend/headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost
268270
else
269271
@echo "**** Running on Windows without bash or zsh. ****"
270-
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_TRACING_ENABLED=true&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
272+
@cmd /c "set HEADLAMP_BACKEND_TOKEN=headlamp&& set HEADLAMP_CONFIG_TRACING_ENABLED=true&& set HEADLAMP_CONFIG_ENABLE_HELM=true&& set HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS=true&& set HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL=true&& backend\headlamp-server -dev -proxy-urls https://artifacthub.io/* -listen-addr=localhost"
271273
endif
272274

273275
run-frontend:

app/electron/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,7 @@ async function startServer(flags: string[] = []): Promise<ChildProcessWithoutNul
754754
// Enable the Helm and dynamic cluster endpoints
755755
process.env.HEADLAMP_CONFIG_ENABLE_HELM = 'true';
756756
process.env.HEADLAMP_CONFIG_ENABLE_DYNAMIC_CLUSTERS = 'true';
757+
process.env.HEADLAMP_CONFIG_ALLOW_KUBECONFIG_REMOVAL = 'true';
757758

758759
// Pass a token to the backend that can be used for auth on some routes
759760
process.env.HEADLAMP_BACKEND_TOKEN = backendToken;

backend/cmd/headlamp.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ const (
9797
type clientConfig struct {
9898
Clusters []Cluster `json:"clusters"`
9999
IsDynamicClusterEnabled bool `json:"isDynamicClusterEnabled"`
100+
AllowKubeconfigRemoval bool `json:"allowKubeconfigRemoval"`
100101
}
101102

102103
type OauthConfig struct {
@@ -1749,7 +1750,11 @@ func parseClusterFromKubeConfig(kubeConfigs []string) ([]Cluster, []error) {
17491750
func (c *HeadlampConfig) getConfig(w http.ResponseWriter, r *http.Request) {
17501751
w.Header().Set("Content-Type", "application/json")
17511752

1752-
clientConfig := clientConfig{c.getClusters(), c.EnableDynamicClusters}
1753+
clientConfig := clientConfig{
1754+
Clusters: c.getClusters(),
1755+
IsDynamicClusterEnabled: c.EnableDynamicClusters,
1756+
AllowKubeconfigRemoval: c.AllowKubeconfigRemoval,
1757+
}
17531758

17541759
if err := json.NewEncoder(w).Encode(&clientConfig); err != nil {
17551760
logger.Log(logger.LevelError, nil, err, "encoding config")

backend/cmd/stateless.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,11 @@ func (c *HeadlampConfig) parseKubeConfig(w http.ResponseWriter, r *http.Request)
178178
return
179179
}
180180

181-
clientConfig := clientConfig{contexts, c.EnableDynamicClusters}
181+
clientConfig := clientConfig{
182+
Clusters: contexts,
183+
IsDynamicClusterEnabled: c.EnableDynamicClusters,
184+
AllowKubeconfigRemoval: c.AllowKubeconfigRemoval,
185+
}
182186

183187
if err := json.NewEncoder(w).Encode(&clientConfig); err != nil {
184188
logger.Log(logger.LevelError, nil, err, "encoding config")

backend/pkg/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type Config struct {
4545
CacheEnabled bool `koanf:"cache-enabled"`
4646
EnableHelm bool `koanf:"enable-helm"`
4747
EnableDynamicClusters bool `koanf:"enable-dynamic-clusters"`
48+
AllowKubeconfigRemoval bool `koanf:"allow-kubeconfig-removal"`
4849
ListenAddr string `koanf:"listen-addr"`
4950
WatchPluginsChanges bool `koanf:"watch-plugins-changes"`
5051
Port uint `koanf:"port"`
@@ -421,6 +422,7 @@ func addGeneralFlags(f *flag.FlagSet) {
421422
f.Bool("insecure-ssl", false, "Accept/Ignore all server SSL certificates")
422423
f.String("log-level", "info", "Set backend log verbosity. Options: debug, info (default), warn, error")
423424
f.Bool("enable-dynamic-clusters", false, "Enable dynamic clusters, which stores stateless clusters in the frontend.")
425+
f.Bool("allow-kubeconfig-removal", false, "Allow users to remove clusters from their kubeconfig via the UI")
424426
// Note: When running in-cluster and if not explicitly set, this flag defaults to false.
425427
f.Bool("watch-plugins-changes", true, "Reloads plugins when there are changes to them or their directory")
426428

backend/pkg/headlampconfig/headlampConfig.go

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,28 @@ type HeadlampConfig struct {
3939
}
4040

4141
type HeadlampCFG struct {
42-
UseInCluster bool
43-
InClusterContextName string
44-
ListenAddr string
45-
CacheEnabled bool
46-
DevMode bool
47-
Insecure bool
48-
EnableHelm bool
49-
EnableDynamicClusters bool
50-
WatchPluginsChanges bool
51-
Port uint
52-
KubeConfigPath string
53-
SkippedKubeContexts string
54-
StaticDir string
55-
PluginDir string
56-
UserPluginDir string
57-
StaticPluginDir string
58-
KubeConfigStore kubeconfig.ContextStore
59-
Telemetry *telemetry.Telemetry
60-
Metrics *telemetry.Metrics
61-
BaseURL string
62-
ProxyURLs []string
63-
TLSCertPath string
64-
TLSKeyPath string
42+
UseInCluster bool
43+
InClusterContextName string
44+
ListenAddr string
45+
CacheEnabled bool
46+
DevMode bool
47+
Insecure bool
48+
EnableHelm bool
49+
EnableDynamicClusters bool
50+
AllowKubeconfigRemoval bool
51+
WatchPluginsChanges bool
52+
Port uint
53+
KubeConfigPath string
54+
SkippedKubeContexts string
55+
StaticDir string
56+
PluginDir string
57+
UserPluginDir string
58+
StaticPluginDir string
59+
KubeConfigStore kubeconfig.ContextStore
60+
Telemetry *telemetry.Telemetry
61+
Metrics *telemetry.Metrics
62+
BaseURL string
63+
ProxyURLs []string
64+
TLSCertPath string
65+
TLSKeyPath string
6566
}

frontend/src/components/App/Home/ClusterContextMenu.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export default function ClusterContextMenu({ cluster }: ClusterContextMenuProps)
5353
const dialogs = useTypedSelector(state => state.clusterProvider.dialogs);
5454
const menuItems = useTypedSelector(state => state.clusterProvider.menuItems);
5555
const isDynamicClusterEnabled = useTypedSelector(state => state.config.isDynamicClusterEnabled);
56+
const allowKubeconfigRemoval = useTypedSelector(state => state.config.allowKubeconfigRemoval);
5657

5758
const kubeconfigOrigin = cluster.meta_data?.origin?.kubeconfig;
5859
const deleteFromKubeconfig = cluster.meta_data?.source === 'kubeconfig';
@@ -155,9 +156,10 @@ export default function ClusterContextMenu({ cluster }: ClusterContextMenuProps)
155156
<ListItemText>{t('translation|Settings')}</ListItemText>
156157
</MenuItem>
157158
{(!menuItems || menuItems.length === 0) &&
158-
(helpers.isElectron() || isDynamicClusterEnabled) &&
159-
(cluster.meta_data?.source === 'dynamic_cluster' ||
160-
cluster.meta_data?.source === 'kubeconfig') && (
159+
((cluster.meta_data?.source === 'dynamic_cluster' &&
160+
(helpers.isElectron() || isDynamicClusterEnabled)) ||
161+
(cluster.meta_data?.source === 'kubeconfig' &&
162+
(helpers.isElectron() || allowKubeconfigRemoval))) && (
161163
<MenuItem
162164
onClick={() => {
163165
setOpenConfirmDialog('deleteDynamic');

frontend/src/components/project/NewProjectPopup.stories.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ const makeStore = () => {
4646
timezone: 'UTC',
4747
useEvict: true,
4848
},
49+
isDynamicClusterEnabled: false,
50+
allowKubeconfigRemoval: false,
4951
},
5052
projects: {
5153
headerActions: {},

frontend/src/components/project/ProjectCreateFromYaml.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const makeStore = () => {
4141
'cluster-b': { name: 'cluster-b' },
4242
} as any,
4343
isDynamicClusterEnabled: true,
44+
allowKubeconfigRemoval: false,
4445
settings: {
4546
tableRowsPerPageOptions: [15, 25, 50],
4647
timezone: 'UTC',

frontend/src/redux/configSlice.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ export interface ConfigState {
4747
* When true, users can add and delete clusters dynamically.
4848
*/
4949
isDynamicClusterEnabled: boolean;
50+
/**
51+
* Whether users are allowed to remove clusters from their kubeconfig.
52+
* When true, the UI will show options to delete kubeconfig-sourced clusters.
53+
* Defaults to false to prevent accidental removal in company-deployed environments.
54+
*/
55+
allowKubeconfigRemoval: boolean;
5056
/**
5157
* Settings is a map of settings names to settings values.
5258
*/
@@ -77,6 +83,7 @@ export const initialState: ConfigState = {
7783
statelessClusters: null,
7884
allClusters: null,
7985
isDynamicClusterEnabled: false,
86+
allowKubeconfigRemoval: false,
8087
settings: {
8188
tableRowsPerPageOptions:
8289
storedSettings.tableRowsPerPageOptions || defaultTableRowsPerPageOptions,
@@ -100,12 +107,16 @@ const configSlice = createSlice({
100107
action: PayloadAction<{
101108
clusters: ConfigState['clusters'];
102109
isDynamicClusterEnabled?: boolean;
110+
allowKubeconfigRemoval?: boolean;
103111
}>
104112
) {
105113
state.clusters = action.payload.clusters;
106114
if (action.payload.isDynamicClusterEnabled !== undefined) {
107115
state.isDynamicClusterEnabled = action.payload.isDynamicClusterEnabled;
108116
}
117+
if (action.payload.allowKubeconfigRemoval !== undefined) {
118+
state.allowKubeconfigRemoval = action.payload.allowKubeconfigRemoval;
119+
}
109120
},
110121
/**
111122
* Save the config. To both the store, and localStorage.

0 commit comments

Comments
 (0)