Skip to content

Commit

Permalink
feat(plugins/plugin-kubectl): Show Previous option for Logs tab
Browse files Browse the repository at this point in the history
Fixes #4864
  • Loading branch information
starpit committed Jun 12, 2020
1 parent 990a6fa commit 39d8d74
Show file tree
Hide file tree
Showing 12 changed files with 308 additions and 94 deletions.
21 changes: 21 additions & 0 deletions plugins/plugin-kubectl/i18n/logs_en_US.json
Expand Up @@ -8,16 +8,37 @@
"Details": "Details",
"No log data": "No log data",
"No matching pods": "No matching pods",

"Logs are live streaming. Showing all containers.": "Logs are live **streaming**. Showing **all containers**.",
"Logs are live streaming. Showing all containers. Showing previous instance.": "Logs are live **streaming**. Showing **all containers** of **previous instance**.",

"Log streaming is paused. Showing all containers.": "Log streaming is **paused**. Showing **all containers**.",
"Log streaming is paused. Showing all containers. Showing previous instance.": "Log streaming is **paused**. Showing **all containers** of **previous instance**.",

"Log streaming stopped. Showing all containers.": "Log streaming **stopped**. Showing **all containers**.",
"Log streaming stopped. Showing all containers. Showing previous instance.": "Log streaming **stopped**. Showing **all containers** of **previous instance**.",

"Log streaming stopped abnormally. Showing all containers.": "Log streaming **stopped abnormally**. Showing **all containers**.",
"Log streaming stopped abnormally. Showing all containers. Showing previous instance.": "Log streaming **stopped abnormally**. Showing **all containers** of **previous instance**.",

"Logs are live streaming. Showing container X.": "Logs are live **streaming**. Showing container **{0}**.",
"Logs are live streaming. Showing container X. Showing previous instance.": "Logs are live **streaming**. Showing container **{0}** of **previous instance**.",

"Log streaming is paused. Showing container X.": "Log streaming is **paused**. Showing container **{0}**.",
"Log streaming is paused. Showing container X. Showing previous instance.": "Log streaming is **paused**. Showing container **{0}** of **previous instance**.",

"Log streaming stopped. Showing container X.": "Log streaming **stopped**. Showing container **{0}**.",
"Log streaming stopped. Showing container X. Showing previous instance.": "Log streaming **stopped**. Showing container **{0}** of **previous instance**.",

"Log streaming stopped abnormally. Showing container X.": "Log streaming **stopped abnormally**. Showing container **{0}**.",
"Log streaming stopped abnormally. Showing container X. Showing previous instance.": "Log streaming **stopped abnormally**. Showing container **{0}** of **previous instance**.",

"Log streaming stopped.": "Log streaming **stopped**.",
"Log streaming stopped. Showing previous instance.": "Log streaming **stopped**. Showing **previous instance**.",

"Log streaming stopped abnormally.": "Log streaming **stopped abnormally**.",
"Log streaming stopped abnormally. Showing previous instance.": "Log streaming **stopped abnormally**. Showing **previous instance**.",

"Pause Streaming": "Pause Streaming",
"Resume Streaming": "Resume Streaming",
"Retry": "Retry",
Expand Down
15 changes: 10 additions & 5 deletions plugins/plugin-kubectl/logs/src/controller/kubectl/logs.ts
Expand Up @@ -21,7 +21,8 @@ import {
KubeOptions,
doExecWithPty,
doExecWithStdout,
defaultFlags as flags,
defaultFlags,
flags,
getLabel,
getTransformer,
getCommandFromArgs,
Expand All @@ -40,12 +41,16 @@ import commandPrefix from '../command-prefix'
const strings = i18n('plugin-kubectl', 'logs')

interface LogOptions extends KubeOptions {
f: string
follow: string
// f: boolean
follow: boolean
p: boolean
previous: boolean
tail: number
}

/** for command registration, which of those options is a boolean? */
const booleans = ['p', 'previous', 'f', 'follow']

/**
* Send the request to a PTY for deeper handling, then (possibly) add
* some ANSI control codes for coloring.
Expand Down Expand Up @@ -166,10 +171,10 @@ function viewTransformer(defaultMode: string) {
}

export const doLogs = getOrPty('logs')
export const logsFlags = Object.assign({}, flags, { viewTransformer: viewTransformer('logs') })
export const logsFlags = Object.assign({}, flags(booleans), { viewTransformer: viewTransformer('logs') })

export const doExec = getOrPty('exec')
export const execFlags = Object.assign({}, flags, { viewTransformer: viewTransformer('terminal') })
export const execFlags = Object.assign({}, defaultFlags, { viewTransformer: viewTransformer('terminal') })

export default (registrar: Registrar) => {
registrar.listen(`/${commandPrefix}/kubectl/logs`, doLogs, logsFlags)
Expand Down
117 changes: 117 additions & 0 deletions plugins/plugin-kubectl/logs/src/test/logs/helpers.ts
@@ -0,0 +1,117 @@
/*
* Copyright 2020 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { CLI, Common, ReplExpect, Selectors, SidecarExpect } from '@kui-shell/test'
import { defaultModeForGet, waitForGreen } from '@kui-shell/plugin-kubectl/tests/lib/k8s/utils'

export function create(this: Common.ISuite, ns: string, inputEncoded: string, podName: string) {
it(`should create sample pod from URL`, () => {
return CLI.command(`echo ${inputEncoded} | base64 --decode | kubectl create -f - -n ${ns}`, this.app)
.then(ReplExpect.okWithPtyOutput(podName))
.catch(Common.oops(this, true))
})
}

export function wait(this: Common.ISuite, ns: string, podName: string) {
it(`should wait for the pod to come up`, () => {
return CLI.command(`kubectl get pod ${podName} -n ${ns} -w`, this.app)
.then(ReplExpect.okWithCustom({ selector: Selectors.BY_NAME(podName) }))
.then(selector => waitForGreen(this.app, selector))
.catch(Common.oops(this, true))
})
}

export function get(this: Common.ISuite, ns: string, podName: string, wait = true) {
it(`should get pod ${podName} via kubectl then click`, async () => {
try {
const selector: string = await CLI.command(`kubectl get pods ${podName} -n ${ns}`, this.app).then(
ReplExpect.okWithCustom({ selector: Selectors.BY_NAME(podName) })
)

if (wait) {
// wait for the badge to become green
await waitForGreen(this.app, selector)
}

// now click on the table row
await this.app.client.click(`${selector} .clickable`)
await SidecarExpect.open(this.app)
.then(SidecarExpect.mode(defaultModeForGet))
.then(SidecarExpect.showing(podName))
} catch (err) {
return Common.oops(this, true)(err)
}
})
}

export async function clickRetry(this: Common.ISuite) {
await this.app.client.waitForVisible(Selectors.SIDECAR_MODE_BUTTON('retry-streaming'))
await this.app.client.click(Selectors.SIDECAR_MODE_BUTTON('retry-streaming'))
}

async function waitUntilPreviousIs(this: Common.ISuite, type: 'info' | 'warning', previous: boolean) {
const click = clickRetry.bind(this)

await this.app.client.waitUntil(async () => {
if (!this.app.client.isExisting(Selectors.SIDECAR_TOOLBAR_TEXT(type))) {
await click()
return false
} else {
return true
}
})

await SidecarExpect.toolbarText({ type, text: previous ? 'previous instance' : '' })
}

export function logs(
this: Common.ISuite,
ns: string,
podName: string,
containerName: string,
type: 'info' | 'warning',
previous: boolean
) {
const wait = waitUntilPreviousIs.bind(this, type, previous)

it(`should get logs for ${podName} with previous=${previous} via command`, async () => {
try {
await CLI.command(
`kubectl logs ${podName} -c ${containerName} -n ${ns} ${previous ? '--previous' : ''}`,
this.app
)
.then(ReplExpect.justOK)
.then(SidecarExpect.open)
.then(SidecarExpect.showing(podName, undefined, undefined, ns))
.then(SidecarExpect.mode('logs'))

await wait()
} catch (err) {
await Common.oops(this, true)
}
})
}

export function clickPrevious(this: Common.ISuite, type: 'info' | 'warning', previous: boolean) {
const wait = waitUntilPreviousIs.bind(this, type, previous)

it(`should click the previous toggle button and expect previous=${previous}`, async () => {
const mode = 'kubectl-logs-previous-toggle'
await this.app.client.waitForVisible(Selectors.SIDECAR_MODE_BUTTON(mode))
await this.app.client.click(Selectors.SIDECAR_MODE_BUTTON(mode))
await wait()
})
}
89 changes: 21 additions & 68 deletions plugins/plugin-kubectl/logs/src/test/logs/logs-dash-c.ts
Expand Up @@ -14,17 +14,17 @@
* limitations under the License.
*/

import { Common, CLI, ReplExpect, Selectors, SidecarExpect } from '@kui-shell/test'
import { Common, Selectors, SidecarExpect } from '@kui-shell/test'
import {
createNS,
allocateNS,
deleteNS,
waitForGreen,
waitForTerminalText,
defaultModeForGet,
deletePodByName
} from '@kui-shell/plugin-kubectl/tests/lib/k8s/utils'

import { create, get, clickRetry, wait } from './helpers'

import { readFileSync } from 'fs'
import { dirname, join } from 'path'
const ROOT = dirname(require.resolve('@kui-shell/plugin-kubectl/tests/package.json'))
Expand All @@ -46,67 +46,21 @@ wdescribe(`kubectl Logs tab ${process.env.MOCHA_RUN_TARGET || ''}`, function(thi
before(Common.before(this))
after(Common.after(this))

const waitForLogText = waitForTerminalText.bind(this)

const ns: string = createNS()
allocateNS(this, ns)

const waitForLogText = waitForTerminalText.bind(this)
const createPodWithoutWaiting = create.bind(this, ns)
const waitForPod = wait.bind(this, ns)
const getPodViaClick = get.bind(this, ns)
const click = clickRetry.bind(this)

const podName1 = 'kui-two-containers'
const allContainers = 'All Containers'
const containerName1 = 'nginx'
const containerName2 = 'vim'
const podName2 = 'nginx'

const createPodWithoutWaiting = (inputEncoded: string, podName: string) => {
it(`should create sample pod from URL`, () => {
return CLI.command(`echo ${inputEncoded} | base64 --decode | kubectl create -f - -n ${ns}`, this.app)
.then(ReplExpect.okWithPtyOutput(podName))
.catch(Common.oops(this, true))
})
}

const waitForPod = (podName: string) => {
it(`should wait for the pod to come up`, () => {
return CLI.command(`kubectl get pod ${podName} -n ${ns} -w`, this.app)
.then(ReplExpect.okWithCustom({ selector: Selectors.BY_NAME(podName) }))
.then(selector => waitForGreen(this.app, selector))
.catch(Common.oops(this, true))
})
}

const getPodViaClick = (podName: string, wait = true) => {
it(`should get pods via kubectl then click`, async () => {
try {
const selector: string = await CLI.command(`kubectl get pods ${podName} -n ${ns}`, this.app).then(
ReplExpect.okWithCustom({ selector: Selectors.BY_NAME(podName) })
)

if (wait) {
// wait for the badge to become green
await waitForGreen(this.app, selector)
}

// now click on the table row
await this.app.client.click(`${selector} .clickable`)
await SidecarExpect.open(this.app)
.then(SidecarExpect.mode(defaultModeForGet))
.then(SidecarExpect.showing(podName))
} catch (err) {
return Common.oops(this, true)(err)
}
})
}

/* const getPodViaYaml = (podName: string) => {
it('should get pods via kubectl get -o yaml', async () =>
CLI.command(`kubectl get pods ${podName} -n ${ns} -o yaml`, this.app)
.then(ReplExpect.justOK)
.then(SidecarExpect.open)
.then(SidecarExpect.showing(podName, undefined, undefined, ns))
.then(SidecarExpect.mode(defaultModeForGet))
.catch(Common.oops(this, true)))
} */

const testLogsContent = (show: string[], notShow?: string[]) => {
if (show) {
show.forEach(showInLog => {
Expand Down Expand Up @@ -135,6 +89,18 @@ wdescribe(`kubectl Logs tab ${process.env.MOCHA_RUN_TARGET || ''}`, function(thi
}
}

const doRetry = (showInLog: string[], toolbar: { text: string; type: string }) => {
it('should hit retry', async () => {
try {
await click()
await SidecarExpect.toolbarText({ text: toolbar.text, type: toolbar.type, exact: false })(this.app)
testLogsContent(showInLog)
} catch (err) {
return Common.oops(this, true)(err)
}
})
}

const switchToLogsTab = (showInLog: string[], toolbar: { text: string; type: string }) => {
it('should show logs tab', async () => {
try {
Expand Down Expand Up @@ -190,19 +156,6 @@ wdescribe(`kubectl Logs tab ${process.env.MOCHA_RUN_TARGET || ''}`, function(thi
})
}

const doRetry = (showInLog: string[], toolbar: { text: string; type: string }) => {
it('should hit retry', async () => {
try {
await this.app.client.waitForVisible(Selectors.SIDECAR_MODE_BUTTON('retry-streaming'))
await this.app.client.click(Selectors.SIDECAR_MODE_BUTTON('retry-streaming'))
await SidecarExpect.toolbarText({ text: toolbar.text, type: toolbar.type, exact: false })(this.app)
testLogsContent(showInLog)
} catch (err) {
return Common.oops(this, true)(err)
}
})
}

/* Here comes the test */

createPodWithoutWaiting(inputEncoded2, podName2)
Expand Down
56 changes: 56 additions & 0 deletions plugins/plugin-kubectl/logs/src/test/logs/logs-tab-previous.ts
@@ -0,0 +1,56 @@
/*
* Copyright 2020 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Common } from '@kui-shell/test'
import { createNS, allocateNS, deleteNS } from '@kui-shell/plugin-kubectl/tests/lib/k8s/utils'

import { clickPrevious, create, logs, wait } from './helpers'

import { readFileSync } from 'fs'
import { dirname, join } from 'path'
const ROOT = dirname(require.resolve('@kui-shell/plugin-kubectl/tests/package.json'))
const inputBuffer = readFileSync(join(ROOT, 'data/k8s/crashy.yaml'))
const inputEncoded = inputBuffer.toString('base64')

const podName = 'kui-crashy'
const containerName = 'crashy'

describe(`kubectl Logs previous tab ${process.env.MOCHA_RUN_TARGET || ''}`, function(this: Common.ISuite) {
before(Common.before(this))
after(Common.after(this))

const ns = createNS()

const createPodWithoutWaiting = create.bind(this, ns)
const waitForPod = wait.bind(this, ns, podName)
const kubectlLogs = logs.bind(this, ns, podName, containerName, 'warning', false)
const kubectlLogsPrevious = logs.bind(this, ns, podName, containerName, 'warning', true)
const clickPreviousToggle = clickPrevious.bind(this)

/* Here comes the test */
allocateNS(this, ns)

createPodWithoutWaiting(inputEncoded, podName)
waitForPod()

kubectlLogsPrevious()
clickPreviousToggle('warning', false) // click toggle and expect previous=false

kubectlLogs()
clickPreviousToggle('warning', true) // click toggle and expect previous=true

deleteNS(this, ns)
})

0 comments on commit 39d8d74

Please sign in to comment.