Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit c28c800

Browse files
committed
fix(plugins/plugin-kubectl): kubernetes namespace list does not update after namespace deletion
Fixes #6521
1 parent f6d0a98 commit c28c800

File tree

10 files changed

+62
-43
lines changed

10 files changed

+62
-43
lines changed

plugins/plugin-kubectl/components/src/CurrentContext.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ export default class CurrentContext extends React.PureComponent<{}, State> {
106106
return last && now - last < 250
107107
}
108108

109-
private async reportCurrentContext(idx?: Tab | number) {
110-
const tab = getTab(idx)
109+
private async reportCurrentContext(idx?: Tab | number | string) {
110+
const tab = getTab(typeof idx === 'string' ? undefined : idx)
111111
if (!tab || !tab.REPL) {
112112
if (tab && !tab.REPL) {
113113
eventChannelUnsafe.once(`/tab/new/${tab.uuid}`, () => this.reportCurrentContext())

plugins/plugin-kubectl/components/src/CurrentNamespace.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ export default class CurrentNamespace extends React.PureComponent<{}, State> {
7272
return last && now - last < 250
7373
}
7474

75-
private async reportCurrentNamespace(idx?: Tab | number) {
76-
const tab = getTab(idx)
75+
private async reportCurrentNamespace(idx?: Tab | number | string) {
76+
const tab = getTab(typeof idx === 'string' ? undefined : idx)
7777
if (!tab || !tab.REPL) {
7878
if (tab && !tab.REPL) {
7979
eventChannelUnsafe.once(`/tab/new/${tab.uuid}`, () => this.reportCurrentNamespace())

plugins/plugin-kubectl/oc/src/controller/oc/get/projects.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ import {
2727
export default function registerOcProjectGet(registrar: Registrar) {
2828
registrar.listen(`/${commandPrefix}/oc/project`, async args => {
2929
const response = await doExecWithStdout(args, undefined, 'oc')
30-
emitKubectlConfigChangeEvent(args)
30+
31+
const newNamespace = args.argvNoOptions[args.argvNoOptions.indexOf('project') + 1]
32+
if (newNamespace) {
33+
emitKubectlConfigChangeEvent('SetNamespaceOrContext', newNamespace)
34+
}
35+
3136
return response
3237
})
3338

plugins/plugin-kubectl/src/controller/client/direct/create.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ import { KubeOptions, fileOf, getLabel, getNamespace } from '../../kubectl/optio
2525
import status from './status'
2626
import handleErrors from './errors'
2727
import { urlFormatterForArgs } from './url'
28+
import { headersForPlainRequest as headers } from './headers'
29+
2830
import { FinalState } from '../../../lib/model/states'
2931
import { getCommandFromArgs } from '../../../lib/util/util'
30-
import { headersForPlainRequest as headers } from './headers'
3132

3233
const debug = Debug('plugin-kubectl/controller/client/direct/create')
3334

plugins/plugin-kubectl/src/controller/client/direct/status.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import makeWatchable, { DirectWatcher, SingleKindDirectWatcher } from './watch'
2727
import { Explained } from '../../kubectl/explain'
2828
import { KubeOptions } from '../../kubectl/options'
2929
import { isResourceReady } from '../../kubectl/status'
30+
import { emitKubectlConfigChangeEvent } from '../../kubectl/config'
3031

3132
import { FinalState } from '../../../lib/model/states'
3233
import { getCommandFromArgs } from '../../../lib/util/util'
@@ -203,6 +204,9 @@ export default async function watchMulti(
203204
if (nNotReady === 0) {
204205
// sub-case 1: nothing to watch, as everything is already "ready"
205206
debug('special case: single-group watching, all-ready all ready!', nNotReady, groups[0])
207+
if (groups[0].explainedKind.kind === 'Namespace') {
208+
emitKubectlConfigChangeEvent('CreateOrDeleteNamespace')
209+
}
206210
return tables[0].table
207211
} else {
208212
// sub-case 2: a subset may be done, but we need to fire up a

plugins/plugin-kubectl/src/controller/client/direct/watch.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@
1717
import Debug from 'debug'
1818
import { Abortable, Arguments, FlowControllable, Row, Table, Watchable, Watcher, WatchPusher } from '@kui-shell/core'
1919

20-
import { toKuiTable } from '../../../lib/view/formatTable'
21-
import { fetchFile, openStream } from '../../../lib/util/fetch-file'
22-
import { KubeOptions, withKubeconfigFrom } from '../../kubectl/options'
23-
2420
import URLFormatter from './url'
2521
import { headersForTableRequest } from './headers'
22+
2623
import { FinalState } from '../../../lib/model/states'
27-
import { isResourceReady } from '../../kubectl/status'
24+
import { toKuiTable } from '../../../lib/view/formatTable'
25+
import { fetchFile, openStream } from '../../../lib/util/fetch-file'
2826
import { MetaTable, isMetaTable } from '../../../lib/model/resource'
2927

28+
import { isResourceReady } from '../../kubectl/status'
29+
import { emitKubectlConfigChangeEvent } from '../../kubectl/config'
30+
import { KubeOptions, withKubeconfigFrom } from '../../kubectl/options'
31+
3032
const debug = Debug('plugin-kubectl/client/direct/watch')
3133

3234
/** The apiServer will emit a stream of these messages */
@@ -296,6 +298,14 @@ export class SingleKindDirectWatcher extends DirectWatcher implements Abortable,
296298
this.pusher.offline(row.rowKey)
297299
}
298300

301+
if (this.kind === 'Namespace') {
302+
if (update.type === 'ADDED') {
303+
emitKubectlConfigChangeEvent('CreateOrDeleteNamespace', row.name)
304+
} else if (update.type === 'DELETED') {
305+
emitKubectlConfigChangeEvent('CreateOrDeleteNamespace', row.name)
306+
}
307+
}
308+
299309
this.checkIfReady(row, idx, update)
300310
})
301311

plugins/plugin-kubectl/src/controller/client/proxy/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,13 @@ function initProxyState() {
170170
const myProxyState = startProxy()
171171
currentProxyState = myProxyState
172172

173-
myProxyState.then(state => onKubectlConfigChangeEvents(state.onQuitHandler))
173+
myProxyState.then(state =>
174+
onKubectlConfigChangeEvents(type => {
175+
if (type === 'SetNamespaceOrContext') {
176+
state.onQuitHandler()
177+
}
178+
})
179+
)
174180
}
175181

176182
return currentProxyState

plugins/plugin-kubectl/src/controller/kubectl/config.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ import { Arguments, Registrar, eventChannelUnsafe } from '@kui-shell/core'
1818

1919
import flags from './flags'
2020
import { doExecWithPty } from './exec'
21-
import { KubeOptions } from './options'
2221
import commandPrefix from '../command-prefix'
22+
import { KubeOptions, getNamespaceAsExpressed } from './options'
2323

2424
const kubectlConfigChangeChannel = '/kubectl/config/change'
2525
type Change = 'NewContext' | 'AlteredContext'
26-
type Handler = (args: Arguments<KubeOptions>) => void
26+
type Handler = (type: 'SetNamespaceOrContext' | 'CreateOrDeleteNamespace', namespace?: string) => void
2727

2828
const mutators = [
2929
'delete-cluster',
@@ -37,8 +37,15 @@ const mutators = [
3737
'use-context'
3838
]
3939

40-
export function emitKubectlConfigChangeEvent(args: Arguments<KubeOptions>) {
41-
eventChannelUnsafe.emit(kubectlConfigChangeChannel, args)
40+
export function emitKubectlConfigChangeEvent(
41+
type: 'SetNamespaceOrContext' | 'CreateOrDeleteNamespace',
42+
namespace?: string
43+
) {
44+
try {
45+
eventChannelUnsafe.emit(kubectlConfigChangeChannel, type, namespace)
46+
} catch (err) {
47+
console.error('Error in onKubectlConfigChangeEvent handler', err)
48+
}
4249
}
4350

4451
export function onKubectlConfigChangeEvents(handler: Handler) {
@@ -67,7 +74,7 @@ async function doConfig(args: Arguments<KubeOptions>) {
6774
: undefined
6875

6976
if (change) {
70-
emitKubectlConfigChangeEvent(args)
77+
emitKubectlConfigChangeEvent('SetNamespaceOrContext', getNamespaceAsExpressed(args))
7178
}
7279

7380
return response

plugins/plugin-kubectl/src/controller/kubectl/contexts.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { REPL as REPLType, Table, Row, RawResponse, Arguments, Registrar, UsageM
1818

1919
import flags from './flags'
2020
import apiVersion from './apiVersion'
21-
import { KubeOptions } from './options'
2221
import { doExecWithTable } from './exec'
2322
import commandPrefix from '../command-prefix'
2423
import { KubeContext } from '../../lib/model/resource'
@@ -74,11 +73,14 @@ export async function getCurrentContextName({ REPL }: { REPL: REPLType }) {
7473

7574
/** Extract the namespace from the current context */
7675
let currentDefaultNamespaceCache: string
77-
onKubectlConfigChangeEvents(({ command, parsedOptions }: Pick<Arguments<KubeOptions>, 'command' | 'parsedOptions'>) => {
78-
if (/k(ubectl?)\s+config\s+set-context/.test(command) && parsedOptions.namespace) {
79-
currentDefaultNamespaceCache = parsedOptions.namespace
80-
} else {
81-
currentDefaultNamespaceCache = undefined
76+
onKubectlConfigChangeEvents((type, namespace) => {
77+
if (type === 'SetNamespaceOrContext') {
78+
if (typeof namespace === 'string') {
79+
currentDefaultNamespaceCache = namespace
80+
} else {
81+
// invalidate cache
82+
currentDefaultNamespaceCache = undefined
83+
}
8284
}
8385
})
8486
export async function getCurrentDefaultNamespace({ REPL }: { REPL: REPLType }) {

plugins/plugin-kubectl/src/test/k8s/contexts.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,7 @@ import * as assert from 'assert'
2222

2323
import { expandHomeDir } from '@kui-shell/core'
2424
import { Common, CLI, ReplExpect, SidecarExpect, Selectors } from '@kui-shell/test'
25-
import {
26-
waitForGreen,
27-
waitForRed,
28-
createNS,
29-
waitTillNone,
30-
defaultModeForGet
31-
} from '@kui-shell/plugin-kubectl/tests/lib/k8s/utils'
25+
import { waitForGreen, createNS, defaultModeForGet } from '@kui-shell/plugin-kubectl/tests/lib/k8s/utils'
3226

3327
const synonyms = ['kubectl']
3428

@@ -49,19 +43,9 @@ Common.localDescribe('kubectl context switching', function(this: Common.ISuite)
4943

5044
synonyms.forEach(kubectl => {
5145
/** delete the given namespace */
52-
const deleteIt = (name: string, errOk = false) => {
46+
const deleteIt = (name: string, context: string, kubeconfig: string) => {
5347
it(`should delete the namespace ${name} via ${kubectl}`, () => {
54-
return CLI.command(`${kubectl} delete namespace ${name}`, this.app)
55-
.then(
56-
ReplExpect.okWithCustom<string>({ selector: Selectors.BY_NAME(name), errOk })
57-
) // FIXME
58-
.then(selector => waitForRed(this.app, selector))
59-
.then(() => waitTillNone('namespace', undefined, name))
60-
.catch(err => {
61-
if (!errOk) {
62-
return Common.oops(this, true)(err)
63-
}
64-
})
48+
execSync(`kubectl delete namespace ${name} --context ${context} --kubeconfig ${kubeconfig}`)
6549
})
6650
}
6751

@@ -259,6 +243,6 @@ Common.localDescribe('kubectl context switching', function(this: Common.ISuite)
259243
getPodInSidecar('nginx', ns, `--kubeconfig ${initialKubeConfig}`)
260244
switchToContextByCommand('holla')
261245
listPodsAndExpectOne('nginx')
262-
deleteIt(ns)
246+
deleteIt(ns, initialContext, initialKubeConfig)
263247
})
264248
})

0 commit comments

Comments
 (0)