From 9d15367533f376e5c46f1da7ee601478d98e4694 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Thu, 26 Aug 2021 16:23:25 +0300 Subject: [PATCH 01/21] Intermediate state Signed-off-by: Mykola Morhun --- src/api/backup-restore.ts | 15 ++- src/api/kube.ts | 35 ++++-- src/api/typings/backup-restore-crds.d.ts | 1 + src/commands/server/backup.ts | 6 +- src/commands/server/restore.ts | 132 +++++++++++++++++++++-- 5 files changed, 162 insertions(+), 27 deletions(-) diff --git a/src/api/backup-restore.ts b/src/api/backup-restore.ts index 6507553d5..72daf125d 100644 --- a/src/api/backup-restore.ts +++ b/src/api/backup-restore.ts @@ -52,9 +52,6 @@ export interface SftpBackupServerCredentials { sshKey: string } -export const BACKUP_CR_NAME = 'eclipse-che-backup' -export const RESTORE_CR_NAME = 'eclipse-che-restore' - export const BACKUP_SERVER_CONFIG_NAME = 'eclipse-che-backup-server-config' export const BACKUP_REPOSITORY_PASSWORD_SECRET_NAME = 'chectl-backup-repository-password' @@ -80,26 +77,28 @@ export function getBackupServerType(url: string): BackupServerType { /** * Submits backup of Che installation task. * @param namespace namespace in which Che is installed + * @param name name of the backup CR to create * @param backupServerConfig backup server configuration data or name of the config CR */ -export async function requestBackup(namespace: string, backupServerConfig?: BackupServerConfig | string): Promise { +export async function requestBackup(namespace: string, name: string, backupServerConfig?: BackupServerConfig | string): Promise { const kube = new KubeHelper() const backupServerConfigName = await getBackupServerConfigurationName(namespace, backupServerConfig) - return kube.recreateBackupCr(namespace, BACKUP_CR_NAME, backupServerConfigName) + return kube.recreateBackupCr(namespace, name, backupServerConfigName) } /** * Submits Che restore task. * @param namespace namespace in which Che should be restored + * @param name name of the restore CR to create * @param backupServerConfig backup server configuration data or name of the config CR */ -export async function requestRestore(namespace: string, backupServerConfig?: BackupServerConfig | string, snapshotId?: string): Promise { +export async function requestRestore(namespace: string, name: string, backupServerConfig?: BackupServerConfig | string, snapshotId?: string): Promise { const kube = new KubeHelper() const backupServerConfigName = await getBackupServerConfigurationName(namespace, backupServerConfig) if (!backupServerConfigName) { throw new Error(`No backup server configuration found in ${namespace} namespace`) } - return kube.recreateRestoreCr(namespace, RESTORE_CR_NAME, backupServerConfigName, snapshotId) + return kube.recreateRestoreCr(namespace, name, backupServerConfigName, snapshotId) } /** @@ -118,7 +117,7 @@ async function getBackupServerConfigurationName(namespace: string, backupServerC if (typeof backupServerConfig === 'string') { // Name of CR with backup server configuration provided // Check if it exists - const backupServerConfigCr = await kube.getCustomResource(namespace, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_BACKUP_SERVER_CONFIG_KIND_PLURAL) + const backupServerConfigCr = await kube.getCustomResource(namespace, backupServerConfig, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_BACKUP_SERVER_CONFIG_KIND_PLURAL) if (!backupServerConfigCr) { throw new Error(`Backup server configuration with '${backupServerConfig}' name not found in '${namespace}' namespace.`) } diff --git a/src/api/kube.ts b/src/api/kube.ts index c666b90b9..0c65dd77a 100644 --- a/src/api/kube.ts +++ b/src/api/kube.ts @@ -1627,13 +1627,35 @@ export class KubeHelper { * Returns `checlusters.org.eclipse.che' in the given namespace. */ async getCheCluster(cheNamespace: string): Promise { - return this.getCustomResource(cheNamespace, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_KIND_PLURAL) + return this.findCustomResource(cheNamespace, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_KIND_PLURAL) } /** - * Returns custom resource in the given namespace. + * Deletes `checlusters.org.eclipse.che' resources in the given namespace. + */ + async getAllCheClusters(): Promise { + return this.getAllCustomResources(CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_KIND_PLURAL) + } + + /** + * Returns custom resource object by its name in the given namespace. */ - async getCustomResource(namespace: string, resourceAPIGroup: string, resourceAPIVersion: string, resourcePlural: string): Promise { + async getCustomResource(namespace: string, name: string, resourceAPIGroup: string, resourceAPIVersion: string, resourcePlural: string): Promise { + const customObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi) + try { + return await customObjectsApi.getNamespacedCustomObject(resourceAPIGroup, resourceAPIVersion, namespace, resourcePlural, name) + } catch (e) { + if (e.response && e.response.statusCode !== 404) { + throw this.wrapK8sClientError(e) + } + } + } + + /** + * Returns the only custom resource in the given namespace. + * Throws error if there is more than one object of given kind. + */ + async findCustomResource(namespace: string, resourceAPIGroup: string, resourceAPIVersion: string, resourcePlural: string): Promise { const customObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi) try { const { body } = await customObjectsApi.listNamespacedCustomObject(resourceAPIGroup, resourceAPIVersion, namespace, resourcePlural) @@ -1656,13 +1678,6 @@ export class KubeHelper { } } - /** - * Deletes `checlusters.org.eclipse.che' resources in the given namespace. - */ - async getAllCheClusters(): Promise { - return this.getAllCustomResources(CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_KIND_PLURAL) - } - /** * Returns all custom resources */ diff --git a/src/api/typings/backup-restore-crds.d.ts b/src/api/typings/backup-restore-crds.d.ts index 839c66c4a..11a467899 100644 --- a/src/api/typings/backup-restore-crds.d.ts +++ b/src/api/typings/backup-restore-crds.d.ts @@ -29,6 +29,7 @@ export interface V1CheClusterBackupStatus { message?: string state?: string stage?: string + cheVersion?: string snapshotId?: string } diff --git a/src/commands/server/backup.ts b/src/commands/server/backup.ts index 76ba0c70f..d3afddd2a 100644 --- a/src/commands/server/backup.ts +++ b/src/commands/server/backup.ts @@ -92,6 +92,8 @@ export const backupServerConfigName = string({ exclusive: [BACKUP_REPOSITORY_URL_KEY, BACKUP_REPOSITORY_PASSWORD_KEY], }) +const BACKUP_CR_NAME = 'eclipse-che-backup' + export default class Backup extends Command { static description = 'Backup Eclipse Che installation' @@ -150,7 +152,7 @@ export default class Backup extends Command { title: 'Scheduling backup...', task: async (_ctx: any, task: any) => { const backupServerConfig = getBackupServerConfiguration(flags) - await requestBackup(flags.chenamespace, backupServerConfig) + await requestBackup(flags.chenamespace, BACKUP_CR_NAME, backupServerConfig) task.title = `${task.title}OK` }, }, @@ -161,7 +163,7 @@ export default class Backup extends Command { let backupStatus: V1CheClusterBackupStatus = {} do { await cli.wait(1000) - const backupCr: V1CheClusterBackup = await kube.getCustomResource(flags.chenamespace, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL) + const backupCr: V1CheClusterBackup = await kube.getCustomResource(flags.chenamespace, BACKUP_CR_NAME, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL) if (!backupCr.status) { continue } diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index b4dde7a4b..37495a8d4 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -11,10 +11,10 @@ */ import { Command, flags } from '@oclif/command' -import { string } from '@oclif/parser/lib/flags' +import { boolean, string } from '@oclif/parser/lib/flags' import * as Listr from 'listr' -import { CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_RESTORE_KIND_PLURAL, DEFAULT_ANALYTIC_HOOK_NAME } from '../../constants' +import { CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL, CHE_CLUSTER_RESTORE_KIND_PLURAL, DEFAULT_ANALYTIC_HOOK_NAME, OPERATOR_DEPLOYMENT_NAME } from '../../constants' import { ChectlContext } from '../../api/context' import { KubeHelper } from '../../api/kube' import { cheNamespace } from '../../common-flags' @@ -22,10 +22,12 @@ import { requestRestore } from '../../api/backup-restore' import { cli } from 'cli-ux' import { ApiTasks } from '../../tasks/platforms/api' import { findWorkingNamespace, getCommandSuccessMessage, notifyCommandCompletedSuccessfully, wrapCommandError } from '../../util' -import { V1CheClusterRestore, V1CheClusterRestoreStatus } from '../../api/typings/backup-restore-crds' +import { V1CheClusterBackup, V1CheClusterRestore, V1CheClusterRestoreStatus } from '../../api/typings/backup-restore-crds' import { awsAccessKeyId, awsSecretAccessKey, AWS_ACCESS_KEY_ID_KEY, AWS_SECRET_ACCESS_KEY_KEY, backupRepositoryPassword, backupRepositoryUrl, backupRestServerPassword, backupRestServerUsername, backupServerConfigName, BACKUP_REPOSITORY_PASSWORD_KEY, BACKUP_REPOSITORY_URL_KEY, BACKUP_REST_SERVER_PASSWORD_KEY, BACKUP_REST_SERVER_USERNAME_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY, getBackupServerConfiguration, sshKey, sshKeyFile, SSH_KEY_FILE_KEY, SSH_KEY_KEY } from './backup' +const RESTORE_CR_NAME = 'eclipse-che-restore' + export default class Restore extends Command { static description = 'Restore Eclipse Che installation' @@ -59,6 +61,25 @@ export default class Restore extends Command { description: 'ID of a snapshot to restore from', required: false, }), + version: string({ + char: 'v', + description: ` + Che Operator version to restore to (e.g. 7.35.1). + Must comply with the version in backup snapshot. + Defaults to the existing operator version or to chectl version if none deployed. + `, + required: false, + }), + 'backup-cr': string({ + description: 'Name of a backup custom resource to restore from', + required: false, + exclusive: ['version', 'snapshot-id', BACKUP_REPOSITORY_URL_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY], + }), + 'rollback': boolean({ + description: 'Rolling back to previous version of Eclipse Che if a backup of that version is available', + required: false, + exclusive: ['version', 'snapshot-id', 'backup-cr', BACKUP_REPOSITORY_URL_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY], + }), } async run() { @@ -84,11 +105,93 @@ export default class Restore extends Command { private getRestoreTasks(flags: any): Listr.ListrTask[] { return [ + { + title: 'Detecting existing operator version...', + enabled: flags.version || flags.rollback || flags['backup-cr'], + task: async (ctx: any, task: any) => { + const kube = new KubeHelper(flags) + const operatorDeploymentYaml = await kube.getDeployment(OPERATOR_DEPLOYMENT_NAME, flags.chenamespace) + if (!operatorDeploymentYaml) { + // There is no operator deployment + ctx.currentOperatorVersion = '' + task.title = `${task.title} operator is not deployed` + return + } + const operatorEnv = operatorDeploymentYaml.spec!.template.spec!.containers[0].env! + const currentVersionEnvVar = operatorEnv.find(envVar => envVar.name === 'CHE_VERSION') + if (!currentVersionEnvVar) { + throw new Error(`Failed to find Che operator version in '${OPERATOR_DEPLOYMENT_NAME}' deployment in '${flags.chenamespace}' namespace`) + } + ctx.currentOperatorVersion = currentVersionEnvVar.value + task.title = `${task.title} ${ctx.currentOperatorVersion} found` + }, + }, + { + title: 'Looking for corresponding backup object...', + enabled: flags.rollback, + task: async (ctx: any, task: any) => { + const currentOperatorVersion: string | undefined = ctx.currentOperatorVersion + if (!currentOperatorVersion) { + throw new Error('Che operator not found. Cannot detect version to use.') + } + const backupCrName = "backup-before-update-to-" + currentOperatorVersion.replace(/./g, '-') + + const kube = new KubeHelper(flags) + const backupCr = await kube.getCustomResource(flags.chenamespace, backupCrName, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL) + if (!backupCr) { + throw new Error(`Cannot find backup: ${backupCrName}`) + } + flags['backup-cr'] = backupCrName + task.title = `${task.title} ${backupCrName} found` + } + }, + { + title: 'Gathering information about backup...', + enabled: flags['backup-cr'], + task: async (ctx: any, task: any) => { + const backupCrName = flags['backup-cr'] + const kube = new KubeHelper(flags) + const backupCr: V1CheClusterBackup | undefined = await kube.getCustomResource(flags.chenamespace, backupCrName, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL) + if (!backupCr) { + throw new Error(`Backup CR with name '${backupCrName}' not found`) + } + + if (!backupCr.spec.backupServerConfigRef) { + throw new Error(`Backup CR '${backupCrName}' missing backup server configuration reference`) + } + if (!backupCr.status || !backupCr.status.cheVersion) { + throw new Error(`Backup CR '${backupCrName}' missing Che version`) + } + if (!backupCr.status || !backupCr.status.snapshotId) { + throw new Error(`Backup CR '${backupCrName}' missing snapshot ID`) + } + + flags.version = backupCr.status.cheVersion + flags['snapshot-id'] = backupCr.status.snapshotId + task.title = `${task.title}OK` + } + }, + { + title: 'Getting backup server info...', + task: async (ctx: any, task: any) => { + // Get all information about backup server and validate it where possible + // before redeploying operator to requested version. + ctx.backupServerConfig = getBackupServerConfiguration(flags) + task.title = `${task.title}OK` + }, + }, + { + title: 'Deploy Che operator of requested version', + enabled: (ctx: any) => flags.version && ctx.currentOperatorVersion !== flags.version, + task: async (_ctx: any, _task: any) => { + // All preparations and validations must be done before this task + return this.getRedeployOperatorTasks(flags) + }, + }, { title: 'Scheduling restore...', - task: async (_ctx: any, task: any) => { - const backupServerConfig = getBackupServerConfiguration(flags) - await requestRestore(flags.chenamespace, backupServerConfig, flags['snapshot-id']) + task: async (ctx: any, task: any) => { + await requestRestore(flags.chenamespace, RESTORE_CR_NAME, ctx.backupServerConfig, flags['snapshot-id']) task.title = `${task.title}OK` }, }, @@ -99,7 +202,7 @@ export default class Restore extends Command { let restoreStatus: V1CheClusterRestoreStatus = {} do { await cli.wait(1000) - const restoreCr: V1CheClusterRestore = await kube.getCustomResource(flags.chenamespace, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_RESTORE_KIND_PLURAL) + const restoreCr: V1CheClusterRestore = await kube.getCustomResource(flags.chenamespace, RESTORE_CR_NAME, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_RESTORE_KIND_PLURAL) if (!restoreCr.status) { continue } @@ -119,4 +222,19 @@ export default class Restore extends Command { }, ] } + + /** + * Returns list of tasks that (re)deploys the flags.version version of Che operator. + */ + private getRedeployOperatorTasks(flags: any): ReadonlyArray { + return [ + { + title: '', + task: async (ctx: any, task: any) => { + + } + }, + ] + } + } From 347866651586ad1dbd97e7ac8eaa861890bccb2f Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Tue, 31 Aug 2021 11:29:21 +0300 Subject: [PATCH 02/21] Finish restore draft without all namespaces support Signed-off-by: Mykola Morhun --- src/api/context.ts | 6 +- src/api/kube.ts | 13 +++ src/commands/server/deploy.ts | 6 +- src/commands/server/restore.ts | 147 +++++++++++++++++++++++++----- src/constants.ts | 5 +- src/tasks/installers/installer.ts | 2 +- src/tasks/installers/olm.ts | 12 +-- src/tasks/installers/operator.ts | 6 +- 8 files changed, 153 insertions(+), 44 deletions(-) diff --git a/src/api/context.ts b/src/api/context.ts index 183e96d89..bfc21f3e7 100644 --- a/src/api/context.ts +++ b/src/api/context.ts @@ -16,7 +16,7 @@ import * as os from 'os' import * as path from 'path' import { CHE_OPERATOR_CR_PATCH_YAML_KEY, CHE_OPERATOR_CR_YAML_KEY, LOG_DIRECTORY_KEY } from '../common-flags' -import { CHECTL_PROJECT_NAME, DEFAULT_CHE_NAMESPACE, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, STABLE_ALL_NAMESPACES_CHANNEL_NAME } from '../constants' +import { CHECTL_PROJECT_NAME, DEFAULT_CHE_NAMESPACE, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME } from '../constants' import { getProjectName, getProjectVersion, readCRFile } from '../util' import { CHECTL_DEVELOPMENT_VERSION } from './version' @@ -45,12 +45,12 @@ export namespace ChectlContext { export async function init(flags: any, command: Command): Promise { ctx.isChectl = getProjectName() === CHECTL_PROJECT_NAME ctx.isDevVersion = getProjectVersion().includes('next') || getProjectVersion() === CHECTL_DEVELOPMENT_VERSION - ctx.operatorNamespace = flags.chenamespace || DEFAULT_CHE_NAMESPACE if (flags['listr-renderer'] as any) { ctx.listrOptions = { renderer: (flags['listr-renderer'] as any), collapse: false } as Listr.ListrOptions } - if (flags['olm-channel'] === STABLE_ALL_NAMESPACES_CHANNEL_NAME) { + ctx.operatorNamespace = flags.chenamespace || DEFAULT_CHE_NAMESPACE + if (flags['olm-channel'] === OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME) { ctx.operatorNamespace = DEFAULT_OPENSHIFT_OPERATORS_NS_NAME } diff --git a/src/api/kube.ts b/src/api/kube.ts index 0c65dd77a..e7b5a27b9 100644 --- a/src/api/kube.ts +++ b/src/api/kube.ts @@ -1199,6 +1199,19 @@ export class KubeHelper { } } + async deleteDeployment(namespace: string, name: string): Promise { + const k8sAppsApi = this.kubeConfig.makeApiClient(AppsV1Api) + try { + k8sAppsApi.deleteNamespacedDeployment(name, namespace) + } catch (error) { + if (error.response && error.response.statusCode === 404) { + return false + } + throw this.wrapK8sClientError(error) + } + return true + } + async deleteAllDeployments(namespace: string): Promise { const k8sAppsApi = this.kubeConfig.makeApiClient(AppsV1Api) try { diff --git a/src/commands/server/deploy.ts b/src/commands/server/deploy.ts index a6d4fa002..57bf673fc 100644 --- a/src/commands/server/deploy.ts +++ b/src/commands/server/deploy.ts @@ -18,7 +18,7 @@ import * as semver from 'semver' import { ChectlContext } from '../../api/context' import { KubeHelper } from '../../api/kube' import { batch, cheDeployment, cheDeployVersion, cheNamespace, cheOperatorCRPatchYaml, cheOperatorCRYaml, CHE_OPERATOR_CR_PATCH_YAML_KEY, CHE_OPERATOR_CR_YAML_KEY, CHE_TELEMETRY, DEPLOY_VERSION_KEY, devWorkspaceControllerNamespace, k8sPodDownloadImageTimeout, K8SPODDOWNLOADIMAGETIMEOUT_KEY, k8sPodErrorRecheckTimeout, K8SPODERRORRECHECKTIMEOUT_KEY, k8sPodReadyTimeout, K8SPODREADYTIMEOUT_KEY, k8sPodWaitTimeout, K8SPODWAITTIMEOUT_KEY, listrRenderer, logsDirectory, LOG_DIRECTORY_KEY, skipKubeHealthzCheck as skipK8sHealthCheck } from '../../common-flags' -import { DEFAULT_ANALYTIC_HOOK_NAME, DEFAULT_CHE_NAMESPACE, DEFAULT_OLM_SUGGESTED_NAMESPACE, DOCS_LINK_INSTALL_RUNNING_CHE_LOCALLY, MIN_CHE_OPERATOR_INSTALLER_VERSION, MIN_HELM_INSTALLER_VERSION, MIN_OLM_INSTALLER_VERSION, STABLE_ALL_NAMESPACES_CHANNEL_NAME } from '../../constants' +import { DEFAULT_ANALYTIC_HOOK_NAME, DEFAULT_CHE_NAMESPACE, DEFAULT_OLM_SUGGESTED_NAMESPACE, DOCS_LINK_INSTALL_RUNNING_CHE_LOCALLY, MIN_CHE_OPERATOR_INSTALLER_VERSION, MIN_HELM_INSTALLER_VERSION, MIN_OLM_INSTALLER_VERSION, OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME } from '../../constants' import { CheTasks } from '../../tasks/che' import { DevWorkspaceTasks } from '../../tasks/component-installers/devfile-workspace-operator-installer' import { checkChectlAndCheVersionCompatibility, downloadTemplates, getPrintHighlightedMessagesTask, retrieveCheCaCertificateTask } from '../../tasks/installers/common-tasks' @@ -295,7 +295,7 @@ export default class Deploy extends Command { this.error(`🛑 The specified installer ${flags.installer} does not support Minishift`) } - if (flags['olm-channel'] === STABLE_ALL_NAMESPACES_CHANNEL_NAME && isKubernetesPlatformFamily(flags.platform)) { + if (flags['olm-channel'] === OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME && isKubernetesPlatformFamily(flags.platform)) { this.error('"stable-all-namespaces" channel is supported only in "openshift" platform') } @@ -418,7 +418,7 @@ export default class Deploy extends Command { enabled: () => (this.isDevWorkspaceEnabled(ctx) || flags['workspace-engine'] === 'dev-workspace') && !ctx.isOpenShift, task: () => new Listr(devWorkspaceTasks.getInstallTasks(flags)), }) - const installTasks = new Listr(installerTasks.installTasks(flags, this), ctx.listrOptions) + const installTasks = new Listr(await installerTasks.installTasks(flags, this), ctx.listrOptions) // Post Install Checks const postInstallTasks = new Listr([ diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index 37495a8d4..0003a158c 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -14,13 +14,17 @@ import { Command, flags } from '@oclif/command' import { boolean, string } from '@oclif/parser/lib/flags' import * as Listr from 'listr' -import { CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL, CHE_CLUSTER_RESTORE_KIND_PLURAL, DEFAULT_ANALYTIC_HOOK_NAME, OPERATOR_DEPLOYMENT_NAME } from '../../constants' +import { CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL, CHE_CLUSTER_RESTORE_KIND_PLURAL, DEFAULT_ANALYTIC_HOOK_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME, OLM_STABLE_CHANNEL_NAME, OPERATOR_DEPLOYMENT_NAME, SUBSCRIPTION_NAME } from '../../constants' import { ChectlContext } from '../../api/context' import { KubeHelper } from '../../api/kube' +import { Subscription } from '../../api/typings/olm' import { cheNamespace } from '../../common-flags' import { requestRestore } from '../../api/backup-restore' import { cli } from 'cli-ux' import { ApiTasks } from '../../tasks/platforms/api' +import { OLMTasks } from '../../tasks/installers/olm' +import { OperatorTasks } from '../../tasks/installers/operator' +import { checkChectlAndCheVersionCompatibility, downloadTemplates } from '../../tasks/installers/common-tasks' import { findWorkingNamespace, getCommandSuccessMessage, notifyCommandCompletedSuccessfully, wrapCommandError } from '../../util' import { V1CheClusterBackup, V1CheClusterRestore, V1CheClusterRestoreStatus } from '../../api/typings/backup-restore-crds' @@ -110,12 +114,17 @@ export default class Restore extends Command { enabled: flags.version || flags.rollback || flags['backup-cr'], task: async (ctx: any, task: any) => { const kube = new KubeHelper(flags) - const operatorDeploymentYaml = await kube.getDeployment(OPERATOR_DEPLOYMENT_NAME, flags.chenamespace) + let operatorDeploymentYaml = await kube.getDeployment(OPERATOR_DEPLOYMENT_NAME, flags.chenamespace) if (!operatorDeploymentYaml) { - // There is no operator deployment - ctx.currentOperatorVersion = '' - task.title = `${task.title} operator is not deployed` - return + // There is no operator deployment in the namespace + // Check if the operator in all namespaces mode + operatorDeploymentYaml = await kube.getDeployment(OPERATOR_DEPLOYMENT_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME) + if (!operatorDeploymentYaml) { + // Still no operator deployment found + ctx.currentOperatorVersion = '' + task.title = `${task.title} operator is not deployed` + return + } } const operatorEnv = operatorDeploymentYaml.spec!.template.spec!.containers[0].env! const currentVersionEnvVar = operatorEnv.find(envVar => envVar.name === 'CHE_VERSION') @@ -126,6 +135,36 @@ export default class Restore extends Command { task.title = `${task.title} ${ctx.currentOperatorVersion} found` }, }, + { + title: 'Detecting operator installer...', + // It is possible to skip '&& (flags.version || flags.rollback || flags['backup-cr'])' in the nabled condition below, + // as ctx.currentOperatorVersion is set only if previous task, that has the condition, is executed. + enabled: (ctx: any) => ctx.currentOperatorVersion && !flags['olm-channel'], + task: async (ctx: any, task: any) => { + const kube = new KubeHelper(flags) + let operatorSubscriptionYaml: Subscription = await kube.getOperatorSubscription(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME) + if (operatorSubscriptionYaml) { + // OLM in all namespaces mode + flags.installer = 'olm' + flags['olm-channel'] = operatorSubscriptionYaml.spec.channel + task.title = `${task.title}OLM` + return + } + + operatorSubscriptionYaml = await kube.getOperatorSubscription(SUBSCRIPTION_NAME, flags.chenamespace) + if (operatorSubscriptionYaml) { + // OLM in single namespace mode + flags.installer = 'olm' + flags['olm-channel'] = operatorSubscriptionYaml.spec.channel + task.title = `${task.title}OLM` + return + } + + // As ctx.currentOperatorVersion is set, the operator deployment exists + flags.installer = 'operator' + task.title = `${task.title}Operator` + } + }, { title: 'Looking for corresponding backup object...', enabled: flags.rollback, @@ -181,16 +220,89 @@ export default class Restore extends Command { }, }, { - title: 'Deploy Che operator of requested version', + title: 'Detecting additional parameters...', + task: async (ctx: any, task: any) => { + const kube = new KubeHelper(flags) + ctx.isOpenshift = await kube.isOpenShift() + + // Set defaults for some parameters if they weren't set + if (!flags.installer) { + flags.installer = ctx.isOpenshift ? 'olm' : 'operator' + } + if (flags.installer === 'olm' && !flags['olm-channel']) { + if (await kube.getOperatorSubscription(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME)) { + flags['olm-channel'] = OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME + } else { + flags['olm-channel'] = OLM_STABLE_CHANNEL_NAME + } + } + if (ctx.isOpenshift) { + flags.platform = 'openshift' + flags['cluster-monitoring'] = true + } else { + flags.platform = 'kubernetes' + } + + task.title = `${task.title}OK` + } + }, + { + title: 'Remove current Che operator', + enabled: (ctx: any) => ctx.currentOperatorVersion && flags.version && ctx.currentOperatorVersion !== flags.version, + task: async (ctx: any, _task: any) => { + // All preparations and validations must be done before this task! + + // Delete old operator if any + if (flags.installer === 'olm') { + const olmTasks = new OLMTasks() + const olmDeleteTasks = olmTasks.deleteTasks(flags) + return new Listr(olmDeleteTasks, ctx.listrOptions) + } else { + // Operator + const operatorTasks = new OperatorTasks() + const operatorDeleteTasks = operatorTasks.deleteTasks(flags) + return new Listr(operatorDeleteTasks, ctx.listrOptions) + } + } + }, + { + title: 'Deploy requested version of Che operator', enabled: (ctx: any) => flags.version && ctx.currentOperatorVersion !== flags.version, - task: async (_ctx: any, _task: any) => { - // All preparations and validations must be done before this task - return this.getRedeployOperatorTasks(flags) + task: async (ctx: any, _task: any) => { + // Use plain operator on Kubernetes or if it is requested instead of OLM + if (!ctx.isOpenshift || flags.installer === 'operator') { + const deployOperatorOnlyTasks = new Listr(undefined, ctx.listrOptions) + deployOperatorOnlyTasks.add(checkChectlAndCheVersionCompatibility(flags)) + deployOperatorOnlyTasks.add(downloadTemplates(flags)) + + const operatorTasks = new OperatorTasks() + const operatorInstallTasks = await operatorTasks.deployTasks(flags, this) + // Remove last tasks that deploys CR (it will be done on restore) + operatorInstallTasks.splice(-2) + deployOperatorOnlyTasks.add(operatorInstallTasks) + + return deployOperatorOnlyTasks + } else { + // OLM on Openshift + const olmTasks = new OLMTasks() + let olmInstallTasks = olmTasks.startTasks(flags, this) + // Remove last tasks that deploys CR (it will be done on restore) + olmInstallTasks.splice(-2) + // Remove other redundant for restoring tasks + const tasksToDelete = [ + 'Create custom catalog source from file', + 'Set custom operator image', + ] + olmInstallTasks = olmInstallTasks.filter(task => tasksToDelete.indexOf(task.title) === -1) + + return new Listr(olmInstallTasks, ctx.listrOptions) + } }, }, { title: 'Scheduling restore...', task: async (ctx: any, task: any) => { + // At this point deployed operator should be of the version to restore to await requestRestore(flags.chenamespace, RESTORE_CR_NAME, ctx.backupServerConfig, flags['snapshot-id']) task.title = `${task.title}OK` }, @@ -222,19 +334,4 @@ export default class Restore extends Command { }, ] } - - /** - * Returns list of tasks that (re)deploys the flags.version version of Che operator. - */ - private getRedeployOperatorTasks(flags: any): ReadonlyArray { - return [ - { - title: '', - task: async (ctx: any, task: any) => { - - } - }, - ] - } - } diff --git a/src/constants.ts b/src/constants.ts index ce2246269..55016280d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -44,6 +44,7 @@ export const LEGACY_CHE_NAMESPACE = 'che' // OLM export const DEFAULT_CHE_OLM_PACKAGE_NAME = 'eclipse-che' export const OLM_STABLE_CHANNEL_NAME = 'stable' +export const OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME = 'stable-all-namespaces' export const OLM_NEXT_CHANNEL_NAME = 'next' export const DEFAULT_OPENSHIFT_MARKET_PLACE_NAMESPACE = 'openshift-marketplace' export const DEFAULT_OLM_KUBERNETES_NAMESPACE = 'olm' @@ -55,6 +56,7 @@ export const OPENSHIFT_OLM_CATALOG = 'community-operators' export const CVS_PREFIX = 'eclipse-che' export const NEXT_CATALOG_SOURCE_NAME = 'eclipse-che-preview' export const DEFAULT_OLM_SUGGESTED_NAMESPACE = 'eclipse-che' +export const DEFAULT_OPENSHIFT_OPERATORS_NS_NAME = 'openshift-operators' // Documentation links export const DOC_LINK = 'https://www.eclipse.org/che/docs/' @@ -90,6 +92,3 @@ export const CHE_CLUSTER_BACKUP_CRD = 'checlusterbackups.org.eclipse.che' export const CHE_CLUSTER_BACKUP_KIND_PLURAL = 'checlusterbackups' export const CHE_CLUSTER_RESTORE_CRD = 'checlusterrestores.org.eclipse.che' export const CHE_CLUSTER_RESTORE_KIND_PLURAL = 'checlusterrestores' - -export const DEFAULT_OPENSHIFT_OPERATORS_NS_NAME = 'openshift-operators' -export const STABLE_ALL_NAMESPACES_CHANNEL_NAME = 'tech-preview-stable-all-namespaces' diff --git a/src/tasks/installers/installer.ts b/src/tasks/installers/installer.ts index 9f5f3d8da..06b138e49 100644 --- a/src/tasks/installers/installer.ts +++ b/src/tasks/installers/installer.ts @@ -81,7 +81,7 @@ export class InstallerTasks { }] } - installTasks(flags: any, command: Command): ReadonlyArray { + async installTasks(flags: any, command: Command): Promise> { const helmTasks = new HelmTasks(flags) const operatorTasks = new OperatorTasks() const olmTasks = new OLMTasks() diff --git a/src/tasks/installers/olm.ts b/src/tasks/installers/olm.ts index 90efc9e1d..aa66756fa 100644 --- a/src/tasks/installers/olm.ts +++ b/src/tasks/installers/olm.ts @@ -20,7 +20,7 @@ import { CheHelper } from '../../api/che' import { KubeHelper } from '../../api/kube' import { CatalogSource, Subscription } from '../../api/typings/olm' import { VersionHelper } from '../../api/version' -import { CUSTOM_CATALOG_SOURCE_NAME, CVS_PREFIX, DEFAULT_CHE_NAMESPACE, DEFAULT_CHE_OLM_PACKAGE_NAME, DEFAULT_OLM_KUBERNETES_NAMESPACE, DEFAULT_OPENSHIFT_MARKET_PLACE_NAMESPACE, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, KUBERNETES_OLM_CATALOG, NEXT_CATALOG_SOURCE_NAME, OLM_NEXT_CHANNEL_NAME, OLM_STABLE_CHANNEL_NAME, OPENSHIFT_OLM_CATALOG, OPERATOR_GROUP_NAME, STABLE_ALL_NAMESPACES_CHANNEL_NAME, DEFAULT_CHE_OPERATOR_SUBSCRIPTION_NAME } from '../../constants' +import { CUSTOM_CATALOG_SOURCE_NAME, CVS_PREFIX, DEFAULT_CHE_NAMESPACE, DEFAULT_CHE_OLM_PACKAGE_NAME, DEFAULT_OLM_KUBERNETES_NAMESPACE, DEFAULT_OPENSHIFT_MARKET_PLACE_NAMESPACE, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, KUBERNETES_OLM_CATALOG, NEXT_CATALOG_SOURCE_NAME, OLM_NEXT_CHANNEL_NAME, OLM_STABLE_CHANNEL_NAME, OPENSHIFT_OLM_CATALOG, OPERATOR_GROUP_NAME, OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME, DEFAULT_CHE_OPERATOR_SUBSCRIPTION_NAME } from '../../constants' import { isKubernetesPlatformFamily } from '../../util' import { createEclipseCheCluster, createNamespaceTask, patchingEclipseCheCluster } from './common-tasks' @@ -33,10 +33,10 @@ export class OLMTasks { /** * Returns list of tasks which perform preflight platform checks. */ - startTasks(flags: any, command: Command): Listr { + startTasks(flags: any, command: Command): Listr.ListrTask[] { const kube = new KubeHelper(flags) const che = new CheHelper(flags) - return new Listr([ + return [ this.isOlmPreInstalledTask(command, kube), createNamespaceTask(flags.chenamespace, this.getOlmNamespaceLabels(flags)), { @@ -162,9 +162,9 @@ export class OLMTasks { } else if (VersionHelper.isDeployingStableVersion(flags) || flags['olm-channel'] === OLM_STABLE_CHANNEL_NAME) { // stable Che CatalogSource subscription = this.constructSubscription(ctx.subscriptionName, DEFAULT_CHE_OLM_PACKAGE_NAME, ctx.operatorNamespace, ctx.defaultCatalogSourceNamespace, OLM_STABLE_CHANNEL_NAME, ctx.catalogSourceNameStable, ctx.approvalStarategy, ctx.startingCSV) - } else if (flags['olm-channel'] === STABLE_ALL_NAMESPACES_CHANNEL_NAME) { + } else if (flags['olm-channel'] === OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME) { // stable Che CatalogSource - subscription = this.constructSubscription(ctx.subscriptionName, DEFAULT_CHE_OLM_PACKAGE_NAME, ctx.operatorNamespace, ctx.defaultCatalogSourceNamespace, STABLE_ALL_NAMESPACES_CHANNEL_NAME, ctx.catalogSourceNameStable, ctx.approvalStarategy, ctx.startingCSV) + subscription = this.constructSubscription(ctx.subscriptionName, DEFAULT_CHE_OLM_PACKAGE_NAME, ctx.operatorNamespace, ctx.defaultCatalogSourceNamespace, OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME, ctx.catalogSourceNameStable, ctx.approvalStarategy, ctx.startingCSV) } else { // next Che CatalogSource subscription = this.constructSubscription(ctx.subscriptionName, `eclipse-che-preview-${ctx.generalPlatformName}`, ctx.operatorNamespace, ctx.operatorNamespace, OLM_NEXT_CHANNEL_NAME, NEXT_CATALOG_SOURCE_NAME, ctx.approvalStarategy, ctx.startingCSV) @@ -227,7 +227,7 @@ export class OLMTasks { }, }, createEclipseCheCluster(flags, kube), - ], { renderer: flags['listr-renderer'] as any }) + ] } preUpdateTasks(flags: any, command: Command): Listr { diff --git a/src/tasks/installers/operator.ts b/src/tasks/installers/operator.ts index 34cd2a3cb..d99c22063 100644 --- a/src/tasks/installers/operator.ts +++ b/src/tasks/installers/operator.ts @@ -148,7 +148,7 @@ export class OperatorTasks { /** * Returns tasks list which perform preflight platform checks. */ - async deployTasks(flags: any, command: Command): Promise { + async deployTasks(flags: any, command: Command): Promise { const kube = new KubeHelper(flags) const kubeTasks = new KubeTasks(flags) const ctx = ChectlContext.get() @@ -156,7 +156,7 @@ export class OperatorTasks { if (VersionHelper.isDeployingStableVersion(flags) && !await kube.isOpenShift3()) { command.warn('Consider using the more reliable \'OLM\' installer when deploying a stable release of Eclipse Che (--installer=olm).') } - return new Listr([ + return [ createNamespaceTask(flags.chenamespace, {}), { title: `Create ServiceAccount ${this.operatorServiceAccount} in namespace ${flags.chenamespace}`, @@ -268,7 +268,7 @@ export class OperatorTasks { }, }, createEclipseCheCluster(flags, kube), - ], { renderer: flags['listr-renderer'] as any }) + ] } preUpdateTasks(flags: any, command: Command): Listr { From 3ebaecc422a78b4140ac40973248937eb6bc58c4 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Thu, 9 Sep 2021 15:21:27 +0300 Subject: [PATCH 03/21] Make the flow work for stable OLM channel and --rollback flag Signed-off-by: Mykola Morhun --- src/api/kube.ts | 3 ++- src/commands/server/deploy.ts | 3 --- src/commands/server/restore.ts | 44 +++++++++++++++++++------------ src/tasks/installers/installer.ts | 10 +++++-- src/tasks/installers/olm.ts | 2 +- 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/api/kube.ts b/src/api/kube.ts index e7b5a27b9..82b16945c 100644 --- a/src/api/kube.ts +++ b/src/api/kube.ts @@ -1656,7 +1656,8 @@ export class KubeHelper { async getCustomResource(namespace: string, name: string, resourceAPIGroup: string, resourceAPIVersion: string, resourcePlural: string): Promise { const customObjectsApi = this.kubeConfig.makeApiClient(CustomObjectsApi) try { - return await customObjectsApi.getNamespacedCustomObject(resourceAPIGroup, resourceAPIVersion, namespace, resourcePlural, name) + const res = await customObjectsApi.getNamespacedCustomObject(resourceAPIGroup, resourceAPIVersion, namespace, resourcePlural, name) + return res.body } catch (e) { if (e.response && e.response.statusCode !== 404) { throw this.wrapK8sClientError(e) diff --git a/src/commands/server/deploy.ts b/src/commands/server/deploy.ts index 57bf673fc..b7b05d4ea 100644 --- a/src/commands/server/deploy.ts +++ b/src/commands/server/deploy.ts @@ -306,9 +306,6 @@ export default class Deploy extends Command { if (flags['starting-csv']) { this.error('"starting-csv" and "version" flags are mutually exclusive. Please specify only one of them.') } - if (flags['olm-channel']) { - this.error('"starting-csv" and "version" flags are mutually exclusive. Use "starting-csv" with "olm-channel" flag.') - } if (flags['auto-update']) { this.error('enabled "auto-update" flag cannot be used with version flag. Deploy latest version instead.') } diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index 0003a158c..b706dce80 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -17,7 +17,6 @@ import * as Listr from 'listr' import { CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL, CHE_CLUSTER_RESTORE_KIND_PLURAL, DEFAULT_ANALYTIC_HOOK_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME, OLM_STABLE_CHANNEL_NAME, OPERATOR_DEPLOYMENT_NAME, SUBSCRIPTION_NAME } from '../../constants' import { ChectlContext } from '../../api/context' import { KubeHelper } from '../../api/kube' -import { Subscription } from '../../api/typings/olm' import { cheNamespace } from '../../common-flags' import { requestRestore } from '../../api/backup-restore' import { cli } from 'cli-ux' @@ -25,7 +24,7 @@ import { ApiTasks } from '../../tasks/platforms/api' import { OLMTasks } from '../../tasks/installers/olm' import { OperatorTasks } from '../../tasks/installers/operator' import { checkChectlAndCheVersionCompatibility, downloadTemplates } from '../../tasks/installers/common-tasks' -import { findWorkingNamespace, getCommandSuccessMessage, notifyCommandCompletedSuccessfully, wrapCommandError } from '../../util' +import { findWorkingNamespace, getCommandSuccessMessage, getEmbeddedTemplatesDirectory, notifyCommandCompletedSuccessfully, wrapCommandError } from '../../util' import { V1CheClusterBackup, V1CheClusterRestore, V1CheClusterRestoreStatus } from '../../api/typings/backup-restore-crds' import { awsAccessKeyId, awsSecretAccessKey, AWS_ACCESS_KEY_ID_KEY, AWS_SECRET_ACCESS_KEY_KEY, backupRepositoryPassword, backupRepositoryUrl, backupRestServerPassword, backupRestServerUsername, backupServerConfigName, BACKUP_REPOSITORY_PASSWORD_KEY, BACKUP_REPOSITORY_URL_KEY, BACKUP_REST_SERVER_PASSWORD_KEY, BACKUP_REST_SERVER_USERNAME_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY, getBackupServerConfiguration, sshKey, sshKeyFile, SSH_KEY_FILE_KEY, SSH_KEY_KEY } from './backup' @@ -43,9 +42,15 @@ export default class Restore extends Command { '# Create and use configuration for REST backup server:\n' + 'chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword', '# Create and use configuration for AWS S3 (and API compatible) backup server (bucket should be precreated):\n' + - 'chectl server:backup -r s3:s3.amazonaws.com/bucketche -p repopassword', + 'chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword', '# Create and use configuration for SFTP backup server:\n' + - 'chectl server:backup -r=sftp:user@my-server.net:/srv/sftp/che-data -p repopassword', + 'chectl server:restore -r=sftp:user@my-server.net:/srv/sftp/che-data -p repopassword', + '# Rollback to previous version (if it was installed):\n' + + 'chectl server:restore --rollback', + '# Restore from specific backup object:\n' + + 'chectl server:restore --backup-cr=backup-object-name', + '# Restore from specific backup of different version:\n' + + 'chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p repopassword', ] static flags: flags.Input = { @@ -111,7 +116,7 @@ export default class Restore extends Command { return [ { title: 'Detecting existing operator version...', - enabled: flags.version || flags.rollback || flags['backup-cr'], + enabled: () => flags.version || flags.rollback || flags['backup-cr'], task: async (ctx: any, task: any) => { const kube = new KubeHelper(flags) let operatorDeploymentYaml = await kube.getDeployment(OPERATOR_DEPLOYMENT_NAME, flags.chenamespace) @@ -136,24 +141,25 @@ export default class Restore extends Command { }, }, { - title: 'Detecting operator installer...', - // It is possible to skip '&& (flags.version || flags.rollback || flags['backup-cr'])' in the nabled condition below, + title: 'Detecting installer...', + // It is possible to skip '&& (flags.version || flags.rollback || flags['backup-cr'])' in the enabled condition below, // as ctx.currentOperatorVersion is set only if previous task, that has the condition, is executed. enabled: (ctx: any) => ctx.currentOperatorVersion && !flags['olm-channel'], task: async (ctx: any, task: any) => { const kube = new KubeHelper(flags) - let operatorSubscriptionYaml: Subscription = await kube.getOperatorSubscription(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME) - if (operatorSubscriptionYaml) { + + if (await kube.operatorSubscriptionExists(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME)) { // OLM in all namespaces mode + const operatorSubscriptionYaml = await kube.getOperatorSubscription(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME) flags.installer = 'olm' flags['olm-channel'] = operatorSubscriptionYaml.spec.channel task.title = `${task.title}OLM` return } - operatorSubscriptionYaml = await kube.getOperatorSubscription(SUBSCRIPTION_NAME, flags.chenamespace) - if (operatorSubscriptionYaml) { + if (await kube.operatorSubscriptionExists(SUBSCRIPTION_NAME, flags.chenamespace)) { // OLM in single namespace mode + const operatorSubscriptionYaml = await kube.getOperatorSubscription(SUBSCRIPTION_NAME, flags.chenamespace) flags.installer = 'olm' flags['olm-channel'] = operatorSubscriptionYaml.spec.channel task.title = `${task.title}OLM` @@ -167,13 +173,13 @@ export default class Restore extends Command { }, { title: 'Looking for corresponding backup object...', - enabled: flags.rollback, + enabled: () => flags.rollback, task: async (ctx: any, task: any) => { const currentOperatorVersion: string | undefined = ctx.currentOperatorVersion if (!currentOperatorVersion) { throw new Error('Che operator not found. Cannot detect version to use.') } - const backupCrName = "backup-before-update-to-" + currentOperatorVersion.replace(/./g, '-') + const backupCrName = "backup-before-update-to-" + currentOperatorVersion.replace(/\./g, '-') const kube = new KubeHelper(flags) const backupCr = await kube.getCustomResource(flags.chenamespace, backupCrName, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL) @@ -186,8 +192,8 @@ export default class Restore extends Command { }, { title: 'Gathering information about backup...', - enabled: flags['backup-cr'], - task: async (ctx: any, task: any) => { + enabled: () => flags['backup-cr'], + task: async (_ctx: any, task: any) => { const backupCrName = flags['backup-cr'] const kube = new KubeHelper(flags) const backupCr: V1CheClusterBackup | undefined = await kube.getCustomResource(flags.chenamespace, backupCrName, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL) @@ -230,13 +236,17 @@ export default class Restore extends Command { flags.installer = ctx.isOpenshift ? 'olm' : 'operator' } if (flags.installer === 'olm' && !flags['olm-channel']) { - if (await kube.getOperatorSubscription(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME)) { + if (await kube.operatorSubscriptionExists(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME)) { flags['olm-channel'] = OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME } else { flags['olm-channel'] = OLM_STABLE_CHANNEL_NAME } } if (ctx.isOpenshift) { + // Using embedded templates here as they are used in install flow for OLM. + // OLM install flow should be reworked to use templates of a version, + // then templates should be downloaded in deploy task below. + flags.templates = getEmbeddedTemplatesDirectory() flags.platform = 'openshift' flags['cluster-monitoring'] = true } else { @@ -323,7 +333,7 @@ export default class Restore extends Command { if (restoreStatus.stage) { task.title = `Waiting until restore process finishes: ${restoreStatus.stage}` } - } while (restoreStatus.state === 'InProgress') + } while (!restoreStatus.state || restoreStatus.state === 'InProgress') if (restoreStatus.state === 'Failed') { throw new Error(`Failed to restore installation: ${restoreStatus.message}`) diff --git a/src/tasks/installers/installer.ts b/src/tasks/installers/installer.ts index 06b138e49..f9a27ff46 100644 --- a/src/tasks/installers/installer.ts +++ b/src/tasks/installers/installer.ts @@ -13,6 +13,8 @@ import Command from '@oclif/command' import * as Listr from 'listr' +import { ChectlContext } from '../../api/context' + import { HelmTasks } from './helm' import { OLMTasks } from './olm' import { OperatorTasks } from './operator' @@ -82,6 +84,8 @@ export class InstallerTasks { } async installTasks(flags: any, command: Command): Promise> { + const ctx = ChectlContext.get() + const helmTasks = new HelmTasks(flags) const operatorTasks = new OperatorTasks() const olmTasks = new OLMTasks() @@ -91,10 +95,12 @@ export class InstallerTasks { if (flags.installer === 'operator') { title = '🏃‍ Running the Eclipse Che operator' - task = () => operatorTasks.deployTasks(flags, command) + task = async () => { + return new Listr(await operatorTasks.deployTasks(flags, command), ctx.listrOptions) + } } else if (flags.installer === 'olm') { title = '🏃‍ Running Olm installaion Eclipse Che' - task = () => olmTasks.startTasks(flags, command) + task = () => new Listr(olmTasks.startTasks(flags, command), ctx.listrOptions) // installer.ts BEGIN CHE ONLY } else if (flags.installer === 'helm') { title = '🏃‍ Running Helm to install Eclipse Che' diff --git a/src/tasks/installers/olm.ts b/src/tasks/installers/olm.ts index aa66756fa..df705b2f8 100644 --- a/src/tasks/installers/olm.ts +++ b/src/tasks/installers/olm.ts @@ -412,7 +412,7 @@ export class OLMTasks { if (!await kube.isPreInstalledOLM()) { cli.warn('Looks like your platform hasn\'t got embedded OLM, so you should install it manually. For quick start you can use:') cli.url('install.sh', 'https://raw.githubusercontent.com/operator-framework/operator-lifecycle-manager/master/deploy/upstream/quickstart/install.sh') - command.error('OLM is required for installation Eclipse Che with installer flag \'olm\'') + command.error('OLM is required for installation of Eclipse Che with installer flag \'olm\'') } task.title = `${task.title}...done.` }, From b78b71ffdc5f43646e987df2f04bbdbb19d69495 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Tue, 14 Sep 2021 10:05:37 +0300 Subject: [PATCH 04/21] Make operator rollback flow working Signed-off-by: Mykola Morhun --- README.md | 21 +++++++++- src/commands/server/restore.ts | 70 +++++++++++++++---------------- src/tasks/installers/installer.ts | 4 +- src/tasks/installers/operator.ts | 6 +-- 4 files changed, 59 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 394672fcf..26c0a77b9 100644 --- a/README.md +++ b/README.md @@ -671,14 +671,24 @@ OPTIONS -s, --snapshot-id=snapshot-id ID of a snapshot to restore from + -v, --version=version Che Operator version to restore to (e.g. 7.35.1). + Must comply with the version in backup snapshot. + Defaults to the existing operator version or to chectl + version if none deployed. + --aws-access-key-id=aws-access-key-id AWS access key ID --aws-secret-access-key=aws-secret-access-key AWS secret access key + --backup-cr=backup-cr Name of a backup custom resource to restore from + --backup-server-config-name=backup-server-config-name Name of custom resource with backup server config --password=password Authentication password for backup REST server + --rollback Rolling back to previous version of Eclipse Che if a backup of + that version is available + --ssh-key=ssh-key Private SSH key for authentication on SFTP server --ssh-key-file=ssh-key-file Path to file with private SSH key for authentication on SFTP @@ -694,9 +704,16 @@ EXAMPLES # Create and use configuration for REST backup server: chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword # Create and use configuration for AWS S3 (and API compatible) backup server (bucket should be precreated): - chectl server:backup -r s3:s3.amazonaws.com/bucketche -p repopassword + chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword # Create and use configuration for SFTP backup server: - chectl server:backup -r=sftp:user@my-server.net:/srv/sftp/che-data -p repopassword + chectl server:restore -r=sftp:user@my-server.net:/srv/sftp/che-data -p repopassword + # Rollback to previous version (if it was installed): + chectl server:restore --rollback + # Restore from specific backup object: + chectl server:restore --backup-cr=backup-object-name + # Restore from specific backup of different version: + chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p + repopassword ``` _See code: [src/commands/server/restore.ts](https://github.com/che-incubator/chectl/blob/v0.0.2/src/commands/server/restore.ts)_ diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index b706dce80..3d207da05 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -84,7 +84,7 @@ export default class Restore extends Command { required: false, exclusive: ['version', 'snapshot-id', BACKUP_REPOSITORY_URL_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY], }), - 'rollback': boolean({ + rollback: boolean({ description: 'Rolling back to previous version of Eclipse Che if a backup of that version is available', required: false, exclusive: ['version', 'snapshot-id', 'backup-cr', BACKUP_REPOSITORY_URL_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY], @@ -121,8 +121,8 @@ export default class Restore extends Command { const kube = new KubeHelper(flags) let operatorDeploymentYaml = await kube.getDeployment(OPERATOR_DEPLOYMENT_NAME, flags.chenamespace) if (!operatorDeploymentYaml) { - // There is no operator deployment in the namespace - // Check if the operator in all namespaces mode + // There is no operator deployment in Che namespace + // Check if the operator is in all namespaces mode operatorDeploymentYaml = await kube.getDeployment(OPERATOR_DEPLOYMENT_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME) if (!operatorDeploymentYaml) { // Still no operator deployment found @@ -169,7 +169,7 @@ export default class Restore extends Command { // As ctx.currentOperatorVersion is set, the operator deployment exists flags.installer = 'operator' task.title = `${task.title}Operator` - } + }, }, { title: 'Looking for corresponding backup object...', @@ -179,7 +179,7 @@ export default class Restore extends Command { if (!currentOperatorVersion) { throw new Error('Che operator not found. Cannot detect version to use.') } - const backupCrName = "backup-before-update-to-" + currentOperatorVersion.replace(/\./g, '-') + const backupCrName = 'backup-before-update-to-' + currentOperatorVersion.replace(/\./g, '-') const kube = new KubeHelper(flags) const backupCr = await kube.getCustomResource(flags.chenamespace, backupCrName, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL) @@ -188,7 +188,7 @@ export default class Restore extends Command { } flags['backup-cr'] = backupCrName task.title = `${task.title} ${backupCrName} found` - } + }, }, { title: 'Gathering information about backup...', @@ -214,7 +214,7 @@ export default class Restore extends Command { flags.version = backupCr.status.cheVersion flags['snapshot-id'] = backupCr.status.snapshotId task.title = `${task.title}OK` - } + }, }, { title: 'Getting backup server info...', @@ -243,10 +243,6 @@ export default class Restore extends Command { } } if (ctx.isOpenshift) { - // Using embedded templates here as they are used in install flow for OLM. - // OLM install flow should be reworked to use templates of a version, - // then templates should be downloaded in deploy task below. - flags.templates = getEmbeddedTemplatesDirectory() flags.platform = 'openshift' flags['cluster-monitoring'] = true } else { @@ -254,26 +250,36 @@ export default class Restore extends Command { } task.title = `${task.title}OK` - } + }, }, { - title: 'Remove current Che operator', - enabled: (ctx: any) => ctx.currentOperatorVersion && flags.version && ctx.currentOperatorVersion !== flags.version, - task: async (ctx: any, _task: any) => { - // All preparations and validations must be done before this task! - - // Delete old operator if any + title: 'Getting installation templates...', + enabled: (ctx: any) => flags.version && ctx.currentOperatorVersion !== flags.version && !flags.templates, + task: async (ctx: any, task: any) => { if (flags.installer === 'olm') { - const olmTasks = new OLMTasks() - const olmDeleteTasks = olmTasks.deleteTasks(flags) - return new Listr(olmDeleteTasks, ctx.listrOptions) + // Using embedded templates here as they are used in install flow for OLM. + // OLM install flow should be reworked to not to use templates, but OLM dependencies. + flags.templates = getEmbeddedTemplatesDirectory() } else { - // Operator - const operatorTasks = new OperatorTasks() - const operatorDeleteTasks = operatorTasks.deleteTasks(flags) - return new Listr(operatorDeleteTasks, ctx.listrOptions) + const getTemplatesTasks = new Listr(undefined, ctx.listrOptions) + getTemplatesTasks.add(checkChectlAndCheVersionCompatibility(flags)) + getTemplatesTasks.add(downloadTemplates(flags)) + return getTemplatesTasks } - } + task.title = `${task.title}OK` + }, + }, + { + title: 'Remove current Che operator', + enabled: (ctx: any) => flags.installer === 'olm' && ctx.currentOperatorVersion && flags.version && ctx.currentOperatorVersion !== flags.version, + task: async (ctx: any, _task: any) => { + // All preparations and validations must be done before this task! + // Delete old operator if any in case of OLM installer. + // For Operator installer, the operator deployment will be downgraded if needed. + const olmTasks = new OLMTasks() + const olmDeleteTasks = olmTasks.deleteTasks(flags) + return new Listr(olmDeleteTasks, ctx.listrOptions) + }, }, { title: 'Deploy requested version of Che operator', @@ -281,17 +287,11 @@ export default class Restore extends Command { task: async (ctx: any, _task: any) => { // Use plain operator on Kubernetes or if it is requested instead of OLM if (!ctx.isOpenshift || flags.installer === 'operator') { - const deployOperatorOnlyTasks = new Listr(undefined, ctx.listrOptions) - deployOperatorOnlyTasks.add(checkChectlAndCheVersionCompatibility(flags)) - deployOperatorOnlyTasks.add(downloadTemplates(flags)) - const operatorTasks = new OperatorTasks() - const operatorInstallTasks = await operatorTasks.deployTasks(flags, this) + const operatorUpdateTasks = operatorTasks.updateTasks(flags, this) // Remove last tasks that deploys CR (it will be done on restore) - operatorInstallTasks.splice(-2) - deployOperatorOnlyTasks.add(operatorInstallTasks) - - return deployOperatorOnlyTasks + operatorUpdateTasks.splice(-1) + return new Listr(operatorUpdateTasks, ctx.listrOptions) } else { // OLM on Openshift const olmTasks = new OLMTasks() diff --git a/src/tasks/installers/installer.ts b/src/tasks/installers/installer.ts index f9a27ff46..2253abe31 100644 --- a/src/tasks/installers/installer.ts +++ b/src/tasks/installers/installer.ts @@ -32,8 +32,8 @@ export class InstallerTasks { if (flags.installer === 'operator') { title = '🏃‍ Running the Eclipse Che operator Update' - task = () => { - return operatorTasks.updateTasks(flags, command) + task = (ctx: any) => { + return new Listr(operatorTasks.updateTasks(flags, command), ctx.listrOptions) } } else if (flags.installer === 'olm') { title = '🏃‍ Running the Eclipse Che operator Update using OLM' diff --git a/src/tasks/installers/operator.ts b/src/tasks/installers/operator.ts index d99c22063..ad8e268c3 100644 --- a/src/tasks/installers/operator.ts +++ b/src/tasks/installers/operator.ts @@ -310,11 +310,11 @@ export class OperatorTasks { ]) } - updateTasks(flags: any, command: Command): Listr { + updateTasks(flags: any, command: Command): Array { const kube = new KubeHelper(flags) const ctx = ChectlContext.get() ctx.resourcesPath = path.join(flags.templates, OPERATOR_TEMPLATE_DIR) - return new Listr([ + return [ { title: `Updating ServiceAccount ${this.operatorServiceAccount} in namespace ${flags.chenamespace}`, task: async (ctx: any, task: any) => { @@ -429,7 +429,7 @@ export class OperatorTasks { }, }, patchingEclipseCheCluster(flags, kube, command), - ], { renderer: flags['listr-renderer'] as any }) + ] } /** From 8eb8bb2c7a46e0e9be84b78454e04f27d254eab6 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Tue, 21 Sep 2021 17:38:56 +0300 Subject: [PATCH 05/21] Fixes for all namespaces mode Signed-off-by: Mykola Morhun --- src/commands/server/restore.ts | 5 ++++- src/constants.ts | 2 +- src/tasks/installers/olm.ts | 26 +++++++++++++++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index 3d207da05..edb31da13 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -182,10 +182,13 @@ export default class Restore extends Command { const backupCrName = 'backup-before-update-to-' + currentOperatorVersion.replace(/\./g, '-') const kube = new KubeHelper(flags) - const backupCr = await kube.getCustomResource(flags.chenamespace, backupCrName, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL) + const backupCr: V1CheClusterBackup = await kube.getCustomResource(flags.chenamespace, backupCrName, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL) if (!backupCr) { throw new Error(`Cannot find backup: ${backupCrName}`) } + if (!backupCr.status || backupCr.status.state !== 'Succeeded') { + throw new Error(`Backup with name '${backupCrName}' is not successful`) + } flags['backup-cr'] = backupCrName task.title = `${task.title} ${backupCrName} found` }, diff --git a/src/constants.ts b/src/constants.ts index 55016280d..3592f6962 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -44,7 +44,7 @@ export const LEGACY_CHE_NAMESPACE = 'che' // OLM export const DEFAULT_CHE_OLM_PACKAGE_NAME = 'eclipse-che' export const OLM_STABLE_CHANNEL_NAME = 'stable' -export const OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME = 'stable-all-namespaces' +export const OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME = 'tech-preview-stable-all-namespaces' export const OLM_NEXT_CHANNEL_NAME = 'next' export const DEFAULT_OPENSHIFT_MARKET_PLACE_NAMESPACE = 'openshift-marketplace' export const DEFAULT_OLM_KUBERNETES_NAMESPACE = 'olm' diff --git a/src/tasks/installers/olm.ts b/src/tasks/installers/olm.ts index df705b2f8..abac8564b 100644 --- a/src/tasks/installers/olm.ts +++ b/src/tasks/installers/olm.ts @@ -96,7 +96,13 @@ export class OLMTasks { // Convert version flag to channel (see subscription object), starting CSV and approval starategy flags.version = VersionHelper.removeVPrefix(flags.version, true) // Need to point to specific CSV - ctx.startingCSV = `eclipse-che.v${flags.version}` + if (flags['starting-csv']) { + ctx.startingCSV = flags['starting-csv'] + } else if (flags['olm-channel'] === OLM_STABLE_CHANNEL_NAME) { + ctx.startingCSV = `eclipse-che.v${flags.version}` + } else if (flags['olm-channel'] === OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME) { + ctx.startingCSV = `eclipse-che-preview-openshift.v${flags.version}-all-namespaces` + } // else use latest in the channel // Set approval starategy to manual to prevent autoupdate to the latest version right before installation ctx.approvalStarategy = 'Manual' } else { @@ -159,7 +165,7 @@ export class OLMTasks { // custom Che CatalogSource const catalogSourceNamespace = flags['catalog-source-namespace'] || ctx.operatorNamespace subscription = this.constructSubscription(ctx.subscriptionName, flags['package-manifest-name'], ctx.operatorNamespace, catalogSourceNamespace, flags['olm-channel'], ctx.sourceName, ctx.approvalStarategy, ctx.startingCSV) - } else if (VersionHelper.isDeployingStableVersion(flags) || flags['olm-channel'] === OLM_STABLE_CHANNEL_NAME) { + } else if (flags['olm-channel'] === OLM_STABLE_CHANNEL_NAME || (VersionHelper.isDeployingStableVersion(flags) && !flags['olm-channel'])) { // stable Che CatalogSource subscription = this.constructSubscription(ctx.subscriptionName, DEFAULT_CHE_OLM_PACKAGE_NAME, ctx.operatorNamespace, ctx.defaultCatalogSourceNamespace, OLM_STABLE_CHANNEL_NAME, ctx.catalogSourceNameStable, ctx.approvalStarategy, ctx.startingCSV) } else if (flags['olm-channel'] === OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME) { @@ -353,13 +359,27 @@ export class OLMTasks { task.title = `${task.title}...OK` }, }, + { + title: `Delete(OLM) Eclipse Che cluster service versions for all namespaces mode`, + enabled: ctx => ctx.isPreInstalledOLM && flags.chenamespace !== ctx.operatorNamespace, + task: async (ctx: any, task: any) => { + const csvs = await kube.getClusterServiceVersions(ctx.operatorNamespace) + const csvsToDelete = csvs.items.filter(csv => csv.metadata.name!.startsWith(CVS_PREFIX)) + for (const csv of csvsToDelete) { + await kube.deleteClusterServiceVersion(ctx.operatorNamespace, csv.metadata.name!) + } + task.title = `${task.title}...OK` + }, + }, { title: 'Delete(OLM) Eclipse Che cluster service versions', enabled: ctx => ctx.isPreInstalledOLM, task: async (_ctx: any, task: any) => { const csvs = await kube.getClusterServiceVersions(flags.chenamespace) const csvsToDelete = csvs.items.filter(csv => csv.metadata.name!.startsWith(CVS_PREFIX)) - csvsToDelete.forEach(csv => kube.deleteClusterServiceVersion(flags.chenamespace, csv.metadata.name!)) + for (const csv of csvsToDelete) { + await kube.deleteClusterServiceVersion(flags.chenamespace, csv.metadata.name!) + } task.title = `${task.title}...OK` }, }, From 1f6507c09091acff4841ab710d429ee8e8450940 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Wed, 22 Sep 2021 09:05:02 +0300 Subject: [PATCH 06/21] Fix linter error Signed-off-by: Mykola Morhun --- src/tasks/installers/olm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tasks/installers/olm.ts b/src/tasks/installers/olm.ts index abac8564b..16ae4b12d 100644 --- a/src/tasks/installers/olm.ts +++ b/src/tasks/installers/olm.ts @@ -360,7 +360,7 @@ export class OLMTasks { }, }, { - title: `Delete(OLM) Eclipse Che cluster service versions for all namespaces mode`, + title: 'Delete(OLM) Eclipse Che cluster service versions for all namespaces mode', enabled: ctx => ctx.isPreInstalledOLM && flags.chenamespace !== ctx.operatorNamespace, task: async (ctx: any, task: any) => { const csvs = await kube.getClusterServiceVersions(ctx.operatorNamespace) From 2296642b3f1c0f33257774b3a0e1640b3714f677 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Wed, 22 Sep 2021 14:34:56 +0300 Subject: [PATCH 07/21] Fixes according to review Signed-off-by: Mykola Morhun --- README.md | 6 +++--- src/api/kube.ts | 5 ++--- src/commands/server/restore.ts | 20 +++++++++++--------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 26c0a77b9..5e735c6ee 100644 --- a/README.md +++ b/README.md @@ -672,9 +672,9 @@ OPTIONS -s, --snapshot-id=snapshot-id ID of a snapshot to restore from -v, --version=version Che Operator version to restore to (e.g. 7.35.1). - Must comply with the version in backup snapshot. - Defaults to the existing operator version or to chectl - version if none deployed. + Must comply with the version in used backup snapshot. + Defaults to the existing operator version or to chectl version + if none deployed. --aws-access-key-id=aws-access-key-id AWS access key ID diff --git a/src/api/kube.ts b/src/api/kube.ts index 82b16945c..a004402c4 100644 --- a/src/api/kube.ts +++ b/src/api/kube.ts @@ -1199,17 +1199,16 @@ export class KubeHelper { } } - async deleteDeployment(namespace: string, name: string): Promise { + async deleteDeployment(namespace: string, name: string): Promise { const k8sAppsApi = this.kubeConfig.makeApiClient(AppsV1Api) try { k8sAppsApi.deleteNamespacedDeployment(name, namespace) } catch (error) { if (error.response && error.response.statusCode === 404) { - return false + return } throw this.wrapK8sClientError(error) } - return true } async deleteAllDeployments(namespace: string): Promise { diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index edb31da13..f17870895 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -72,11 +72,10 @@ export default class Restore extends Command { }), version: string({ char: 'v', - description: ` - Che Operator version to restore to (e.g. 7.35.1). - Must comply with the version in backup snapshot. - Defaults to the existing operator version or to chectl version if none deployed. - `, + description: + 'Che Operator version to restore to (e.g. 7.35.1). ' + + 'Must comply with the version in used backup snapshot. ' + + 'Defaults to the existing operator version or to chectl version if none deployed.', required: false, }), 'backup-cr': string({ @@ -127,6 +126,7 @@ export default class Restore extends Command { if (!operatorDeploymentYaml) { // Still no operator deployment found ctx.currentOperatorVersion = '' + ctx.isOperatorDeployed = false task.title = `${task.title} operator is not deployed` return } @@ -137,14 +137,15 @@ export default class Restore extends Command { throw new Error(`Failed to find Che operator version in '${OPERATOR_DEPLOYMENT_NAME}' deployment in '${flags.chenamespace}' namespace`) } ctx.currentOperatorVersion = currentVersionEnvVar.value + ctx.isOperatorDeployed = true task.title = `${task.title} ${ctx.currentOperatorVersion} found` }, }, { title: 'Detecting installer...', // It is possible to skip '&& (flags.version || flags.rollback || flags['backup-cr'])' in the enabled condition below, - // as ctx.currentOperatorVersion is set only if previous task, that has the condition, is executed. - enabled: (ctx: any) => ctx.currentOperatorVersion && !flags['olm-channel'], + // as ctx.isOperatorDeployed is set only if previous task, that has the condition, is executed. + enabled: (ctx: any) => ctx.isOperatorDeployed, task: async (ctx: any, task: any) => { const kube = new KubeHelper(flags) @@ -166,7 +167,7 @@ export default class Restore extends Command { return } - // As ctx.currentOperatorVersion is set, the operator deployment exists + // As isOperatorDeployed is set, the operator deployment exists flags.installer = 'operator' task.title = `${task.title}Operator` }, @@ -274,7 +275,7 @@ export default class Restore extends Command { }, { title: 'Remove current Che operator', - enabled: (ctx: any) => flags.installer === 'olm' && ctx.currentOperatorVersion && flags.version && ctx.currentOperatorVersion !== flags.version, + enabled: (ctx: any) => ctx.isOperatorDeployed && flags.installer === 'olm' && flags.version && ctx.currentOperatorVersion !== flags.version, task: async (ctx: any, _task: any) => { // All preparations and validations must be done before this task! // Delete old operator if any in case of OLM installer. @@ -291,6 +292,7 @@ export default class Restore extends Command { // Use plain operator on Kubernetes or if it is requested instead of OLM if (!ctx.isOpenshift || flags.installer === 'operator') { const operatorTasks = new OperatorTasks() + // Update tasks can deploy operator as well const operatorUpdateTasks = operatorTasks.updateTasks(flags, this) // Remove last tasks that deploys CR (it will be done on restore) operatorUpdateTasks.splice(-1) From 59a3be9dfcd0314a878526d9e7c405740d5fa2f3 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Thu, 23 Sep 2021 10:09:18 +0300 Subject: [PATCH 08/21] Fixes accoeding to review Signed-off-by: Mykola Morhun --- src/commands/server/deploy.ts | 34 ++++++++-------- src/commands/server/restore.ts | 74 +++++++++++++++------------------- src/tasks/installers/olm.ts | 1 + 3 files changed, 51 insertions(+), 58 deletions(-) diff --git a/src/commands/server/deploy.ts b/src/commands/server/deploy.ts index b7b05d4ea..6d06c3aa2 100644 --- a/src/commands/server/deploy.ts +++ b/src/commands/server/deploy.ts @@ -210,7 +210,7 @@ export default class Deploy extends Command { } if (!flags.installer) { - await this.setDefaultInstaller(flags, ctx) + await setDefaultInstaller(flags) cli.info(`› Installer type is set to: '${flags.installer}'`) } @@ -457,24 +457,24 @@ export default class Deploy extends Command { notifyCommandCompletedSuccessfully() this.exit(0) } +} - /** - * Sets default installer which is `olm` for OpenShift 4 with stable version of chectl - * and `operator` for other cases. - */ - async setDefaultInstaller(flags: any, _ctx: any): Promise { - const kubeHelper = new KubeHelper(flags) +/** + * Sets default installer which is `olm` for OpenShift 4 with stable version of chectl + * and `operator` for other cases. + */ +export async function setDefaultInstaller(flags: any): Promise { + const kubeHelper = new KubeHelper(flags) - const isOlmPreinstalled = await kubeHelper.isPreInstalledOLM() - if ((flags['catalog-source-name'] || flags['catalog-source-yaml']) && isOlmPreinstalled) { - flags.installer = 'olm' - return - } + const isOlmPreinstalled = await kubeHelper.isPreInstalledOLM() + if ((flags['catalog-source-name'] || flags['catalog-source-yaml']) && isOlmPreinstalled) { + flags.installer = 'olm' + return + } - if (flags.platform === 'openshift' && await kubeHelper.isOpenShift4() && isOlmPreinstalled) { - flags.installer = 'olm' - } else { - flags.installer = 'operator' - } + if (flags.platform === 'openshift' && await kubeHelper.isOpenShift4() && isOlmPreinstalled) { + flags.installer = 'olm' + } else { + flags.installer = 'operator' } } diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index f17870895..31b1e4b5e 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -28,6 +28,7 @@ import { findWorkingNamespace, getCommandSuccessMessage, getEmbeddedTemplatesDir import { V1CheClusterBackup, V1CheClusterRestore, V1CheClusterRestoreStatus } from '../../api/typings/backup-restore-crds' import { awsAccessKeyId, awsSecretAccessKey, AWS_ACCESS_KEY_ID_KEY, AWS_SECRET_ACCESS_KEY_KEY, backupRepositoryPassword, backupRepositoryUrl, backupRestServerPassword, backupRestServerUsername, backupServerConfigName, BACKUP_REPOSITORY_PASSWORD_KEY, BACKUP_REPOSITORY_URL_KEY, BACKUP_REST_SERVER_PASSWORD_KEY, BACKUP_REST_SERVER_USERNAME_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY, getBackupServerConfiguration, sshKey, sshKeyFile, SSH_KEY_FILE_KEY, SSH_KEY_KEY } from './backup' +import { setDefaultInstaller } from './deploy' const RESTORE_CR_NAME = 'eclipse-che-restore' @@ -48,7 +49,7 @@ export default class Restore extends Command { '# Rollback to previous version (if it was installed):\n' + 'chectl server:restore --rollback', '# Restore from specific backup object:\n' + - 'chectl server:restore --backup-cr=backup-object-name', + 'chectl server:restore --backup-cr-name=backup-object-name', '# Restore from specific backup of different version:\n' + 'chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p repopassword', ] @@ -78,7 +79,7 @@ export default class Restore extends Command { 'Defaults to the existing operator version or to chectl version if none deployed.', required: false, }), - 'backup-cr': string({ + 'backup-cr-name': string({ description: 'Name of a backup custom resource to restore from', required: false, exclusive: ['version', 'snapshot-id', BACKUP_REPOSITORY_URL_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY], @@ -86,7 +87,7 @@ export default class Restore extends Command { rollback: boolean({ description: 'Rolling back to previous version of Eclipse Che if a backup of that version is available', required: false, - exclusive: ['version', 'snapshot-id', 'backup-cr', BACKUP_REPOSITORY_URL_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY], + exclusive: ['version', 'snapshot-id', 'backup-cr-name', BACKUP_REPOSITORY_URL_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY], }), } @@ -112,10 +113,10 @@ export default class Restore extends Command { } private getRestoreTasks(flags: any): Listr.ListrTask[] { + const kube = new KubeHelper(flags) return [ { title: 'Detecting existing operator version...', - enabled: () => flags.version || flags.rollback || flags['backup-cr'], task: async (ctx: any, task: any) => { const kube = new KubeHelper(flags) let operatorDeploymentYaml = await kube.getDeployment(OPERATOR_DEPLOYMENT_NAME, flags.chenamespace) @@ -143,11 +144,19 @@ export default class Restore extends Command { }, { title: 'Detecting installer...', - // It is possible to skip '&& (flags.version || flags.rollback || flags['backup-cr'])' in the enabled condition below, - // as ctx.isOperatorDeployed is set only if previous task, that has the condition, is executed. - enabled: (ctx: any) => ctx.isOperatorDeployed, task: async (ctx: any, task: any) => { - const kube = new KubeHelper(flags) + if (!ctx.isOperatorDeployed) { + setDefaultInstaller(flags) + if (flags.installer === 'olm') { + if (await kube.operatorSubscriptionExists(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME)) { + flags['olm-channel'] = OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME + } else { + flags['olm-channel'] = OLM_STABLE_CHANNEL_NAME + } + } + task.title = `${task.title}${flags.installer}` + return + } if (await kube.operatorSubscriptionExists(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME)) { // OLM in all namespaces mode @@ -176,10 +185,10 @@ export default class Restore extends Command { title: 'Looking for corresponding backup object...', enabled: () => flags.rollback, task: async (ctx: any, task: any) => { - const currentOperatorVersion: string | undefined = ctx.currentOperatorVersion - if (!currentOperatorVersion) { + if (!ctx.isOperatorDeployed) { throw new Error('Che operator not found. Cannot detect version to use.') } + const currentOperatorVersion: string = ctx.currentOperatorVersion const backupCrName = 'backup-before-update-to-' + currentOperatorVersion.replace(/\./g, '-') const kube = new KubeHelper(flags) @@ -190,15 +199,15 @@ export default class Restore extends Command { if (!backupCr.status || backupCr.status.state !== 'Succeeded') { throw new Error(`Backup with name '${backupCrName}' is not successful`) } - flags['backup-cr'] = backupCrName + flags['backup-cr-name'] = backupCrName task.title = `${task.title} ${backupCrName} found` }, }, { title: 'Gathering information about backup...', - enabled: () => flags['backup-cr'], + enabled: () => flags['backup-cr-name'], task: async (_ctx: any, task: any) => { - const backupCrName = flags['backup-cr'] + const backupCrName = flags['backup-cr-name'] const kube = new KubeHelper(flags) const backupCr: V1CheClusterBackup | undefined = await kube.getCustomResource(flags.chenamespace, backupCrName, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL) if (!backupCr) { @@ -229,33 +238,6 @@ export default class Restore extends Command { task.title = `${task.title}OK` }, }, - { - title: 'Detecting additional parameters...', - task: async (ctx: any, task: any) => { - const kube = new KubeHelper(flags) - ctx.isOpenshift = await kube.isOpenShift() - - // Set defaults for some parameters if they weren't set - if (!flags.installer) { - flags.installer = ctx.isOpenshift ? 'olm' : 'operator' - } - if (flags.installer === 'olm' && !flags['olm-channel']) { - if (await kube.operatorSubscriptionExists(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME)) { - flags['olm-channel'] = OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME - } else { - flags['olm-channel'] = OLM_STABLE_CHANNEL_NAME - } - } - if (ctx.isOpenshift) { - flags.platform = 'openshift' - flags['cluster-monitoring'] = true - } else { - flags.platform = 'kubernetes' - } - - task.title = `${task.title}OK` - }, - }, { title: 'Getting installation templates...', enabled: (ctx: any) => flags.version && ctx.currentOperatorVersion !== flags.version && !flags.templates, @@ -289,8 +271,18 @@ export default class Restore extends Command { title: 'Deploy requested version of Che operator', enabled: (ctx: any) => flags.version && ctx.currentOperatorVersion !== flags.version, task: async (ctx: any, _task: any) => { + const isOpenshift = await kube.isOpenShift() + // Set defaults for some parameters if they weren't set + if (isOpenshift) { + flags.platform = 'openshift' + flags['cluster-monitoring'] = true + } else { + flags.platform = 'kubernetes' + // TODO domain ? + } + // Use plain operator on Kubernetes or if it is requested instead of OLM - if (!ctx.isOpenshift || flags.installer === 'operator') { + if (!isOpenshift || flags.installer === 'operator') { const operatorTasks = new OperatorTasks() // Update tasks can deploy operator as well const operatorUpdateTasks = operatorTasks.updateTasks(flags, this) diff --git a/src/tasks/installers/olm.ts b/src/tasks/installers/olm.ts index 16ae4b12d..ebec80630 100644 --- a/src/tasks/installers/olm.ts +++ b/src/tasks/installers/olm.ts @@ -385,6 +385,7 @@ export class OLMTasks { }, { title: 'Delete(OLM) operator group', + // Do not delete global operator group if operator is in all namespaces mode enabled: ctx => ctx.isPreInstalledOLM && ctx.operatorNamespace !== DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, task: async (ctx: any, task: any) => { const opgr = ctx.operatorGroup From 5361d351102bcfbb1cbc062e73c27f1f580f218e Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Thu, 23 Sep 2021 13:25:15 +0300 Subject: [PATCH 09/21] Fixes Signed-off-by: Mykola Morhun --- src/commands/server/restore.ts | 8 +++----- src/tasks/installers/olm.ts | 16 ++-------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index 31b1e4b5e..4e0bff2a4 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -75,7 +75,6 @@ export default class Restore extends Command { char: 'v', description: 'Che Operator version to restore to (e.g. 7.35.1). ' + - 'Must comply with the version in used backup snapshot. ' + 'Defaults to the existing operator version or to chectl version if none deployed.', required: false, }), @@ -278,13 +277,12 @@ export default class Restore extends Command { flags['cluster-monitoring'] = true } else { flags.platform = 'kubernetes' - // TODO domain ? } - // Use plain operator on Kubernetes or if it is requested instead of OLM - if (!isOpenshift || flags.installer === 'operator') { + if (flags.installer === 'operator') { const operatorTasks = new OperatorTasks() - // Update tasks can deploy operator as well + // Update tasks can also deploy operator. If a resource already exist, it will be replaced. + // When operator of requested version is deployed, then restore will rollout data from the backup. const operatorUpdateTasks = operatorTasks.updateTasks(flags, this) // Remove last tasks that deploys CR (it will be done on restore) operatorUpdateTasks.splice(-1) diff --git a/src/tasks/installers/olm.ts b/src/tasks/installers/olm.ts index ebec80630..2b105e39d 100644 --- a/src/tasks/installers/olm.ts +++ b/src/tasks/installers/olm.ts @@ -360,8 +360,8 @@ export class OLMTasks { }, }, { - title: 'Delete(OLM) Eclipse Che cluster service versions for all namespaces mode', - enabled: ctx => ctx.isPreInstalledOLM && flags.chenamespace !== ctx.operatorNamespace, + title: 'Delete(OLM) Eclipse Che cluster service versions', + enabled: ctx => ctx.isPreInstalledOLM, task: async (ctx: any, task: any) => { const csvs = await kube.getClusterServiceVersions(ctx.operatorNamespace) const csvsToDelete = csvs.items.filter(csv => csv.metadata.name!.startsWith(CVS_PREFIX)) @@ -371,18 +371,6 @@ export class OLMTasks { task.title = `${task.title}...OK` }, }, - { - title: 'Delete(OLM) Eclipse Che cluster service versions', - enabled: ctx => ctx.isPreInstalledOLM, - task: async (_ctx: any, task: any) => { - const csvs = await kube.getClusterServiceVersions(flags.chenamespace) - const csvsToDelete = csvs.items.filter(csv => csv.metadata.name!.startsWith(CVS_PREFIX)) - for (const csv of csvsToDelete) { - await kube.deleteClusterServiceVersion(flags.chenamespace, csv.metadata.name!) - } - task.title = `${task.title}...OK` - }, - }, { title: 'Delete(OLM) operator group', // Do not delete global operator group if operator is in all namespaces mode From dc090cf0c2704f4e32660bbb2d09ab212d17e91b Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Thu, 23 Sep 2021 16:13:56 +0300 Subject: [PATCH 10/21] Changes after rebasing conflicting files Signed-off-by: Mykola Morhun --- src/commands/server/restore.ts | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index 4e0bff2a4..4fdca056e 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -14,9 +14,10 @@ import { Command, flags } from '@oclif/command' import { boolean, string } from '@oclif/parser/lib/flags' import * as Listr from 'listr' -import { CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL, CHE_CLUSTER_RESTORE_KIND_PLURAL, DEFAULT_ANALYTIC_HOOK_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME, OLM_STABLE_CHANNEL_NAME, OPERATOR_DEPLOYMENT_NAME, SUBSCRIPTION_NAME } from '../../constants' +import { CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL, CHE_CLUSTER_RESTORE_KIND_PLURAL, DEFAULT_ANALYTIC_HOOK_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, OLM_STABLE_CHANNEL_NAME, OPERATOR_DEPLOYMENT_NAME } from '../../constants' import { ChectlContext } from '../../api/context' import { KubeHelper } from '../../api/kube' +import { CheHelper } from '../../api/che' import { cheNamespace } from '../../common-flags' import { requestRestore } from '../../api/backup-restore' import { cli } from 'cli-ux' @@ -113,6 +114,7 @@ export default class Restore extends Command { private getRestoreTasks(flags: any): Listr.ListrTask[] { const kube = new KubeHelper(flags) + const che = new CheHelper(flags) return [ { title: 'Detecting existing operator version...', @@ -147,30 +149,19 @@ export default class Restore extends Command { if (!ctx.isOperatorDeployed) { setDefaultInstaller(flags) if (flags.installer === 'olm') { - if (await kube.operatorSubscriptionExists(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME)) { - flags['olm-channel'] = OLM_STABLE_ALL_NAMESPACES_CHANNEL_NAME - } else { - flags['olm-channel'] = OLM_STABLE_CHANNEL_NAME - } + flags['olm-channel'] = OLM_STABLE_CHANNEL_NAME + task.title = `${task.title}OLM` + } else { + task.title = `${task.title}Operator` } - task.title = `${task.title}${flags.installer}` return } - if (await kube.operatorSubscriptionExists(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME)) { - // OLM in all namespaces mode - const operatorSubscriptionYaml = await kube.getOperatorSubscription(SUBSCRIPTION_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME) + const subscription = await che.findCheOperatorSubscription(flags.chenamespace) + if (subscription) { + // OLM flags.installer = 'olm' - flags['olm-channel'] = operatorSubscriptionYaml.spec.channel - task.title = `${task.title}OLM` - return - } - - if (await kube.operatorSubscriptionExists(SUBSCRIPTION_NAME, flags.chenamespace)) { - // OLM in single namespace mode - const operatorSubscriptionYaml = await kube.getOperatorSubscription(SUBSCRIPTION_NAME, flags.chenamespace) - flags.installer = 'olm' - flags['olm-channel'] = operatorSubscriptionYaml.spec.channel + flags['olm-channel'] = subscription.spec.channel task.title = `${task.title}OLM` return } From 3a0a9063b5abc61329bffcfd08191a63c6cfbeee Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Thu, 23 Sep 2021 16:22:46 +0300 Subject: [PATCH 11/21] Update readme Signed-off-by: Mykola Morhun --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5e735c6ee..874639f80 100644 --- a/README.md +++ b/README.md @@ -671,16 +671,15 @@ OPTIONS -s, --snapshot-id=snapshot-id ID of a snapshot to restore from - -v, --version=version Che Operator version to restore to (e.g. 7.35.1). - Must comply with the version in used backup snapshot. - Defaults to the existing operator version or to chectl version - if none deployed. + -v, --version=version Che Operator version to restore to (e.g. 7.35.1). Defaults to + the existing operator version or to chectl version if none + deployed. --aws-access-key-id=aws-access-key-id AWS access key ID --aws-secret-access-key=aws-secret-access-key AWS secret access key - --backup-cr=backup-cr Name of a backup custom resource to restore from + --backup-cr-name=backup-cr-name Name of a backup custom resource to restore from --backup-server-config-name=backup-server-config-name Name of custom resource with backup server config @@ -710,7 +709,7 @@ EXAMPLES # Rollback to previous version (if it was installed): chectl server:restore --rollback # Restore from specific backup object: - chectl server:restore --backup-cr=backup-object-name + chectl server:restore --backup-cr-name=backup-object-name # Restore from specific backup of different version: chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p repopassword From 98accb73eba62ad1755f0bb35d844a2e7cc826aa Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Fri, 24 Sep 2021 15:01:14 +0300 Subject: [PATCH 12/21] Fixes according to review Signed-off-by: Mykola Morhun --- README.md | 7 ++++--- src/commands/server/restore.ts | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 874639f80..212f067a3 100644 --- a/README.md +++ b/README.md @@ -700,11 +700,12 @@ EXAMPLES chectl server:restore # Restore from specific backup snapshot using previos backup configuration: chectl server:restore -s 585421f3 - # Create and use configuration for REST backup server: + # Restore from latest snapshot located in provided REST backup server: chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword - # Create and use configuration for AWS S3 (and API compatible) backup server (bucket should be precreated): + # Restore from latest snapshot located in provided AWS S3 (or API compatible) backup server (bucket should be + precreated): chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword - # Create and use configuration for SFTP backup server: + # Restore from latest snapshot located in provided SFTP backup server: chectl server:restore -r=sftp:user@my-server.net:/srv/sftp/che-data -p repopassword # Rollback to previous version (if it was installed): chectl server:restore --rollback diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index 4fdca056e..95141c6ba 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -41,11 +41,11 @@ export default class Restore extends Command { 'chectl server:restore', '# Restore from specific backup snapshot using previos backup configuration:\n' + 'chectl server:restore -s 585421f3', - '# Create and use configuration for REST backup server:\n' + + '# Restore from latest snapshot located in provided REST backup server:\n' + 'chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword', - '# Create and use configuration for AWS S3 (and API compatible) backup server (bucket should be precreated):\n' + + '# Restore from latest snapshot located in provided AWS S3 (or API compatible) backup server (bucket should be precreated):\n' + 'chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword', - '# Create and use configuration for SFTP backup server:\n' + + '# Restore from latest snapshot located in provided SFTP backup server:\n' + 'chectl server:restore -r=sftp:user@my-server.net:/srv/sftp/che-data -p repopassword', '# Rollback to previous version (if it was installed):\n' + 'chectl server:restore --rollback', @@ -78,6 +78,7 @@ export default class Restore extends Command { 'Che Operator version to restore to (e.g. 7.35.1). ' + 'Defaults to the existing operator version or to chectl version if none deployed.', required: false, + dependsOn: ['snapshot-id'], }), 'backup-cr-name': string({ description: 'Name of a backup custom resource to restore from', From 66bd6bad75b60918bd2430ef2d3f98dfd34f392a Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Tue, 28 Sep 2021 17:43:43 +0300 Subject: [PATCH 13/21] Add restore server information output and confirmation dialog Signed-off-by: Mykola Morhun --- README.md | 8 ++- src/api/backup-restore.ts | 2 +- src/commands/server/restore.ts | 103 ++++++++++++++++++++++++++++++--- src/util.ts | 33 +++++++++++ 4 files changed, 136 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 212f067a3..997b7cce3 100644 --- a/README.md +++ b/README.md @@ -683,6 +683,8 @@ OPTIONS --backup-server-config-name=backup-server-config-name Name of custom resource with backup server config + --batch Batch mode. Running a command without end user interaction. + --password=password Authentication password for backup REST server --rollback Rolling back to previous version of Eclipse Che if a backup of @@ -699,14 +701,14 @@ EXAMPLES # Reuse existing backup configuration: chectl server:restore # Restore from specific backup snapshot using previos backup configuration: - chectl server:restore -s 585421f3 + chectl server:restore --snapshot-id=585421f3 # Restore from latest snapshot located in provided REST backup server: chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword # Restore from latest snapshot located in provided AWS S3 (or API compatible) backup server (bucket should be precreated): chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword # Restore from latest snapshot located in provided SFTP backup server: - chectl server:restore -r=sftp:user@my-server.net:/srv/sftp/che-data -p repopassword + chectl server:restore -r sftp:user@my-server.net:/srv/sftp/che-data -p repopassword # Rollback to previous version (if it was installed): chectl server:restore --rollback # Restore from specific backup object: @@ -714,6 +716,8 @@ EXAMPLES # Restore from specific backup of different version: chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p repopassword + # Restore from the latest backup of different version: + chectl server:restore --version=7.36.1 --snapshot-id=latest ``` _See code: [src/commands/server/restore.ts](https://github.com/che-incubator/chectl/blob/v0.0.2/src/commands/server/restore.ts)_ diff --git a/src/api/backup-restore.ts b/src/api/backup-restore.ts index 72daf125d..1eeecd626 100644 --- a/src/api/backup-restore.ts +++ b/src/api/backup-restore.ts @@ -110,7 +110,7 @@ export async function requestRestore(namespace: string, name: string, backupServ * @param backupServerConfig backup server configuration data or name of the backup server config CR * @returns name of existing backup server configuration in the given namespace or empty string if none suitable */ -async function getBackupServerConfigurationName(namespace: string, backupServerConfig?: BackupServerConfig | string): Promise { +export async function getBackupServerConfigurationName(namespace: string, backupServerConfig?: BackupServerConfig | string): Promise { const kube = new KubeHelper() if (backupServerConfig) { diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index 95141c6ba..4067666f4 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -14,19 +14,20 @@ import { Command, flags } from '@oclif/command' import { boolean, string } from '@oclif/parser/lib/flags' import * as Listr from 'listr' -import { CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL, CHE_CLUSTER_RESTORE_KIND_PLURAL, DEFAULT_ANALYTIC_HOOK_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, OLM_STABLE_CHANNEL_NAME, OPERATOR_DEPLOYMENT_NAME } from '../../constants' +import { CHE_BACKUP_SERVER_CONFIG_KIND_PLURAL, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL, CHE_CLUSTER_RESTORE_KIND_PLURAL, DEFAULT_ANALYTIC_HOOK_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, OLM_STABLE_CHANNEL_NAME, OPERATOR_DEPLOYMENT_NAME } from '../../constants' +import { batch, listrRenderer } from '../../common-flags' import { ChectlContext } from '../../api/context' import { KubeHelper } from '../../api/kube' import { CheHelper } from '../../api/che' import { cheNamespace } from '../../common-flags' -import { requestRestore } from '../../api/backup-restore' +import { getBackupServerConfigurationName, parseBackupServerConfig, requestRestore } from '../../api/backup-restore' import { cli } from 'cli-ux' import { ApiTasks } from '../../tasks/platforms/api' import { OLMTasks } from '../../tasks/installers/olm' import { OperatorTasks } from '../../tasks/installers/operator' import { checkChectlAndCheVersionCompatibility, downloadTemplates } from '../../tasks/installers/common-tasks' -import { findWorkingNamespace, getCommandSuccessMessage, getEmbeddedTemplatesDirectory, notifyCommandCompletedSuccessfully, wrapCommandError } from '../../util' -import { V1CheClusterBackup, V1CheClusterRestore, V1CheClusterRestoreStatus } from '../../api/typings/backup-restore-crds' +import { confirmYN, findWorkingNamespace, getCommandSuccessMessage, getEmbeddedTemplatesDirectory, notifyCommandCompletedSuccessfully, wrapCommandError } from '../../util' +import { V1CheBackupServerConfiguration, V1CheClusterBackup, V1CheClusterRestore, V1CheClusterRestoreStatus } from '../../api/typings/backup-restore-crds' import { awsAccessKeyId, awsSecretAccessKey, AWS_ACCESS_KEY_ID_KEY, AWS_SECRET_ACCESS_KEY_KEY, backupRepositoryPassword, backupRepositoryUrl, backupRestServerPassword, backupRestServerUsername, backupServerConfigName, BACKUP_REPOSITORY_PASSWORD_KEY, BACKUP_REPOSITORY_URL_KEY, BACKUP_REST_SERVER_PASSWORD_KEY, BACKUP_REST_SERVER_USERNAME_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY, getBackupServerConfiguration, sshKey, sshKeyFile, SSH_KEY_FILE_KEY, SSH_KEY_KEY } from './backup' import { setDefaultInstaller } from './deploy' @@ -40,24 +41,28 @@ export default class Restore extends Command { '# Reuse existing backup configuration:\n' + 'chectl server:restore', '# Restore from specific backup snapshot using previos backup configuration:\n' + - 'chectl server:restore -s 585421f3', + 'chectl server:restore --snapshot-id=585421f3', '# Restore from latest snapshot located in provided REST backup server:\n' + 'chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword', '# Restore from latest snapshot located in provided AWS S3 (or API compatible) backup server (bucket should be precreated):\n' + 'chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword', '# Restore from latest snapshot located in provided SFTP backup server:\n' + - 'chectl server:restore -r=sftp:user@my-server.net:/srv/sftp/che-data -p repopassword', + 'chectl server:restore -r sftp:user@my-server.net:/srv/sftp/che-data -p repopassword', '# Rollback to previous version (if it was installed):\n' + 'chectl server:restore --rollback', '# Restore from specific backup object:\n' + 'chectl server:restore --backup-cr-name=backup-object-name', '# Restore from specific backup of different version:\n' + 'chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p repopassword', + '# Restore from the latest backup of different version:\n' + + 'chectl server:restore --version=7.36.1 --snapshot-id=latest', ] static flags: flags.Input = { help: flags.help({ char: 'h' }), chenamespace: cheNamespace, + 'listr-renderer': listrRenderer, + batch, [BACKUP_REPOSITORY_URL_KEY]: backupRepositoryUrl, [BACKUP_REPOSITORY_PASSWORD_KEY]: backupRepositoryPassword, [BACKUP_REST_SERVER_USERNAME_KEY]: backupRestServerUsername, @@ -148,7 +153,7 @@ export default class Restore extends Command { title: 'Detecting installer...', task: async (ctx: any, task: any) => { if (!ctx.isOperatorDeployed) { - setDefaultInstaller(flags) + await setDefaultInstaller(flags) if (flags.installer === 'olm') { flags['olm-channel'] = OLM_STABLE_CHANNEL_NAME task.title = `${task.title}OLM` @@ -246,6 +251,90 @@ export default class Restore extends Command { task.title = `${task.title}OK` }, }, + { + title: 'Print restore information', + task: async (ctx: any) => { + const outputLines: string[] = [] + + // Che Operator version change + if (ctx.isOperatorDeployed && flags.version && ctx.currentOperatorVersion !== flags.version) { + outputLines.push(`Change Che version from ${ctx.currentOperatorVersion} to ${flags.version}`) + } else if (!ctx.isOperatorDeployed && flags.version) { + outputLines.push(`Deploy Che version ${flags.version}`) + } else if (ctx.isOperatorDeployed && !flags.version) { + outputLines.push(`Che version is ${flags.version}`) + } + + // Backup server configuration + try { + let backupServerConfigCr: V1CheBackupServerConfiguration + if (ctx.backupServerConfig && typeof ctx.backupServerConfig !== 'string') { + // ctx.backupServerConfig is BackupServerConfig + backupServerConfigCr = parseBackupServerConfig(ctx.backupServerConfig) + } else { + // ctx.backupServerConfig is string | undefined + const backupServerConfigName = ctx.backupServerConfig ? ctx.backupServerConfig : await getBackupServerConfigurationName(flags.chenamespace) + backupServerConfigCr = await kube.getCustomResource(flags.chenamespace, backupServerConfigName, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_BACKUP_SERVER_CONFIG_KIND_PLURAL) + if (!backupServerConfigCr) { + throw new Error('No backup server config found, nothing to restore from') + } + } + + let hostname: string + let repository: string + if (backupServerConfigCr.spec.rest) { + hostname = backupServerConfigCr.spec.rest.hostname + repository = backupServerConfigCr.spec.rest.repositoryPath + } else if (backupServerConfigCr.spec.awss3) { + hostname = backupServerConfigCr.spec.awss3.hostname || 's3.amazonaws.com' + repository = backupServerConfigCr.spec.awss3.repositoryPath + } else if (backupServerConfigCr.spec.sftp) { + hostname = backupServerConfigCr.spec.sftp.hostname + repository = backupServerConfigCr.spec.sftp.repositoryPath + } else { + throw new Error('Unknown backup server config type') + } + + outputLines.push(`Use ${repository} backup repository on ${hostname}`) + } catch (error) { + const details = error.message ? `: ${error.message}` : '' + outputLines.push(`Failed to get backup server info ${details}`) + } + + // Backup snapshot id + const snapshotId = flags['snapshot-id'] ? flags['snapshot-id'] : 'latest' + outputLines.push(`Use ${snapshotId} snapshot to restore Che`) + + // Print information + const printTasks = new Listr([], ctx.listrOptions) + for (const line of outputLines) { + printTasks.add({ + title: line, + task: () => { }, + }) + } + return printTasks + }, + }, + { + title: 'Asking for restore confirmation: Do you want to proceed? [y/n]', + enabled: () => !flags.batch, + task: async (_ctx: any, task: any) => { + let isConfirmed: boolean + try { + isConfirmed = await confirmYN() + } catch { + isConfirmed = false + } + + if (isConfirmed) { + task.title = 'Asking for restore confirmation...OK' + } else { + task.title = 'Asking for restore confirmation...Cancelled' + this.exit(0) + } + }, + }, { title: 'Remove current Che operator', enabled: (ctx: any) => ctx.isOperatorDeployed && flags.installer === 'olm' && flags.version && ctx.currentOperatorVersion !== flags.version, diff --git a/src/util.ts b/src/util.ts index 9c21c67d6..dc901ca34 100644 --- a/src/util.ts +++ b/src/util.ts @@ -21,6 +21,7 @@ import * as yaml from 'js-yaml' import * as notifier from 'node-notifier' import * as os from 'os' import * as path from 'path' +import * as readline from 'readline' import { promisify } from 'util' import { ChectlContext } from './api/context' @@ -345,3 +346,35 @@ export function addTrailingSlash(url: string): string { } return url + '/' } + +/** + * Waits until y or n is pressed (no return press needed) and returns confirmation result. + * ctrl+c causes exception. + * This function doesn't print anything, this is the task of the code that invokes it. + */ +export function confirmYN(): Promise { + return new Promise((resolve, reject) => { + readline.emitKeypressEvents(process.stdin) + if (process.stdin.isTTY) { + process.stdin.setRawMode(true) + } + + const keyPressHandler = (_string: any, key: any) => { + // Handle brake + if (key.ctrl && key.name === 'c') { + process.stdin.removeListener('keypress', keyPressHandler) + reject('Interrupted') + } + + // Check if y or n pressed + if (key.name === 'y' || key.name === 'Y') { + process.stdin.removeListener('keypress', keyPressHandler) + resolve(true) + } else if (key.name === 'n' || key.name === 'N') { + process.stdin.removeListener('keypress', keyPressHandler) + resolve(false) + } + } + process.stdin.on('keypress', keyPressHandler) + }) +} From 9fe725600cadc3d5f5d677238e54e890ad9e11ba Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Wed, 29 Sep 2021 11:50:58 +0300 Subject: [PATCH 14/21] Fix i/o Signed-off-by: Mykola Morhun --- src/commands/server/restore.ts | 2 +- src/util.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index 4067666f4..24182e52a 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -262,7 +262,7 @@ export default class Restore extends Command { } else if (!ctx.isOperatorDeployed && flags.version) { outputLines.push(`Deploy Che version ${flags.version}`) } else if (ctx.isOperatorDeployed && !flags.version) { - outputLines.push(`Che version is ${flags.version}`) + outputLines.push(`Che version is ${ctx.currentOperatorVersion}`) } // Backup server configuration diff --git a/src/util.ts b/src/util.ts index dc901ca34..172f79b6a 100644 --- a/src/util.ts +++ b/src/util.ts @@ -359,19 +359,23 @@ export function confirmYN(): Promise { process.stdin.setRawMode(true) } + const removeKeyPressHandler = () => { + process.stdin.removeListener('keypress', keyPressHandler) + process.stdin.setRawMode(false) + } const keyPressHandler = (_string: any, key: any) => { // Handle brake if (key.ctrl && key.name === 'c') { - process.stdin.removeListener('keypress', keyPressHandler) + removeKeyPressHandler() reject('Interrupted') } // Check if y or n pressed if (key.name === 'y' || key.name === 'Y') { - process.stdin.removeListener('keypress', keyPressHandler) + removeKeyPressHandler() resolve(true) } else if (key.name === 'n' || key.name === 'N') { - process.stdin.removeListener('keypress', keyPressHandler) + removeKeyPressHandler() resolve(false) } } From 0050e005f2e920f01cd42c67e2d023d3d31ab9ae Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Thu, 30 Sep 2021 12:40:03 +0300 Subject: [PATCH 15/21] Changes according to review Signed-off-by: Mykola Morhun --- README.md | 28 +++++------ src/commands/server/backup.ts | 2 +- src/commands/server/restore.ts | 86 +++++++++++++++++++--------------- src/tasks/installers/olm.ts | 17 +++++-- 4 files changed, 75 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 997b7cce3..3adcefac2 100644 --- a/README.md +++ b/README.md @@ -669,11 +669,11 @@ OPTIONS -r, --repository-url=repository-url Full address of backup repository. Format is identical to restic. - -s, --snapshot-id=snapshot-id ID of a snapshot to restore from + -s, --snapshot-id=snapshot-id (required) ID of a snapshot to restore from. Value "latest" + means restoring from the most recent snapshot. - -v, --version=version Che Operator version to restore to (e.g. 7.35.1). Defaults to - the existing operator version or to chectl version if none - deployed. + -v, --version=version Che Operator version to restore to (e.g. 7.35.1). If the flag + is not set, restore to the current version. --aws-access-key-id=aws-access-key-id AWS access key ID @@ -698,26 +698,24 @@ OPTIONS --username=username Username for authentication in backup REST server EXAMPLES - # Reuse existing backup configuration: - chectl server:restore # Restore from specific backup snapshot using previos backup configuration: chectl server:restore --snapshot-id=585421f3 # Restore from latest snapshot located in provided REST backup server: - chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword + chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword --snapshot-id=latest # Restore from latest snapshot located in provided AWS S3 (or API compatible) backup server (bucket should be precreated): - chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword + chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword --snapshot-id=latest # Restore from latest snapshot located in provided SFTP backup server: - chectl server:restore -r sftp:user@my-server.net:/srv/sftp/che-data -p repopassword - # Rollback to previous version (if it was installed): + chectl server:restore -r sftp:user@my-server.net:/srv/sftp/che-data -p repopassword --snapshot-id=latest + # Restore from the latest backup of different version: + chectl server:restore --version=7.36.1 --snapshot-id=latest + # Restore from specific backup located on another backup server and of different version: + chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p + repopassword + # Rollback to the previous version (if it was installed): chectl server:restore --rollback # Restore from specific backup object: chectl server:restore --backup-cr-name=backup-object-name - # Restore from specific backup of different version: - chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p - repopassword - # Restore from the latest backup of different version: - chectl server:restore --version=7.36.1 --snapshot-id=latest ``` _See code: [src/commands/server/restore.ts](https://github.com/che-incubator/chectl/blob/v0.0.2/src/commands/server/restore.ts)_ diff --git a/src/commands/server/backup.ts b/src/commands/server/backup.ts index d3afddd2a..49fdc6b40 100644 --- a/src/commands/server/backup.ts +++ b/src/commands/server/backup.ts @@ -172,7 +172,7 @@ export default class Backup extends Command { if (backupStatus.stage) { task.title = `Waiting until backup process finishes: ${backupStatus.stage}` } - } while (backupStatus.state === 'InProgress') + } while (!backupStatus.state || backupStatus.state === 'InProgress') if (backupStatus.state === 'Failed') { throw new Error(`Failed to create backup: ${backupStatus.message}`) diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index 24182e52a..d7993fc66 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -23,7 +23,7 @@ import { cheNamespace } from '../../common-flags' import { getBackupServerConfigurationName, parseBackupServerConfig, requestRestore } from '../../api/backup-restore' import { cli } from 'cli-ux' import { ApiTasks } from '../../tasks/platforms/api' -import { OLMTasks } from '../../tasks/installers/olm' +import { CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE_TASK_TITLE, DELETE_CUSTOM_CATALOG_SOURCE_TASK_TITLE, DELETE_NIGHTLY_CATALOG_SOURCE_TASK_TITLE, DELETE_OPERATOR_GROUP_TASK_TITLE, OLMTasks, SET_CUSTOM_OPERATOR_IMAGE_TASK_TITLE } from '../../tasks/installers/olm' import { OperatorTasks } from '../../tasks/installers/operator' import { checkChectlAndCheVersionCompatibility, downloadTemplates } from '../../tasks/installers/common-tasks' import { confirmYN, findWorkingNamespace, getCommandSuccessMessage, getEmbeddedTemplatesDirectory, notifyCommandCompletedSuccessfully, wrapCommandError } from '../../util' @@ -38,24 +38,22 @@ export default class Restore extends Command { static description = 'Restore Eclipse Che installation' static examples = [ - '# Reuse existing backup configuration:\n' + - 'chectl server:restore', '# Restore from specific backup snapshot using previos backup configuration:\n' + 'chectl server:restore --snapshot-id=585421f3', '# Restore from latest snapshot located in provided REST backup server:\n' + - 'chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword', + 'chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword --snapshot-id=latest', '# Restore from latest snapshot located in provided AWS S3 (or API compatible) backup server (bucket should be precreated):\n' + - 'chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword', + 'chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword --snapshot-id=latest', '# Restore from latest snapshot located in provided SFTP backup server:\n' + - 'chectl server:restore -r sftp:user@my-server.net:/srv/sftp/che-data -p repopassword', - '# Rollback to previous version (if it was installed):\n' + + 'chectl server:restore -r sftp:user@my-server.net:/srv/sftp/che-data -p repopassword --snapshot-id=latest', + '# Restore from the latest backup of different version:\n' + + 'chectl server:restore --version=7.36.1 --snapshot-id=latest', + '# Restore from specific backup located on another backup server and of different version:\n' + + 'chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p repopassword', + '# Rollback to the previous version (if it was installed):\n' + 'chectl server:restore --rollback', '# Restore from specific backup object:\n' + 'chectl server:restore --backup-cr-name=backup-object-name', - '# Restore from specific backup of different version:\n' + - 'chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p repopassword', - '# Restore from the latest backup of different version:\n' + - 'chectl server:restore --version=7.36.1 --snapshot-id=latest', ] static flags: flags.Input = { @@ -74,14 +72,16 @@ export default class Restore extends Command { [BACKUP_SERVER_CONFIG_CR_NAME_KEY]: backupServerConfigName, 'snapshot-id': string({ char: 's', - description: 'ID of a snapshot to restore from', - required: false, + description: + 'ID of a snapshot to restore from. ' + + 'Value "latest" means restoring from the most recent snapshot.', + required: true, }), version: string({ char: 'v', description: 'Che Operator version to restore to (e.g. 7.35.1). ' + - 'Defaults to the existing operator version or to chectl version if none deployed.', + 'If the flag is not set, restore to the current version.', required: false, dependsOn: ['snapshot-id'], }), @@ -236,7 +236,7 @@ export default class Restore extends Command { }, { title: 'Getting installation templates...', - enabled: (ctx: any) => flags.version && ctx.currentOperatorVersion !== flags.version && !flags.templates, + enabled: (ctx: any) => flags.version && ctx.currentOperatorVersion !== flags.version, task: async (ctx: any, task: any) => { if (flags.installer === 'olm') { // Using embedded templates here as they are used in install flow for OLM. @@ -254,18 +254,13 @@ export default class Restore extends Command { { title: 'Print restore information', task: async (ctx: any) => { - const outputLines: string[] = [] - - // Che Operator version change - if (ctx.isOperatorDeployed && flags.version && ctx.currentOperatorVersion !== flags.version) { - outputLines.push(`Change Che version from ${ctx.currentOperatorVersion} to ${flags.version}`) - } else if (!ctx.isOperatorDeployed && flags.version) { - outputLines.push(`Deploy Che version ${flags.version}`) - } else if (ctx.isOperatorDeployed && !flags.version) { - outputLines.push(`Che version is ${ctx.currentOperatorVersion}`) - } + const currentVersion = ctx.isOperatorDeployed ? ctx.currentOperatorVersion : 'Not deployed' + const restoreVersion = flags.version ? flags.version : currentVersion + const snapshotId = flags['snapshot-id'] ? flags['snapshot-id'] : 'latest' - // Backup server configuration + // Get backup server configuration + let backupServer: string + let backupRepository: string try { let backupServerConfigCr: V1CheBackupServerConfiguration if (ctx.backupServerConfig && typeof ctx.backupServerConfig !== 'string') { @@ -281,31 +276,41 @@ export default class Restore extends Command { } let hostname: string + let port: number | undefined let repository: string if (backupServerConfigCr.spec.rest) { hostname = backupServerConfigCr.spec.rest.hostname + port = backupServerConfigCr.spec.rest.port repository = backupServerConfigCr.spec.rest.repositoryPath } else if (backupServerConfigCr.spec.awss3) { hostname = backupServerConfigCr.spec.awss3.hostname || 's3.amazonaws.com' + port = backupServerConfigCr.spec.awss3.port repository = backupServerConfigCr.spec.awss3.repositoryPath } else if (backupServerConfigCr.spec.sftp) { hostname = backupServerConfigCr.spec.sftp.hostname + port = backupServerConfigCr.spec.sftp.port repository = backupServerConfigCr.spec.sftp.repositoryPath } else { throw new Error('Unknown backup server config type') } - outputLines.push(`Use ${repository} backup repository on ${hostname}`) + backupServer = hostname + (port ? `:${port}` : '') + backupRepository = repository } catch (error) { const details = error.message ? `: ${error.message}` : '' - outputLines.push(`Failed to get backup server info ${details}`) + backupServer = `Failed to get backup server info ${details}` + backupRepository = 'Unknown' } - // Backup snapshot id - const snapshotId = flags['snapshot-id'] ? flags['snapshot-id'] : 'latest' - outputLines.push(`Use ${snapshotId} snapshot to restore Che`) + const outputLines = [ + `Current version: ${currentVersion}`, + `Restore version: ${restoreVersion}`, + `Backup server: ${backupServer}`, + `Backup repository: ${backupRepository}`, + `Snapshot: ${snapshotId}`, + ] - // Print information + // Print the information const printTasks = new Listr([], ctx.listrOptions) for (const line of outputLines) { printTasks.add({ @@ -336,19 +341,26 @@ export default class Restore extends Command { }, }, { - title: 'Remove current Che operator', - enabled: (ctx: any) => ctx.isOperatorDeployed && flags.installer === 'olm' && flags.version && ctx.currentOperatorVersion !== flags.version, + title: 'Uninstall Eclipse Che operator', + enabled: (ctx: any) => ctx.isOperatorDeployed && flags.version && ctx.currentOperatorVersion !== flags.version && flags.installer === 'olm', task: async (ctx: any, _task: any) => { // All preparations and validations must be done before this task! // Delete old operator if any in case of OLM installer. // For Operator installer, the operator deployment will be downgraded if needed. const olmTasks = new OLMTasks() - const olmDeleteTasks = olmTasks.deleteTasks(flags) + let olmDeleteTasks = olmTasks.deleteTasks(flags) + const tasksToDelete = [ + DELETE_OPERATOR_GROUP_TASK_TITLE, + DELETE_CUSTOM_CATALOG_SOURCE_TASK_TITLE, + DELETE_NIGHTLY_CATALOG_SOURCE_TASK_TITLE, + ] + olmDeleteTasks = olmDeleteTasks.filter(task => tasksToDelete.indexOf(task.title) === -1) return new Listr(olmDeleteTasks, ctx.listrOptions) }, }, { title: 'Deploy requested version of Che operator', + // Deploy new operator only if there is a request for a different version given through flags.version enabled: (ctx: any) => flags.version && ctx.currentOperatorVersion !== flags.version, task: async (ctx: any, _task: any) => { const isOpenshift = await kube.isOpenShift() @@ -376,8 +388,8 @@ export default class Restore extends Command { olmInstallTasks.splice(-2) // Remove other redundant for restoring tasks const tasksToDelete = [ - 'Create custom catalog source from file', - 'Set custom operator image', + CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE_TASK_TITLE, + SET_CUSTOM_OPERATOR_IMAGE_TASK_TITLE, ] olmInstallTasks = olmInstallTasks.filter(task => tasksToDelete.indexOf(task.title) === -1) diff --git a/src/tasks/installers/olm.ts b/src/tasks/installers/olm.ts index 2b105e39d..a7f893ad8 100644 --- a/src/tasks/installers/olm.ts +++ b/src/tasks/installers/olm.ts @@ -25,6 +25,13 @@ import { isKubernetesPlatformFamily } from '../../util' import { createEclipseCheCluster, createNamespaceTask, patchingEclipseCheCluster } from './common-tasks' +export const SET_CUSTOM_OPERATOR_IMAGE_TASK_TITLE = 'Set custom operator image' +export const CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE_TASK_TITLE = 'Create custom catalog source from file' + +export const DELETE_OPERATOR_GROUP_TASK_TITLE = 'Delete(OLM) operator group' +export const DELETE_CUSTOM_CATALOG_SOURCE_TASK_TITLE = `Delete(OLM) custom catalog source ${CUSTOM_CATALOG_SOURCE_NAME}` +export const DELETE_NIGHTLY_CATALOG_SOURCE_TASK_TITLE = `Delete(OLM) nigthly catalog source ${NEXT_CATALOG_SOURCE_NAME}` + export class OLMTasks { prometheusRoleName = 'prometheus-k8s' @@ -135,8 +142,8 @@ export class OLMTasks { }, }, { + title: CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE_TASK_TITLE, enabled: () => flags['catalog-source-yaml'], - title: 'Create custom catalog source from file', task: async (ctx: any, task: any) => { const customCatalogSource: CatalogSource = kube.readCatalogSourceFromFile(flags['catalog-source-yaml']) if (!await kube.catalogSourceExists(customCatalogSource.metadata!.name!, flags.chenamespace)) { @@ -203,7 +210,7 @@ export class OLMTasks { }, }, { - title: 'Set custom operator image', + title: SET_CUSTOM_OPERATOR_IMAGE_TASK_TITLE, enabled: () => flags['che-operator-image'], task: async (_ctx: any, task: any) => { const csvList = await kube.getClusterServiceVersions(flags.chenamespace) @@ -372,7 +379,7 @@ export class OLMTasks { }, }, { - title: 'Delete(OLM) operator group', + title: DELETE_OPERATOR_GROUP_TASK_TITLE, // Do not delete global operator group if operator is in all namespaces mode enabled: ctx => ctx.isPreInstalledOLM && ctx.operatorNamespace !== DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, task: async (ctx: any, task: any) => { @@ -384,14 +391,14 @@ export class OLMTasks { }, }, { - title: `Delete(OLM) custom catalog source ${CUSTOM_CATALOG_SOURCE_NAME}`, + title: DELETE_CUSTOM_CATALOG_SOURCE_TASK_TITLE, task: async (ctx: any, task: any) => { await kube.deleteCatalogSource(ctx.operatorNamespace, CUSTOM_CATALOG_SOURCE_NAME) task.title = `${task.title}...OK` }, }, { - title: `Delete(OLM) nigthly catalog source ${NEXT_CATALOG_SOURCE_NAME}`, + title: DELETE_NIGHTLY_CATALOG_SOURCE_TASK_TITLE, task: async (ctx: any, task: any) => { await kube.deleteCatalogSource(ctx.operatorNamespace, NEXT_CATALOG_SOURCE_NAME) task.title = `${task.title}...OK` From f44de3b83975302eb76b61b33d4a718bcd287ede Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Fri, 1 Oct 2021 10:13:52 +0300 Subject: [PATCH 16/21] More review fixes Signed-off-by: Mykola Morhun --- README.md | 6 ++--- src/commands/server/restore.ts | 36 +++++++++++++++------------- src/tasks/installers/common-tasks.ts | 10 ++++++-- src/tasks/installers/olm.ts | 23 +++++++++--------- 4 files changed, 42 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 3adcefac2..839d1aecf 100644 --- a/README.md +++ b/README.md @@ -669,8 +669,8 @@ OPTIONS -r, --repository-url=repository-url Full address of backup repository. Format is identical to restic. - -s, --snapshot-id=snapshot-id (required) ID of a snapshot to restore from. Value "latest" - means restoring from the most recent snapshot. + -s, --snapshot-id=snapshot-id (required) snapshot identificator to restore from. Value + "latest" means restoring from the most recent snapshot. -v, --version=version Che Operator version to restore to (e.g. 7.35.1). If the flag is not set, restore to the current version. @@ -698,8 +698,6 @@ OPTIONS --username=username Username for authentication in backup REST server EXAMPLES - # Restore from specific backup snapshot using previos backup configuration: - chectl server:restore --snapshot-id=585421f3 # Restore from latest snapshot located in provided REST backup server: chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword --snapshot-id=latest # Restore from latest snapshot located in provided AWS S3 (or API compatible) backup server (bucket should be diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index d7993fc66..d2266a8c0 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -23,9 +23,9 @@ import { cheNamespace } from '../../common-flags' import { getBackupServerConfigurationName, parseBackupServerConfig, requestRestore } from '../../api/backup-restore' import { cli } from 'cli-ux' import { ApiTasks } from '../../tasks/platforms/api' -import { CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE_TASK_TITLE, DELETE_CUSTOM_CATALOG_SOURCE_TASK_TITLE, DELETE_NIGHTLY_CATALOG_SOURCE_TASK_TITLE, DELETE_OPERATOR_GROUP_TASK_TITLE, OLMTasks, SET_CUSTOM_OPERATOR_IMAGE_TASK_TITLE } from '../../tasks/installers/olm' +import { TASK_TITLE_CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE, TASK_TITLE_DELETE_CUSTOM_CATALOG_SOURCE, TASK_TITLE_DELETE_NIGHTLY_CATALOG_SOURCE, TASK_TITLE_DELETE_OPERATOR_GROUP, OLMTasks, TASK_TITLE_SET_CUSTOM_OPERATOR_IMAGE, TASK_TITLE_PREPARE_CHE_CLUSTER_CR } from '../../tasks/installers/olm' import { OperatorTasks } from '../../tasks/installers/operator' -import { checkChectlAndCheVersionCompatibility, downloadTemplates } from '../../tasks/installers/common-tasks' +import { checkChectlAndCheVersionCompatibility, downloadTemplates, TASK_TITLE_CREATE_CHE_CLUSTER_CRD, TASK_TITLE_PATCH_CHECLUSTER_CR } from '../../tasks/installers/common-tasks' import { confirmYN, findWorkingNamespace, getCommandSuccessMessage, getEmbeddedTemplatesDirectory, notifyCommandCompletedSuccessfully, wrapCommandError } from '../../util' import { V1CheBackupServerConfiguration, V1CheClusterBackup, V1CheClusterRestore, V1CheClusterRestoreStatus } from '../../api/typings/backup-restore-crds' @@ -38,8 +38,6 @@ export default class Restore extends Command { static description = 'Restore Eclipse Che installation' static examples = [ - '# Restore from specific backup snapshot using previos backup configuration:\n' + - 'chectl server:restore --snapshot-id=585421f3', '# Restore from latest snapshot located in provided REST backup server:\n' + 'chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword --snapshot-id=latest', '# Restore from latest snapshot located in provided AWS S3 (or API compatible) backup server (bucket should be precreated):\n' + @@ -73,7 +71,7 @@ export default class Restore extends Command { 'snapshot-id': string({ char: 's', description: - 'ID of a snapshot to restore from. ' + + 'snapshot identificator to restore from. ' + 'Value "latest" means restoring from the most recent snapshot.', required: true, }), @@ -350,9 +348,9 @@ export default class Restore extends Command { const olmTasks = new OLMTasks() let olmDeleteTasks = olmTasks.deleteTasks(flags) const tasksToDelete = [ - DELETE_OPERATOR_GROUP_TASK_TITLE, - DELETE_CUSTOM_CATALOG_SOURCE_TASK_TITLE, - DELETE_NIGHTLY_CATALOG_SOURCE_TASK_TITLE, + TASK_TITLE_DELETE_OPERATOR_GROUP, + TASK_TITLE_DELETE_CUSTOM_CATALOG_SOURCE, + TASK_TITLE_DELETE_NIGHTLY_CATALOG_SOURCE, ] olmDeleteTasks = olmDeleteTasks.filter(task => tasksToDelete.indexOf(task.title) === -1) return new Listr(olmDeleteTasks, ctx.listrOptions) @@ -376,20 +374,26 @@ export default class Restore extends Command { const operatorTasks = new OperatorTasks() // Update tasks can also deploy operator. If a resource already exist, it will be replaced. // When operator of requested version is deployed, then restore will rollout data from the backup. - const operatorUpdateTasks = operatorTasks.updateTasks(flags, this) - // Remove last tasks that deploys CR (it will be done on restore) - operatorUpdateTasks.splice(-1) + let operatorUpdateTasks = operatorTasks.updateTasks(flags, this) + // Remove redundant for restoring tasks + const tasksToDelete = [ + TASK_TITLE_PATCH_CHECLUSTER_CR, + ] + operatorUpdateTasks = operatorUpdateTasks.filter(task => tasksToDelete.indexOf(task.title) === -1) + return new Listr(operatorUpdateTasks, ctx.listrOptions) } else { // OLM on Openshift const olmTasks = new OLMTasks() let olmInstallTasks = olmTasks.startTasks(flags, this) - // Remove last tasks that deploys CR (it will be done on restore) - olmInstallTasks.splice(-2) - // Remove other redundant for restoring tasks + // Remove redundant for restoring tasks const tasksToDelete = [ - CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE_TASK_TITLE, - SET_CUSTOM_OPERATOR_IMAGE_TASK_TITLE, + // Remove customization and dev tasks + TASK_TITLE_CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE, + TASK_TITLE_SET_CUSTOM_OPERATOR_IMAGE, + // Remove tasks that deploys CR (it will be done on restore) + TASK_TITLE_PREPARE_CHE_CLUSTER_CR, + TASK_TITLE_CREATE_CHE_CLUSTER_CRD, ] olmInstallTasks = olmInstallTasks.filter(task => tasksToDelete.indexOf(task.title) === -1) diff --git a/src/tasks/installers/common-tasks.ts b/src/tasks/installers/common-tasks.ts index 7573f302f..52c12f086 100644 --- a/src/tasks/installers/common-tasks.ts +++ b/src/tasks/installers/common-tasks.ts @@ -26,6 +26,9 @@ import { VersionHelper } from '../../api/version' import { CHE_CLUSTER_CRD, DOCS_LINK_IMPORT_CA_CERT_INTO_BROWSER, OPERATOR_TEMPLATE_DIR } from '../../constants' import { getProjectVersion } from '../../util' +export const TASK_TITLE_CREATE_CHE_CLUSTER_CRD = `Create the Custom Resource of type ${CHE_CLUSTER_CRD}` +export const TASK_TITLE_PATCH_CHECLUSTER_CR = `Patching the Custom Resource of type ${CHE_CLUSTER_CRD}` + export function createNamespaceTask(namespaceName: string, labels: {}): Listr.ListrTask { return { title: `Create Namespace (${namespaceName})`, @@ -117,9 +120,11 @@ export function downloadTemplates(flags: any): Listr.ListrTask { export function createEclipseCheCluster(flags: any, kube: KubeHelper): Listr.ListrTask { return { - title: `Create the Custom Resource of type ${CHE_CLUSTER_CRD} in the namespace ${flags.chenamespace}`, + title: TASK_TITLE_CREATE_CHE_CLUSTER_CRD, enabled: ctx => Boolean(ctx.customCR) || Boolean(ctx.defaultCR), task: async (ctx: any, task: any) => { + task.title = `${task.title} in the namespace ${flags.chenamespace}` + ctx.isCheDeployed = true ctx.isPostgresDeployed = true ctx.isKeycloakDeployed = true @@ -160,9 +165,10 @@ export function createEclipseCheCluster(flags: any, kube: KubeHelper): Listr.Lis */ export function patchingEclipseCheCluster(flags: any, kube: KubeHelper, command: Command): Listr.ListrTask { return { - title: `Patching the Custom Resource of type '${CHE_CLUSTER_CRD}' in the namespace '${flags.chenamespace}'`, + title: TASK_TITLE_PATCH_CHECLUSTER_CR, skip: (ctx: any) => isEmpty(ctx[ChectlContext.CR_PATCH]), task: async (ctx: any, task: any) => { + task.title = `${task.title} in the namespace ${flags.chenamespace}` const cheCluster = await kube.getCheCluster(flags.chenamespace) if (!cheCluster) { command.error(`Eclipse Che cluster CR is not found in the namespace '${flags.chenamespace}'`) diff --git a/src/tasks/installers/olm.ts b/src/tasks/installers/olm.ts index a7f893ad8..752f983c6 100644 --- a/src/tasks/installers/olm.ts +++ b/src/tasks/installers/olm.ts @@ -25,12 +25,13 @@ import { isKubernetesPlatformFamily } from '../../util' import { createEclipseCheCluster, createNamespaceTask, patchingEclipseCheCluster } from './common-tasks' -export const SET_CUSTOM_OPERATOR_IMAGE_TASK_TITLE = 'Set custom operator image' -export const CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE_TASK_TITLE = 'Create custom catalog source from file' +export const TASK_TITLE_SET_CUSTOM_OPERATOR_IMAGE = 'Set custom operator image' +export const TASK_TITLE_CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE = 'Create custom catalog source from file' +export const TASK_TITLE_PREPARE_CHE_CLUSTER_CR = 'Prepare Eclipse Che cluster CR' -export const DELETE_OPERATOR_GROUP_TASK_TITLE = 'Delete(OLM) operator group' -export const DELETE_CUSTOM_CATALOG_SOURCE_TASK_TITLE = `Delete(OLM) custom catalog source ${CUSTOM_CATALOG_SOURCE_NAME}` -export const DELETE_NIGHTLY_CATALOG_SOURCE_TASK_TITLE = `Delete(OLM) nigthly catalog source ${NEXT_CATALOG_SOURCE_NAME}` +export const TASK_TITLE_DELETE_OPERATOR_GROUP = 'Delete(OLM) operator group' +export const TASK_TITLE_DELETE_CUSTOM_CATALOG_SOURCE = `Delete(OLM) custom catalog source ${CUSTOM_CATALOG_SOURCE_NAME}` +export const TASK_TITLE_DELETE_NIGHTLY_CATALOG_SOURCE = `Delete(OLM) nigthly catalog source ${NEXT_CATALOG_SOURCE_NAME}` export class OLMTasks { prometheusRoleName = 'prometheus-k8s' @@ -142,7 +143,7 @@ export class OLMTasks { }, }, { - title: CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE_TASK_TITLE, + title: TASK_TITLE_CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE, enabled: () => flags['catalog-source-yaml'], task: async (ctx: any, task: any) => { const customCatalogSource: CatalogSource = kube.readCatalogSourceFromFile(flags['catalog-source-yaml']) @@ -210,7 +211,7 @@ export class OLMTasks { }, }, { - title: SET_CUSTOM_OPERATOR_IMAGE_TASK_TITLE, + title: TASK_TITLE_SET_CUSTOM_OPERATOR_IMAGE, enabled: () => flags['che-operator-image'], task: async (_ctx: any, task: any) => { const csvList = await kube.getClusterServiceVersions(flags.chenamespace) @@ -224,7 +225,7 @@ export class OLMTasks { }, }, { - title: 'Prepare Eclipse Che cluster CR', + title: TASK_TITLE_PREPARE_CHE_CLUSTER_CR, task: async (ctx: any, task: any) => { const cheCluster = await kube.getCheCluster(flags.chenamespace) if (cheCluster) { @@ -379,7 +380,7 @@ export class OLMTasks { }, }, { - title: DELETE_OPERATOR_GROUP_TASK_TITLE, + title: TASK_TITLE_DELETE_OPERATOR_GROUP, // Do not delete global operator group if operator is in all namespaces mode enabled: ctx => ctx.isPreInstalledOLM && ctx.operatorNamespace !== DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, task: async (ctx: any, task: any) => { @@ -391,14 +392,14 @@ export class OLMTasks { }, }, { - title: DELETE_CUSTOM_CATALOG_SOURCE_TASK_TITLE, + title: TASK_TITLE_DELETE_CUSTOM_CATALOG_SOURCE, task: async (ctx: any, task: any) => { await kube.deleteCatalogSource(ctx.operatorNamespace, CUSTOM_CATALOG_SOURCE_NAME) task.title = `${task.title}...OK` }, }, { - title: DELETE_NIGHTLY_CATALOG_SOURCE_TASK_TITLE, + title: TASK_TITLE_DELETE_NIGHTLY_CATALOG_SOURCE, task: async (ctx: any, task: any) => { await kube.deleteCatalogSource(ctx.operatorNamespace, NEXT_CATALOG_SOURCE_NAME) task.title = `${task.title}...OK` From e18026127cfbe0168402126ed1889e1343b6c39f Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Fri, 1 Oct 2021 15:49:46 +0300 Subject: [PATCH 17/21] Fixes according to review Signed-off-by: Mykola Morhun --- src/commands/server/restore.ts | 46 +++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index d2266a8c0..fd9785ad7 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -14,7 +14,7 @@ import { Command, flags } from '@oclif/command' import { boolean, string } from '@oclif/parser/lib/flags' import * as Listr from 'listr' -import { CHE_BACKUP_SERVER_CONFIG_KIND_PLURAL, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL, CHE_CLUSTER_RESTORE_KIND_PLURAL, DEFAULT_ANALYTIC_HOOK_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, OLM_STABLE_CHANNEL_NAME, OPERATOR_DEPLOYMENT_NAME } from '../../constants' +import { CHE_BACKUP_SERVER_CONFIG_KIND_PLURAL, CHE_CLUSTER_API_GROUP, CHE_CLUSTER_API_VERSION, CHE_CLUSTER_BACKUP_KIND_PLURAL, CHE_CLUSTER_RESTORE_KIND_PLURAL, DEFAULT_ANALYTIC_HOOK_NAME, DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, OPERATOR_DEPLOYMENT_NAME } from '../../constants' import { batch, listrRenderer } from '../../common-flags' import { ChectlContext } from '../../api/context' import { KubeHelper } from '../../api/kube' @@ -30,7 +30,6 @@ import { confirmYN, findWorkingNamespace, getCommandSuccessMessage, getEmbeddedT import { V1CheBackupServerConfiguration, V1CheClusterBackup, V1CheClusterRestore, V1CheClusterRestoreStatus } from '../../api/typings/backup-restore-crds' import { awsAccessKeyId, awsSecretAccessKey, AWS_ACCESS_KEY_ID_KEY, AWS_SECRET_ACCESS_KEY_KEY, backupRepositoryPassword, backupRepositoryUrl, backupRestServerPassword, backupRestServerUsername, backupServerConfigName, BACKUP_REPOSITORY_PASSWORD_KEY, BACKUP_REPOSITORY_URL_KEY, BACKUP_REST_SERVER_PASSWORD_KEY, BACKUP_REST_SERVER_USERNAME_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY, getBackupServerConfiguration, sshKey, sshKeyFile, SSH_KEY_FILE_KEY, SSH_KEY_KEY } from './backup' -import { setDefaultInstaller } from './deploy' const RESTORE_CR_NAME = 'eclipse-che-restore' @@ -38,19 +37,17 @@ export default class Restore extends Command { static description = 'Restore Eclipse Che installation' static examples = [ - '# Restore from latest snapshot located in provided REST backup server:\n' + - 'chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword --snapshot-id=latest', - '# Restore from latest snapshot located in provided AWS S3 (or API compatible) backup server (bucket should be precreated):\n' + + '# Restore from the latest snapshot from a provided REST backup server:\n' + + 'chectl server:restore -r rest:http://my-sert-server.net:4000/che-backup -p repopassword --snapshot-id=latest', + '# Restore from the latest snapshot from a provided AWS S3 (or API compatible) backup server (bucket must be precreated):\n' + 'chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword --snapshot-id=latest', - '# Restore from latest snapshot located in provided SFTP backup server:\n' + + '# Restore from the latest snapshot from a provided SFTP backup server:\n' + 'chectl server:restore -r sftp:user@my-server.net:/srv/sftp/che-data -p repopassword --snapshot-id=latest', - '# Restore from the latest backup of different version:\n' + - 'chectl server:restore --version=7.36.1 --snapshot-id=latest', - '# Restore from specific backup located on another backup server and of different version:\n' + - 'chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p repopassword', - '# Rollback to the previous version (if it was installed):\n' + + '# Restore from a specific snapshot to a given Eclipse Che version from a provided REST backup server:\n' + + 'chectl server:restore -r rest:http://my-sert-server.net:4000/che-backup -p repopassword --version=7.35.2 --snapshot-id=9ea02f58', + '# Rollback to a previous version only if backup exists:\n' + 'chectl server:restore --rollback', - '# Restore from specific backup object:\n' + + '# Restore from a specific backup object:\n' + 'chectl server:restore --backup-cr-name=backup-object-name', ] @@ -89,10 +86,21 @@ export default class Restore extends Command { exclusive: ['version', 'snapshot-id', BACKUP_REPOSITORY_URL_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY], }), rollback: boolean({ - description: 'Rolling back to previous version of Eclipse Che if a backup of that version is available', + description: 'Rolling back to previous version of Eclipse Che only if backup exists', required: false, exclusive: ['version', 'snapshot-id', 'backup-cr-name', BACKUP_REPOSITORY_URL_KEY, BACKUP_SERVER_CONFIG_CR_NAME_KEY], }), + installer: string({ + description: 'Installer type. Should be passed only if restoring without previous installation in place.', + options: ['operator', 'olm'], + hidden: true, + }), + 'olm-channel': string({ + description: + 'Olm channel that was used when backup was done, e.g. "stable".' + + 'Should be passed only if restoring without previous installation in place and installer type is "olm".', + hidden: true, + }), } async run() { @@ -151,9 +159,14 @@ export default class Restore extends Command { title: 'Detecting installer...', task: async (ctx: any, task: any) => { if (!ctx.isOperatorDeployed) { - await setDefaultInstaller(flags) + if (!flags.installer) { + throw new Error('Cannot detect previous installer automatically, provide --installer flag') + } + if (flags.installer === 'olm') { - flags['olm-channel'] = OLM_STABLE_CHANNEL_NAME + if (!flags['olm-channel']) { + throw new Error('Cannot detect OLM channel automatically, provide --olm-channel flag') + } task.title = `${task.title}OLM` } else { task.title = `${task.title}Operator` @@ -382,8 +395,7 @@ export default class Restore extends Command { operatorUpdateTasks = operatorUpdateTasks.filter(task => tasksToDelete.indexOf(task.title) === -1) return new Listr(operatorUpdateTasks, ctx.listrOptions) - } else { - // OLM on Openshift + } else { // OLM const olmTasks = new OLMTasks() let olmInstallTasks = olmTasks.startTasks(flags, this) // Remove redundant for restoring tasks From f9276646a74e07468dd8163f4a9fab11731f60fe Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Fri, 1 Oct 2021 15:55:42 +0300 Subject: [PATCH 18/21] Update readme Signed-off-by: Mykola Morhun --- README.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 839d1aecf..ce238d279 100644 --- a/README.md +++ b/README.md @@ -687,8 +687,8 @@ OPTIONS --password=password Authentication password for backup REST server - --rollback Rolling back to previous version of Eclipse Che if a backup of - that version is available + --rollback Rolling back to previous version of Eclipse Che only if backup + exists --ssh-key=ssh-key Private SSH key for authentication on SFTP server @@ -698,21 +698,19 @@ OPTIONS --username=username Username for authentication in backup REST server EXAMPLES - # Restore from latest snapshot located in provided REST backup server: - chectl server:resotre -r rest:http://my-sert-server.net:4000/che-backup -p repopassword --snapshot-id=latest - # Restore from latest snapshot located in provided AWS S3 (or API compatible) backup server (bucket should be + # Restore from the latest snapshot from a provided REST backup server: + chectl server:restore -r rest:http://my-sert-server.net:4000/che-backup -p repopassword --snapshot-id=latest + # Restore from the latest snapshot from a provided AWS S3 (or API compatible) backup server (bucket must be precreated): chectl server:restore -r s3:s3.amazonaws.com/bucketche -p repopassword --snapshot-id=latest - # Restore from latest snapshot located in provided SFTP backup server: + # Restore from the latest snapshot from a provided SFTP backup server: chectl server:restore -r sftp:user@my-server.net:/srv/sftp/che-data -p repopassword --snapshot-id=latest - # Restore from the latest backup of different version: - chectl server:restore --version=7.36.1 --snapshot-id=latest - # Restore from specific backup located on another backup server and of different version: - chectl server:restore --version=7.35.2 --snapshot-id=9ea02f58 -r rest:http://my-sert-server.net:4000/che-backup -p - repopassword - # Rollback to the previous version (if it was installed): + # Restore from a specific snapshot to a given Eclipse Che version from a provided REST backup server: + chectl server:restore -r rest:http://my-sert-server.net:4000/che-backup -p repopassword --version=7.35.2 + --snapshot-id=9ea02f58 + # Rollback to a previous version only if backup exists: chectl server:restore --rollback - # Restore from specific backup object: + # Restore from a specific backup object: chectl server:restore --backup-cr-name=backup-object-name ``` From 3bece28573d26325c3c9ae0f447cb30fff2d9f2b Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Mon, 4 Oct 2021 09:27:17 +0300 Subject: [PATCH 19/21] Fix snapshot-id flag Signed-off-by: Mykola Morhun --- src/commands/server/restore.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index fd9785ad7..cb007c1ea 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -70,7 +70,7 @@ export default class Restore extends Command { description: 'snapshot identificator to restore from. ' + 'Value "latest" means restoring from the most recent snapshot.', - required: true, + required: false, }), version: string({ char: 'v', @@ -110,6 +110,10 @@ export default class Restore extends Command { await this.config.runHook(DEFAULT_ANALYTIC_HOOK_NAME, { command: Restore.id, flags }) + if (!flags['snapshot-id'] && !(flags.rollback || flags['backup-cr-name'])) { + this.error('"--snapshot-id" flag is required') + } + const tasks = new Listr([], ctx.listrOptions) const apiTasks = new ApiTasks() tasks.add(apiTasks.testApiTasks(flags)) From 48a578bae02859626b9e09934df0f1eb34a33e97 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Mon, 4 Oct 2021 09:43:45 +0300 Subject: [PATCH 20/21] Update readme Signed-off-by: Mykola Morhun --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ce238d279..ed917c628 100644 --- a/README.md +++ b/README.md @@ -669,8 +669,8 @@ OPTIONS -r, --repository-url=repository-url Full address of backup repository. Format is identical to restic. - -s, --snapshot-id=snapshot-id (required) snapshot identificator to restore from. Value - "latest" means restoring from the most recent snapshot. + -s, --snapshot-id=snapshot-id snapshot identificator to restore from. Value "latest" means + restoring from the most recent snapshot. -v, --version=version Che Operator version to restore to (e.g. 7.35.1). If the flag is not set, restore to the current version. From 14a68ae84bdb2c5aa5076d5525f1bb22b1014e33 Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Mon, 4 Oct 2021 10:29:16 +0300 Subject: [PATCH 21/21] Recreate Che operator group on restore Signed-off-by: Mykola Morhun --- src/commands/server/restore.ts | 3 +-- src/tasks/installers/olm.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/commands/server/restore.ts b/src/commands/server/restore.ts index cb007c1ea..ec0c186be 100644 --- a/src/commands/server/restore.ts +++ b/src/commands/server/restore.ts @@ -23,7 +23,7 @@ import { cheNamespace } from '../../common-flags' import { getBackupServerConfigurationName, parseBackupServerConfig, requestRestore } from '../../api/backup-restore' import { cli } from 'cli-ux' import { ApiTasks } from '../../tasks/platforms/api' -import { TASK_TITLE_CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE, TASK_TITLE_DELETE_CUSTOM_CATALOG_SOURCE, TASK_TITLE_DELETE_NIGHTLY_CATALOG_SOURCE, TASK_TITLE_DELETE_OPERATOR_GROUP, OLMTasks, TASK_TITLE_SET_CUSTOM_OPERATOR_IMAGE, TASK_TITLE_PREPARE_CHE_CLUSTER_CR } from '../../tasks/installers/olm' +import { TASK_TITLE_CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE, TASK_TITLE_DELETE_CUSTOM_CATALOG_SOURCE, TASK_TITLE_DELETE_NIGHTLY_CATALOG_SOURCE, OLMTasks, TASK_TITLE_SET_CUSTOM_OPERATOR_IMAGE, TASK_TITLE_PREPARE_CHE_CLUSTER_CR } from '../../tasks/installers/olm' import { OperatorTasks } from '../../tasks/installers/operator' import { checkChectlAndCheVersionCompatibility, downloadTemplates, TASK_TITLE_CREATE_CHE_CLUSTER_CRD, TASK_TITLE_PATCH_CHECLUSTER_CR } from '../../tasks/installers/common-tasks' import { confirmYN, findWorkingNamespace, getCommandSuccessMessage, getEmbeddedTemplatesDirectory, notifyCommandCompletedSuccessfully, wrapCommandError } from '../../util' @@ -365,7 +365,6 @@ export default class Restore extends Command { const olmTasks = new OLMTasks() let olmDeleteTasks = olmTasks.deleteTasks(flags) const tasksToDelete = [ - TASK_TITLE_DELETE_OPERATOR_GROUP, TASK_TITLE_DELETE_CUSTOM_CATALOG_SOURCE, TASK_TITLE_DELETE_NIGHTLY_CATALOG_SOURCE, ] diff --git a/src/tasks/installers/olm.ts b/src/tasks/installers/olm.ts index 752f983c6..6fe58ecd9 100644 --- a/src/tasks/installers/olm.ts +++ b/src/tasks/installers/olm.ts @@ -29,7 +29,6 @@ export const TASK_TITLE_SET_CUSTOM_OPERATOR_IMAGE = 'Set custom operator image' export const TASK_TITLE_CREATE_CUSTOM_CATALOG_SOURCE_FROM_FILE = 'Create custom catalog source from file' export const TASK_TITLE_PREPARE_CHE_CLUSTER_CR = 'Prepare Eclipse Che cluster CR' -export const TASK_TITLE_DELETE_OPERATOR_GROUP = 'Delete(OLM) operator group' export const TASK_TITLE_DELETE_CUSTOM_CATALOG_SOURCE = `Delete(OLM) custom catalog source ${CUSTOM_CATALOG_SOURCE_NAME}` export const TASK_TITLE_DELETE_NIGHTLY_CATALOG_SOURCE = `Delete(OLM) nigthly catalog source ${NEXT_CATALOG_SOURCE_NAME}` @@ -380,7 +379,7 @@ export class OLMTasks { }, }, { - title: TASK_TITLE_DELETE_OPERATOR_GROUP, + title: 'Delete(OLM) operator group', // Do not delete global operator group if operator is in all namespaces mode enabled: ctx => ctx.isPreInstalledOLM && ctx.operatorNamespace !== DEFAULT_OPENSHIFT_OPERATORS_NS_NAME, task: async (ctx: any, task: any) => {