From 0d4880d7dd96d5e4d75ac38199ba664d61d51f5b Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Thu, 15 Oct 2020 09:48:39 +0300 Subject: [PATCH] Use Olm CR sample from CSV Signed-off-by: Oleksandr Andriienko --- src/api/kube.ts | 74 +++++++++++++++------------- src/api/typings/olm.d.ts | 5 +- src/tasks/installers/common-tasks.ts | 47 ++++++++---------- src/tasks/installers/olm.ts | 30 +++++++++-- src/tasks/installers/operator.ts | 15 ++++++ 5 files changed, 105 insertions(+), 66 deletions(-) diff --git a/src/api/kube.ts b/src/api/kube.ts index 1e8dd6754..d3c152c92 100644 --- a/src/api/kube.ts +++ b/src/api/kube.ts @@ -24,7 +24,7 @@ import { CHE_CLUSTER_CRD, DEFAULT_CHE_IMAGE, OLM_STABLE_CHANNEL_NAME } from '../ import { getClusterClientCommand, isKubernetesPlatformFamily } from '../util' import { V1alpha2Certificate } from './typings/cert-manager' -import { CatalogSource, ClusterServiceVersionList, InstallPlan, OperatorGroup, PackageManifest, Subscription } from './typings/olm' +import { CatalogSource, ClusterServiceVersion, ClusterServiceVersionList, InstallPlan, OperatorGroup, PackageManifest, Subscription } from './typings/olm' import { IdentityProvider, OAuth } from './typings/openshift' const AWAIT_TIMEOUT_S = 30 @@ -1265,9 +1265,7 @@ export class KubeHelper { } } - async createCheClusterFromFile(filePath: string, flags: any, ctx: any, useDefaultCR: boolean): Promise { - let yamlCr = this.safeLoadFromYamlFile(filePath) - + async createCheCluster(CR: any, flags: any, ctx: any, useDefaultCR: boolean): Promise { const cheNamespace = flags.chenamespace if (useDefaultCR) { // If we don't use an explicitly provided CheCluster CR, @@ -1275,63 +1273,63 @@ export class KubeHelper { // derived from the other parameters const cheImage = flags.cheimage const imageAndTag = cheImage.split(':', 2) - yamlCr.spec.server.cheImage = imageAndTag[0] - yamlCr.spec.server.cheImageTag = imageAndTag.length === 2 ? imageAndTag[1] : 'latest' + CR.spec.server.cheImage = imageAndTag[0] + CR.spec.server.cheImageTag = imageAndTag.length === 2 ? imageAndTag[1] : 'latest' if ((flags.installer === 'olm' && !flags['catalog-source-yaml']) || (flags['catalog-source-yaml'] && flags['olm-channel'] === OLM_STABLE_CHANNEL_NAME)) { // use default image tag for `olm` to install stable Che, because we don't have nightly channel for OLM catalog. - yamlCr.spec.server.cheImageTag = '' + CR.spec.server.cheImageTag = '' } - yamlCr.spec.server.cheDebug = flags.debug ? flags.debug.toString() : 'false' + CR.spec.server.cheDebug = flags.debug ? flags.debug.toString() : 'false' - if (isKubernetesPlatformFamily(flags.platform) || !yamlCr.spec.auth.openShiftoAuth) { - yamlCr.spec.auth.updateAdminPassword = true + if (isKubernetesPlatformFamily(flags.platform) || !CR.spec.auth.openShiftoAuth) { + CR.spec.auth.updateAdminPassword = true } - if (!yamlCr.spec.k8s) { - yamlCr.spec.k8s = {} + if (!CR.spec.k8s) { + CR.spec.k8s = {} } if (flags.tls) { - yamlCr.spec.server.tlsSupport = flags.tls - if (!yamlCr.spec.k8s.tlsSecretName) { - yamlCr.spec.k8s.tlsSecretName = 'che-tls' + CR.spec.server.tlsSupport = flags.tls + if (!CR.spec.k8s.tlsSecretName) { + CR.spec.k8s.tlsSecretName = 'che-tls' } } if (flags.domain) { - yamlCr.spec.k8s.ingressDomain = flags.domain + CR.spec.k8s.ingressDomain = flags.domain } const pluginRegistryUrl = flags['plugin-registry-url'] if (pluginRegistryUrl) { - yamlCr.spec.server.pluginRegistryUrl = pluginRegistryUrl - yamlCr.spec.server.externalPluginRegistry = true + CR.spec.server.pluginRegistryUrl = pluginRegistryUrl + CR.spec.server.externalPluginRegistry = true } const devfileRegistryUrl = flags['devfile-registry-url'] if (devfileRegistryUrl) { - yamlCr.spec.server.devfileRegistryUrl = devfileRegistryUrl - yamlCr.spec.server.externalDevfileRegistry = true + CR.spec.server.devfileRegistryUrl = devfileRegistryUrl + CR.spec.server.externalDevfileRegistry = true } - yamlCr.spec.storage.postgresPVCStorageClassName = flags['postgres-pvc-storage-class-name'] - yamlCr.spec.storage.workspacePVCStorageClassName = flags['workspace-pvc-storage-class-name'] + CR.spec.storage.postgresPVCStorageClassName = flags['postgres-pvc-storage-class-name'] + CR.spec.storage.workspacePVCStorageClassName = flags['workspace-pvc-storage-class-name'] if (flags.cheimage === DEFAULT_CHE_IMAGE && - yamlCr.spec.server.cheImageTag !== 'nightly' && - yamlCr.spec.server.cheImageTag !== 'latest') { + CR.spec.server.cheImageTag !== 'nightly' && + CR.spec.server.cheImageTag !== 'latest') { // We obviously are using a release version of chectl with the default `cheimage` // => We should use the operator defaults for docker images - yamlCr.spec.server.cheImage = '' - yamlCr.spec.server.cheImageTag = '' - yamlCr.spec.server.pluginRegistryImage = '' - yamlCr.spec.server.devfileRegistryImage = '' - yamlCr.spec.auth.identityProviderImage = '' + CR.spec.server.cheImage = '' + CR.spec.server.cheImageTag = '' + CR.spec.server.pluginRegistryImage = '' + CR.spec.server.devfileRegistryImage = '' + CR.spec.auth.identityProviderImage = '' } } - yamlCr = this.overrideDefaultValues(yamlCr, flags['che-operator-cr-patch-yaml']) + CR = this.overrideDefaultValues(CR, flags['che-operator-cr-patch-yaml']) // Back off some configuration properties(chectl estimated them like not working or not desired) - merge(yamlCr, ctx.CROverrides) + merge(CR, ctx.CROverrides) const customObjectsApi = KubeHelper.KUBE_CONFIG.makeApiClient(CustomObjectsApi) try { - const { body } = await customObjectsApi.createNamespacedCustomObject('org.eclipse.che', 'v1', cheNamespace, 'checlusters', yamlCr) + const { body } = await customObjectsApi.createNamespacedCustomObject('org.eclipse.che', 'v1', cheNamespace, 'checlusters', CR) return body } catch (e) { throw this.wrapK8sClientError(e) @@ -1545,7 +1543,7 @@ export class KubeHelper { async createCatalogSource(catalogSource: CatalogSource) { const customObjectsApi = KubeHelper.KUBE_CONFIG.makeApiClient(CustomObjectsApi) try { - const namespace = catalogSource.metadata.namespace + const namespace = catalogSource.metadata.namespace! const { body } = await customObjectsApi.createNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'catalogsources', catalogSource) return body } catch (e) { @@ -1627,7 +1625,7 @@ export class KubeHelper { async createOperatorSubscription(subscription: Subscription) { const customObjectsApi = KubeHelper.KUBE_CONFIG.makeApiClient(CustomObjectsApi) try { - const { body } = await customObjectsApi.createNamespacedCustomObject('operators.coreos.com', 'v1alpha1', subscription.metadata.namespace, 'subscriptions', subscription) + const { body } = await customObjectsApi.createNamespacedCustomObject('operators.coreos.com', 'v1alpha1', subscription.metadata.namespace!, 'subscriptions', subscription) return body } catch (e) { throw this.wrapK8sClientError(e) @@ -1715,7 +1713,7 @@ export class KubeHelper { if (installPlan.status && installPlan.status.conditions) { for (const condition of installPlan.status.conditions) { if (condition.type === 'Installed' && condition.status === 'True') { - resolve() + resolve(installPlan) } } } @@ -1733,6 +1731,12 @@ export class KubeHelper { }) } + async getCSV(csvName: string, namespace: string): Promise { + const csvs = await this.getClusterServiceVersions(namespace) + const csv = csvs.items.find(item => item.metadata.name === csvName) + return csv + } + async getClusterServiceVersions(namespace: string): Promise { const customObjectsApi = KubeHelper.KUBE_CONFIG.makeApiClient(CustomObjectsApi) try { diff --git a/src/api/typings/olm.d.ts b/src/api/typings/olm.d.ts index e98aba326..a57ef2394 100644 --- a/src/api/typings/olm.d.ts +++ b/src/api/typings/olm.d.ts @@ -8,6 +8,8 @@ * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ +import { V1ObjectMeta } from '@kubernetes/client-node'; + export interface OperatorGroup { apiVersion: string; kind: string; @@ -41,6 +43,7 @@ export interface SubscriptionSpec { export interface SubscriptionStatus { conditions: SubscriptionStatusCondition[] currentCSV: string + installedCSV?: string installplan: InstallPlan state: string } @@ -98,7 +101,7 @@ export interface CatalogSourceSpec { mediatype?: string sourceType: string image: string - updateStrategy: CatalogSourceUpdateStrategy + updateStrategy?: CatalogSourceUpdateStrategy } export interface CatalogSourceUpdateStrategy { diff --git a/src/tasks/installers/common-tasks.ts b/src/tasks/installers/common-tasks.ts index f4f6f87ef..1573aaca6 100644 --- a/src/tasks/installers/common-tasks.ts +++ b/src/tasks/installers/common-tasks.ts @@ -62,34 +62,29 @@ async function copyCheOperatorResources(templatesDir: string, cacheDir: string): 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}`, + enabled: ctx => !!ctx.CR, task: async (ctx: any, task: any) => { - const cheCluster = await kube.getCheCluster(flags.chenamespace) - if (cheCluster) { - task.title = `${task.title}...It already exists.` - } else { - // Eclipse Che operator supports only Multi-User Che - ctx.isCheDeployed = true - ctx.isPostgresDeployed = true - ctx.isKeycloakDeployed = true - - // plugin and devfile registry will be deployed only when external ones are not configured - ctx.isPluginRegistryDeployed = !(flags['plugin-registry-url'] as boolean) - ctx.isDevfileRegistryDeployed = !(flags['devfile-registry-url'] as boolean) - - const yamlFilePath = flags['che-operator-cr-yaml'] === '' ? ctx.resourcesPath + 'crds/org_v1_che_cr.yaml' : flags['che-operator-cr-yaml'] - const cr = await kube.createCheClusterFromFile(yamlFilePath, flags, ctx, flags['che-operator-cr-yaml'] === '') - ctx.cr = cr - ctx.isKeycloakReady = ctx.isKeycloakReady || cr.spec.auth.externalIdentityProvider - ctx.isPostgresReady = ctx.isPostgresReady || cr.spec.database.externalDb - ctx.isDevfileRegistryReady = ctx.isDevfileRegistryReady || cr.spec.server.externalDevfileRegistry - ctx.isPluginRegistryReady = ctx.isPluginRegistryReady || cr.spec.server.externalPluginRegistry - - if (cr.spec.server.customCheProperties && cr.spec.server.customCheProperties.CHE_MULTIUSER === 'false') { - flags.multiuser = false - } - - task.title = `${task.title}...done.` + // Eclipse Che operator supports only Multi-User Che + ctx.isCheDeployed = true + ctx.isPostgresDeployed = true + ctx.isKeycloakDeployed = true + + // plugin and devfile registry will be deployed only when external ones are not configured + ctx.isPluginRegistryDeployed = !(flags['plugin-registry-url'] as boolean) + ctx.isDevfileRegistryDeployed = !(flags['devfile-registry-url'] as boolean) + + const cr = await kube.createCheCluster(ctx.CR, flags, ctx, flags['che-operator-cr-yaml'] === '') + ctx.cr = cr + ctx.isKeycloakReady = ctx.isKeycloakReady || cr.spec.auth.externalIdentityProvider + ctx.isPostgresReady = ctx.isPostgresReady || cr.spec.database.externalDb + ctx.isDevfileRegistryReady = ctx.isDevfileRegistryReady || cr.spec.server.externalDevfileRegistry + ctx.isPluginRegistryReady = ctx.isPluginRegistryReady || cr.spec.server.externalPluginRegistry + + if (cr.spec.server.customCheProperties && cr.spec.server.customCheProperties.CHE_MULTIUSER === 'false') { + flags.multiuser = false } + + task.title = `${task.title}...done.` } } } diff --git a/src/tasks/installers/olm.ts b/src/tasks/installers/olm.ts index d7150eb24..5e0b4e617 100644 --- a/src/tasks/installers/olm.ts +++ b/src/tasks/installers/olm.ts @@ -10,6 +10,7 @@ import Command from '@oclif/command' import { cli } from 'cli-ux' +import * as yaml from 'js-yaml' import Listr = require('listr') import { KubeHelper } from '../../api/kube' @@ -17,7 +18,7 @@ import { CatalogSource, Subscription } from '../../api/typings/olm' import { CUSTOM_CATALOG_SOURCE_NAME, CVS_PREFIX, DEFAULT_CHE_OLM_PACKAGE_NAME, DEFAULT_OLM_KUBERNETES_NAMESPACE, DEFAULT_OPENSHIFT_MARKET_PLACE_NAMESPACE, KUBERNETES_OLM_CATALOG, NIGHTLY_CATALOG_SOURCE_NAME, OLM_NIGHTLY_CHANNEL_NAME, OLM_STABLE_CHANNEL_NAME, OPENSHIFT_OLM_CATALOG, OPERATOR_GROUP_NAME, SUBSCRIPTION_NAME } from '../../constants' import { isKubernetesPlatformFamily, isStableVersion } from '../../util' -import { copyOperatorResources, createEclipseCheCluster, createNamespaceTask } from './common-tasks' +import { createEclipseCheCluster, createNamespaceTask } from './common-tasks' export class OLMTasks { /** @@ -27,7 +28,6 @@ export class OLMTasks { const kube = new KubeHelper(flags) return new Listr([ this.isOlmPreInstalledTask(command, kube), - copyOperatorResources(flags, command.config.cacheDir), createNamespaceTask(flags.chenamespace, flags.platform), { title: 'Create operator group', @@ -136,6 +136,28 @@ export class OLMTasks { task.title = `${task.title}...done.` } }, + { + title: 'Prepare Eclipse Che cluster CR', + task: async (ctx: any, task: any) => { + const cheCluster = await kube.getCheCluster(flags.chenamespace) + if (cheCluster) { + ctx.cheClusterCreated = true + task.title = `${task.title}...It already exists..` + } else { + const subscription: Subscription = await kube.getOperatorSubscription(SUBSCRIPTION_NAME, flags.chenamespace) + const currentCSV = subscription.status!.currentCSV + const csv = await kube.getCSV(currentCSV, flags.chenamespace) + if (csv && csv.metadata.annotations) { + const CRRaw = csv.metadata.annotations!['alm-examples'] + ctx.CR = (yaml.safeLoad(CRRaw) as Array)[0] + } else { + throw new Error(`Unable to retrieve Che cluster CR definition from CSV: ${currentCSV}`) + } + ctx.cheClusterCreated = false + task.title = `${task.title}...Done.` + } + } + }, createEclipseCheCluster(flags, kube) ], { renderer: flags['listr-renderer'] as any }) } @@ -235,8 +257,8 @@ export class OLMTasks { 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)) + const csvsToDelete = csvs.items.filter(csv => csv.metadata.name!.startsWith(CVS_PREFIX)) + csvsToDelete.forEach(csv => kube.deleteClusterServiceVersion(flags.chenamespace, csv.metadata.name!)) task.title = `${task.title}...OK` } }, diff --git a/src/tasks/installers/operator.ts b/src/tasks/installers/operator.ts index a90700813..fe792550c 100644 --- a/src/tasks/installers/operator.ts +++ b/src/tasks/installers/operator.ts @@ -149,6 +149,21 @@ export class OperatorTasks { } } }, + { + title: 'Prepare Eclipse Che cluster CR', + task: async (ctx: any, task: any) => { + const cheCluster = await kube.getCheCluster(flags.chenamespace) + if (cheCluster) { + ctx.cheClusterCreated = true + task.title = `${task.title}...It already exists..` + } else { + const yamlFilePath = flags['che-operator-cr-yaml'] === '' ? ctx.resourcesPath + 'crds/org_v1_che_cr.yaml' : flags['che-operator-cr-yaml'] + ctx.CR = yaml.safeLoad(fs.readFileSync(yamlFilePath).toString()) + + task.title = `${task.title}...Done.` + } + } + }, createEclipseCheCluster(flags, kube) ], { renderer: flags['listr-renderer'] as any }) }