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

Commit 80ea40f

Browse files
committed
feat: add Show Owner Reference button for kube resources
Fixes #4106
1 parent f55b8c6 commit 80ea40f

File tree

8 files changed

+133
-17
lines changed

8 files changed

+133
-17
lines changed

packages/test/src/api/selectors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export const LIST_RESULT_BY_N_FOR_NAME = (N: number, name: string) => `${LIST_RE
9090
export const TABLE_HEADER_CELL = (cellKey: string) => `thead tr th[data-key="${cellKey}"]`
9191
export const TABLE_CELL = (rowKey: string, cellKey: string) => `tbody [data-name="${rowKey}"] [data-key="${cellKey}"]`
9292
export const BY_NAME = (name: string) => `tbody [data-name="${name}"]`
93+
export const LIST_RESULT_FIRST = 'tbody tr:first-child .clickable'
9394
export const LIST_RESULT_BY_N_AND_NAME = (N: number, name: string) =>
9495
`${LIST_RESULT_BY_N_FOR_NAME(N, name)} .entity-name`
9596
export const OK_N = (N: number) => `${PROMPT_BLOCK_N(N)} .repl-output .ok`

plugins/plugin-client-common/web/css/static/Tooltip.scss

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
[kui-theme-style] {
2-
.bx--tooltip,
32
.bx--tooltip__trigger {
4-
&,
5-
&.bx--tooltip--bottom .bx--assistive-text,
6-
.bx--tooltip__caret {
7-
background-color: var(--color-sidecar-toolbar-background);
8-
color: var(--color-sidecar-toolbar-foreground);
9-
}
10-
113
&.bx--tooltip--bottom.bx--tooltip--align-end {
4+
.bx--assistive-text {
5+
background-color: var(--color-sidecar-toolbar-background);
6+
color: var(--color-sidecar-toolbar-foreground);
7+
}
8+
129
&:before {
1310
border-bottom-color: var(--color-sidecar-toolbar-background);
1411
}

plugins/plugin-client-common/web/css/static/ui.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ body.still-loading .repl {
203203
}
204204

205205
/* generic */
206+
.kui--rotate-180 {
207+
transform: rotate(180deg);
208+
}
206209
.full-height {
207210
flex: 1;
208211
display: flex;

plugins/plugin-kubectl/i18n/resources_en_US.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"keditUsageRequiredDocs": "A kubernetes resource file or kind",
2121
"keditUsageDocs": "Edit a resource definition file",
2222
"keditUsageOptionalDocs": "a resource within the file to view",
23+
"Show Owner Reference": "Show Owner Reference",
2324
"Show Involved Object": "Show Involved Object",
2425
"Show Resources": "Show Resources",
2526
"conditions": "Conditions",

plugins/plugin-kubectl/src/lib/model/resource.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,21 +63,42 @@ export class DefaultKubeStatus implements KubeStatus {
6363
public message = undefined
6464
}
6565

66-
interface OwnerReferences {
67-
kind: string
68-
name: string
66+
interface WithOwnerReferences {
67+
ownerReferences: {
68+
apiVersion: string
69+
kind: string
70+
name: string
71+
}[]
6972
}
7073

71-
export interface KubeMetadata {
74+
export type KubeMetadata = Partial<WithOwnerReferences> & {
7275
name: string
7376
namespace?: string
7477
labels?: { [key: string]: string }
7578
annotations?: object
7679
creationTimestamp?: string
7780
generation?: string
7881
generateName?: string
79-
ownerReferences?: OwnerReferences[]
8082
}
83+
84+
export type KubeResourceWithOwnerReferences = KubeResource<{}, KubeMetadata & Required<WithOwnerReferences>>
85+
86+
export function hasSingleOwnerReference(resource: KubeResource): resource is KubeResourceWithOwnerReferences {
87+
if (!resource.metadata) {
88+
return false
89+
}
90+
91+
const { ownerReferences } = resource.metadata as WithOwnerReferences
92+
return (
93+
ownerReferences &&
94+
Array.isArray(ownerReferences) &&
95+
ownerReferences.length === 1 &&
96+
typeof ownerReferences[0].apiVersion === 'string' &&
97+
typeof ownerReferences[0].kind === 'string' &&
98+
typeof ownerReferences[0].name === 'string'
99+
)
100+
}
101+
81102
export class DefaultKubeMetadata implements KubeMetadata {
82103
public kind = undefined
83104

@@ -109,11 +130,11 @@ export function hasRawData(resource: ResourceWithMetadata) {
109130
* The basic Kubernetes resource
110131
*
111132
*/
112-
export type KubeResource<Status = KubeStatus> = ResourceWithMetadata &
133+
export type KubeResource<Status = KubeStatus, Metadata = KubeMetadata> = ResourceWithMetadata &
113134
WithRawData & {
114135
apiVersion: string
115136
kind: string
116-
metadata?: KubeMetadata
137+
metadata?: Metadata
117138
status?: Status
118139
spec?: any // eslint-disable-line @typescript-eslint/no-explicit-any
119140

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2020 IBM Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as React from 'react'
18+
import { JumpLink16 as ShowOwnerIcon } from '@carbon/icons-react'
19+
import { i18n, encodeComponent, Tab, ModeRegistration } from '@kui-shell/core'
20+
21+
import { fqn } from '../../../controller/kubectl/fqn'
22+
import { hasSingleOwnerReference, KubeResourceWithOwnerReferences } from '../../model/resource'
23+
import { getCommandFromArgs } from '../../../lib/util/util'
24+
25+
const strings = i18n('plugin-kubectl')
26+
27+
/**
28+
* Extract the events
29+
*
30+
*/
31+
function command(
32+
tab: Tab,
33+
{
34+
metadata: {
35+
namespace,
36+
ownerReferences: [{ apiVersion, kind, name }]
37+
}
38+
}: KubeResourceWithOwnerReferences,
39+
args: { argvNoOptions: string[] }
40+
) {
41+
return `${getCommandFromArgs(args)} get ${fqn(
42+
apiVersion,
43+
encodeComponent(kind),
44+
encodeComponent(name),
45+
encodeComponent(namespace || 'default')
46+
)} -o yaml`
47+
}
48+
49+
/**
50+
* Add an Involved Object mode button
51+
*
52+
*/
53+
const mode: ModeRegistration<KubeResourceWithOwnerReferences> = {
54+
when: hasSingleOwnerReference,
55+
mode: {
56+
mode: 'ownerReference',
57+
kind: 'drilldown',
58+
label: strings('Show Owner Reference'),
59+
icon: <ShowOwnerIcon className="kui--rotate-180" />,
60+
command
61+
}
62+
}
63+
64+
export default mode

plugins/plugin-kubectl/src/non-headless-preload.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import namespaceSummaryMode from './lib/view/modes/namespace-summary'
2626
import conditionsMode from './lib/view/modes/conditions'
2727
import containersMode from './lib/view/modes/containers'
2828
import lastAppliedMode from './lib/view/modes/last-applied'
29-
import deleteResourceMode from './lib/view/modes/DeleteButton'
29+
import showOwnerButton from './lib/view/modes/ShowOwnerButton'
30+
import deleteResourceButton from './lib/view/modes/DeleteButton'
3031
import involvedObjectMode from './lib/view/modes/involved-object'
3132
import showCRDResources from './lib/view/modes/show-crd-managed-resources'
3233
import { eventsMode, eventsBadge } from './lib/view/modes/events'
@@ -47,7 +48,8 @@ export default async (registrar: PreloadRegistrar) => {
4748
containersMode,
4849
lastAppliedMode,
4950
showCRDResources,
50-
deleteResourceMode,
51+
showOwnerButton,
52+
deleteResourceButton,
5153
involvedObjectMode
5254
)
5355

plugins/plugin-kubectl/src/test/k8s1/deployment.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,32 @@ describe(`kubectl deployment ${process.env.MOCHA_RUN_TARGET || ''}`, function(th
105105
})
106106
}
107107

108+
const getPods = () => {
109+
it('should list pods in deployment, then navigate using Show Owner Reference button', async () => {
110+
try {
111+
const selector = await CLI.command(`kubectl get pod -lapp=drone-app ${inNamespace}`, this.app).then(
112+
ReplExpect.okWithCustom({ selector: Selectors.LIST_RESULT_FIRST })
113+
)
114+
115+
await this.app.client.click(selector)
116+
117+
await SidecarExpect.open(this.app)
118+
.then(SidecarExpect.showing('myapp', undefined, undefined, ns))
119+
.then(SidecarExpect.button({ mode: 'ownerReference', label: 'Show Owner Reference' }))
120+
121+
await this.app.client.click(Selectors.SIDECAR_MODE_BUTTON('ownerReference'))
122+
await SidecarExpect.open(this.app)
123+
.then(SidecarExpect.kind('ReplicaSet'))
124+
.then(SidecarExpect.button({ mode: 'ownerReference', label: 'Show Owner Reference' }))
125+
126+
await this.app.client.click(Selectors.SIDECAR_MODE_BUTTON('ownerReference'))
127+
await SidecarExpect.open(this.app).then(SidecarExpect.kind('Deployment'))
128+
} catch (err) {
129+
return Common.oops(this, true)(err)
130+
}
131+
})
132+
}
133+
108134
const deleteItByName = () => {
109135
it('should delete the deployment by name', () => {
110136
return CLI.command(`kubectl delete deployment myapp ${inNamespace}`, this.app)
@@ -134,6 +160,7 @@ describe(`kubectl deployment ${process.env.MOCHA_RUN_TARGET || ''}`, function(th
134160

135161
createIt()
136162
listIt()
163+
getPods()
137164
deleteItByName()
138165

139166
createIt()

0 commit comments

Comments
 (0)