Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Wait namespace active state #1012

Merged
merged 1 commit into from
Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions src/api/che.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { che as chetypes } from '@eclipse-che/api'
import { CoreV1Api, V1Pod, Watch } from '@kubernetes/client-node'
import axios, { AxiosInstance } from 'axios'
import * as cp from 'child_process'
import { cli } from 'cli-ux'
import * as commandExists from 'command-exists'
import * as fs from 'fs-extra'
import * as https from 'https'
Expand Down Expand Up @@ -101,7 +102,7 @@ export class CheHelper {
}

async cheURL(namespace = ''): Promise<string> {
if (!await this.cheNamespaceExist(namespace)) {
if (!await this.kube.getNamespace(namespace)) {
throw new Error(`ERR_NAMESPACE_NO_EXIST - No namespace ${namespace} is found`)
}

Expand All @@ -119,7 +120,7 @@ export class CheHelper {
return this.flags['plugin-registry-url']
}
// check
if (!await this.cheNamespaceExist(namespace)) {
if (!await this.kube.getNamespace(namespace)) {
throw new Error(`ERR_NAMESPACE_NO_EXIST - No namespace ${namespace} is found`)
}

Expand Down Expand Up @@ -278,10 +279,6 @@ export class CheHelper {
throw new Error(`ERR_ROUTE_NO_EXIST - No route ${route_names} in namespace ${namespace}`)
}

async cheNamespaceExist(namespace = '') {
return this.kube.namespaceExist(namespace)
}

async createWorkspaceFromDevfile(cheApiEndpoint: string, devfilePath: string, workspaceName?: string, accessToken?: string): Promise<chetypes.workspace.Workspace> {
let devfile: string | undefined
try {
Expand Down Expand Up @@ -421,6 +418,22 @@ export class CheHelper {
() => { })
}

/**
* Wait until workspace is in 'Active` state.
*/
async waitNamespaceActive(namespaceName: string, intervalMs = 500, timeoutMs = 60000) {
const iterations = timeoutMs / intervalMs
for (let index = 0; index < iterations; index++) {
const namespace = await this.kube.getNamespace(namespaceName)
if (namespace && namespace.status && namespace.status.phase && namespace.status.phase === 'Active') {
return
}
await cli.wait(intervalMs)
}

throw new Error(`ERR_TIMEOUT: ${namespaceName} is not 'Active'.`)
}

/**
* Indicates if pod matches given labels.
*/
Expand Down
15 changes: 4 additions & 11 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, AuthorizationV1Api, BatchV1Api, CoreV1Api, CustomObjectsApi, ExtensionsV1beta1Api, ExtensionsV1beta1IngressList, KubeConfig, Log, PortForward, RbacAuthorizationV1Api, V1beta1CustomResourceDefinition, V1ClusterRole, V1ClusterRoleBinding, V1ConfigMap, V1ConfigMapEnvSource, V1Container, V1ContainerStateTerminated, V1ContainerStateWaiting, V1Deployment, V1DeploymentList, V1DeploymentSpec, V1EnvFromSource, V1Job, V1JobSpec, V1LabelSelector, V1NamespaceList, V1ObjectMeta, V1PersistentVolumeClaimList, V1Pod, V1PodCondition, V1PodList, V1PodSpec, V1PodTemplateSpec, V1PolicyRule, V1Role, V1RoleBinding, V1RoleRef, V1Secret, V1SelfSubjectAccessReview, V1SelfSubjectAccessReviewSpec, V1Service, V1ServiceAccount, V1ServiceList, V1Subject, Watch } from '@kubernetes/client-node'
import { ApiextensionsV1beta1Api, ApisApi, AppsV1Api, AuthorizationV1Api, BatchV1Api, CoreV1Api, CustomObjectsApi, ExtensionsV1beta1Api, ExtensionsV1beta1IngressList, KubeConfig, Log, PortForward, RbacAuthorizationV1Api, V1beta1CustomResourceDefinition, V1ClusterRole, V1ClusterRoleBinding, V1ConfigMap, V1ConfigMapEnvSource, V1Container, V1ContainerStateTerminated, V1ContainerStateWaiting, V1Deployment, V1DeploymentList, V1DeploymentSpec, V1EnvFromSource, V1Job, V1JobSpec, V1LabelSelector, V1Namespace, V1NamespaceList, V1ObjectMeta, V1PersistentVolumeClaimList, V1Pod, V1PodCondition, V1PodList, V1PodSpec, V1PodTemplateSpec, V1PolicyRule, V1Role, V1RoleBinding, V1RoleRef, V1Secret, V1SelfSubjectAccessReview, V1SelfSubjectAccessReviewSpec, V1Service, V1ServiceAccount, V1ServiceList, V1Subject, Watch } from '@kubernetes/client-node'
import { Cluster, Context } from '@kubernetes/client-node/dist/config_types'
import axios, { AxiosRequestConfig } from 'axios'
import { cli } from 'cli-ux'
Expand Down Expand Up @@ -581,19 +581,12 @@ export class KubeHelper {
}
}

async namespaceExist(namespace: string) {
async getNamespace(namespace: string): Promise<V1Namespace | undefined> {
const k8sApi = KubeHelper.KUBE_CONFIG.makeApiClient(CoreV1Api)
try {
const res = await k8sApi.readNamespace(namespace)
if (res && res.body &&
res.body.metadata && res.body.metadata.name
&& res.body.metadata.name === namespace) {
return true
} else {
return false
}
const { body } = await k8sApi.readNamespace(namespace)
return body
} catch {
return false
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/commands/cacert/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default class Export extends Command {
if (!await kube.hasReadPermissionsForNamespace(flags.chenamespace)) {
throw new Error(`E_PERM_DENIED - Permission denied: no read access to '${flags.chenamespace}' namespace`)
}
if (!await cheHelper.cheNamespaceExist(flags.chenamespace)) {
if (!await kube.getNamespace(flags.chenamespace)) {
throw new Error(`E_BAD_NS - Namespace ${flags.chenamespace} does not exist. Please specify it with --chenamespace flag`)
}

Expand Down
3 changes: 0 additions & 3 deletions src/commands/server/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,6 @@ export default class Deploy extends Command {
await postInstallTasks.run(ctx)
this.log(getCommandSuccessMessage())
}

await postInstallTasks.run(ctx)
this.log(getCommandSuccessMessage())
} catch (err) {
this.error(getCommandErrorMessage(err))
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/workspace/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default class Delete extends Command {
}

const kube = new KubeHelper(flags)
if (await kube.namespaceExist(infrastructureNamespace)) {
if (await kube.getNamespace(infrastructureNamespace)) {
try {
await kube.deleteNamespace(infrastructureNamespace)
cli.log(`Namespace '${infrastructureNamespace}' deleted.`)
Expand Down
9 changes: 3 additions & 6 deletions src/tasks/che.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ export class CheTasks {
return [{
title: `Delete namespace ${flags.chenamespace}`,
task: async (task: any) => {
const namespaceExist = await this.kube.namespaceExist(flags.chenamespace)
const namespaceExist = await this.kube.getNamespace(flags.chenamespace)
if (namespaceExist) {
await this.kube.deleteNamespace(flags.chenamespace)
}
Expand All @@ -508,7 +508,7 @@ export class CheTasks {
return [{
title: `Verify if namespace '${flags.chenamespace}' exists`,
task: async () => {
if (!await this.che.cheNamespaceExist(flags.chenamespace)) {
if (!await this.kube.getNamespace(flags.chenamespace)) {
command.error(`E_BAD_NS - Namespace does not exist.\nThe Kubernetes Namespace "${flags.chenamespace}" doesn't exist.`, { code: 'EBADNS' })
}
}
Expand Down Expand Up @@ -636,10 +636,7 @@ export class CheTasks {

const cheUrl = await this.che.cheURL(flags.chenamespace)
messages.push(`Users Dashboard : ${cheUrl}`)
const cheCluster = await this.kube.getCheCluster(flags.chenamespace)
if (cheCluster && cheCluster.spec.auth && cheCluster.spec.auth.updateAdminPassword) {
messages.push('Admin user login : "admin:admin". NOTE: must change after first login.')
}
messages.push('Admin user login : "admin:admin". NOTE: must change after first login.')
messages.push(OUTPUT_SEPARATOR)

const cheConfigMap = await this.kube.getConfigMap('che', flags.chenamespace)
Expand Down
2 changes: 1 addition & 1 deletion src/tasks/component-installers/cert-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class CertManagerTasks {
title: 'Check Cert Manager deployment',
task: async (ctx: any, task: any) => {
// Check only one CRD of cert-manager assuming that it is installed or not.
ctx.certManagerInstalled = await this.kubeHelper.namespaceExist(CERT_MANAGER_NAMESPACE_NAME) && await this.kubeHelper.crdExist('certificates.cert-manager.io')
ctx.certManagerInstalled = await this.kubeHelper.getNamespace(CERT_MANAGER_NAMESPACE_NAME) && await this.kubeHelper.crdExist('certificates.cert-manager.io')
if (ctx.certManagerInstalled) {
task.title = `${task.title}...already deployed`
} else {
Expand Down
8 changes: 6 additions & 2 deletions src/tasks/installers/common-tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ export function createNamespaceTask(namespaceName: string, labels: {}): Listr.Li
title: `Create Namespace (${namespaceName})`,
task: async (_ctx: any, task: any) => {
const kube = new KubeHelper()
const exist = await kube.namespaceExist(namespaceName)
if (exist) {
const che = new CheHelper({})

const namespace = await kube.getNamespace(namespaceName)
if (namespace) {
await che.waitNamespaceActive(namespaceName)
task.title = `${task.title}...It already exists.`
} else {
await kube.createNamespace(namespaceName, labels)
await che.waitNamespaceActive(namespaceName)
task.title = `${task.title}...Done.`
}
}
Expand Down
6 changes: 2 additions & 4 deletions src/tasks/installers/helm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { copy, mkdirp, remove } from 'fs-extra'
import * as Listr from 'listr'
import * as path from 'path'

import { CheHelper } from '../../api/che'
import { KubeHelper } from '../../api/kube'
import { VersionHelper } from '../../api/version'
import { CHE_ROOT_CA_SECRET_NAME, CHE_TLS_SECRET_NAME, DEFAULT_CHE_IMAGE } from '../../constants'
Expand Down Expand Up @@ -65,9 +64,8 @@ export class HelmTasks {
{
title: `Create Namespace (${flags.chenamespace})`,
task: async (_ctx: any, task: any) => {
const che = new CheHelper(flags)
const exist = await che.cheNamespaceExist(flags.chenamespace)
if (exist) {
const kube = new KubeHelper(flags)
if (await kube.getNamespace(flags.chenamespace)) {
task.title = `${task.title}...does already exist.`
} else {
await execa(`kubectl create namespace ${flags.chenamespace}`, { shell: true })
Expand Down
14 changes: 7 additions & 7 deletions test/api/che.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ let k8sApi = new CoreV1Api()
describe('Eclipse Che helper', () => {
describe('cheURL', () => {
fancy
.stub(ch, 'cheNamespaceExist', () => true)
.stub(kube, 'getNamespace', () => ({}))
.stub(kube, 'ingressExist', () => true)
.stub(kube, 'getIngressProtocol', () => 'https')
.stub(kube, 'getIngressHost', () => 'example.org')
Expand All @@ -39,13 +39,13 @@ describe('Eclipse Che helper', () => {
expect(cheURL).to.equals('https://example.org')
})
fancy
.stub(ch, 'cheNamespaceExist', () => true)
.stub(kube, 'getNamespace', () => ({}))
.stub(kube, 'ingressExist', () => false)
.do(() => ch.cheURL('che-namespace'))
.catch(err => expect(err.message).to.match(/ERR_INGRESS_NO_EXIST/))
.it('fails fetching Eclipse Che URL when ingress does not exist')
fancy
.stub(ch, 'cheNamespaceExist', () => true)
.stub(kube, 'getNamespace', () => ({}))
.stub(ctx, 'get', () => ({ isOpenShift: true }))
.stub(oc, 'routeExist', () => true)
.stub(oc, 'getRouteProtocol', () => 'https')
Expand All @@ -55,14 +55,14 @@ describe('Eclipse Che helper', () => {
expect(cheURL).to.equals('https://example.org')
})
fancy
.stub(ch, 'cheNamespaceExist', () => true)
.stub(kube, 'getNamespace', () => ({}))
.stub(ctx, 'get', () => ({ isOpenShift: true }))
.stub(oc, 'routeExist', () => false)
.do(() => ch.cheURL('che-namespace'))
.catch(/ERR_ROUTE_NO_EXIST/)
.it('fails fetching Eclipse Che URL when route does not exist')
fancy
.stub(ch, 'cheNamespaceExist', () => false)
.stub(kube, 'getNamespace', () => undefined)
.do(() => ch.cheURL('che-namespace'))
.catch(err => expect(err.message).to.match(/ERR_NAMESPACE_NO_EXIST/))
.it('fails fetching Eclipse Che URL when namespace does not exist')
Expand All @@ -72,14 +72,14 @@ describe('Eclipse Che helper', () => {
.stub(KubeHelper.KUBE_CONFIG, 'makeApiClient', () => k8sApi)
.stub(k8sApi, 'readNamespace', jest.fn().mockImplementation(() => { throw new Error() }))
.it('founds out that a namespace doesn\'t exist', async () => {
const res = await ch.cheNamespaceExist(namespace)
const res = !!await kube.getNamespace(namespace)
expect(res).to.equal(false)
})
fancy
.stub(KubeHelper.KUBE_CONFIG, 'makeApiClient', () => k8sApi)
.stub(k8sApi, 'readNamespace', () => ({ response: '', body: { metadata: { name: `${namespace}` } } }))
.it('founds out that a namespace does exist', async () => {
const res = await ch.cheNamespaceExist(namespace)
const res = !!await kube.getNamespace(namespace)
expect(res).to.equal(true)
})
})
Expand Down