Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: OLM operator installer #611

Merged
merged 46 commits into from
Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
93454d9
Implement olm installer and tasks
AndrienkoAleksandr Mar 30, 2020
cb2eab1
Add update tasks.
AndrienkoAleksandr Mar 30, 2020
f54c691
Improve delete tasks and reuse tls tasks.
AndrienkoAleksandr Mar 30, 2020
0edc6fe
Add api OLM api group check.
AndrienkoAleksandr Mar 31, 2020
117b372
Fix up.
AndrienkoAleksandr Mar 31, 2020
7d04cd6
Clean up.
AndrienkoAleksandr Mar 31, 2020
0772409
Improve OLM checks.
AndrienkoAleksandr Mar 31, 2020
12c91fa
Fix installation using OLM on the minikube.
AndrienkoAleksandr Apr 5, 2020
c2ff468
Merge branch 'master' of github.com:che-incubator/chectl into operato…
AndrienkoAleksandr Apr 5, 2020
47da78b
Fix catalogsource namespaces for minikube, so crc and minikube should…
AndrienkoAleksandr Apr 5, 2020
43a94c3
Install stable Che using community catalog.
AndrienkoAleksandr Apr 6, 2020
25da32d
Don't remove operatorsource, for optimization we can share it between…
AndrienkoAleksandr Apr 6, 2020
ba19ec0
Code clean up.
AndrienkoAleksandr Apr 6, 2020
dc1ad64
Address request changes.
AndrienkoAleksandr Apr 13, 2020
c0e6701
Notify user that minishift platform is not supported.
AndrienkoAleksandr Apr 13, 2020
0971a1f
Merge branch 'master' of github.com:che-incubator/chectl into operato…
AndrienkoAleksandr Apr 13, 2020
b63a21d
Improve olm update method.
AndrienkoAleksandr Apr 13, 2020
e204fb9
Fix up.
AndrienkoAleksandr Apr 13, 2020
b13ec1c
Add ability to use OLM automatic installation mode.
AndrienkoAleksandr Apr 14, 2020
2ff5de0
Add ability to set up starting csv for olm installer.
AndrienkoAleksandr Apr 14, 2020
2cbaf98
Simplify code .
AndrienkoAleksandr Apr 14, 2020
13d39f1
Fix format.
AndrienkoAleksandr Apr 15, 2020
6265a5e
Fix lint.
AndrienkoAleksandr Apr 15, 2020
c583ce5
Fix lint.
AndrienkoAleksandr Apr 15, 2020
c138c33
Merge branch 'master' of github.com:che-incubator/chectl into operato…
AndrienkoAleksandr Apr 15, 2020
ebb1442
Fix bad merge.
AndrienkoAleksandr Apr 15, 2020
448d0fc
Address requsted changes.
AndrienkoAleksandr Apr 16, 2020
9a89da4
Fix up
AndrienkoAleksandr Apr 16, 2020
a5c3d18
Merge branch 'master' of github.com:che-incubator/chectl into operato…
AndrienkoAleksandr Apr 16, 2020
faed040
Create custom catalogsource from yaml. Remove deprecated operator sou…
AndrienkoAleksandr Apr 18, 2020
9e27163
Don't use nightly channel, we don't have it.
AndrienkoAleksandr Apr 20, 2020
628daf6
Delete custom catalogsource subscription on Che deletion.
AndrienkoAleksandr Apr 20, 2020
9a551ec
Add to more flags for custom operator source: olm-channel and package…
AndrienkoAleksandr Apr 20, 2020
668953d
Add link with OLM install script.
AndrienkoAleksandr Apr 20, 2020
82ba76b
Fix tslint, remove bad changes, update README.md, remove unused code.
AndrienkoAleksandr Apr 20, 2020
3d3eeaa
Fix up.
AndrienkoAleksandr Apr 20, 2020
c11fee4
Add more checks
AndrienkoAleksandr Apr 20, 2020
2f9c873
Clean up unused const
AndrienkoAleksandr Apr 21, 2020
fa6a7a2
Fix installation stable Che image for nightly chectl, add useful warn…
AndrienkoAleksandr Apr 21, 2020
cd43a08
Merge branch 'master' of github.com:che-incubator/chectl into operato…
AndrienkoAleksandr Apr 21, 2020
320161c
Fix compilation after resolve merge conflict.
AndrienkoAleksandr Apr 21, 2020
2c06ec2
Fix lints.
AndrienkoAleksandr Apr 21, 2020
d089441
Merge branch 'master' of github.com:che-incubator/chectl into operato…
AndrienkoAleksandr Apr 22, 2020
87a4e8e
Fix up.
AndrienkoAleksandr Apr 22, 2020
bbdf48c
Fix custom catalog source with nightly channel.
AndrienkoAleksandr Apr 22, 2020
5609ff9
Fix lint again.
AndrienkoAleksandr Apr 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ USAGE
$ chectl server:start

