From 55a8c6eab84291078f279c8885dca78c000d097c Mon Sep 17 00:00:00 2001 From: Rob Chartier Date: Wed, 9 Dec 2020 13:50:21 -0800 Subject: [PATCH] chore(manifest refactor) (#128) * basic orchestration in place for appengine * end to end orchestration, with support for mysql * minor adjustments * CLI refactor (manifest vs spec) mostly complete * Rolling in manifest/spec/state changes, CLI works, WebUI in progress. * End to end web ui and cli UI with new manifest and state working. * Continued to iron out issues with appEngine installer. * Implemented support for the 'tag' property on provisioners, for upgrades, etc. * Skipping welcome screen. Support release tag, imagePullPolicy. * Updated branch with develop --- packages/adminer/c6o.yaml | 6 +- packages/akaunting/c6o.yaml | 3 +- packages/alertmanager/c6o.yaml | 3 +- packages/appengine/package.json | 2 +- packages/appengine/src/appObject.ts | 202 ++++++++++++++++++ .../appengine/src/applying/appliers/index.ts | 1 - .../appengine/src/applying/appliers/object.ts | 175 +++++++++------ .../src/applying/appliers/orchestration.ts | 187 ++++++++++++++++ .../appengine/src/applying/appliers/string.ts | 101 --------- packages/appengine/src/applying/index.ts | 4 +- packages/appengine/src/index.ts | 9 +- packages/appengine/src/mixins/createApply.ts | 37 ++-- .../appengine/src/mixins/createInquire.ts | 67 +++--- packages/appengine/src/parser.ts | 23 +- packages/appengine/src/parsing/index.ts | 19 +- .../src/templates/latest/deployment.ts | 38 +++- .../src/templates/latest/features/mysql.ts | 22 ++ .../appengine/src/templates/latest/labels.ts | 23 +- packages/appengine/src/ui/main.ts | 111 ++-------- packages/appengine/src/ui/tsconfig.json | 17 +- .../src/ui/views/appEngineBaseView.ts | 34 +++ packages/appengine/src/ui/views/base.ts | 107 ++++------ packages/appengine/src/ui/views/configs.ts | 30 ++- packages/appengine/src/ui/views/secrets.ts | 15 +- packages/bonita/c6o.yaml | 6 +- packages/build.sh | 3 - packages/cachet/c6o.yaml | 3 +- packages/calibre-web/c6o.yaml | 33 ++- packages/cassandra/c6o.yaml | 3 +- packages/checkmk/c6o.yaml | 6 +- packages/cloud9/c6o.yaml | 12 +- packages/codeserver/c6o.yaml | 3 +- packages/common/src/app.ts | 14 +- packages/cryptpad/c6o.yaml | 3 +- packages/duplicati/c6o.yaml | 3 +- packages/embyserver/c6o.yaml | 3 +- packages/embystat/c6o.yaml | 3 +- packages/firefly-iii/c6o.yaml | 3 +- packages/grocy/c6o.yaml | 3 +- packages/heimdall/c6o.yaml | 3 +- packages/lazylibrarian/c6o.yaml | 2 +- packages/lidarr/c6o.yaml | 3 +- packages/mariadb/c6o.yaml | 4 +- packages/mautic/c6o.yaml | 3 +- packages/mibew/c6o.yaml | 2 +- packages/mongo/c6o/app.yaml | 6 +- packages/mongodb/c6o.yaml | 6 +- packages/moodle/c6o.yaml | 3 +- packages/mysql/c6o.yaml | 6 +- packages/n8nserver/c6o.yaml | 3 +- packages/nats-webui/c6o.yaml | 3 +- packages/natsserver/c6o.yaml | 3 +- packages/nodered/c6o.yaml | 1 - packages/objectdetection/c6o.yaml | 2 +- packages/ombiserver/c6o.yaml | 3 +- packages/openmaptiles/c6o.yaml | 2 +- packages/openproject/c6o.yaml | 3 +- packages/plexserver/c6o.yaml | 3 +- packages/postgresql/c6o.yaml | 6 +- packages/pylon/c6o.yaml | 3 +- packages/radarr/c6o.yaml | 3 +- packages/rdesktop/c6o.yaml | 36 ++-- packages/recorder/c6o.yaml | 3 +- packages/redis/c6o.yaml | 4 +- packages/restyaboard/c6o.yaml | 3 +- packages/seafile/c6o.yaml | 3 +- packages/seq/c6o.yaml | 3 +- packages/snipeit/c6o.yaml | 137 +++++++++++- packages/sonarr/c6o.yaml | 3 +- packages/sshwifty/c6o.yaml | 3 +- packages/syncthing/c6o.yaml | 3 +- packages/teamcity/c6o.yaml | 6 +- packages/wallabag/c6o.yaml | 2 +- packages/wikijs/c6o.yaml | 2 +- packages/zimbra/c6o.yaml | 2 +- 75 files changed, 1113 insertions(+), 507 deletions(-) create mode 100644 packages/appengine/src/appObject.ts create mode 100644 packages/appengine/src/applying/appliers/orchestration.ts delete mode 100644 packages/appengine/src/applying/appliers/string.ts create mode 100644 packages/appengine/src/templates/latest/features/mysql.ts create mode 100644 packages/appengine/src/ui/views/appEngineBaseView.ts delete mode 100755 packages/build.sh diff --git a/packages/adminer/c6o.yaml b/packages/adminer/c6o.yaml index 41ef5928..a25acafd 100644 --- a/packages/adminer/c6o.yaml +++ b/packages/adminer/c6o.yaml @@ -23,6 +23,7 @@ keywords: - SimpleDB - Elasticsearch - MongoDB + - robc repo: https://github.com/vrana/adminer/ license: https://www.apache.org/licenses/LICENSE-2.0.html @@ -43,7 +44,7 @@ editions: provisioner: package: '@provisioner/appengine' name: adminer - image: 'adminer:latest' + image: adminer port: 8080 automated: true tag-prefix: appengine @@ -63,7 +64,8 @@ editions: provisioner: package: '@provisioner/appengine' name: adminer - image: 'adminer:latest' + tag: latest + image: adminer port: - port: 8080 name: http diff --git a/packages/akaunting/c6o.yaml b/packages/akaunting/c6o.yaml index fba1f965..2ea938cb 100644 --- a/packages/akaunting/c6o.yaml +++ b/packages/akaunting/c6o.yaml @@ -67,8 +67,9 @@ editions: package: '@provisioner/appengine' automated: true tag-prefix: appengine + tag: 1.3.9 name: akaunting - image: 'sameersbn/akaunting:1.3.9' + image: sameersbn/akaunting port: 80 volume: diff --git a/packages/alertmanager/c6o.yaml b/packages/alertmanager/c6o.yaml index fa8c503b..8e6e9f92 100644 --- a/packages/alertmanager/c6o.yaml +++ b/packages/alertmanager/c6o.yaml @@ -36,7 +36,8 @@ editions: tag-prefix: appengine package: '@provisioner/appengine' name: alertmanager - image: 'prom/alertmanager:latest' + tag: latest + image: prom/alertmanager port: 9093 automated: true marina: diff --git a/packages/appengine/package.json b/packages/appengine/package.json index 934b4939..e4e71b21 100644 --- a/packages/appengine/package.json +++ b/packages/appengine/package.json @@ -43,5 +43,5 @@ "parcel-bundler": "^1.12.4", "tslib": "^1.11.1" }, - "gitHead": "76270c0e55c21dd5e02cd24c03c84567f77af028" + "gitHead": "d24a33960e0efd45fd795d1c34112fe8174fa19c" } diff --git a/packages/appengine/src/appObject.ts b/packages/appengine/src/appObject.ts new file mode 100644 index 00000000..4b7edfbf --- /dev/null +++ b/packages/appengine/src/appObject.ts @@ -0,0 +1,202 @@ +import { LabelsMetadata } from "./parsing" +import * as fs from 'fs' +import createDebug from 'debug' +const debug = createDebug('@appengine:timing') + +export class TimingReporter implements TimingReporter { + report(state: AppEngineState) { + debug('TimingReporter:', state) + return true + } +} + +export interface TimingReporter { + report(state: AppEngineState) +} + +export class AppEngineState { + timing: Array + labels: LabelsMetadata + args: any + payload: any + parsed: boolean + platform: string + timestamp: Date + + timerChangedAction + + onTimerChanged(action) { + this.timerChangedAction = action + } + + startTimer(name: string) { + let existing = this.timing.find(e => e.name === name) + if (existing === undefined) { + existing = new AppProvisionerTimer() + existing.name = name + this.timing.push(existing) + } + existing.start = (new Date()).getTime() + if(this.timerChangedAction) this.timerChangedAction({action: 'startTimer', name, state: this}) + return existing + } + + endTimer(name?: string) { + let existing = this.timing.find(e => e.name === name) + if (existing === undefined) { + existing = new AppProvisionerTimer() + existing.name = name + existing.start = (new Date()).getTime() + this.timing.push(existing) + } + existing.end = (new Date()).getTime() + existing.duration = existing.end - existing.start + if(this.timerChangedAction) this.timerChangedAction({action: 'endTimer', name, state: this}) + + } + + constructor(labels: LabelsMetadata, args?: any, payload?: any) { + this.timing = new Array() + this.labels = labels + this.parsed = false + this.timestamp = new Date() + this.platform = 'Web' + + if (this.labels.instanceId === undefined) { + const helper = new Helper() + this.labels.instanceId = helper.makeRandom(5) + } + if (args === undefined) + this.args = {} + else + this.args = args + + if (payload === undefined) + this.payload = {} + else + this.payload = payload + } +} +export class AppProvisionerTimer { + name: string + start: number + end: number + duration: number +} + +export interface AppManifest { + readonly document: any + readonly edition: string + readonly description: string + readonly displayName: string + readonly iconUrl: string + readonly appId: string + readonly namespace: string + readonly provisioner: any + readonly routes: string + readonly name: string + readonly spec: string + hasCustomConfigFields(): boolean + hasCustomSecretFields(): boolean + customConfigFields() + customSecretFields() +} + + +export class AppObject implements AppManifest { + + constructor(public document) { } + + private fieldTypes = ['text', 'password', 'checkbox', 'timezone', 'combobox'] + + hasCustomConfigFields(): boolean { + return this.customConfigFields().length > 0 + } + hasCustomSecretFields(): boolean { + return this.customSecretFields().length > 0 + } + customConfigFields() { + return this.provisioner.configs.filter(e=> this.fieldTypes.includes(e.fieldType?.toLowerCase())) + } + customSecretFields() { + return this.provisioner.secrets.filter(e=> this.fieldTypes.includes(e.fieldType?.toLowerCase())) + } + + + getAppEdition() { + return this.document.metadata.labels?.['system.codezero.io/edition'] || 'latest' + } + + getAppName() { + return this.document.metadata.name + } + + getAppNamespace() { + return this.document.metadata.namespace + } + + + //Required for appEngine provisioner + get edition() { + return this.getAppEdition() + } + get description() { + return this.document.metadata.annotations?.['system.codezero.io/description'] || this.appId + } + get displayName() { + return this.document.metadata.annotations?.['system.codezero.io/display'] || this.appId + } + get iconUrl() { + return this.document.metadata.annotations?.['system.codezero.io/iconUrl'] + } + + //Provisioner appId itself and NOT the database identifier + get appId() { + return this.document.metadata.name + } + + get namespace() { + return this.getAppNamespace() + } + + get spec() { + return this.document.spec + } + + get provisioner() { + return this.document.spec.provisioner + } + + get routes() { + return this.document.spec.routes + } + + get name() { + return this.getAppName() + } + +} + +export class Helper { + + makeRandom(len) { + let text = '' + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + + for (let i = 0; i < len; i++) + text += possible.charAt(Math.floor(Math.random() * possible.length)) + + return text + } + + emitFile = true + PrettyPrintJsonFile(json: any, file = 'debug.json') { + if (!this.emitFile) return + if (!file) file = 'debug.json' + file = `${__dirname}/${file}` + if(!file.endsWith('.json')) file = `${file}.json` + fs.writeFileSync(file, JSON.stringify(json, null, 2)) + debug(file, json) + return file + } +} \ No newline at end of file diff --git a/packages/appengine/src/applying/appliers/index.ts b/packages/appengine/src/applying/appliers/index.ts index 541aa3a5..a71977d8 100644 --- a/packages/appengine/src/applying/appliers/index.ts +++ b/packages/appengine/src/applying/appliers/index.ts @@ -1,2 +1 @@ -export * from './string' export * from './object' diff --git a/packages/appengine/src/applying/appliers/object.ts b/packages/appengine/src/applying/appliers/object.ts index 6b4b938d..0d7089cd 100644 --- a/packages/appengine/src/applying/appliers/object.ts +++ b/packages/appengine/src/applying/appliers/object.ts @@ -1,74 +1,89 @@ import { ProvisionerManager } from '@provisioner/common' +import { AppEngineState, AppManifest, Helper } from '../../appObject' import { Applier } from '..' import { Buffer } from 'buffer' import { templates } from '../../templates/latest' +//import { applySql } from '../../templates/latest/features/mysql' import createDebug from 'debug' -import { LabelsMetadata } from '../../parsing' const debug = createDebug('@appengine:ObjectApplier') - export class ObjectApplier implements Applier { + helper = new Helper() + // eslint-disable-next-line @typescript-eslint/no-explicit-any - async apply(namespace: string, spec: any, manager: ProvisionerManager) { + async apply(manifest: AppManifest, state: AppEngineState, manager: ProvisionerManager) { - if (!spec.metaData) { - spec.metaData = { - instanceId: this.makeRandom(6), - edition: spec.edition - } as LabelsMetadata - } - if (!spec.metaData.instanceId) spec.metaData.instanceId = this.makeRandom(6) - if (!spec.metaData.edition) spec.metaData.edition = spec.edition + state.startTimer('object-apply') - debug(`BOOSTRAP:${JSON.stringify(spec)}`) + const deployment = await templates.getDeploymentTemplate( + manifest.provisioner.name, + manifest.namespace, + manifest.provisioner.image, + state.labels, + manifest.provisioner.tag, + manifest.provisioner.imagePullPolicy, + manifest.provisioner.command, + ) - const deployment = await templates.getDeploymentTemplate(spec.name, namespace, spec.image, spec.metaData) - debug(`deployment:${JSON.stringify(deployment)}`) + // if (spec.link) { + // //we have features/dependancies to deal with, lets jump to that first + // await this.installFeatures(manifest.namespace, spec, manager) + // } debug('applying secrets') - await this.applySecrets(namespace, spec, manager, deployment) + await this.applySecrets(manifest, state, manager, deployment) debug('applying configs') - await this.applyConfigs(namespace, spec, manager, deployment) + await this.applyConfigs(manifest, state, manager, deployment) debug('applying ports') - await this.applyPorts(namespace, spec, manager, deployment) + await this.applyPorts(manifest, state, manager, deployment) debug('applying volumes') - await this.applyVolumes(namespace, spec, manager, deployment) + await this.applyVolumes(manifest, state, manager, deployment) debug('applying deployment') - await this.applyDeployment(spec, manager, deployment) + await this.applyDeployment(manifest, state, manager, deployment) debug('done') + state.endTimer('object-apply') } // eslint-disable-next-line @typescript-eslint/no-explicit-any - async applyDeployment(spec: any, manager: ProvisionerManager, deployment: any) { + async applyDeployment(manifest: AppManifest, state: AppEngineState, manager: ProvisionerManager, deployment: any) { + state.startTimer('apply-deployment') debug(`Installing the Deployment:${JSON.stringify(deployment)}`) + this.helper.PrettyPrintJsonFile(deployment, `${manifest.appId}-deployment.json`) await manager.cluster - .begin('Installing the Deployment') + .begin(`Installing the Deployment for ${manifest.displayName}`) .addOwner(manager.document) .upsert(deployment) .end() + + state.endTimer('apply-deployment') } // eslint-disable-next-line @typescript-eslint/no-explicit-any - async applyVolumes(namespace: string, spec: any, manager: ProvisionerManager, deployment: any) { + async applyVolumes(manifest: AppManifest, state: AppEngineState, manager: ProvisionerManager, deployment: any) { + state.startTimer('apply-volumes') + + if (manifest.provisioner.volumes?.length) { - if (spec.volumes?.length) { + if (!deployment.spec.template.spec.containers[0].volumeMounts) + deployment.spec.template.spec.containers[0].volumeMounts = [] - deployment.spec.template.spec.containers[0].volumeMounts = [] - deployment.spec.template.spec.volumes = [] + if (!deployment.spec.template.spec.volumes) + deployment.spec.template.spec.volumes = [] - for (const item of spec.volumes) { + for (const item of manifest.provisioner.volumes) { if (item.size && item.size !== '') { - const pvc = templates.getPVCTemplate(item, namespace, spec.metaData) + const pvc = templates.getPVCTemplate(item, manifest.namespace, state.labels) debug(`Installing Volume Claim:${JSON.stringify(pvc)}`) + this.helper.PrettyPrintJsonFile(pvc, `${manifest.appId}-pvc.json`) await manager.cluster - .begin(`Installing Volume Claim: '${item.name}'`) + .begin(`Installing the Volume Claim for ${manifest.displayName}`) //TODO: Advanced installer needs to choose the volumes to delete .addOwner(manager.document) .upsert(pvc) @@ -78,7 +93,7 @@ export class ObjectApplier implements Applier { } if (item.mountPath && item.mountPath !== '') { - let mount = { name: item.name, mountPath: item.mountPath, subPath: undefined } + const mount = { name: item.name, mountPath: item.mountPath, subPath: undefined } if (item.subPath && item.subPath !== '') mount.subPath = item.subPath @@ -90,18 +105,22 @@ export class ObjectApplier implements Applier { } } + + state.endTimer('apply-volumes') } // eslint-disable-next-line @typescript-eslint/no-explicit-any - async applyPorts(namespace: string, spec: any, manager: ProvisionerManager, deployment: any) { + async applyPorts(manifest: AppManifest, state: AppEngineState, manager: ProvisionerManager, deployment: any) { + state.startTimer('apply-ports') - if (spec.ports?.length) { + if (manifest.provisioner.ports?.length) { - const service = templates.getPortTemplate(spec.name, namespace, spec.metaData) + const service = templates.getPortTemplate(manifest.appId, manifest.namespace, state.labels) - deployment.spec.template.spec.containers[0].ports = [] + if (!deployment.spec.template.spec.containers[0].ports) + deployment.spec.template.spec.containers[0].ports = [] - for (const item of spec.ports) { + for (const item of manifest.provisioner.ports) { if (item.protocol) item.protocol = item.protocol.toUpperCase() service.spec.ports.push({ name: item.name, port: item.port, targetPort: item.targetPort, protocol: item.protocol }) deployment.spec.template.spec.containers[0].ports.push({ name: item.name, containerPort: item.port }) @@ -123,38 +142,40 @@ export class ObjectApplier implements Applier { } debug(`Installing Networking Services:${JSON.stringify(deployment)}|${JSON.stringify(deployment.spec.template.spec.containers[0].ports)}`,) + this.helper.PrettyPrintJsonFile(service, `${manifest.appId}-service.json`) await manager.cluster - .begin('Installing Networking Services') + .begin(`Installing the Networking Services for ${manifest.displayName}`) .addOwner(manager.document) .upsert(service) .end() } + state.endTimer('apply-ports') } // eslint-disable-next-line @typescript-eslint/no-explicit-any - async applyConfigs(namespace: string, spec: any, manager: ProvisionerManager, deployment: any) { + async applyConfigs(manifest: AppManifest, state: AppEngineState, manager: ProvisionerManager, deployment: any) { - if (!spec.configs?.length) spec.configs = [] + state.startTimer('apply-configs') + + if (!manifest.provisioner.configs?.length) manifest.provisioner.configs = [] //provide some basic codezero app details to the provisioner - spec.configs.push({ name: 'name', value: spec.name, env: 'CZ_APP' }) - spec.configs.push({ name: 'edition', value: spec.edition, env: 'CZ_EDITION' }) - spec.configs.push({ name: 'instanceId', value: spec.metaData.instanceId, env: 'CZ_INSTANCE_ID' }) + manifest.provisioner.configs.push({ name: 'name', value: state.labels.appId, env: 'CZ_APP' }) + manifest.provisioner.configs.push({ name: 'edition', value: state.labels.edition, env: 'CZ_EDITION' }) + manifest.provisioner.configs.push({ name: 'instanceId', value: state.labels.instanceId, env: 'CZ_INSTANCE_ID' }) - const config = templates.getConfigTemplate(spec.name, namespace, spec.metaData) + const config = templates.getConfigTemplate(manifest.appId, manifest.namespace, state.labels) - for (const item of spec.configs) { + for (const item of manifest.provisioner.configs) { if (!item.env || item.env === '') item.env = item.name if (item.value === '$PUBLIC_DNS') { item.value = '' } - config.data[item.name] = String(item.value) - if (item.env !== 'NONE') { deployment.spec.template.spec.containers[0].env.push( { @@ -168,25 +189,65 @@ export class ObjectApplier implements Applier { }) } } + // if(spec.name === 'mysql') { + + // const configAuth = { + // apiVersion: 'v1', + // kind: 'ConfigMap', + // metadata: { + // namespace, + // name: 'mysql-config', + // labels: { + // app: spec.name + // } + // }, + // data: { + // 'default_auth': '[mysqld]\ndefault_authentication_plugin=mysql_native_password' + // } + // } + + // await manager.cluster + // .begin('Installing mysql specific configuration') + // .addOwner(manager.document) + // .upsert(configAuth) + // .end() + + // if(!deployment.spec.template.spec.volumes) + // deployment.spec.template.spec.volumes = [] + + // if(!deployment.spec.template.spec.containers[0].volumeMounts) + // deployment.spec.template.spec.containers[0].volumeMounts = [] + + + // deployment.spec.template.spec.volumes.push({ name: 'mysql-config-volume', configMap: { name: 'mysql-config' }}) + // deployment.spec.template.spec.containers[0].volumeMounts.push({ name: 'mysql-config-volume', mountPath: '/etc/mysql/conf.d/default_auth.cnf', subPath: 'default_auth' }) + + // } debug(`Installing configs:${JSON.stringify(deployment.spec.template.spec.containers[0].env)}`,) + this.helper.PrettyPrintJsonFile(config, `${manifest.appId}-config.json`) await manager.cluster - .begin('Installing the Configuration Settings') + .begin(`Installing the Configuration Settings for ${manifest.displayName}`) .addOwner(manager.document) .upsert(config) .end() + + state.endTimer('apply-configs') + } // eslint-disable-next-line @typescript-eslint/no-explicit-any - async applySecrets(namespace: string, spec: any, manager: ProvisionerManager, deployment: any) { + async applySecrets(manifest: AppManifest, state: AppEngineState, manager: ProvisionerManager, deployment: any) { + + state.startTimer('apply-secrets') - if (spec.secrets && spec.secrets.length > 0) { + if (manifest.provisioner.secrets && manifest.provisioner.secrets.length > 0) { - const secret = templates.getSecretTemplate(spec.name, namespace, spec.metaData) + const secret = templates.getSecretTemplate(manifest.appId, manifest.namespace, state.labels) - for (const item of spec.secrets) { + for (const item of manifest.provisioner.secrets) { if (!item.env || item.env === '') item.env = item.name @@ -194,11 +255,11 @@ export class ObjectApplier implements Applier { if (val !== '') { if (val.startsWith('$RANDOM')) { if (val === '$RANDOM') - val = this.makeRandom(10) + val = this.helper.makeRandom(10) else { if (val.indexOf(':') > 0) { const len = Number(val.substr(val.indexOf(':') + 1)) - val = this.makeRandom(len) + val = this.helper.makeRandom(len) } } } @@ -221,23 +282,17 @@ export class ObjectApplier implements Applier { } debug(`Installing secrets:${JSON.stringify(deployment.spec.template.spec.containers[0].env)}`) + this.helper.PrettyPrintJsonFile(secret, `${manifest.appId}-secret.json`) await manager.cluster - .begin('Installing the Secrets') + .begin(`Installing the Secrets for ${manifest.displayName}`) .addOwner(manager.document) .upsert(secret) .end() } + state.endTimer('apply-secrets') } - makeRandom(len) { - let text = '' - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' - for (let i = 0; i < len; i++) - text += possible.charAt(Math.floor(Math.random() * possible.length)) - - return text - } } \ No newline at end of file diff --git a/packages/appengine/src/applying/appliers/orchestration.ts b/packages/appengine/src/applying/appliers/orchestration.ts new file mode 100644 index 00000000..62775a49 --- /dev/null +++ b/packages/appengine/src/applying/appliers/orchestration.ts @@ -0,0 +1,187 @@ +import { AppObject, ProvisionerManager } from '@provisioner/common' + +export class Orchestration { + + // async installFeatures(namespace: string, spec: any, manager: ProvisionerManager) { + + // // stages: + // // 1. Scan all "features", and create specs internally for each + // // 2. Take the "values" and apply them to each features spec + // // 3. Apply all features into the cluster + // // 4. Apply the "script" section to the feature. This will be very hard coded + // // 5. Take the finished spec from each feature, and apply mappings to the App being installed + // // 6. Install the App requested + + // // #Drawback, cant map values from one feature to another + // // #Scripts and their relationship with the feature will be very fixed (mysql, mariadb, etc..) AppEngine will need to support all database types and such to execute these scripts against + + // //make sure our current provisioner is listed as a dpeendancy so it can take part in the value and mappings + // if (!spec.configs) spec.configs = [] + // if (!spec.secrets) spec.secrets = [] + + // this.PrettyPrintJsonFile(spec, 'pre-setup-spec.json') + + // this.setupDependancies(spec) + + // this.PrettyPrintJsonFile(spec, 'pre-link-spec.json') + + // debug('----------------------------------VALUES----------------------------------') + // this.mapAndLink(spec.link.values, spec) + // debug('----------------------------------DONE VALUES----------------------------------') + + // this.PrettyPrintJsonFile(spec, 'pre-install-spec.json') + + // await this.installDependancies(spec.name, spec.link.dependancies, manifest.state.labels, manager, namespace) + + // this.PrettyPrintJsonFile(spec, 'pre-map-spec.json') + + // debug('----------------------------------MAPPING----------------------------------') + // this.mapAndLink(spec.link.mappings, spec) + // debug('----------------------------------DONE MAPPING----------------------------------') + + // this.PrettyPrintJsonFile(spec) + + // } + // setupDependancies(spec: any) { + // const fullAppName = `${spec.name}-${manifest.state.labels.edition}-${manifest.state.labels.instanceId}` + // for (const dependancy of spec.link.dependancies) { + // if (!dependancy.spec) dependancy.spec = { name: dependancy.name} + // if (!dependancy.spec.configs) dependancy.spec.configs = [] + // if (!dependancy.spec.secrets) dependancy.spec.secrets = [] + // if (!dependancy.spec.name) dependancy.spec.name = dependancy.name + + // dependancy.manifest.state.labels = JSON.parse(JSON.stringify(manifest.state.labels)) + // dependancy.manifest.state.labels.partOf = fullAppName + // dependancy.manifest.state.labels.component = 'database' + // } + // this.PrettyPrintJsonFile(spec, 'setup-dependancies.json') + + // } + // async ensurePodIsRunning(manager: ProvisionerManager, namespace: string, app: string) { + // await manager.cluster + // .begin('Ensure pod is running') + // .beginWatch({ + // kind: 'Pod', + // metadata: { + // namespace, + // labels: { + // app + // } + // } + // }) + // .whenWatch(({ condition }) => condition.Ready == 'True', (processor, pod) => { + // processor.endWatch() + // }) + // .end() + // } + // async installDependancies(rootName: string, dependancies: any, metaData: LabelsMetadata, manager: ProvisionerManager, namespace: string) { + // let database: any + // for (const dependancy of dependancies) { + // if(dependancy.name === 'mysql') database = dependancy + // if(dependancy.name === 'mariadb') database = dependancy + + // dependancy.spec.configs.push({ name: 'DB_HOST', value: `${dependancy.name}.${namespace}` }) + // const deployment = await templates.getDeploymentTemplate(dependancy.spec.name, namespace, dependancy.spec.image, dependancy.spec.command, dependancy.manifest.state.labels) + // debug('applying secrets') + // await this.applySecrets(namespace, dependancy.spec, manager, deployment) + // debug('applying configs') + // await this.applyConfigs(namespace, dependancy.spec, manager, deployment) + // debug('applying ports') + // await this.applyPorts(namespace, dependancy.spec, manager, deployment) + // debug('applying volumes') + // await this.applyVolumes(namespace, dependancy.spec, manager, deployment) + // debug('applying deployment') + // await this.applyDeployment(dependancy.spec, manager, deployment) + // debug('ensure dependancy is runing') + // await this.ensurePodIsRunning(manager, namespace, dependancy.name) + // debug('done') + // } + + // if(database.spec.scripts) { + + // debug('Waiting for 5 seconds to ensure database health...') + + // await new Promise(resolve => setTimeout(resolve, 5000)) + + // const scripts = [] + // for(const script of database.spec.scripts) { + // scripts.push(script.script) + // } + + // applySql({ + // host: database.spec.configs.filter(e=>e.name === 'DB_HOST')[0].value, + // port: database.spec.configs.filter(e=>e.name === 'DB_PORT')[0].value, + // password: database.spec.secrets.filter(e=>e.name === 'MYSQL_ROOT_PASSWORD')[0].value, + // user: 'root', + // sql: scripts, + // insecureAuth: true + // }) + // } + + + // } + // mapAndLink(root: any, spec: any) { + + // //iterate over all values + // for (const i of root) { + + // const value = i.item + // const source = value.source + // const destination = value.destination + + // //polyfill will our known values + // if (source.value) { + // if (typeof (source.value) === 'string' && source.value.startsWith('$RANDOM')) { + // if (source.value === '$RANDOM') + // source.value = this.makeRandom(10) + // else { + // if (source.value.indexOf(':') > 0) { + // const len = Number(source.value.substr(source.value.indexOf(':') + 1)) + // source.value = this.makeRandom(len) + // } + // } + // } + // } + + // let destinationSpec = undefined + // if (destination.name === spec.name) { + // destinationSpec = spec + // } else { + // //copy over from source to destination + // const featureLst = spec.link.dependancies.filter(e => e.name === destination.name) + + // if (featureLst && featureLst.length > 0) { + // if (!featureLst[0].spec) featureLst[0].spec = { configs: [], secrets: [] } + // destinationSpec = featureLst[0].spec + // } + // } + + + // //we have a value and a place for it to go to + // if (destinationSpec) { + + // //if the source was a straight value + // if (source.value) { + // destinationSpec[destination.type].push({ name: destination.field, value: source.value }) + // } else { + // //need to dig the value out of the other provisioner itself + + // let sourceSpec = undefined + + // //if we need to get it from the root spec + // if (source.name === spec.name) { + // sourceSpec = spec + // } else { + // sourceSpec = spec.link.dependancies.filter(e => e.name === source.name)[0]?.spec + // } + + // if (sourceSpec) { + // const value = sourceSpec[destination.type].filter(e => e.name === source.field)[0]?.value + // destinationSpec[destination.type].push({ name: destination.field, value }) + // } + // } + // } + // } + // } + +} \ No newline at end of file diff --git a/packages/appengine/src/applying/appliers/string.ts b/packages/appengine/src/applying/appliers/string.ts deleted file mode 100644 index f2c77356..00000000 --- a/packages/appengine/src/applying/appliers/string.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { ProvisionerManager } from '@provisioner/common' -import { Applier } from '..' -import { Buffer } from 'buffer' -import { IDebugger } from 'debug' - -export class StringApplier implements Applier { - - async apply(namespace: string, spec: any, manager : ProvisionerManager, debug: IDebugger) { - - spec.env = '' - - if (spec.secrets) { - spec.secretsContent = '' - for (const item of spec.secrets) { - if (!item.env || item.env === '') item.env = item.name - const value = Buffer.from(item.value).toString('base64') - spec.secretsContent += ` ${item.name}: '${value}'\n` - spec.env += ` - name: ${item.env}\n valueFrom:\n secretKeyRef:\n name: ${spec.name}secrets\n key: ${item.name}\n` - } - - debug(`secretsContent:\n${spec.secretsContent}`) - - await manager.cluster - .begin('Installing the Secrets') - .addOwner(manager.document) - .upsertFile('../../../k8s/latest/1-secrets.yaml', { ...spec, namespace }) - .end() - } - - if (spec.configs) { - spec.configsContent = '' - for (const item of spec.configs) { - if (!item.env || item.env === '') item.env = item.name - spec.configsContent += ` ${item.name}: '${item.value}'\n` - spec.env += ` - name: ${item.env}\n valueFrom:\n configMapKeyRef:\n name: ${spec.name}configs\n key: ${item.name}\n` - } - debug(`configsContent:\n${spec.configsContent}`) - - await manager.cluster - .begin('Installing the Configuration Settings') - .addOwner(manager.document) - .upsertFile('../../../k8s/latest/2-configmap.yaml', { ...spec, namespace }) - .end() - } - - spec.portsContent = '' //deployment - - if (spec.ports && spec.ports.length > 0) { - - spec.portsContent = ' ports:\n' //deployment - spec.servicePortContent = ' ports:\n' //service/nodePort - - for (const item of spec.ports) { - spec.portsContent += ` - name: '${item.name}'\n containerPort: ${item.port}\n` - spec.servicePortContent += ` - name: '${item.name}'\n port: ${item.number}\n targetPort: '${item.targetPort}'\n` - } - debug(`portsContent:\n${spec.portsContent}`) - debug(`servicePortContent:\n${spec.servicePortContent}`) - - await manager.cluster - .begin('Installing Networking Services') - .addOwner(manager.document) - .upsertFile('../../../k8s/latest/5-service.yaml', { ...spec, namespace }) - .end() - - } - - spec.volumeMounts = '' - spec.deployVolumes = '' - - if (spec.volumes && spec.volumes.length > 0) { - spec.volumeMounts = ' volumeMounts:\n' - spec.deployVolumes = ' volumes:\n' - - for (const item of spec.volumes) { - if (item.name && item.name !== '') { - spec.volumeMounts += ` - name: '${item.name}'\n mountPath: ${item.mountPath}\n` - spec.deployVolumes += ` - name: '${item.name}'\n persistentVolumeClaim:\n claimName: ${item.name}\n` - - await manager.cluster - .begin(`Installing Volume ${item.name}`) - .addOwner(manager.document) - .upsertFile('../../../k8s/latest/3-pvc.yaml', { ...spec, namespace, size: item.size, volumeName: item.name }) - .end() - } - } - } - debug(`volumeMounts:\n${spec.volumeMounts}`) - debug(`deployVolumes:\n${spec.deployVolumes}`) - - await manager.cluster - .begin('Installing the Deployment') - .addOwner(manager.document) - .upsertFile('../../../k8s/latest/4-deployment.yaml', { ...spec, namespace }) - .end() - - - - } - -} \ No newline at end of file diff --git a/packages/appengine/src/applying/index.ts b/packages/appengine/src/applying/index.ts index 218a4702..b1b88b8a 100644 --- a/packages/appengine/src/applying/index.ts +++ b/packages/appengine/src/applying/index.ts @@ -1,5 +1,5 @@ import { ProvisionerManager } from '@provisioner/common' -import { IDebugger } from 'debug' +import { AppEngineState, AppManifest } from '../appObject' import * as appliers from './appliers' @@ -10,5 +10,5 @@ export class ApplierFactory{ } export interface Applier { - apply(namespace: string, spec: any, manager : ProvisionerManager, debug: IDebugger) + apply(manifest: AppManifest, state: AppEngineState, manager : ProvisionerManager) } diff --git a/packages/appengine/src/index.ts b/packages/appengine/src/index.ts index a94b267c..8087f408 100644 --- a/packages/appengine/src/index.ts +++ b/packages/appengine/src/index.ts @@ -1,5 +1,7 @@ -import { mix } from 'mixwith' -import { ProvisionerBase } from '@provisioner/common' +import { mix } from "mixwith" +import { ProvisionerBase } from "@provisioner/common" +import { AppEngineState, AppManifest, AppProvisionerTimer } from "./appObject" + import { createApplyMixin, createInquireMixin @@ -12,5 +14,6 @@ export interface Provisioner extends ProvisionerBase { } export class Provisioner extends mix(ProvisionerBase).with(createApplyMixin, createInquireMixin) { - + state: AppEngineState + manifest: AppManifest } \ No newline at end of file diff --git a/packages/appengine/src/mixins/createApply.ts b/packages/appengine/src/mixins/createApply.ts index 28555b84..e799e355 100644 --- a/packages/appengine/src/mixins/createApply.ts +++ b/packages/appengine/src/mixins/createApply.ts @@ -1,41 +1,54 @@ import { baseProvisionerType } from '../index' import { ApplierFactory as applierFactory } from '../applying/' import createDebug from 'debug' +import { AppObject, AppManifest, TimingReporter } from '../appObject' const debug = createDebug('@appengine:createApply') export const createApplyMixin = (base: baseProvisionerType) => class extends base { - get pods() { + pods(namespace, app) { return { kind: 'Pod', metadata: { - namespace: this.serviceNamespace, + namespace, labels: { - app: this.spec.name + app } } } } - async createApply() { + const manifest = new AppObject(this.manager.document) as AppManifest + this.state.startTimer('apply') await this.ensureServiceNamespacesExist() - await this.installApp() - await this.ensureAppIsRunning() + await this.installApp(manifest) + await this.ensureAppIsRunning(manifest) + this.state.endTimer('apply') + + this.helper.PrettyPrintJsonFile(manifest, `${manifest.appId}-completed-manifest`) + this.helper.PrettyPrintJsonFile(this.state, `${manifest.appId}-completed-state`) + + new TimingReporter().report(this.state) } - async installApp() { - const applierType = this.spec.applier || 'ObjectApplier' - await applierFactory.getApplier(applierType).apply(this.serviceNamespace, this.spec, this.manager, debug) + async installApp(manifest: AppManifest) { + this.state.startTimer('install') + const applierType = manifest.provisioner.applier || 'ObjectApplier' + await applierFactory.getApplier(applierType).apply(manifest, this.state, this.manager) + this.state.endTimer('install') } - async ensureAppIsRunning() { + async ensureAppIsRunning(manifest: AppManifest) { + this.state.startTimer('watch-pod') await this.manager.cluster. - begin(`Ensure ${this.spec.name} services are running`) - .beginWatch(this.pods) + begin(`Ensure ${manifest.displayName} services are running`) + .beginWatch(this.pods(manifest.namespace, manifest.appId)) .whenWatch(({ condition }) => condition.Ready === 'True', (processor) => { processor.endWatch() }) .end() + this.state.endTimer('watch-pod') } + } \ No newline at end of file diff --git a/packages/appengine/src/mixins/createInquire.ts b/packages/appengine/src/mixins/createInquire.ts index 93c66dc5..d1e595a9 100644 --- a/packages/appengine/src/mixins/createInquire.ts +++ b/packages/appengine/src/mixins/createInquire.ts @@ -1,20 +1,36 @@ import { baseProvisionerType } from '../index' -import createDebug from 'debug' import { parser } from '../parser' +import { AppEngineState, AppManifest, AppObject, Helper } from '../appObject' +import createDebug from 'debug' const debug = createDebug('@appengine:createInquire') export const createInquireMixin = (base: baseProvisionerType) => class extends base { + helper = new Helper() + async createInquire(args) { + const manifest = new AppObject(this.manager.document) as AppManifest + + this.state = new AppEngineState( + { + name: manifest.name, + appId: manifest.appId, + partOf: manifest.appId, + edition: manifest.edition, + }, args) + + this.state.platform = 'Console' + this.state.startTimer('inquire') const answers = { - image: args['image'] || this.spec.image, - name: args['name'] || this.spec.name, + image: args['image'] || manifest.provisioner.image, + name: args['name'] || manifest.appId, } - const automated = args['automated'] || this.spec.automated - debug('Inquire started\n', 'spec:\n', this.spec, 'args:\n', args) + const automated = args['automated'] || manifest.provisioner.automated + + debug('Inquire started\n', 'manifest:\n', manifest, 'args:\n', args) const responses = await this.manager.inquirer?.prompt([ { @@ -33,38 +49,29 @@ export const createInquireMixin = (base: baseProvisionerType) => class extends b } ], answers) - this.spec.image = responses.image - this.spec.name = responses.name - this.spec.edition = this.edition - parser.parseInputsToSpec(args, this.spec) - this.spec.configs = await this.askConfig(args, automated) - this.spec.secrets = await this.askSecrets(args, automated) - this.spec.ports = await this.askPorts(args, automated) - this.spec.volumes = await this.askVolumes(args, automated) + manifest.provisioner.image = responses.image + manifest.provisioner.name = responses.name + manifest.provisioner.edition = manifest.edition - debug('Inquire Completed\n', 'spec:\n', this.spec, 'args:\n', args) + parser.parseInputsToSpec(args, manifest) - //czctl install appengine --local -n testing --image redis --name redis --port "6379/TCP/TCP" --automated - //czctl install redis --local -n testing + manifest.provisioner.configs = await this.askConfig(args, automated, manifest.provisioner.configs) + manifest.provisioner.secrets = await this.askSecrets(args, automated, manifest.provisioner.secrets) + manifest.provisioner.ports = await this.askPorts(args, automated, manifest.provisioner.ports) + manifest.provisioner.volumes = await this.askVolumes(args, automated, manifest.provisioner.volumes) - //czctl install redis --local -n testing --specOnly > foo.yaml - //czctl provision foo.yaml - - //appEngine -> provisions docker containers - //appStudio -> UI to manage appEngine based configuration (czctl and webui -> icon in marina) - //appSuite -> provisioner for provisioners + this.state.endTimer('inquire') } - async askConfig(args, automated) { + async askConfig(args, automated, configs) { - const configs = this.spec.configs if (configs.length > 0 || automated) return configs - let responses = { hasConfig: false, configName : '', configValue : '', envName : '' } + let responses = { hasConfig: false, configName: '', configValue: '', envName: '' } do { responses = await this.manager.inquirer?.prompt([ @@ -105,9 +112,8 @@ export const createInquireMixin = (base: baseProvisionerType) => class extends b } - async askSecrets(args, automated) { + async askSecrets(args, automated, secrets) { - const secrets = this.spec.configs if (secrets && secrets.length > 0 || automated) return secrets let responses = { hasSecret: false, secretName: '', secretValue: '', envName: '' } @@ -151,9 +157,8 @@ export const createInquireMixin = (base: baseProvisionerType) => class extends b } - async askPorts(args, automated) { + async askPorts(args, automated, ports) { - const ports = this.spec.ports if (ports && ports.length > 0 || automated) return ports let responses = { hasPorts: false, name: '', protocol: 'TCP', port: 8080, targetPort: 0, externalPort: 8080 } @@ -217,9 +222,8 @@ export const createInquireMixin = (base: baseProvisionerType) => class extends b return !isNaN(parseFloat(n)) && isFinite(n) } - async askVolumes(args, automated) { + async askVolumes(args, automated, volumes) { - const volumes = this.spec.volumes if (volumes && volumes.length > 0 || automated) return volumes const storageChoices = ['1Gi', '2Gi', '5Gi', '10Gi', '20Gi', '50Gi', '100Gi'] @@ -275,4 +279,5 @@ export const createInquireMixin = (base: baseProvisionerType) => class extends b return volumes } + } diff --git a/packages/appengine/src/parser.ts b/packages/appengine/src/parser.ts index 7f8ae9ed..e5761413 100644 --- a/packages/appengine/src/parser.ts +++ b/packages/appengine/src/parser.ts @@ -1,27 +1,34 @@ import { ParserFactory as parserFactory } from './parsing' +import createDebug from 'debug' +import { AppManifest } from './appObject' +const debug = createDebug('@appengine:Parser') class Parser { - parseInputsToSpec(args, spec) { + parseInputsToSpec(args, manifest: AppManifest) { - if(args === null) args = {} + if (args === null) args = {} + + const spec = manifest.provisioner spec.parsed = true const configParserType = spec.configParser || 'BasicSettingParser' - spec.configs = parserFactory.getSettingsParser(configParserType).parse(args, spec, 'config') - if(spec.config) delete spec.config + spec.configs = [] + spec.configs = spec.configs.concat(parserFactory.getSettingsParser(configParserType).parse(args, spec, 'config')) + if (spec.config) delete spec.config const secretsParserType = spec.secretParser || 'BasicSettingParser' - spec.secrets = parserFactory.getSettingsParser(secretsParserType).parse(args, spec, 'secret') - if(spec.secret) delete spec.secret + spec.secrets = [] + spec.secrets = spec.secrets.concat(parserFactory.getSettingsParser(secretsParserType).parse(args, spec, 'secret')) + if (spec.secret) delete spec.secret const portParserType = spec.portParser || 'BasicPortParser' spec.ports = parserFactory.getPortParser(portParserType).parse(args, spec) - if(spec.port) delete spec.port + if (spec.port) delete spec.port const volumeParserType = spec.volumeParser || 'BasicVolumeParser' spec.volumes = parserFactory.getVolumeParser(volumeParserType).parse(args, spec) - if(spec.volume) delete spec.volume + if (spec.volume) delete spec.volume } } diff --git a/packages/appengine/src/parsing/index.ts b/packages/appengine/src/parsing/index.ts index 08786bd2..c44f2cb4 100644 --- a/packages/appengine/src/parsing/index.ts +++ b/packages/appengine/src/parsing/index.ts @@ -1,6 +1,9 @@ import * as portParsers from './port' import * as settingsParsers from './setting' import * as volumeParsers from './volume' +import createDebug from 'debug' + +const debug = createDebug('@appengine:Parser') export class ParserFactory{ static getPortParser(type: string) : PortParser { @@ -27,11 +30,13 @@ export interface VolumeParser { //https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ export interface LabelsMetadata { - instanceId: string - partOf: string - component: string - version: string - edition: string + name?: string + appId?: string + instanceId?: string + partOf?: string + component?: string + version?: string + edition?: string } @@ -89,7 +94,7 @@ export interface Port { export interface Volume { size: string - mountPath: string - subPath: string + mountPath?: string + subPath?: string name: string } \ No newline at end of file diff --git a/packages/appengine/src/templates/latest/deployment.ts b/packages/appengine/src/templates/latest/deployment.ts index 8d34c66d..97707efc 100644 --- a/packages/appengine/src/templates/latest/deployment.ts +++ b/packages/appengine/src/templates/latest/deployment.ts @@ -2,7 +2,34 @@ import { LabelsMetadata } from '../../parsing' import { getLabels } from './labels' -export function getDeploymentTemplate(name: string, namespace: string, image: string, metaData: LabelsMetadata) { +export function getDeploymentTemplate( + name: string, + namespace: string, + image: string, + labels: LabelsMetadata, + tag: string = undefined, + imagePullPolicy = undefined, + command: string[] = undefined, +) { + //imagePullPolicy https://kubernetes.io/docs/concepts/configuration/overview/ + //https://kubernetes.io/docs/concepts/containers/images/#updating-images + + //if there is no explicity set value + if (imagePullPolicy === undefined) { + //default value + imagePullPolicy = 'IfNotPresent' + //set it to Always, if the tag is explictly set to latest + if (tag !== undefined && tag === 'latest') imagePullPolicy = 'Always' + } + + //support docker tags being specified in the manifest + //used for upgrades + let imageWithTag = image + if (tag !== undefined && imageWithTag.indexOf(':') <= 0) { + imageWithTag = `${image}:${tag}` + } + + return { apiVersion: 'apps/v1', @@ -10,7 +37,7 @@ export function getDeploymentTemplate(name: string, namespace: string, image: st metadata: { namespace: namespace, name: name, - labels: getLabels(name, metaData) + labels: getLabels(name, labels) }, spec: { selector: { @@ -20,14 +47,15 @@ export function getDeploymentTemplate(name: string, namespace: string, image: st }, template: { metadata: { - labels: getLabels(name, metaData) + labels: getLabels(name, labels) }, spec: { containers: [ { name: name, - image: image, - imagePullPolicy: 'Always', + image: imageWithTag, + imagePullPolicy, + command, env: [] } ] diff --git a/packages/appengine/src/templates/latest/features/mysql.ts b/packages/appengine/src/templates/latest/features/mysql.ts new file mode 100644 index 00000000..c7262aa8 --- /dev/null +++ b/packages/appengine/src/templates/latest/features/mysql.ts @@ -0,0 +1,22 @@ + +// import { createConnection } from 'mysql' + +// import createDebug from 'debug' +// const debug = createDebug('@appengine:mysql') + +// export function applySql(options: { host: string, port: number, user: string, password: string, sql: string[], insecureAuth: boolean }) { + +// debug('applySql', options) + +// const connection = createConnection(options) +// connection.connect() + +// for (const sqlStatement of options.sql) { +// connection.query(sqlStatement, function (error, results) { +// if (error) throw error +// debug('MySql statement executed:', results) +// }) +// } +// connection.end() + +// } \ No newline at end of file diff --git a/packages/appengine/src/templates/latest/labels.ts b/packages/appengine/src/templates/latest/labels.ts index 6f13c5a0..657d421d 100644 --- a/packages/appengine/src/templates/latest/labels.ts +++ b/packages/appengine/src/templates/latest/labels.ts @@ -1,23 +1,22 @@ import { LabelsMetadata } from '../../parsing' //https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ -export function getLabels(name: string, metaData: LabelsMetadata) { - if(!metaData.edition || metaData.edition === '') metaData.edition = 'preview' - const fullAppName = `${name}-${metaData.edition}-${metaData.instanceId}` - const labels = { +export function getLabels(name: string, labels: LabelsMetadata) { + if(!labels.edition || labels.edition === '') labels.edition = 'preview' + const fullAppName = `${name}-${labels.edition}-${labels.instanceId}` + const finalLabels = { app: name, name: name, - 'system.codezero.io/instance': fullAppName, 'system.codezero.io/appengine': 'v1', 'system.codezero.io/app': fullAppName, - 'system.codezero.io/id': metaData.instanceId, + 'system.codezero.io/id': labels.instanceId, 'app.kubernetes.io/name': name, - 'app.kubernetes.io/managed-by': 'codezero', + 'app.kubernetes.io/managed-by': 'codezero' } - if(metaData.version) labels['app.kubernetes.io/version'] = metaData.version - if(metaData.component) labels['app.kubernetes.io/component'] = metaData.component - if(metaData.partOf) labels['app.kubernetes.io/part-of'] = metaData.partOf - if(metaData.edition) labels['system.codezero.io/edition'] = metaData.edition + if(labels.version) finalLabels['app.kubernetes.io/version'] = labels.version + if(labels.component) finalLabels['app.kubernetes.io/component'] = labels.component + if(labels.partOf) finalLabels['app.kubernetes.io/part-of'] = labels.partOf + if(labels.edition) finalLabels['system.codezero.io/edition'] = labels.edition - return labels + return finalLabels } \ No newline at end of file diff --git a/packages/appengine/src/ui/main.ts b/packages/appengine/src/ui/main.ts index 4ad4aadf..24636526 100644 --- a/packages/appengine/src/ui/main.ts +++ b/packages/appengine/src/ui/main.ts @@ -1,106 +1,29 @@ -import { LitElement, html, customElement, css, CSSResult } from 'lit-element' -import { StoreFlowStep, StoreFlowMediator } from '@provisioner/common' -// @ts-ignore +import { StoreFlowStep } from '@provisioner/common' +import { customElement, html } from 'lit-element' +import { TimingReporter } from '../appObject' import { parser } from '../parser' +import { AppEngineBaseView } from './views/appEngineBaseView' @customElement('appengine-install-main') -export class AppEngineSettings extends LitElement implements StoreFlowStep { - - mediator: StoreFlowMediator - - get spec() { - return this.mediator.applicationSpec.spec.provisioner - } - - static get styles(): (CSSResult[] | CSSResult)[] { - return [ - css` - h3 { - color: #2a343e; - font-size: 1.25rem; - font-weight: 700; - margin-bottom: 15px; - margin-top: 0; - } - - p { - margin-bottom: 15px; - margin-top: 0; - } - - #description { - margin-bottom: 30px; - } - - #instructions { - text-align: right; - } - ` - ] - } - - render() { - return html` -

Welcome to the ${this.spec.metaData.display} installation.

-

${this.spec.metaData.description}

-

Press 'Next' to get started.

- ` - } +export class AppEngineSettings extends AppEngineBaseView implements StoreFlowStep { async begin() { - this.handleMetaData() + super.init() + this.state.startTimer('ui-main-begin') - if (!this.spec.parsed) - parser.parseInputsToSpec(null, this.spec) + if (!this.state.parsed) + parser.parseInputsToSpec(null, this.manifest) - this.inspectFieldsForInputs() - } + this.state.endTimer('ui-main-begin') - handleMetaData() { - this.spec.metaData = this.mediator.applicationSpec.metadata - this.spec.edition = this.spec.metaData.labels['system.codezero.io/edition'] - - if (this.spec.metaData.annotations) { - this.spec.metaData.appId = this.spec.metaData.annotations['system.codezero.io/appId'] - this.spec.metaData.description = this.spec.metaData.annotations['system.codezero.io/description'] - this.spec.metaData.display = this.spec.metaData.annotations['system.codezero.io/display'] - this.spec.metaData.iconUrl = this.spec.metaData.annotations['system.codezero.io/iconUrl'] - this.spec.metaData.screenshots = this.spec.metaData.annotations['system.codezero.io/screenshots'] - this.spec.metaData.edition = this.spec.metaData.labels['system.codezero.io/edition'] - } - if (!this.spec.metaData.display) this.spec.metaData.display = this.spec.name - } - - inspectFieldsForInputs() { - const fieldTypes = ['text', 'password', 'checkbox', 'timezone', 'combobox'] - this.spec._ui = { configs: false, secrets: false } - - if (this.spec.configs) { - for (const config of this.spec.configs) { - if (fieldTypes.includes(config.fieldType?.toLowerCase())) { - config.fieldType = config.fieldType.toLowerCase() - this.spec._ui.configs = true - break - } - } - } - if (this.spec.secrets) { - for (const secret of this.spec.secrets) { - if (fieldTypes.includes(secret.fieldType.toLowerCase())) { - secret.fieldType = secret.fieldType.toLowerCase() - this.spec._ui.secrets = true - break - } - } - } - } - - async end() { - if (this.spec._ui.configs) + if (this.manifest.hasCustomConfigFields()) { this.mediator.appendFlow('appengine-install-configs') - else if (this.spec._ui.secrets) + } else if (this.manifest.hasCustomSecretFields()) { this.mediator.appendFlow('appengine-install-secrets') + } else { + new TimingReporter().report(this.state) + } - return true + await (this.mediator as any).handleNext() } -} +} \ No newline at end of file diff --git a/packages/appengine/src/ui/tsconfig.json b/packages/appengine/src/ui/tsconfig.json index 9b536641..d903e206 100644 --- a/packages/appengine/src/ui/tsconfig.json +++ b/packages/appengine/src/ui/tsconfig.json @@ -1,6 +1,7 @@ // Extends is not supported at this time by parcel-cli { "compilerOptions": { + "composite": true, "module": "esnext", "moduleResolution": "node", "declaration": true, @@ -15,6 +16,18 @@ "skipLibCheck": true, "target": "es2018", "sourceMap": true, - "lib": ["es2018", "dom"], - } + "lib": [ + "es2018", + "dom" + ], + }, + "exclude": [ + "node_modules", + "test" + ], + "references": [ + { + "path": "../../../common" + } + ] } \ No newline at end of file diff --git a/packages/appengine/src/ui/views/appEngineBaseView.ts b/packages/appengine/src/ui/views/appEngineBaseView.ts new file mode 100644 index 00000000..da3524ff --- /dev/null +++ b/packages/appengine/src/ui/views/appEngineBaseView.ts @@ -0,0 +1,34 @@ + +import { StoreFlowMediator, StoreFlowStep } from '@provisioner/common' +import { LitElement } from 'lit-element' +import { AppEngineState, AppManifest, AppObject, Helper } from '../../appObject' + + +export class AppEngineBaseView extends LitElement implements StoreFlowStep { + + manifest: AppManifest + mediator: StoreFlowMediator + state: AppEngineState + helper = new Helper() + + async init() { + + if(this.manifest === undefined) + this.manifest = new AppObject(this.mediator.applicationSpec) as AppManifest + + if(this.state === undefined) { + this.state = new AppEngineState( + { + name: this.manifest.name, + appId: this.manifest.appId, + partOf: this.manifest.appId, + edition: this.manifest.edition + }) + } + console.log('ROBX BEGIN STATE', this.state) + console.log('ROBX BEGIN MANIFEST', this.manifest) + + this.state.onTimerChanged(e=>console.log('ROBX STATE CHANGE:', e)) + } + +} diff --git a/packages/appengine/src/ui/views/base.ts b/packages/appengine/src/ui/views/base.ts index 1d64aa28..207e0a09 100644 --- a/packages/appengine/src/ui/views/base.ts +++ b/packages/appengine/src/ui/views/base.ts @@ -1,71 +1,41 @@ -import { LitElement, css, CSSResult } from 'lit-element' -import { StoreFlowStep, StoreFlowMediator } from '@provisioner/common' -// @ts-ignore +import { StoreFlowStep } from '@provisioner/common' import { getTimeZones } from '../../templates/latest/timeZones' +import { AppEngineBaseView } from './appEngineBaseView' -export class BaseViewSettings extends LitElement implements StoreFlowStep { +export class BaseViewSettings extends AppEngineBaseView implements StoreFlowStep { - mediator: StoreFlowMediator headingText: string bodyLayout: any pageLayout: any - get spec() { - return this.mediator.applicationSpec.spec.provisioner - } - - static get styles(): (CSSResult[] | CSSResult)[] { - return [ - css` - p { - margin-bottom: 15px; - margin-top: 0; - } - - h3 { - color: #2a343e; - font-size: 1.25rem; - font-weight: 700; - margin-bottom: 15px; - margin-top: 0; - } - - .heading-text { - margin-bottom: 20px; - } - - .text-error { - color: #ff5a00; - } - ` - ] - } - render() { return this.pageLayout } handleLayout(items, type) { - const fieldTypes = ['text', 'password', 'checkbox', 'timezone', 'combobox'] - const headingLayout = document.createElement('div') - headingLayout.setAttribute('class', 'heading-text') - headingLayout.innerHTML = this.headingText + this.state.startTimer('ui-configs-handleLayout') - this.pageLayout = document.createElement('section') - this.pageLayout.appendChild(headingLayout) + const headingLayout = document.createElement('c6o-form-layout') + const headingField = document.createElement('p') + this.pageLayout = document.createElement('c6o-form-layout') + this.pageLayout.appendChild(headingLayout) this.bodyLayout = document.createElement('c6o-form-layout') this.pageLayout.appendChild(this.bodyLayout) + headingField.innerHTML = this.headingText + headingLayout.appendChild(headingField) + if (items) { for (const item of items) { - if (fieldTypes.includes(item.fieldType?.toLowerCase())) { - this.renderInputField(type, item) - } + this.renderInputField(type, item) } this.requestUpdate() } + + this.state.endTimer('ui-configs-handleLayout') + } validateItems(items) { @@ -74,8 +44,7 @@ export class BaseViewSettings extends LitElement implements StoreFlowStep { if (item.required && item.required === true) { if (!item.value || item.value === '') { const validationFailedField = document.createElement('p') - validationFailedField.setAttribute('class', 'text-error') - validationFailedField.innerHTML = 'Validation has failed, please try again.' + validationFailedField.innerHTML = 'Validation has failed, try again.' this.bodyLayout.appendChild(validationFailedField) return false } @@ -85,30 +54,28 @@ export class BaseViewSettings extends LitElement implements StoreFlowStep { } renderInputField(type, item) { - if (!item.fieldType) item.fieldType = 'text' - if (item.fieldType === 'text') return this.renderTextFiled(type, item) - if (item.fieldType === 'password') return this.renderTextFiled(type, item) + console.log('ROBX renderInputField', type, item) + if (!item.fieldType) item.fieldType = 'text' + if (item.fieldType === 'text') return this.renderTextField(type, item) + if (item.fieldType === 'password') return this.renderTextField(type, item) if (item.fieldType === 'checkbox') return this.renderCheckboxInputField(type, item) - - if(item.fieldType === 'combobox' && item.items) { - return this.renderComboList(type, item, item.items) - } - - if (item.fieldType === 'timezone') { - const tzList = getTimeZones() - const zones = [] - for (const group of tzList) { - for (const zone of group.zones) - zones.push(zone.value) - } - return this.renderComboList(type, item, zones) - } + if (item.fieldType === 'timezone') return this.renderTimezoneField(type, item) + if (item.fieldType === 'combobox' && item.items) return this.renderComboList(type, item, item.items) return false } + renderTimezoneField(type, item) { + const tzList = getTimeZones() + const zones = [] + for (const group of tzList) { + for (const zone of group.zones) + zones.push({ value: zone.value }) + } + return this.renderComboList(type, item, zones) + } - renderTextFiled(type, item) { + renderTextField(type, item) { const field = document.createElement(`c6o-${item.fieldType}-field`) field['label'] = item.name @@ -132,7 +99,7 @@ export class BaseViewSettings extends LitElement implements StoreFlowStep { field.addEventListener('input', e => { const event = e as any const name = event.target.id - for (const item of this.spec[type]) { + for (const item of this.manifest.provisioner[type]) { if (item.name === name) { item.value = event.target.value break @@ -166,7 +133,7 @@ export class BaseViewSettings extends LitElement implements StoreFlowStep { field.addEventListener('change', e => { const event = e as any const name = event.target.id - for (const item of this.spec[type]) { + for (const item of this.manifest.provisioner[type]) { if (item.name === name) { item.value = !!event.target.checked break @@ -181,6 +148,7 @@ export class BaseViewSettings extends LitElement implements StoreFlowStep { } renderComboList(type, item, items) { + const field = document.createElement('c6o-combo-box') field['label'] = item.name @@ -200,12 +168,12 @@ export class BaseViewSettings extends LitElement implements StoreFlowStep { if (item.value) field['value'] = item.value - field['items'] = items + field['items'] = items.map(({value}) => value) field.addEventListener('change', e => { const event = e as any const name = event.target.id - for (const item of this.spec[type]) { + for (const item of this.manifest.provisioner[type]) { if (item.name === name) { item.value = event.target.value break @@ -217,4 +185,5 @@ export class BaseViewSettings extends LitElement implements StoreFlowStep { return true } + } \ No newline at end of file diff --git a/packages/appengine/src/ui/views/configs.ts b/packages/appengine/src/ui/views/configs.ts index b8aa25e6..d417ce7a 100644 --- a/packages/appengine/src/ui/views/configs.ts +++ b/packages/appengine/src/ui/views/configs.ts @@ -1,26 +1,36 @@ import { StoreFlowStep } from '@provisioner/common' import { customElement } from 'lit-element' +import { TimingReporter } from '../../appObject' import { BaseViewSettings } from './base' @customElement('appengine-install-configs') export class AppEngineConfigsSettings extends BaseViewSettings implements StoreFlowStep { async begin() { - super.headingText = ` -

Configuration

-

This data will be captured as a ConfigMap within Kubernetes.

-

It will also (typically) be set as an environment variable on the container.

- ` + super.init() + + this.state.startTimer('ui-configs-begin') + + this.headingText = ` +

Configuration

+

This data will be captured as a ConfigMap within Kubernetes.

+

It will also (typically) be set as an environment variable on the container.

` + + this.handleLayout(this.manifest.customConfigFields(), 'configs') + this.state.endTimer('ui-configs-begin') - super.handleLayout(super.spec.configs, 'configs') } async end() { - if(!super.validateItems(super.spec.configs)) return false - //intenionally not super - if (super.spec._ui.secrets) - this.mediator.appendFlow('appengine-install-secrets') + if(!this.validateItems(this.manifest.provisioner.configs)) return false + if (this.manifest.hasCustomSecretFields()) { + this.mediator.appendFlow('appengine-install-secrets') + } else { + new TimingReporter().report(this.state) + } return true } + + } \ No newline at end of file diff --git a/packages/appengine/src/ui/views/secrets.ts b/packages/appengine/src/ui/views/secrets.ts index 29cfb793..a608d2a5 100644 --- a/packages/appengine/src/ui/views/secrets.ts +++ b/packages/appengine/src/ui/views/secrets.ts @@ -6,17 +6,24 @@ import { BaseViewSettings } from './base' export class AppEngineSecretsSettings extends BaseViewSettings implements StoreFlowStep { async begin() { - super.headingText = ` + console.log('ROBX BEGIN SECRETS', this) + super.init() + + this.state.startTimer('ui-secrets-begin') + + + this.headingText = `

Secrets

This data will be captured as Secrets within Kubernetes.

It will also (typically) be set as an environment variable on the container.

` - super.handleLayout(super.spec.secrets, 'secrets') + this.handleLayout(this.manifest.customSecretFields(), 'secrets') + this.state.endTimer('ui-secrets-begin') + } async end() { - - if(!super.validateItems(super.spec.secrets)) return false + if(!this.validateItems(this.manifest.provisioner.secrets)) return false return true } diff --git a/packages/bonita/c6o.yaml b/packages/bonita/c6o.yaml index 213d6779..7247a6ae 100644 --- a/packages/bonita/c6o.yaml +++ b/packages/bonita/c6o.yaml @@ -33,7 +33,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: bonita - image: 'bonita:latest' + tag: latest + image: bonita port: 8080 automated: true volume: @@ -93,7 +94,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: bonita - image: 'bonita:latest' + tag: latest + image: bonita port: 8080 automated: true volume: diff --git a/packages/build.sh b/packages/build.sh deleted file mode 100755 index a48f5eb7..00000000 --- a/packages/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -rm ./appengine/lib -rf -rm ./appengine/tsconfig.tsbuildinfo -f -yarn build --scope @provisioner/appengine diff --git a/packages/cachet/c6o.yaml b/packages/cachet/c6o.yaml index 2f4d8928..e25cf8cc 100644 --- a/packages/cachet/c6o.yaml +++ b/packages/cachet/c6o.yaml @@ -55,7 +55,8 @@ editions: provisioner: package: '@provisioner/appengine' name: cachet - image: 'cachethq/docker:latest' + tag: latest + image: cachethq/docker tag-prefix: appengine port: 8000 diff --git a/packages/calibre-web/c6o.yaml b/packages/calibre-web/c6o.yaml index 36dc1629..d5511d5a 100644 --- a/packages/calibre-web/c6o.yaml +++ b/packages/calibre-web/c6o.yaml @@ -1,37 +1,64 @@ name: Calibre Web -appId: calibre-web #App internal name (all lower, no spaces); minimum 5 characters -package: '@provisioner/appengine' +appId: calibre-web #App internal name (all lower, no spaces); minimum 5 characters +package: "@provisioner/appengine" icon: icon.svg description: Calibre-Web is a web app providing a clean interface for browsing, reading and downloading eBooks using an existing Calibre database. + Features + ---- + * Bootstrap 3 HTML5 interface + * full graphical setup + * User management with fine-grained per-user permissions + * Admin interface + * User Interface in czech, dutch, english, finnish, french, german, hungarian, italian, japanese, khmer, polish, russian, simplified chinese, spanish, swedish, turkish, ukrainian + * OPDS feed for eBook reader apps + * Filter and search by titles, authors, tags, series and language + * Create a custom book collection (shelves) + * Support for editing eBook metadata and deleting eBooks from Calibre library + * Support for converting eBooks through Calibre binaries + * Restrict eBook download to logged-in users + * Support for public user registration + * Send eBooks to Kindle devices with the click of a button + * Sync your Kobo devices through Calibre-Web with your Calibre library + * Support for reading eBooks directly in the browser (.txt, .epub, .pdf, .cbr, .cbt, .cbz) + * Upload new books in many formats, including audio formats (.mp3, .m4a, .m4b) + * Support for Calibre Custom Columns + * Ability to hide content based on categories and Custom Column content per user + * Self-update capability + * "Magic Link" login to make it easy to log on eReaders + * Login via LDAP, google/github oauth and via proxy authentication + Default admin login + ---- + Username admin + Password admin123 summary: @@ -62,7 +89,7 @@ editions: - type: http targetService: calibre-web provisioner: - package: '@provisioner/appengine' + package: "@provisioner/appengine" tag-prefix: appengine name: calibre-web image: linuxserver/calibre-web diff --git a/packages/cassandra/c6o.yaml b/packages/cassandra/c6o.yaml index 23b5b5e6..b1f07585 100644 --- a/packages/cassandra/c6o.yaml +++ b/packages/cassandra/c6o.yaml @@ -49,7 +49,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: cassandra - image: 'cassandra:latest' + tag: latest + image: cassandra port: - port: 7000 name: jmx diff --git a/packages/checkmk/c6o.yaml b/packages/checkmk/c6o.yaml index a80f4329..29c633f8 100644 --- a/packages/checkmk/c6o.yaml +++ b/packages/checkmk/c6o.yaml @@ -80,7 +80,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: checkmk - image: 'checkmk/check-mk-raw:latest' + tag: latest + image: checkmk/check-mk-raw port: 5000 automated: true secret: @@ -111,7 +112,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: checkmk - image: 'checkmk/check-mk-raw:latest' + tag: latest + image: checkmk/check-mk-raw port: - port: 5000 name: http diff --git a/packages/cloud9/c6o.yaml b/packages/cloud9/c6o.yaml index f65c6a6e..bd9060a6 100644 --- a/packages/cloud9/c6o.yaml +++ b/packages/cloud9/c6o.yaml @@ -40,7 +40,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: cloud9python - image: 'linuxserver/cloud9:python' + tag: python + image: linuxserver/cloud9 port: 8000 automated: true volume: @@ -92,7 +93,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: cloud9nodejs - image: 'linuxserver/cloud9:nodejs' + tag: nodejs + image: linuxserver/cloud9 port: 8000 automated: true volume: @@ -145,7 +147,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: cloud9go - image: 'linuxserver/cloud9:go' + tag: go + image: linuxserver/cloud9 port: 8000 automated: true volume: @@ -199,7 +202,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: cloud9ruby - image: 'linuxserver/cloud9:ruby' + tag: ruby + image: linuxserver/cloud9 port: 8000 automated: true volume: diff --git a/packages/codeserver/c6o.yaml b/packages/codeserver/c6o.yaml index 6e47ca52..9f651f28 100644 --- a/packages/codeserver/c6o.yaml +++ b/packages/codeserver/c6o.yaml @@ -70,7 +70,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: codeserver - image: 'linuxserver/code-server:latest' + tag: latest + image: linuxserver/code-server port: 8443 automated: true volume: diff --git a/packages/common/src/app.ts b/packages/common/src/app.ts index dcc56e32..e46f5928 100644 --- a/packages/common/src/app.ts +++ b/packages/common/src/app.ts @@ -18,7 +18,7 @@ export interface LaunchType { } } export interface RoutesType { - type: 'tcp'|'http', + type: 'tcp' | 'http', targetService: string, targetPort?: number, disabled?: boolean @@ -135,4 +135,16 @@ export class AppObject { } getServiceName = (serviceObject) => Object.keys(serviceObject)[0] + + //add or update the label + upsertLabel(labelName: string, labelValue: string) { + this.document.metadata.labels[labelName] = labelValue + } + + //only add if it doesnt already exist + insertOnlyLabel(labelName: string, labelValue: string) { + if (!this.document.metadata.labels[labelName]) + this.document.metadata.labels[labelName] = labelValue + } + } \ No newline at end of file diff --git a/packages/cryptpad/c6o.yaml b/packages/cryptpad/c6o.yaml index eaf3f6a6..bf643de8 100644 --- a/packages/cryptpad/c6o.yaml +++ b/packages/cryptpad/c6o.yaml @@ -38,7 +38,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: cryptpad - image: promasu/cryptpad:latest + tag: latest + image: promasu/cryptpad port: 3000 automated: true config: diff --git a/packages/duplicati/c6o.yaml b/packages/duplicati/c6o.yaml index 3d93a33c..0cb1d406 100644 --- a/packages/duplicati/c6o.yaml +++ b/packages/duplicati/c6o.yaml @@ -37,7 +37,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: duplicati - image: 'duplicati/duplicati:latest' + tag: latest + image: duplicati/duplicati port: 8200 automated: true volume: diff --git a/packages/embyserver/c6o.yaml b/packages/embyserver/c6o.yaml index 69c02895..748783e4 100644 --- a/packages/embyserver/c6o.yaml +++ b/packages/embyserver/c6o.yaml @@ -56,7 +56,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: embyserver - image: 'linuxserver/emby:latest' + tag: latest + image: linuxserver/emby config: - name: PUID value: 0 diff --git a/packages/embystat/c6o.yaml b/packages/embystat/c6o.yaml index f13aec5e..18edd8e9 100644 --- a/packages/embystat/c6o.yaml +++ b/packages/embystat/c6o.yaml @@ -40,7 +40,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: embystat - image: 'linuxserver/embystat:latest' + tag: latest + image: linuxserver/embystat port: 6555 automated: true config: diff --git a/packages/firefly-iii/c6o.yaml b/packages/firefly-iii/c6o.yaml index ba23e48f..cb5c5401 100644 --- a/packages/firefly-iii/c6o.yaml +++ b/packages/firefly-iii/c6o.yaml @@ -72,7 +72,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: firefly-iii - image: 'jc5x/firefly-iii:latest' + tag: latest + image: jc5x/firefly-iii port: 8080 automated: true volume: diff --git a/packages/grocy/c6o.yaml b/packages/grocy/c6o.yaml index ec324e8f..a3753494 100644 --- a/packages/grocy/c6o.yaml +++ b/packages/grocy/c6o.yaml @@ -37,7 +37,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: grocy - image: 'linuxserver/grocy:latest' + tag: latest + image: linuxserver/grocy port: 80 automated: true config: diff --git a/packages/heimdall/c6o.yaml b/packages/heimdall/c6o.yaml index 5fe07bf6..d075d9d0 100644 --- a/packages/heimdall/c6o.yaml +++ b/packages/heimdall/c6o.yaml @@ -37,7 +37,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: heimdall - image: 'linuxserver/heimdall:latest' + tag: latest + image: linuxserver/heimdall port: 80 automated: true config: diff --git a/packages/lazylibrarian/c6o.yaml b/packages/lazylibrarian/c6o.yaml index b1dbdfaf..9bc900f4 100644 --- a/packages/lazylibrarian/c6o.yaml +++ b/packages/lazylibrarian/c6o.yaml @@ -14,7 +14,7 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: lazylibrarian - image: 'thraxis/lazylibrarian-calibre' + image: thraxis/lazylibrarian-calibre port: 5299 automated: true config: diff --git a/packages/lidarr/c6o.yaml b/packages/lidarr/c6o.yaml index 31025be6..3bb1c19b 100644 --- a/packages/lidarr/c6o.yaml +++ b/packages/lidarr/c6o.yaml @@ -33,7 +33,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: lidarr - image: 'linuxserver/lidarr:latest' + tag: latest + image: linuxserver/lidarr port: 8686 automated: true config: diff --git a/packages/mariadb/c6o.yaml b/packages/mariadb/c6o.yaml index 9c0fcb23..bcc8966a 100644 --- a/packages/mariadb/c6o.yaml +++ b/packages/mariadb/c6o.yaml @@ -44,7 +44,7 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: mariadb - image: 'mariadb' + image: mariadb port: 3306 automated: true secret: @@ -71,7 +71,7 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: mariadb - image: 'mariadb' + image: mariadb port: 3306 automated: true config: diff --git a/packages/mautic/c6o.yaml b/packages/mautic/c6o.yaml index 93e6be20..31bcc001 100644 --- a/packages/mautic/c6o.yaml +++ b/packages/mautic/c6o.yaml @@ -67,7 +67,8 @@ editions: automated: true tag-prefix: appengine name: mautic - image: 'mautic/mautic:latest' + tag: latest + image: mautic/mautic port: 80 volume: diff --git a/packages/mibew/c6o.yaml b/packages/mibew/c6o.yaml index 465ecfb8..39f5d351 100644 --- a/packages/mibew/c6o.yaml +++ b/packages/mibew/c6o.yaml @@ -67,7 +67,7 @@ editions: automated: true tag-prefix: appengine name: mibew - image: 'turnkeylinux/mibew' + image: turnkeylinux/mibew port: 80 config: diff --git a/packages/mongo/c6o/app.yaml b/packages/mongo/c6o/app.yaml index 52ff0507..33fc3f44 100644 --- a/packages/mongo/c6o/app.yaml +++ b/packages/mongo/c6o/app.yaml @@ -36,7 +36,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: mongodb - image: 'mongo:latest' + tag: latest + image: mongo port: 27017 automated: true @@ -72,7 +73,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: mongodb - image: 'mongo:latest' + tag: latest + image: mongo port: 27017 volume: - size: 5Gi diff --git a/packages/mongodb/c6o.yaml b/packages/mongodb/c6o.yaml index 07d8a345..beb04a39 100644 --- a/packages/mongodb/c6o.yaml +++ b/packages/mongodb/c6o.yaml @@ -36,7 +36,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: mongodb - image: 'mongo:latest' + tag: latest + image: mongo port: 27017 automated: true @@ -72,7 +73,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: mongodb - image: 'mongo:latest' + tag: latest + image: mongo port: 27017 volume: - size: 5Gi diff --git a/packages/moodle/c6o.yaml b/packages/moodle/c6o.yaml index 0713ca54..54454fb4 100644 --- a/packages/moodle/c6o.yaml +++ b/packages/moodle/c6o.yaml @@ -37,7 +37,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: moodle - image: 'bitnami/moodle:latest' + tag: latest + image: bitnami/moodle port: 8080 automated: true diff --git a/packages/mysql/c6o.yaml b/packages/mysql/c6o.yaml index 77494540..7902f769 100644 --- a/packages/mysql/c6o.yaml +++ b/packages/mysql/c6o.yaml @@ -43,7 +43,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: mysql - image: 'mysql:latest' + tag: latest + image: mysql port: 3306 volume: - size: 5Gi @@ -69,7 +70,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: mysql - image: 'mysql:latest' + tag: latest + image: mysql port: 3306 volume: - size: 5Gi diff --git a/packages/n8nserver/c6o.yaml b/packages/n8nserver/c6o.yaml index d81f0552..7c54e75d 100644 --- a/packages/n8nserver/c6o.yaml +++ b/packages/n8nserver/c6o.yaml @@ -33,7 +33,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: n8nserver - image: 'n8nio/n8n:0.43.0-ubuntu' + tag: 0.43.0-ubuntu + image: n8nio/n8n port: 5678 automated: true marina: diff --git a/packages/nats-webui/c6o.yaml b/packages/nats-webui/c6o.yaml index fe731837..1c5775b5 100644 --- a/packages/nats-webui/c6o.yaml +++ b/packages/nats-webui/c6o.yaml @@ -42,7 +42,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: nats-webui - image: 'sphqxe/nats-webui:latest' + tag: latest + image: sphqxe/nats-webui port: 80 automated: true marina: diff --git a/packages/natsserver/c6o.yaml b/packages/natsserver/c6o.yaml index dd1e0303..4cd2679e 100644 --- a/packages/natsserver/c6o.yaml +++ b/packages/natsserver/c6o.yaml @@ -33,7 +33,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: natsserver - image: 'nats:latest' + tag: latest + image: nats port: - name: clients port: 4222 diff --git a/packages/nodered/c6o.yaml b/packages/nodered/c6o.yaml index d69b631d..9a9d1f1e 100644 --- a/packages/nodered/c6o.yaml +++ b/packages/nodered/c6o.yaml @@ -68,4 +68,3 @@ editions: marina: launch: type: inline - popUp: true diff --git a/packages/objectdetection/c6o.yaml b/packages/objectdetection/c6o.yaml index 8c234ee6..1f38cabb 100644 --- a/packages/objectdetection/c6o.yaml +++ b/packages/objectdetection/c6o.yaml @@ -34,7 +34,7 @@ editions: provisioner: package: '@provisioner/appengine' name: objectdetection - image: 'ancs21/tf-od-api' + image: ancs21/tf-od-api port: - name: api port: 5000 diff --git a/packages/ombiserver/c6o.yaml b/packages/ombiserver/c6o.yaml index 36f6fb33..2d36cce8 100644 --- a/packages/ombiserver/c6o.yaml +++ b/packages/ombiserver/c6o.yaml @@ -39,7 +39,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: ombi - image: 'linuxserver/ombi:latest' + tag: latest + image: linuxserver/ombi port: 3579 automated: true marina: diff --git a/packages/openmaptiles/c6o.yaml b/packages/openmaptiles/c6o.yaml index eee99ff0..9f3663b3 100644 --- a/packages/openmaptiles/c6o.yaml +++ b/packages/openmaptiles/c6o.yaml @@ -44,7 +44,7 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: openmaptiles - image: 'klokantech/openmaptiles-server' + image: klokantech/openmaptiles-server port: 80 automated: true volume: diff --git a/packages/openproject/c6o.yaml b/packages/openproject/c6o.yaml index ec8810b8..277239d6 100644 --- a/packages/openproject/c6o.yaml +++ b/packages/openproject/c6o.yaml @@ -32,7 +32,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: openproject - image: 'openproject/community:11' + tag: 11 + image: openproject/community port: 80 automated: true diff --git a/packages/plexserver/c6o.yaml b/packages/plexserver/c6o.yaml index 46dbf07f..eddbafa9 100644 --- a/packages/plexserver/c6o.yaml +++ b/packages/plexserver/c6o.yaml @@ -53,7 +53,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: plexserver - image: 'plexinc/pms-docker:latest' + tag: latest + image: plexinc/pms-docker port: 32400 config: - name: PUID diff --git a/packages/postgresql/c6o.yaml b/packages/postgresql/c6o.yaml index d1541037..b8c1b853 100644 --- a/packages/postgresql/c6o.yaml +++ b/packages/postgresql/c6o.yaml @@ -43,7 +43,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: postgresql - image: postgres:12-alpine + tag: 12-alpine + image: postgres automated: true port: 5432 volume: @@ -79,7 +80,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: postgresql - image: postgres:12-alpine + tag: 12-alpine + image: postgres port: 5432 volume: - size: 5Gi diff --git a/packages/pylon/c6o.yaml b/packages/pylon/c6o.yaml index 08fdf2ae..c28b1e30 100644 --- a/packages/pylon/c6o.yaml +++ b/packages/pylon/c6o.yaml @@ -35,7 +35,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: pylon - image: 'linuxserver/pylon:latest' + tag: latest + image: linuxserver/pylon port: 3131 automated: true config: diff --git a/packages/radarr/c6o.yaml b/packages/radarr/c6o.yaml index 69f92c8a..30f0a161 100644 --- a/packages/radarr/c6o.yaml +++ b/packages/radarr/c6o.yaml @@ -36,7 +36,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: radarr - image: 'linuxserver/radarr:latest' + tag: latest + image: linuxserver/radarr port: 7878 automated: true config: diff --git a/packages/rdesktop/c6o.yaml b/packages/rdesktop/c6o.yaml index 766a19ae..9eaa8a16 100644 --- a/packages/rdesktop/c6o.yaml +++ b/packages/rdesktop/c6o.yaml @@ -49,7 +49,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: xfcefocal - image: 'linuxserver/rdesktop:latest' + tag: latest + image: linuxserver/rdesktop port: - port: 3389 name: tcp @@ -91,7 +92,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: xfcebionic - image: 'linuxserver/rdesktop:xfce-bionic' + tag: xfce-bionic + image: linuxserver/rdesktop port: - port: 3389 name: tcp @@ -135,7 +137,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: kdefocal - image: 'linuxserver/rdesktop:kde-focal' + tag: kde-focal + image: linuxserver/rdesktop port: - port: 3389 name: tcp @@ -178,7 +181,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: kdebionic - image: 'linuxserver/rdesktop:kde-bionic' + tag: kde-bionic + image: linuxserver/rdesktop port: - port: 3389 name: tcp @@ -222,7 +226,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: matefocal - image: 'linuxserver/rdesktop:mate-focal' + tag: mate-focal + image: linuxserver/rdesktop port: - port: 3389 name: tcp @@ -266,7 +271,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: matebionic - image: 'linuxserver/rdesktop:mate-bionic' + tag: mate-bionic + image: linuxserver/rdesktop port: - port: 3389 name: tcp @@ -310,7 +316,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: i3focal - image: 'linuxserver/rdesktop:i3-focal' + tag: i3-focal + image: linuxserver/rdesktop port: - port: 3389 name: tcp @@ -354,7 +361,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: i3bionic - image: 'linuxserver/rdesktop:i3-bionic' + tag: i3-bionic + image: linuxserver/rdesktop port: - port: 3389 name: tcp @@ -397,7 +405,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: openboxfocal - image: 'linuxserver/rdesktop:openbox-focal' + tag: openbox-focal + image: linuxserver/rdesktop port: - port: 3389 name: tcp @@ -440,7 +449,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: openboxbionic - image: 'linuxserver/rdesktop:openbox-bionic' + tag: openbox-bionic + image: linuxserver/rdesktop port: - port: 3389 name: tcp @@ -483,7 +493,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: icewmfocal - image: 'linuxserver/rdesktop:icewm-focal' + tag: icewm-focal + image: linuxserver/rdesktop port: - port: 3389 name: tcp @@ -526,7 +537,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: icewmbionic - image: 'linuxserver/rdesktop:icewm-bionic' + tag: icewm-bionic + image: linuxserver/rdesktop port: - port: 3389 name: tcp diff --git a/packages/recorder/c6o.yaml b/packages/recorder/c6o.yaml index e0632343..8282e26a 100644 --- a/packages/recorder/c6o.yaml +++ b/packages/recorder/c6o.yaml @@ -35,7 +35,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: recorder - image: 'owntracks/recorder:latest' + tag: latest + image: owntracks/recorder port: 8083 automated: true config: diff --git a/packages/redis/c6o.yaml b/packages/redis/c6o.yaml index 911b2cff..74f9e55b 100644 --- a/packages/redis/c6o.yaml +++ b/packages/redis/c6o.yaml @@ -59,7 +59,7 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: redis - image: 'redis' + image: redis config: - name: appendonly value: yes @@ -84,7 +84,7 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: redis - image: 'redis' + image: redis config: - name: appendonly value: yes diff --git a/packages/restyaboard/c6o.yaml b/packages/restyaboard/c6o.yaml index 6f1c9df1..7dd628bf 100644 --- a/packages/restyaboard/c6o.yaml +++ b/packages/restyaboard/c6o.yaml @@ -51,7 +51,8 @@ editions: provisioner: package: '@provisioner/appengine' name: restyaboard - image: 'restyaplatform/restyaboard:dev' + tag: dev + image: restyaplatform/restyaboard tag-prefix: appengine port: 80 diff --git a/packages/seafile/c6o.yaml b/packages/seafile/c6o.yaml index 684d6a69..611cd8fd 100644 --- a/packages/seafile/c6o.yaml +++ b/packages/seafile/c6o.yaml @@ -37,7 +37,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: seafile - image: 'seafileltd/seafile:latest' + tag: latest + image: seafileltd/seafile port: 80 automated: true config: diff --git a/packages/seq/c6o.yaml b/packages/seq/c6o.yaml index 8162cd4f..a6a3879a 100644 --- a/packages/seq/c6o.yaml +++ b/packages/seq/c6o.yaml @@ -41,7 +41,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: seq - image: datalust/seq:latest + tag: latest + image: datalust/seq port: 80 automated: true config: diff --git a/packages/snipeit/c6o.yaml b/packages/snipeit/c6o.yaml index 065d45e5..6ca6e3cb 100644 --- a/packages/snipeit/c6o.yaml +++ b/packages/snipeit/c6o.yaml @@ -56,7 +56,7 @@ provisioner: support: https://github.com/c6o/provisioners/issues editions: - - name: preview + - name: latest status: public default: true spec: @@ -65,8 +65,7 @@ editions: targetService: snipeit provisioner: package: '@provisioner/appengine' - name: snipeit - image: 'snipe/snipe-it' + image: snipe/snipe-it tag-prefix: appengine port: 80 automated: true @@ -117,6 +116,138 @@ editions: - name: APP_KEY value: $RANDOM:32 + marina: + launch: + type: inline + popUp: true + + - name: preview + status: public + default: true + spec: + routes: + - type: http + targetService: snipeit + provisioner: + package: '@provisioner/appengine' + image: snipe/snipe-it + tag-prefix: appengine + port: 80 + automated: true + +#stages: +#1. Scan all "features", and create specs internally for each +#2. Take the "values" and apply them to each features spec +#3. Apply all features into the cluster +#4. Apply the "script" section to the feature. This will be very hard coded +#5. Take the finished spec from each feature, and apply mappings to the App being installed +#6. Install the App requested + +#Drawback, cant map values from one feature to another +#Scripts and their relationship with the feature will be very fixed (mysql, mariadb, etc..) AppEngine will need to support all database types and such to execute these scripts against + + + link: + #WHICH DEPENDANCIES/FEATURES TO INSTALL + dependancies: + - name: mysql + spec: + edition: internal + package: '@provisioner/appengine' + tag-prefix: appengine + tag: latest + image: mysql + ports: + - port: 3306 + protocol: TCP + name: mysql + volumes: + - size: 5Gi + mountPath: /data + name: mysql-data + #POST INSTALL THE FEATURE + #RUN THE SCRIPT AGAINST THE DATABASE + #CAPTURE OUTPUT, CONSOLE.LOG THE RESULT + scripts: + - script: CREATE DATABASE snipeit; + - script: CREATE USER 'snipeit'@'%' IDENTIFIED BY '88888888'; + - script: ALTER USER 'snipeit'@'%' IDENTIFIED WITH mysql_native_password BY '88888888'; + - script: GRANT ALL PRIVILEGES ON snipeit.* TO 'snipeit'@'%'; + - script: FLUSH PRIVILEGES; + + #RUN THE SCRIPT AGAINST THE CLUSTER/NAMESPACE/POD + #kubectl exec shell-demo -- ls -la + #will take the output and console.log the result + # command: + # ls -la + #PRE INSTALL FEATURES + values: + - item: + source: + value: $RANDOM:32 + destination: + name: mysql + type: secrets + field: MYSQL_ROOT_PASSWORD + - item: + source: + value: 3306 + destination: + name: snipeit + type: configs + field: DB_PORT + + - item: + source: + value: 3306 + destination: + name: mysql + type: configs + field: DB_PORT + + - item: + source: + value: snipeit + destination: + name: snipeit + type: configs + field: DB_DATABASE + + - item: + source: + value: snipeit + destination: + name: snipeit + type: configs + field: DB_USERNAME + + - item: + source: + value: '88888888' + destination: + name: snipeit + type: secrets + field: DB_PASSWORD + + - item: + source: + value: $RANDOM:32 + destination: + name: snipeit + type: configs + field: APP_KEY + + #POST INSTALL ALL FEATURES, PRE INSTALL APP + mappings: + - item: + source: + name: mysql + type: configs + field: DB_HOST + destination: + name: snipeit + type: configs + field: DB_HOST marina: launch: diff --git a/packages/sonarr/c6o.yaml b/packages/sonarr/c6o.yaml index e2d3ef1d..128849ef 100644 --- a/packages/sonarr/c6o.yaml +++ b/packages/sonarr/c6o.yaml @@ -33,7 +33,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: sonarr - image: 'linuxserver/sonarr:latest' + tag: latest + image: linuxserver/sonarr port: 8989 automated: true config: diff --git a/packages/sshwifty/c6o.yaml b/packages/sshwifty/c6o.yaml index e8b6940c..d7039d20 100644 --- a/packages/sshwifty/c6o.yaml +++ b/packages/sshwifty/c6o.yaml @@ -36,7 +36,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: sshwifty - image: 'niruix/sshwifty:lates' + tag: latest + image: niruix/sshwifty' port: 8182 automated: true marina: diff --git a/packages/syncthing/c6o.yaml b/packages/syncthing/c6o.yaml index 69c59d87..d0ef3d6b 100644 --- a/packages/syncthing/c6o.yaml +++ b/packages/syncthing/c6o.yaml @@ -90,7 +90,8 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: syncthing - image: 'syncthing/syncthing:latest' + tag: latest + image: syncthing/syncthing port: 8384 automated: true config: diff --git a/packages/teamcity/c6o.yaml b/packages/teamcity/c6o.yaml index a0650d84..0dbf6ffd 100644 --- a/packages/teamcity/c6o.yaml +++ b/packages/teamcity/c6o.yaml @@ -50,7 +50,7 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: teamcityserver - image: 'jetbrains/teamcity-server' + image: jetbrains/teamcity-server port: 8111 automated: true config: @@ -76,13 +76,13 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: teamcityagent - image: 'jetbrains/teamcity-agent' + image: jetbrains/teamcity-agent automated: true config: - name: SERVER_URL value: http://teamcityserver:8111 label: Server Url - hint: The url to your TeamCity Server + hint: The url to your TeamCity Server (service, typically teamcity.NAMESPACE) required: true autoselect: true fieldType: text diff --git a/packages/wallabag/c6o.yaml b/packages/wallabag/c6o.yaml index bf86e517..823152f1 100644 --- a/packages/wallabag/c6o.yaml +++ b/packages/wallabag/c6o.yaml @@ -60,7 +60,7 @@ editions: package: '@provisioner/appengine' tag-prefix: appengine name: wallabag - image: 'wallabag/wallabag' + image: wallabag/wallabag port: 80 automated: true config: diff --git a/packages/wikijs/c6o.yaml b/packages/wikijs/c6o.yaml index 6feceb95..a1cd245f 100644 --- a/packages/wikijs/c6o.yaml +++ b/packages/wikijs/c6o.yaml @@ -63,7 +63,7 @@ editions: provisioner: package: '@provisioner/appengine' name: wikijs - image: 'requarks/wiki' + image: requarks/wiki tag-prefix: appengine port: 3000 automated: true diff --git a/packages/zimbra/c6o.yaml b/packages/zimbra/c6o.yaml index 9300c2bc..bca8240d 100644 --- a/packages/zimbra/c6o.yaml +++ b/packages/zimbra/c6o.yaml @@ -31,7 +31,7 @@ provisioner: editions: - name: preview - status: public + status: private default: true spec: routes: