Skip to content

Commit

Permalink
Fixes
Browse files Browse the repository at this point in the history
Signed-off-by: Anatoliy Bazko <abazko@redhat.com>
  • Loading branch information
tolusha committed Jan 22, 2020
1 parent 2bf831f commit e3be4e3
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 99 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ _See code: [src/commands/workspace/list.ts](/~https://github.com/che-incubator/che

## `chectl workspace:logs`

Collect workspace logs
Collect workspace(s) logs

```
USAGE
Expand All @@ -432,12 +432,11 @@ OPTIONS
-d, --directory=directory Directory to store logs into
-h, --help show CLI help
-n, --chenamespace=chenamespace [default: che] Kubernetes namespace where Che server is supposed to be
deployed
-w, --workspace=workspace Target workspace. Can be omitted if only one Workspace is running
-n, --namespace=namespace (required) The namespace where workspace is located. Can be found in
workspace configuration 'attributes.infrastructureNamespace' field.
--follow Follow workspace creation logs
-w, --workspace=workspace (required) Target workspace id. Can be found in workspace configuration 'id'
field.
--listr-renderer=default|silent|verbose [default: default] Listr renderer
```
Expand Down
103 changes: 50 additions & 53 deletions src/api/che.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as commandExists from 'command-exists'
import * as fs from 'fs-extra'
import * as yaml from 'js-yaml'
import * as path from 'path'
import { setInterval } from 'timers'

import { OpenShiftHelper } from '../api/openshift'

Expand Down Expand Up @@ -305,78 +306,69 @@ export class CheHelper {
}

/**
* Reads logs from all new pods that starting to run.
* It basically lists existed pods and starts following logs from a new ones.
* Finds workspace pods and reads logs from it.
*/
async readAllNewPodLog(namespace: string, directory: string): Promise<void> {
const processedPods = new Set<string>((await this.kube.listNamespacedPod(namespace)).items.map(pod => pod.metadata!.name!))
setInterval(async () => this.readPodLogBySelectorIgnoreProcessed(namespace, undefined, directory, processedPods, true), CheHelper.POLL_INTERVAL)
}
async readWorkspacePodLog(namespace: string, workspaceId: string, directory: string): Promise<boolean> {
const podLabelSelector = `che.workspace_id=${workspaceId}`

/**
* Reads logs from pods that match a given selector.
*/
async readPodLogBySelector(namespace: string, selector: string | undefined, directory: string, follow: boolean): Promise<void> {
const processedPods = new Set<string>()
if (follow) {
setInterval(async () => this.readPodLogBySelectorIgnoreProcessed(namespace, selector, directory, processedPods, follow), CheHelper.POLL_INTERVAL)
} else {
await this.readPodLogBySelectorIgnoreProcessed(namespace, selector, directory, processedPods, follow)
}
}
let workspaceIsRun = false

/**
* Reads logs from pods that match a given selector with exception of already processed ones.
* Once log is read the pod is marked as processed.
*/
async readPodLogBySelectorIgnoreProcessed(namespace: string, selector: string | undefined, directory: string, processedPods: Set<string>, follow: boolean): Promise<void> {
const pods = await this.kube.listNamespacedPod(namespace, selector)
const pods = await this.kube.listNamespacedPod(namespace, undefined, podLabelSelector)
if (pods.items.length) {
workspaceIsRun = true
}

for (const pod of pods.items) {
const podName = pod.metadata!.name!

if (!processedPods.has(podName)) {
processedPods.add(podName)
await this.readPodLogByName(namespace, podName, directory, follow)
for (const containerStatus of pod.status!.containerStatuses!) {
workspaceIsRun = workspaceIsRun && !!containerStatus.state && !!containerStatus.state.running
}
}

const follow = !workspaceIsRun
await this.readPodLog(namespace, podLabelSelector, directory, follow)
await this.readNamespaceEvents(namespace, directory, follow)

return workspaceIsRun
}

/**
* Reads log from pod that matches a given name.
* Reads logs from pods that match a given selector.
*/
async readPodLogByName(namespace: string, podName: string, directory: string, follow: boolean): Promise<void> {
const processedContainers = new Set<string>()
async readPodLog(namespace: string, podLabelSelector: string | undefined, directory: string, follow: boolean): Promise<void> {
const processedContainers = new Map<string, Set<string>>()
if (follow) {
setInterval(async () => this.readPodLogByNameIgnoreProcessed(namespace, podName, directory, processedContainers, follow), 100)
setInterval(async () => this.readContainerLogIgnoreProcessed(namespace, podLabelSelector, directory, processedContainers, follow), CheHelper.POLL_INTERVAL)
} else {
await this.readPodLogByNameIgnoreProcessed(namespace, podName, directory, processedContainers, follow)
await this.readContainerLogIgnoreProcessed(namespace, podLabelSelector, directory, processedContainers, follow)
}
}

/**
* Reads log from all containers in the pod with exception of already processed ones.
* Once log is read the container is marked as processed.
* Reads containers logs inside pod that match a given selector.
*/
async readPodLogByNameIgnoreProcessed(namespace: string, podName: string, directory: string, processedContainers: Set<string>, follow: boolean): Promise<void> {
const pod = await this.kube.readNamespacedPod(podName, namespace)
if (!pod) {
return
}
async readContainerLogIgnoreProcessed(namespace: string, podLabelSelector: string | undefined, directory: string, processedContainers: Map<string, Set<string>>, follow: boolean): Promise<void> {
const pods = await this.kube.listNamespacedPod(namespace, undefined, podLabelSelector)

if (!pod.status || !pod.status.containerStatuses) {
return
}
for (const pod of pods.items) {
const podName = pod.metadata!.name!
if (!processedContainers.has(podName)) {
processedContainers.set(podName, new Set<string>())
}

for (const container of pod.status.containerStatuses) {
if (!container.state || !container.state.running) {
continue
if (!pod.status || !pod.status.containerStatuses) {
return
}

const containerName = container.name
if (!processedContainers.has(containerName)) {
processedContainers.add(containerName)
await this.readContainerLog(namespace, podName, containerName, directory, follow)
for (const containerStatus of pod.status.containerStatuses) {
if (!containerStatus.state || !containerStatus.state.running) {
continue
}

const containerName = containerStatus.name
if (!processedContainers.get(podName)!.has(containerName)) {
processedContainers.get(podName)!.add(containerName)
await this.readContainerLog(namespace, podName, containerName, directory, follow)
}
}
}
}
Expand All @@ -388,9 +380,13 @@ export class CheHelper {
const fileName = path.resolve(directory, namespace, 'events.txt')
fs.ensureFileSync(fileName)

const cliTool = (commandExists.sync('kubectl') && 'kubectl') || (commandExists.sync('oc') && 'oc')
if (cliTool) {
cp.exec(`${cliTool} get events -n ${namespace} ${follow ? '--watch' : ''} >> ${fileName}`)
const cli = (commandExists.sync('kubectl') && 'kubectl') || (commandExists.sync('oc') && 'oc')
if (cli) {
const command = 'get events'
const namespaceParam = `-n ${namespace}`
const watchParam = follow && '--watch' || ''

cp.exec(`${cli} ${command} ${namespaceParam} ${watchParam} >> ${fileName}`)
} else {
throw new Error('No events are collected. \'kubectl\' or \'oc\' is required to perform the task.')
}
Expand All @@ -402,6 +398,7 @@ export class CheHelper {
private async readContainerLog(namespace: string, podName: string, containerName: string, directory: string, follow: boolean): Promise<void> {
const fileName = path.resolve(directory, namespace, podName, `${containerName}.log`)
fs.ensureFileSync(fileName)

return this.kube.readNamespacedPodLog(podName, namespace, containerName, fileName, follow)
}

Expand Down
22 changes: 19 additions & 3 deletions src/api/kube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/

import { ApiextensionsV1beta1Api, ApisApi, AppsV1Api, CoreV1Api, CustomObjectsApi, ExtensionsV1beta1Api, KubeConfig, Log, RbacAuthorizationV1Api, V1beta1CustomResourceDefinition, V1beta1IngressList, V1ClusterRole, V1ClusterRoleBinding, V1ConfigMap, V1ConfigMapEnvSource, V1Container, V1DeleteOptions, V1Deployment, V1DeploymentList, V1DeploymentSpec, V1EnvFromSource, V1LabelSelector, V1ObjectMeta, V1PersistentVolumeClaimList, V1Pod, V1PodList, V1PodSpec, V1PodTemplateSpec, V1Role, V1RoleBinding, V1RoleRef, V1Secret, V1ServiceAccount, V1ServiceList, V1Subject } from '@kubernetes/client-node'
import { ApiextensionsV1beta1Api, ApisApi, AppsV1Api, CoreV1Api, CustomObjectsApi, ExtensionsV1beta1Api, KubeConfig, Log, RbacAuthorizationV1Api, V1beta1CustomResourceDefinition, V1beta1IngressList, V1ClusterRole, V1ClusterRoleBinding, V1ConfigMap, V1ConfigMapEnvSource, V1Container, V1DeleteOptions, V1Deployment, V1DeploymentList, V1DeploymentSpec, V1EnvFromSource, V1LabelSelector, V1NamespaceList, V1ObjectMeta, V1PersistentVolumeClaimList, V1Pod, V1PodList, V1PodSpec, V1PodTemplateSpec, V1Role, V1RoleBinding, V1RoleRef, V1Secret, V1ServiceAccount, V1ServiceList, V1Subject } from '@kubernetes/client-node'
import { Context } from '@kubernetes/client-node/dist/config_types'
import axios from 'axios'
import { cli } from 'cli-ux'
Expand Down Expand Up @@ -1258,10 +1258,26 @@ export class KubeHelper {
throw new Error('ERR_LIST_PVCS')
}

async listNamespacedPod(namespace: string, selector?: string): Promise<V1PodList> {
async listNamespace(): Promise<V1NamespaceList> {
const k8sApi = this.kc.makeApiClient(CoreV1Api)
try {
const res = await k8sApi.listNamespacedPod(namespace, true, undefined, undefined, undefined, selector)
const res = await k8sApi.listNamespace()
if (res && res.body) {
return res.body
} else {
return {
items: []
}
}
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async listNamespacedPod(namespace: string, fieldSelector?: string, labelSelector?: string): Promise<V1PodList> {
const k8sApi = this.kc.makeApiClient(CoreV1Api)
try {
const res = await k8sApi.listNamespacedPod(namespace, true, undefined, undefined, fieldSelector, labelSelector)
if (res && res.body) {
return res.body
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/server/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default class Logs extends Command {
tasks.add(k8sTasks.testApiTasks(flags, this))
tasks.add(cheTasks.verifyCheNamespaceExistsTask(flags, this))
tasks.add(cheTasks.serverLogsTasks(flags, false))
tasks.add(cheTasks.namespaceEventsTask(flags, this, false))
tasks.add(cheTasks.namespaceEventsTask(flags.chenamespace, this, false))

try {
this.log(`Eclipse Che logs will be available in '${ctx.directory}'`)
Expand Down
2 changes: 1 addition & 1 deletion src/commands/server/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export default class Start extends Command {

const eventTasks = new Listr([{
title: 'Start following events',
task: () => new Listr(cheTasks.namespaceEventsTask(flags, this, true))
task: () => new Listr(cheTasks.namespaceEventsTask(flags.chenamespace, this, true))
}], listrOptions)

try {
Expand Down
32 changes: 14 additions & 18 deletions src/commands/workspace/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,25 @@ import * as notifier from 'node-notifier'
import * as os from 'os'
import * as path from 'path'

import { cheNamespace, listrRenderer } from '../../common-flags'
import { listrRenderer } from '../../common-flags'
import { CheTasks } from '../../tasks/che'
import { K8sTasks } from '../../tasks/platforms/k8s'

export default class Logs extends Command {
static description = 'Collect workspace logs'
static description = 'Collect workspace(s) logs'

static flags = {
help: flags.help({ char: 'h' }),
chenamespace: cheNamespace,
'listr-renderer': listrRenderer,
follow: flags.boolean({
description: 'Follow workspace creation logs',
default: false
}),
workspace: string({
char: 'w',
description: 'Target workspace. Can be omitted if only one Workspace is running'
description: 'Target workspace id. Can be found in workspace configuration \'id\' field.',
required: true
}),
namespace: string({
char: 'n',
description: 'The namespace where workspace is located. Can be found in workspace configuration \'attributes.infrastructureNamespace\' field.',
required: true
}),
directory: string({
char: 'd',
Expand All @@ -50,21 +51,16 @@ export default class Logs extends Command {

const tasks = new Listr([], { renderer: flags['listr-renderer'] as any })
tasks.add(k8sTasks.testApiTasks(flags, this))
tasks.add(cheTasks.verifyCheNamespaceExistsTask(flags, this))
if (!flags.follow) {
tasks.add(cheTasks.verifyWorkspaceRunTask(flags, this))
}
tasks.add(cheTasks.workspaceLogsTasks(flags))
tasks.add(cheTasks.namespaceEventsTask(flags, this, flags.follow))
tasks.add(cheTasks.workspaceLogsTasks(flags.namespace, flags.workspace))

try {
this.log(`Eclipse Che logs will be available in '${ctx.directory}'`)
await tasks.run(ctx)

if (flags.follow) {
this.log('chectl is still running and keeps collecting logs.')
} else {
this.log('Command workspace:logs has completed successfully.')
if (!ctx['workspace-run']) {
this.log(`Workspace ${flags.workspace} probably hasn't been started yet.`)
this.log('The program will keep running and collecting logs...')
this.log('Terminate the program when all logs are gathered...')
}
} catch (error) {
this.error(error)
Expand Down
26 changes: 11 additions & 15 deletions src/tasks/che.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,63 +496,59 @@ export class CheTasks {
{
title: `${follow ? 'Start following' : 'Read'} Che logs`,
task: async (ctx: any, task: any) => {
await this.che.readPodLogBySelector(flags.chenamespace, this.cheSelector, ctx.directory, follow)
await this.che.readPodLog(flags.chenamespace, this.cheSelector, ctx.directory, follow)
task.title = await `${task.title}...done`
}
},
{
title: `${follow ? 'Start following' : 'Read'} Postgres logs`,
task: async (ctx: any, task: any) => {
await this.che.readPodLogBySelector(flags.chenamespace, this.postgresSelector, ctx.directory, follow)
await this.che.readPodLog(flags.chenamespace, this.postgresSelector, ctx.directory, follow)
task.title = await `${task.title}...done`
}
},
{
title: `${follow ? 'Start following' : 'Read'} Keycloak logs`,
task: async (ctx: any, task: any) => {
await this.che.readPodLogBySelector(flags.chenamespace, this.keycloakSelector, ctx.directory, follow)
await this.che.readPodLog(flags.chenamespace, this.keycloakSelector, ctx.directory, follow)
task.title = await `${task.title}...done`
}
},
{
title: `${follow ? 'Start following' : 'Read'} Plugin registry logs`,
task: async (ctx: any, task: any) => {
await this.che.readPodLogBySelector(flags.chenamespace, this.pluginRegistrySelector, ctx.directory, follow)
await this.che.readPodLog(flags.chenamespace, this.pluginRegistrySelector, ctx.directory, follow)
task.title = await `${task.title}...done`
}
},
{
title: `${follow ? 'Start following' : 'Read'} Devfile registry logs`,
task: async (ctx: any, task: any) => {
await this.che.readPodLogBySelector(flags.chenamespace, this.devfileRegistrySelector, ctx.directory, follow)
await this.che.readPodLog(flags.chenamespace, this.devfileRegistrySelector, ctx.directory, follow)
task.title = await `${task.title}...done`
}
}
]
}

workspaceLogsTasks(flags: any): ReadonlyArray<Listr.ListrTask> {
workspaceLogsTasks(namespace: string, workspaceId: string): ReadonlyArray<Listr.ListrTask> {
return [
{
title: `${flags.follow ? 'Start following' : 'Read'} workspace logs`,
title: 'Read workspace logs',
task: async (ctx: any, task: any) => {
if (flags.follow) {
await this.che.readAllNewPodLog(flags.chenamespace, ctx.directory)
} else {
await this.che.readPodLogByName(flags.chenamespace, ctx.pod, ctx.directory, false)
}
task.title = await `${task.title}...done`
ctx['workspace-run'] = await this.che.readWorkspacePodLog(namespace, workspaceId, ctx.directory)
task.title = `${task.title}...done`
}
}
]
}

namespaceEventsTask(flags: any, command: Command, follow: boolean): ReadonlyArray<Listr.ListrTask> {
namespaceEventsTask(namespace: string, command: Command, follow: boolean): ReadonlyArray<Listr.ListrTask> {
return [
{
title: `${follow ? 'Start following' : 'Read'} namespace events`,
task: async (ctx: any, task: any) => {
await this.che.readNamespaceEvents(flags.chenamespace, ctx.directory, follow).catch(e => command.error(e.message))
await this.che.readNamespaceEvents(namespace, ctx.directory, follow).catch(e => command.error(e.message))
task.title = await `${task.title}...done`
}
}
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1677,11 +1677,11 @@ ecc-jsbn@~0.1.1:

"eclipse-che-operator@git://github.com/eclipse/che-operator#master":
version "0.0.0"
resolved "git://github.com/eclipse/che-operator#18758810310ee6b163cf59719dcffb9e5b33ea28"
resolved "git://github.com/eclipse/che-operator#9332233235843963488193b9f69985372f87d8e6"

"eclipse-che@git://github.com/eclipse/che#master":
version "0.0.0"
resolved "git://github.com/eclipse/che#5ced53ef23269513de294be9689d91c4117fce95"
resolved "git://github.com/eclipse/che#a5f510162c8e5cf7d886246278193c1289a28b1f"

editorconfig@^0.15.0:
version "0.15.3"
Expand Down

0 comments on commit e3be4e3

Please sign in to comment.