OPTIONS
-a, --installer=helm|operator|minishift-addon
-a, --installer=helm|operator|olm|minishift-addon
Installer type

-b, --domain=domain
Expand All @@ -263,7 +263,7 @@ OPTIONS
show CLI help

-i, --cheimage=cheimage
[default: quay.io/eclipse/che-server:nightly] Eclipse Che server container image
[default: quay.io/eclipse/che-server:7.9.0] Eclipse Che server container image

-m, --multiuser
Starts Eclipse Che in multi-user mode
Expand Down Expand Up @@ -293,6 +293,14 @@ OPTIONS
-t, --templates=templates
[default: templates] Path to the templates folder

--auto-update
Auto update approval strategy for installation Eclipse Che.
With this strategy will be provided auto-update Eclipse Che without any human interaction.
By default strategy this flag is false. It requires approval from user.
To approve installation newer version Eclipse Che user should execute 'chectl server:update'
command.
This parameter is used only when the installer is 'olm'.

--che-operator-cr-patch-yaml=che-operator-cr-patch-yaml
Path to a yaml file that overrides the default values in CheCluster CR used by the operator. This parameter is used
only when the installer is the operator.
Expand Down Expand Up @@ -345,6 +353,15 @@ OPTIONS
--skip-version-check
Skip minimal versions check.

--starting-csv=starting-csv
Starting cluster service version(CSV) for installation Eclipse Che.
Flags uses to set up start installation version Che.
For example: 'starting-csv' provided with value 'eclipse-che.v7.10.0' for stable channel.
Then OLM will install Eclipse Che with version 7.10.0.
Notice: this flag will be ignored with 'auto-update' flag. OLM with auto-update mode installs
the latest known version.
This parameter is used only when the installer is 'olm'.

--workspace-pvc-storage-class-name=workspace-pvc-storage-class-name
persistent volume(s) storage class name to use to store Eclipse Che workspaces data
```
Expand Down Expand Up @@ -385,7 +402,7 @@ USAGE
$ chectl server:update

OPTIONS
-a, --installer=helm|operator|minishift-addon Installer type
-a, --installer=helm|operator|minishift-addon|olm Installer type
-h, --help show CLI help

-n, --chenamespace=chenamespace [default: che] Kubernetes namespace where
Expand Down
296 changes: 295 additions & 1 deletion src/api/kube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ import { merge } from 'lodash'
import * as net from 'net'
import { Writable } from 'stream'

import { DEFAULT_CHE_IMAGE } from '../constants'
import { DEFAULT_CHE_IMAGE, defaultApplicationRegistry } from '../constants'
import { getClusterClientCommand } from '../util'

import { V1alpha2Certificate } from './typings/cert-manager'
import { CatalogSource, ClusterServiceVersionList, InstallPlan, OperatorGroup, OperatorSource, PackageManifest, Subscription } from './typings/olm'

const AWAIT_TIMEOUT_S = 30

Expand Down Expand Up @@ -1211,6 +1212,299 @@ export class KubeHelper {
}
}

async isPreInstalledOLM(): Promise<boolean> {
const apiApi = this.kc.makeApiClient(ApisApi)
try {
const { body } = await apiApi.getAPIVersions()
const OLMAPIGroup = body.groups.find(apiGroup => apiGroup.name === 'operators.coreos.com')
return !!OLMAPIGroup
} catch {
return false
}
}

async operatorSourceExists(name: string, namespace: string): Promise<boolean> {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const { body } = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1', namespace, 'operatorsources', name)
return this.compare(body, name)
} catch {
return false
}
}

async createOperatorSource(name: string, registryNamespace: string, namespace: string) {
const operatorSource: OperatorSource = {
apiVersion: 'operators.coreos.com/v1',
kind: 'OperatorSource',
metadata: {
name,
namespace,
},
spec: {
endpoint: defaultApplicationRegistry,
registryNamespace,
type: 'appregistry'
}
}

const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const { body } = await customObjectsApi.createNamespacedCustomObject('operators.coreos.com', 'v1', namespace, 'operatorsources', operatorSource)
return body
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async deleteOperatorSource(name: string, namespace: string) {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const options = new V1DeleteOptions()
await customObjectsApi.deleteNamespacedCustomObject('operators.coreos.com', 'v1', namespace, 'operatorsources', name, options)
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async catalogSourceExists(name: string, namespace: string): Promise<boolean> {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const { body } = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'catalogsources', name)
return this.compare(body, name)
} catch {
return false
}
}

async getCatalogSource(name: string, namespace: string): Promise<CatalogSource> {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const { body } = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'catalogsources', name)
return body
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async createCatalogSource(catalogSource: CatalogSource) {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const namespace = catalogSource.metadata.namespace
const { body } = await customObjectsApi.createNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'catalogsources', catalogSource)
return body
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async waitCatalogSource(namespace: string, catalogSourceName: string, timeout = 60): Promise<CatalogSource> {
return new Promise<CatalogSource>(async (resolve, reject) => {
const watcher = new Watch(this.kc)
let request: any
request = watcher.watch(`/apis/operators.coreos.com/v1alpha1/namespaces/${namespace}/catalogsources`,
{ fieldSelector: `metadata.name=${catalogSourceName}` },
(_phase: string, obj: any) => {
resolve(obj as CatalogSource)
},
error => {
if (error) {
reject(error)
}
})

setTimeout(() => {
request.abort()
reject(`Timeout reached while waiting for "${catalogSourceName}" catalog source is created.`)
}, timeout * 1000)
})
}

async operatorGroupExists(name: string, namespace: string): Promise<boolean> {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const { body } = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1', namespace, 'operatorgroups', name)
return this.compare(body, name)
} catch {
return false
}
}

async createOperatorGroup(operatorGroupName: string, namespace: string) {
const operatorGroup: OperatorGroup = {
apiVersion: 'operators.coreos.com/v1',
kind: 'OperatorGroup',
metadata: {
name: operatorGroupName,
namespace,
},
spec: {
targetNamespaces: [namespace]
}
}

const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const { body } = await customObjectsApi.createNamespacedCustomObject('operators.coreos.com', 'v1', namespace, 'operatorgroups', operatorGroup)
return body
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async deleteOperatorGroup(operatorGroupName: string, namespace: string) {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const options = new V1DeleteOptions()
await customObjectsApi.deleteNamespacedCustomObject('operators.coreos.com', 'v1', namespace, 'operatorgroups', operatorGroupName, options)
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async createOperatorSubscription(subscription: Subscription) {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const { body } = await customObjectsApi.createNamespacedCustomObject('operators.coreos.com', 'v1alpha1', subscription.metadata.namespace, 'subscriptions', subscription)
return body
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async getOperatorSubscription(name: string, namespace: string): Promise<Subscription> {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const { body } = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'subscriptions', name)
return body as Subscription
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async operatorSubscriptionExists(name: string, namespace: string): Promise<boolean> {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const { body } = await customObjectsApi.getNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'subscriptions', name)
return this.compare(body, name)
} catch {
return false
}
}

async deleteOperatorSubscription(operatorSubscriptionName: string, namespace: string) {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const options = new V1DeleteOptions()
await customObjectsApi.deleteNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'subscriptions', operatorSubscriptionName, options)
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async waitOperatorSubscriptionReadyForApproval(namespace: string, subscriptionName: string, timeout = AWAIT_TIMEOUT_S): Promise<InstallPlan> {
return new Promise<InstallPlan>(async (resolve, reject) => {
const watcher = new Watch(this.kc)
let request: any
request = watcher.watch(`/apis/operators.coreos.com/v1alpha1/namespaces/${namespace}/subscriptions`,
{ fieldSelector: `metadata.name=${subscriptionName}` },
(_phase: string, obj: any) => {
const subscription = obj as Subscription
if (subscription.status && subscription.status.conditions) {
for (const condition of subscription.status.conditions) {
if (condition.type === 'InstallPlanPending' && condition.status === 'True') {
resolve(subscription.status.installplan)
}
}
}
},
error => {
if (error) {
reject(error)
}
})

setTimeout(() => {
request.abort()
reject(`Timeout reached while waiting for "${subscriptionName}" subscription is ready.`)
}, timeout * 1000)
})
}

async approveOperatorInstallationPlan(name = '', namespace = '') {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const patch: InstallPlan = {
spec: {
approved: true
}
}
await customObjectsApi.patchNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'installplans', name, patch, { headers: { 'Content-Type': 'application/merge-patch+json' } })
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async waitWhileOperatorInstalled(installPlanName: string, namespace: string, timeout = 30) {
AndrienkoAleksandr marked this conversation as resolved.
Show resolved Hide resolved
return new Promise<InstallPlan>(async (resolve, reject) => {
const watcher = new Watch(this.kc)
let request: any
request = watcher.watch(`/apis/operators.coreos.com/v1alpha1/namespaces/${namespace}/installplans`,
{ fieldSelector: `metadata.name=${installPlanName}` },
(_phase: string, obj: any) => {
const installPlan = obj as InstallPlan
if (installPlan.status && installPlan.status.conditions) {
for (const condition of installPlan.status.conditions) {
if (condition.type === 'Installed' && condition.status === 'True') {
resolve()
}
}
}
},
error => {
if (error) {
reject(error)
}
})

setTimeout(() => {
request.abort()
reject(`Timeout reached while waiting for "${installPlanName}" has go status 'Installed'.`)
}, timeout * 1000)
})
}

async getClusterServiceVersions(namespace: string): Promise<ClusterServiceVersionList> {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const { body } = await customObjectsApi.listNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'clusterserviceversions')
return body as ClusterServiceVersionList
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async deleteClusterServiceVersion(namespace: string, csvName: string) {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const options = new V1DeleteOptions()
const { body } = await customObjectsApi.deleteNamespacedCustomObject('operators.coreos.com', 'v1alpha1', namespace, 'clusterserviceversions', csvName, options)
return body as ClusterServiceVersionList
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async getPackageManifect(name: string): Promise<PackageManifest> {
const customObjectsApi = this.kc.makeApiClient(CustomObjectsApi)
try {
const { body } = await customObjectsApi.getNamespacedCustomObject('packages.operators.coreos.com', 'v1', 'default', 'packagemanifests', name)
return body as PackageManifest
} catch (e) {
throw this.wrapK8sClientError(e)
}
}

async deleteNamespace(namespace: string): Promise<void> {
const k8sCoreApi = this.kc.makeApiClient(CoreV1Api)
try {
Expand Down
Loading