From b94a34ce8633a9054ff13e09e7e13f64a537aaee Mon Sep 17 00:00:00 2001 From: Kasra Bigdeli Date: Sat, 18 Jul 2026 22:31:14 -0700 Subject: [PATCH] Format naming migration changes --- src/user/ServiceManager.ts | 1874 ++++++++++++++++++------------------ 1 file changed, 922 insertions(+), 952 deletions(-) diff --git a/src/user/ServiceManager.ts b/src/user/ServiceManager.ts index d2c9026..42c4d5d 100644 --- a/src/user/ServiceManager.ts +++ b/src/user/ServiceManager.ts @@ -1,593 +1,572 @@ -import ApiStatusCodes from '../api/ApiStatusCodes' -import DataStore from '../datastore/DataStore' -import DockerApi, { IDockerUpdateOrders } from '../docker/DockerApi' +npm warn Unknown env config "http-proxy". This will stop working in the next major version of npm. +import ApiStatusCodes from "../api/ApiStatusCodes"; +import DataStore from "../datastore/DataStore"; +import DockerApi, { IDockerUpdateOrders } from "../docker/DockerApi"; import { - AppDeployTokenConfig, - IAppDef, - IAppEnvVar, - IAppPort, - IAppTag, - IAppVolume, - IHttpAuth, - RepoInfo, -} from '../models/AppDefinition' -import { DockerAuthObj } from '../models/DockerAuthObj' -import { IHashMapGeneric } from '../models/ICacheGeneric' -import { IImageSource } from '../models/IImageSource' -import { PreDeployFunction } from '../models/OtherTypes' -import CaptainConstants from '../utils/CaptainConstants' -import Logger from '../utils/Logger' -import Utils from '../utils/Utils' -import Authenticator from './Authenticator' -import DockerRegistryHelper from './DockerRegistryHelper' -import ImageMaker, { BuildLogsManager } from './ImageMaker' -import { EventLogger } from './events/EventLogger' + AppDeployTokenConfig, + IAppDef, + IAppEnvVar, + IAppPort, + IAppTag, + IAppVolume, + IHttpAuth, + RepoInfo, +} from "../models/AppDefinition"; +import { DockerAuthObj } from "../models/DockerAuthObj"; +import { IHashMapGeneric } from "../models/ICacheGeneric"; +import { IImageSource } from "../models/IImageSource"; +import { PreDeployFunction } from "../models/OtherTypes"; +import CaptainConstants from "../utils/CaptainConstants"; +import Logger from "../utils/Logger"; +import Utils from "../utils/Utils"; +import Authenticator from "./Authenticator"; +import DockerRegistryHelper from "./DockerRegistryHelper"; +import ImageMaker, { BuildLogsManager } from "./ImageMaker"; +import { EventLogger } from "./events/EventLogger"; import { - CapRoverEventFactory, - CapRoverEventType, -} from './events/ICapRoverEvent' -import DomainResolveChecker from './system/DomainResolveChecker' -import LoadBalancerManager from './system/LoadBalancerManager' -import requireFromString = require('require-from-string') + CapRoverEventFactory, + CapRoverEventType, +} from "./events/ICapRoverEvent"; +import DomainResolveChecker from "./system/DomainResolveChecker"; +import LoadBalancerManager from "./system/LoadBalancerManager"; +import requireFromString = require("require-from-string"); const ERROR_FIRST_ENABLE_ROOT_SSL = - 'You have to first enable SSL for your root domain' + "You have to first enable SSL for your root domain"; -const serviceMangerCache = {} as IHashMapGeneric +const serviceMangerCache = {} as IHashMapGeneric; interface QueuedPromise { - resolve: undefined | ((reason?: unknown) => void) - reject: undefined | ((reason?: any) => void) - promise: undefined | Promise + resolve: undefined | ((reason?: unknown) => void); + reject: undefined | ((reason?: any) => void); + promise: undefined | Promise; } interface QueuedBuild { - appName: string - source: IImageSource - promiseToSave: QueuedPromise + appName: string; + source: IImageSource; + promiseToSave: QueuedPromise; } class ServiceManager { - static get( - namespace: string, - authenticator: Authenticator, - dataStore: DataStore, - dockerApi: DockerApi, - loadBalancerManager: LoadBalancerManager, - eventLogger: EventLogger, - domainResolveChecker: DomainResolveChecker - ) { - if (!serviceMangerCache[namespace]) { - serviceMangerCache[namespace] = new ServiceManager( - dataStore, - authenticator, - dockerApi, - loadBalancerManager, - eventLogger, - domainResolveChecker - ) - } - return serviceMangerCache[namespace] + static get( + namespace: string, + authenticator: Authenticator, + dataStore: DataStore, + dockerApi: DockerApi, + loadBalancerManager: LoadBalancerManager, + eventLogger: EventLogger, + domainResolveChecker: DomainResolveChecker, + ) { + if (!serviceMangerCache[namespace]) { + serviceMangerCache[namespace] = new ServiceManager( + dataStore, + authenticator, + dockerApi, + loadBalancerManager, + eventLogger, + domainResolveChecker, + ); + } + return serviceMangerCache[namespace]; + } + + private activeOrScheduledBuilds: IHashMapGeneric; + private buildLogsManager: BuildLogsManager; + private queuedBuilds: QueuedBuild[]; + private isReady: boolean; + private imageMaker: ImageMaker; + private dockerRegistryHelper: DockerRegistryHelper; + + constructor( + private dataStore: DataStore, + private authenticator: Authenticator, + private dockerApi: DockerApi, + private loadBalancerManager: LoadBalancerManager, + private eventLogger: EventLogger, + private domainResolveChecker: DomainResolveChecker, + ) { + this.activeOrScheduledBuilds = {}; + this.queuedBuilds = []; + this.buildLogsManager = new BuildLogsManager(); + this.isReady = true; + this.dockerRegistryHelper = new DockerRegistryHelper( + this.dataStore, + this.dockerApi, + ); + this.imageMaker = new ImageMaker( + this.dockerRegistryHelper, + this.dockerApi, + this.dataStore.getNameSpace(), + this.buildLogsManager, + ); + } + + getRegistryHelper() { + return this.dockerRegistryHelper; + } + + isInited() { + return this.isReady; + } + + scheduleDeployNewVersion(appName: string, source: IImageSource) { + const self = this; + + const activeBuildAppName = self.isAnyBuildRunning(); + this.activeOrScheduledBuilds[appName] = true; + + self.buildLogsManager.getAppBuildLogs(appName).clear(); + + if (activeBuildAppName) { + const existingBuildForTheSameApp = self.queuedBuilds.find( + (v) => v.appName === appName, + ); + + if (existingBuildForTheSameApp) { + self.buildLogsManager + .getAppBuildLogs(appName) + .log( + `A build for ${appName} was queued, it's now being replaced with a new build...`, + ); + + // replacing the new source! + existingBuildForTheSameApp.source = source; + + const existingPromise = + existingBuildForTheSameApp.promiseToSave.promise; + + if (!existingPromise) + throw new Error("Existing promise for the queued app is NULL!!"); + + return existingPromise; + } + + self.buildLogsManager + .getAppBuildLogs(appName) + .log( + `An active build (${activeBuildAppName}) is in progress. This build is queued...`, + ); + + const promiseToSave: QueuedPromise = { + resolve: undefined, + reject: undefined, + promise: undefined, + }; + + const promise = new Promise(function (resolve, reject) { + promiseToSave.resolve = resolve; + promiseToSave.reject = reject; + }); + + promiseToSave.promise = promise; + + self.queuedBuilds.push({ appName, source, promiseToSave }); + + // This should only return when the build is finished, + // somehow we need save the promise in queue - for "attached builds" + return promise; } - private activeOrScheduledBuilds: IHashMapGeneric - private buildLogsManager: BuildLogsManager - private queuedBuilds: QueuedBuild[] - private isReady: boolean - private imageMaker: ImageMaker - private dockerRegistryHelper: DockerRegistryHelper + return this.startDeployingNewVersion(appName, source); + } - constructor( - private dataStore: DataStore, - private authenticator: Authenticator, - private dockerApi: DockerApi, - private loadBalancerManager: LoadBalancerManager, - private eventLogger: EventLogger, - private domainResolveChecker: DomainResolveChecker - ) { - this.activeOrScheduledBuilds = {} - this.queuedBuilds = [] - this.buildLogsManager = new BuildLogsManager() - this.isReady = true - this.dockerRegistryHelper = new DockerRegistryHelper( - this.dataStore, - this.dockerApi - ) - this.imageMaker = new ImageMaker( - this.dockerRegistryHelper, - this.dockerApi, - this.dataStore.getNameSpace(), - this.buildLogsManager - ) - } + startDeployingNewVersion(appName: string, source: IImageSource) { + const self = this; + const dataStore = this.dataStore; + let deployedVersion: number; - getRegistryHelper() { - return this.dockerRegistryHelper - } + return Promise.resolve() // + .then(function () { + return dataStore.getAppsDataStore().createNewVersion(appName); + }) + .then(function (appVersion) { + deployedVersion = appVersion; + return dataStore + .getAppsDataStore() + .getAppDefinition(appName) + .then(function (app) { + const envVars = app.envVars || []; - isInited() { - return this.isReady - } + return self.imageMaker.ensureImage( + source, + appName, + app.captainDefinitionRelativeFilePath, + appVersion, + envVars, + ); + }); + }) + .then(function (builtImage) { + return dataStore + .getAppsDataStore() + .setDeployedVersionAndImage(appName, deployedVersion, builtImage); + }) + .then(function () { + self.onBuildFinished(appName); - scheduleDeployNewVersion(appName: string, source: IImageSource) { - const self = this + self.eventLogger.trackEvent( + CapRoverEventFactory.create(CapRoverEventType.AppBuildSuccessful, { + appName, + }), + ); - const activeBuildAppName = self.isAnyBuildRunning() - this.activeOrScheduledBuilds[appName] = true + return self.ensureServiceInitedAndUpdated(appName); + }) + .catch(function (error) { + self.onBuildFinished(appName); + return new Promise(function (resolve, reject) { + self.logBuildFailed(appName, error); + reject(error); + }); + }); + } - self.buildLogsManager.getAppBuildLogs(appName).clear() + onBuildFinished(appName: string) { + const self = this; + self.activeOrScheduledBuilds[appName] = false; - if (activeBuildAppName) { - const existingBuildForTheSameApp = self.queuedBuilds.find( - (v) => v.appName === appName - ) + Promise.resolve().then(function () { + const newBuild = self.queuedBuilds.shift(); + if (newBuild) + self.startDeployingNewVersion(newBuild.appName, newBuild.source); + }); + } - if (existingBuildForTheSameApp) { - self.buildLogsManager - .getAppBuildLogs(appName) - .log( - `A build for ${appName} was queued, it's now being replaced with a new build...` - ) + enableCustomDomainSsl(appName: string, customDomain: string) { + const self = this; - // replacing the new source! - existingBuildForTheSameApp.source = source - - const existingPromise = - existingBuildForTheSameApp.promiseToSave.promise - - if (!existingPromise) - throw new Error( - 'Existing promise for the queued app is NULL!!' - ) - - return existingPromise - } - - self.buildLogsManager - .getAppBuildLogs(appName) - .log( - `An active build (${activeBuildAppName}) is in progress. This build is queued...` - ) - - const promiseToSave: QueuedPromise = { - resolve: undefined, - reject: undefined, - promise: undefined, - } - - const promise = new Promise(function (resolve, reject) { - promiseToSave.resolve = resolve - promiseToSave.reject = reject - }) - - promiseToSave.promise = promise - - self.queuedBuilds.push({ appName, source, promiseToSave }) - - // This should only return when the build is finished, - // somehow we need save the promise in queue - for "attached builds" - return promise + return Promise.resolve() + .then(function () { + return self.dataStore.getHasRootSsl(); + }) + .then(function (rootHasSsl) { + if (!rootHasSsl) { + throw ApiStatusCodes.createError( + ApiStatusCodes.STATUS_ERROR_GENERIC, + ERROR_FIRST_ENABLE_ROOT_SSL, + ); } - return this.startDeployingNewVersion(appName, source) - } + Logger.d(`Verifying Captain owns domain: ${customDomain}`); - startDeployingNewVersion(appName: string, source: IImageSource) { - const self = this - const dataStore = this.dataStore - let deployedVersion: number + return self.domainResolveChecker.verifyCaptainOwnsDomainOrThrow( + customDomain, + undefined, + ); + }) + .then(function () { + Logger.d(`Enabling SSL for: ${appName} on ${customDomain}`); - return Promise.resolve() // - .then(function () { - return dataStore.getAppsDataStore().createNewVersion(appName) - }) - .then(function (appVersion) { - deployedVersion = appVersion - return dataStore - .getAppsDataStore() - .getAppDefinition(appName) - .then(function (app) { - const envVars = app.envVars || [] + return self.dataStore + .getAppsDataStore() + .verifyCustomDomainBelongsToApp(appName, customDomain); + }) + .then(function () { + return self.domainResolveChecker.requestCertificateForDomain( + customDomain, + ); + }) + .then(function () { + return self.dataStore + .getAppsDataStore() + .enableCustomDomainSsl(appName, customDomain); + }) + .then(function () { + return self.reloadLoadBalancer(); + }); + } - return self.imageMaker.ensureImage( - source, - appName, - app.captainDefinitionRelativeFilePath, - appVersion, - envVars - ) - }) - }) - .then(function (builtImage) { - return dataStore - .getAppsDataStore() - .setDeployedVersionAndImage( - appName, - deployedVersion, - builtImage - ) - }) - .then(function () { - self.onBuildFinished(appName) + addCustomDomain(appName: string, customDomain: string) { + const self = this; - self.eventLogger.trackEvent( - CapRoverEventFactory.create( - CapRoverEventType.AppBuildSuccessful, - { - appName, - } - ) - ) + return Promise.resolve() + .then(function () { + const rootDomain = self.dataStore.getRootDomain(); - return self.ensureServiceInitedAndUpdated(appName) - }) - .catch(function (error) { - self.onBuildFinished(appName) - return new Promise(function (resolve, reject) { - self.logBuildFailed(appName, error) - reject(error) - }) - }) - } + try { + Utils.checkCustomDomain(customDomain, appName, rootDomain); + } catch (error) { + throw ApiStatusCodes.createError( + ApiStatusCodes.STATUS_ERROR_BAD_NAME, + error, + ); + } + }) + .then(function () { + return self.domainResolveChecker.verifyDomainResolvesToDefaultServerOnHost( + customDomain, + ); + }) + .then(function () { + Logger.d(`Enabling custom domain for: ${appName}`); - onBuildFinished(appName: string) { - const self = this - self.activeOrScheduledBuilds[appName] = false + return self.dataStore + .getAppsDataStore() + .addCustomDomainForApp(appName, customDomain); + }) + .then(function () { + return self.reloadLoadBalancer(); + }); + } - Promise.resolve().then(function () { - const newBuild = self.queuedBuilds.shift() - if (newBuild) - self.startDeployingNewVersion(newBuild.appName, newBuild.source) + removeCustomDomain(appName: string, customDomain: string) { + const self = this; + + return Promise.resolve() + .then(function () { + Logger.d(`Removing custom domain for: ${appName}`); + + return self.dataStore + .getAppsDataStore() + .removeCustomDomainForApp(appName, customDomain); + }) + .then(function () { + return self.reloadLoadBalancer(); + }); + } + + enableSslForApp(appName: string) { + const self = this; + + let rootDomain: string; + + return Promise.resolve() + .then(function () { + return self.dataStore.getHasRootSsl(); + }) + .then(function (rootHasSsl) { + if (!rootHasSsl) { + throw ApiStatusCodes.createError( + ApiStatusCodes.STATUS_ERROR_GENERIC, + ERROR_FIRST_ENABLE_ROOT_SSL, + ); + } + return self.verifyCaptainOwnsGenericSubDomain(appName); + }) + .then(function () { + Logger.d(`Enabling SSL for: ${appName}`); + + return self.dataStore.getRootDomain(); + }) + .then(function (val) { + rootDomain = val; + + if (!rootDomain) { + throw new Error("No rootDomain! Cannot verify domain"); + } + }) + .then(function () { + // it will ensure that the app exists, otherwise it throws an exception + return self.dataStore.getAppsDataStore().getAppDefinition(appName); + }) + .then(function () { + return `${appName}.${rootDomain}`; + }) + .then(function (domainName) { + return self.domainResolveChecker.requestCertificateForDomain( + domainName, + ); + }) + .then(function () { + return self.dataStore + .getAppsDataStore() + .setSslForDefaultSubDomain(appName, true); + }) + .then(function () { + return self.reloadLoadBalancer(); + }); + } + + verifyCaptainOwnsGenericSubDomain(appName: string) { + const self = this; + + let rootDomain: string; + + return Promise.resolve() + .then(function () { + return self.dataStore.getRootDomain(); + }) + .then(function (val) { + rootDomain = val; + }) + .then(function () { + // it will ensure that the app exists, otherwise it throws an exception + return self.dataStore.getAppsDataStore().getAppDefinition(appName); + }) + .then(function () { + return `${appName}.${rootDomain}`; + }) + .then(function (domainName) { + Logger.d(`Verifying Captain owns domain: ${domainName}`); + + return self.domainResolveChecker.verifyCaptainOwnsDomainOrThrow( + domainName, + undefined, + ); + }); + } + + renameApp(oldAppName: string, newAppName: string) { + Logger.d(`Renaming app: ${oldAppName}`); + const self = this; + + const dockerApi = this.dockerApi; + const dataStore = this.dataStore; + + let defaultSslOn = false; + let oldServiceName: string; + + return Promise.resolve() + .then(function () { + return dataStore.getAppsDataStore().getAppDefinition(oldAppName); + }) + .then(function (appDef) { + defaultSslOn = !!appDef.hasDefaultSubDomainSsl; + oldServiceName = dataStore + .getAppsDataStore() + .getServiceName(oldAppName, !!appDef.isLegacyAppName); + + dataStore.getAppsDataStore().nameAllowedOrThrow(newAppName); + + return self.ensureNotBuilding(oldAppName); + }) + .then(function () { + Logger.d(`Check if service is running: ${oldServiceName}`); + return dockerApi.isServiceRunningByName(oldServiceName); + }) + .then(function (isRunning) { + if (!isRunning) { + throw ApiStatusCodes.createError( + ApiStatusCodes.STATUS_ERROR_GENERIC, + "Service is not running!", + ); + } + return dockerApi.removeServiceByName(oldServiceName); + }) + .then(function () { + return dataStore + .getAppsDataStore() + .renameApp(self.authenticator, oldAppName, newAppName); + }) + .then(function () { + return self.ensureServiceInitedAndUpdated(newAppName); + }) + .then(function () { + if (defaultSslOn) return self.enableSslForApp(newAppName); + }); + } + + removeApps(appNames: string[]) { + Logger.d(`Removing service for: ${appNames.join(", ")}`); + const self = this; + + const removeAppPromise = function (appName: string) { + const dockerApi = self.dockerApi; + const dataStore = self.dataStore; + let serviceName: string; + + return Promise.resolve() + .then(function () { + return dataStore.getAppsDataStore().getAppDefinition(appName); }) + .then(function (appDef) { + serviceName = dataStore + .getAppsDataStore() + .getServiceName(appName, !!appDef.isLegacyAppName); + return self.ensureNotBuilding(appName); + }) + .then(function () { + Logger.d(`Check if service is running: ${serviceName}`); + return dockerApi.isServiceRunningByName(serviceName); + }) + .then(function (isRunning) { + if (isRunning) { + return dockerApi.removeServiceByName(serviceName); + } else { + Logger.w( + `Cannot delete service... It is not running: ${serviceName}`, + ); + return true; + } + }) + .then(function () { + return dataStore.getAppsDataStore().deleteAppDefinition(appName); + }) + .then(function () { + return self.reloadLoadBalancer(); + }); + }; + + const promises = []; + for (const appName of appNames) { + promises.push(removeAppPromise(appName)); } - enableCustomDomainSsl(appName: string, customDomain: string) { - const self = this + return Promise.all(promises); + } - return Promise.resolve() - .then(function () { - return self.dataStore.getHasRootSsl() - }) - .then(function (rootHasSsl) { - if (!rootHasSsl) { - throw ApiStatusCodes.createError( - ApiStatusCodes.STATUS_ERROR_GENERIC, - ERROR_FIRST_ENABLE_ROOT_SSL - ) - } + removeVolsSafe(volumes: IHashMapGeneric) { + const dockerApi = this.dockerApi; + const dataStore = this.dataStore; - Logger.d(`Verifying Captain owns domain: ${customDomain}`) + const volsFailedToDelete: IHashMapGeneric = {}; - return self.domainResolveChecker.verifyCaptainOwnsDomainOrThrow( - customDomain, - undefined - ) - }) - .then(function () { - Logger.d(`Enabling SSL for: ${appName} on ${customDomain}`) + return Promise.resolve() + .then(function () { + return dataStore.getAppsDataStore().getAppDefinitions(); + }) + .then(function (apps) { + const physicalVolumesInUse: IHashMapGeneric = {}; - return self.dataStore - .getAppsDataStore() - .verifyCustomDomainBelongsToApp(appName, customDomain) - }) - .then(function () { - return self.domainResolveChecker.requestCertificateForDomain( - customDomain - ) - }) - .then(function () { - return self.dataStore - .getAppsDataStore() - .enableCustomDomainSsl(appName, customDomain) - }) - .then(function () { - return self.reloadLoadBalancer() - }) + Object.keys(apps).forEach((appName) => { + const app = apps[appName]; + const volsInApp = app.volumes || []; + + volsInApp.forEach((volume) => { + const volumeName = volume.volumeName; + if (!volumeName) { + return; + } + + const physicalVolumeName = dataStore + .getAppsDataStore() + .getVolumeName(volumeName, !!app.isLegacyAppName); + physicalVolumesInUse[physicalVolumeName] = true; + }); + }); + + const volumesTryToDelete: string[] = []; + Object.keys(volumes).forEach((physicalVolumeName) => { + const logicalVolumeName = volumes[physicalVolumeName]; + if (physicalVolumesInUse[physicalVolumeName]) { + volsFailedToDelete[logicalVolumeName] = true; + } else { + volumesTryToDelete.push(physicalVolumeName); + } + }); + + return dockerApi.deleteVols(volumesTryToDelete); + }) + .then(function (failedVols) { + failedVols.forEach((physicalVolumeName) => { + const logicalVolumeName = + volumes[physicalVolumeName] || physicalVolumeName; + volsFailedToDelete[logicalVolumeName] = true; + }); + + return Object.keys(volsFailedToDelete); + }); + } + + createPreDeployFunctionIfExist(app: IAppDef): PreDeployFunction | undefined { + let preDeployFunction = app.preDeployFunction; + + if (!preDeployFunction) { + return undefined; } - addCustomDomain(appName: string, customDomain: string) { - const self = this - - return Promise.resolve() - .then(function () { - const rootDomain = self.dataStore.getRootDomain() - - try { - Utils.checkCustomDomain(customDomain, appName, rootDomain) - } catch (error) { - throw ApiStatusCodes.createError( - ApiStatusCodes.STATUS_ERROR_BAD_NAME, - error - ) - } - }) - .then(function () { - return self.domainResolveChecker.verifyDomainResolvesToDefaultServerOnHost( - customDomain - ) - }) - .then(function () { - Logger.d(`Enabling custom domain for: ${appName}`) - - return self.dataStore - .getAppsDataStore() - .addCustomDomainForApp(appName, customDomain) - }) - .then(function () { - return self.reloadLoadBalancer() - }) - } - - removeCustomDomain(appName: string, customDomain: string) { - const self = this - - return Promise.resolve() - .then(function () { - Logger.d(`Removing custom domain for: ${appName}`) - - return self.dataStore - .getAppsDataStore() - .removeCustomDomainForApp(appName, customDomain) - }) - .then(function () { - return self.reloadLoadBalancer() - }) - } - - enableSslForApp(appName: string) { - const self = this - - let rootDomain: string - - return Promise.resolve() - .then(function () { - return self.dataStore.getHasRootSsl() - }) - .then(function (rootHasSsl) { - if (!rootHasSsl) { - throw ApiStatusCodes.createError( - ApiStatusCodes.STATUS_ERROR_GENERIC, - ERROR_FIRST_ENABLE_ROOT_SSL - ) - } - return self.verifyCaptainOwnsGenericSubDomain(appName) - }) - .then(function () { - Logger.d(`Enabling SSL for: ${appName}`) - - return self.dataStore.getRootDomain() - }) - .then(function (val) { - rootDomain = val - - if (!rootDomain) { - throw new Error('No rootDomain! Cannot verify domain') - } - }) - .then(function () { - // it will ensure that the app exists, otherwise it throws an exception - return self.dataStore - .getAppsDataStore() - .getAppDefinition(appName) - }) - .then(function () { - return `${appName}.${rootDomain}` - }) - .then(function (domainName) { - return self.domainResolveChecker.requestCertificateForDomain( - domainName - ) - }) - .then(function () { - return self.dataStore - .getAppsDataStore() - .setSslForDefaultSubDomain(appName, true) - }) - .then(function () { - return self.reloadLoadBalancer() - }) - } - - verifyCaptainOwnsGenericSubDomain(appName: string) { - const self = this - - let rootDomain: string - - return Promise.resolve() - .then(function () { - return self.dataStore.getRootDomain() - }) - .then(function (val) { - rootDomain = val - }) - .then(function () { - // it will ensure that the app exists, otherwise it throws an exception - return self.dataStore - .getAppsDataStore() - .getAppDefinition(appName) - }) - .then(function () { - return `${appName}.${rootDomain}` - }) - .then(function (domainName) { - Logger.d(`Verifying Captain owns domain: ${domainName}`) - - return self.domainResolveChecker.verifyCaptainOwnsDomainOrThrow( - domainName, - undefined - ) - }) - } - - renameApp(oldAppName: string, newAppName: string) { - Logger.d(`Renaming app: ${oldAppName}`) - const self = this - - const dockerApi = this.dockerApi - const dataStore = this.dataStore - - let defaultSslOn = false - let oldServiceName: string - - return Promise.resolve() - .then(function () { - return dataStore.getAppsDataStore().getAppDefinition(oldAppName) - }) - .then(function (appDef) { - defaultSslOn = !!appDef.hasDefaultSubDomainSsl - oldServiceName = dataStore - .getAppsDataStore() - .getServiceName(oldAppName, !!appDef.isLegacyAppName) - - dataStore.getAppsDataStore().nameAllowedOrThrow(newAppName) - - return self.ensureNotBuilding(oldAppName) - }) - .then(function () { - Logger.d(`Check if service is running: ${oldServiceName}`) - return dockerApi.isServiceRunningByName(oldServiceName) - }) - .then(function (isRunning) { - if (!isRunning) { - throw ApiStatusCodes.createError( - ApiStatusCodes.STATUS_ERROR_GENERIC, - 'Service is not running!' - ) - } - return dockerApi.removeServiceByName(oldServiceName) - }) - .then(function () { - return dataStore - .getAppsDataStore() - .renameApp(self.authenticator, oldAppName, newAppName) - }) - .then(function () { - return self.ensureServiceInitedAndUpdated(newAppName) - }) - .then(function () { - if (defaultSslOn) return self.enableSslForApp(newAppName) - }) - } - - removeApps(appNames: string[]) { - Logger.d(`Removing service for: ${appNames.join(', ')}`) - const self = this - - const removeAppPromise = function (appName: string) { - const dockerApi = self.dockerApi - const dataStore = self.dataStore - let serviceName: string - - return Promise.resolve() - .then(function () { - return dataStore - .getAppsDataStore() - .getAppDefinition(appName) - }) - .then(function (appDef) { - serviceName = dataStore - .getAppsDataStore() - .getServiceName(appName, !!appDef.isLegacyAppName) - return self.ensureNotBuilding(appName) - }) - .then(function () { - Logger.d(`Check if service is running: ${serviceName}`) - return dockerApi.isServiceRunningByName(serviceName) - }) - .then(function (isRunning) { - if (isRunning) { - return dockerApi.removeServiceByName(serviceName) - } else { - Logger.w( - `Cannot delete service... It is not running: ${serviceName}` - ) - return true - } - }) - .then(function () { - return dataStore - .getAppsDataStore() - .deleteAppDefinition(appName) - }) - .then(function () { - return self.reloadLoadBalancer() - }) - } - - const promises = [] - for (const appName of appNames) { - promises.push(removeAppPromise(appName)) - } - - return Promise.all(promises) - } - - removeVolsSafe(volumes: IHashMapGeneric) { - const dockerApi = this.dockerApi - const dataStore = this.dataStore - - const volsFailedToDelete: IHashMapGeneric = {} - - return Promise.resolve() - .then(function () { - return dataStore.getAppsDataStore().getAppDefinitions() - }) - .then(function (apps) { - const physicalVolumesInUse: IHashMapGeneric = {} - - Object.keys(apps).forEach((appName) => { - const app = apps[appName] - const volsInApp = app.volumes || [] - - volsInApp.forEach((volume) => { - const volumeName = volume.volumeName - if (!volumeName) { - return - } - - const physicalVolumeName = dataStore - .getAppsDataStore() - .getVolumeName( - volumeName, - !!app.isLegacyAppName - ) - physicalVolumesInUse[physicalVolumeName] = true - }) - }) - - const volumesTryToDelete: string[] = [] - Object.keys(volumes).forEach((physicalVolumeName) => { - const logicalVolumeName = volumes[physicalVolumeName] - if (physicalVolumesInUse[physicalVolumeName]) { - volsFailedToDelete[logicalVolumeName] = true - } else { - volumesTryToDelete.push(physicalVolumeName) - } - }) - - return dockerApi.deleteVols(volumesTryToDelete) - }) - .then(function (failedVols) { - failedVols.forEach((physicalVolumeName) => { - const logicalVolumeName = - volumes[physicalVolumeName] || physicalVolumeName - volsFailedToDelete[logicalVolumeName] = true - }) - - return Object.keys(volsFailedToDelete) - }) - } - - createPreDeployFunctionIfExist( - app: IAppDef - ): PreDeployFunction | undefined { - let preDeployFunction = app.preDeployFunction - - if (!preDeployFunction) { - return undefined - } - - /* + /* ////////////////////////////////// Expected content of the file ////////////////////////// console.log('-------------------------------'+new Date()); @@ -601,424 +580,415 @@ class ServiceManager { }; */ - preDeployFunction = - preDeployFunction + '\n\n module.exports = preDeployFunction' + preDeployFunction = + preDeployFunction + "\n\n module.exports = preDeployFunction"; - return requireFromString(preDeployFunction) - } + return requireFromString(preDeployFunction); + } - ensureNotBuilding(appName: string) { - if (this.activeOrScheduledBuilds[appName]) - throw ApiStatusCodes.createError( - ApiStatusCodes.ILLEGAL_OPERATION, - `Build in-progress for ${appName}. Please wait...` - ) - } + ensureNotBuilding(appName: string) { + if (this.activeOrScheduledBuilds[appName]) + throw ApiStatusCodes.createError( + ApiStatusCodes.ILLEGAL_OPERATION, + `Build in-progress for ${appName}. Please wait...`, + ); + } - updateAppDefinition( - appName: string, - projectId: string, - description: string, - instanceCount: number, - captainDefinitionRelativeFilePath: string, - envVars: IAppEnvVar[], - volumes: IAppVolume[], - tags: IAppTag[], - nodeId: string, - notExposeAsWebApp: boolean, - containerHttpPort: number, - httpAuth: IHttpAuth, - forceSsl: boolean, - ports: IAppPort[], - repoInfo: RepoInfo, - customNginxConfig: string, - redirectDomain: string, - preDeployFunction: string, - serviceUpdateOverride: string, - websocketSupport: boolean, - appDeployTokenConfig: AppDeployTokenConfig - ) { - const self = this - const dataStore = this.dataStore - const dockerApi = this.dockerApi + updateAppDefinition( + appName: string, + projectId: string, + description: string, + instanceCount: number, + captainDefinitionRelativeFilePath: string, + envVars: IAppEnvVar[], + volumes: IAppVolume[], + tags: IAppTag[], + nodeId: string, + notExposeAsWebApp: boolean, + containerHttpPort: number, + httpAuth: IHttpAuth, + forceSsl: boolean, + ports: IAppPort[], + repoInfo: RepoInfo, + customNginxConfig: string, + redirectDomain: string, + preDeployFunction: string, + serviceUpdateOverride: string, + websocketSupport: boolean, + appDeployTokenConfig: AppDeployTokenConfig, + ) { + const self = this; + const dataStore = this.dataStore; + const dockerApi = this.dockerApi; - let serviceName: string + let serviceName: string; - let existingAppDefinition: IAppDef + let existingAppDefinition: IAppDef; - const checkIfNodeIdExists = function (nodeIdToCheck: string) { - return dockerApi.getNodesInfo().then(function (nodeInfo) { - for (let i = 0; i < nodeInfo.length; i++) { - if (nodeIdToCheck === nodeInfo[i].nodeId) { - return - } - } - - throw ApiStatusCodes.createError( - ApiStatusCodes.STATUS_ERROR_GENERIC, - `Node ID you requested is not part of the swarm cluster: ${nodeIdToCheck}` - ) - }) + const checkIfNodeIdExists = function (nodeIdToCheck: string) { + return dockerApi.getNodesInfo().then(function (nodeInfo) { + for (let i = 0; i < nodeInfo.length; i++) { + if (nodeIdToCheck === nodeInfo[i].nodeId) { + return; + } } - return Promise.resolve() - .then(function () { - projectId = `${projectId || ''}`.trim() - if (projectId) { - return dataStore - .getProjectsDataStore() - .getProject(projectId) + throw ApiStatusCodes.createError( + ApiStatusCodes.STATUS_ERROR_GENERIC, + `Node ID you requested is not part of the swarm cluster: ${nodeIdToCheck}`, + ); + }); + }; - // if project is not found, it will throw an error - } - }) - .then(function () { - return self.ensureNotBuilding(appName) - }) - .then(function () { - return dataStore.getAppsDataStore().getAppDefinition(appName) - }) - .then(function (app) { - serviceName = dataStore - .getAppsDataStore() - .getServiceName(appName, !!app.isLegacyAppName) + return Promise.resolve() + .then(function () { + projectId = `${projectId || ""}`.trim(); + if (projectId) { + return dataStore.getProjectsDataStore().getProject(projectId); - // After leaving this block, nodeId will be guaranteed to be NonNull - if (app.hasPersistentData) { - if (nodeId) { - return checkIfNodeIdExists(nodeId) - } else { - if (app.nodeId) { - nodeId = app.nodeId - } else { - return dockerApi - .isServiceRunningByName(serviceName) - .then(function (isRunning: boolean) { - if (!isRunning) { - throw ApiStatusCodes.createError( - ApiStatusCodes.STATUS_ERROR_GENERIC, - 'Cannot find the service. Try again in a minute...' - ) - } - return dockerApi.getNodeIdByServiceName( - serviceName, - 0 - ) - }) - .then(function (nodeIdRunningService: string) { - if (!nodeIdRunningService) { - throw ApiStatusCodes.createError( - ApiStatusCodes.STATUS_ERROR_GENERIC, - 'No NodeId was found. Try again in a minute...' - ) - } + // if project is not found, it will throw an error + } + }) + .then(function () { + return self.ensureNotBuilding(appName); + }) + .then(function () { + return dataStore.getAppsDataStore().getAppDefinition(appName); + }) + .then(function (app) { + serviceName = dataStore + .getAppsDataStore() + .getServiceName(appName, !!app.isLegacyAppName); - nodeId = nodeIdRunningService - }) - } - } - } else { - if (volumes && volumes.length) { - throw ApiStatusCodes.createError( - ApiStatusCodes.ILLEGAL_OPERATION, - 'Cannot set volumes for a non-persistent container!' - ) - } - - if (nodeId) { - return checkIfNodeIdExists(nodeId) - } - } - }) - .then(function () { - serviceUpdateOverride = serviceUpdateOverride - ? `${serviceUpdateOverride}`.trim() - : '' - if (!serviceUpdateOverride) { - // no override! - return - } - - if (!Utils.convertYamlOrJsonToObject(serviceUpdateOverride)) { + // After leaving this block, nodeId will be guaranteed to be NonNull + if (app.hasPersistentData) { + if (nodeId) { + return checkIfNodeIdExists(nodeId); + } else { + if (app.nodeId) { + nodeId = app.nodeId; + } else { + return dockerApi + .isServiceRunningByName(serviceName) + .then(function (isRunning: boolean) { + if (!isRunning) { throw ApiStatusCodes.createError( - ApiStatusCodes.ILLEGAL_PARAMETER, - 'serviceUpdateOverride must be either a valid JSON object starting with { or an equivalent yaml' - ) - } - }) - .then(function () { - return dataStore.getAppsDataStore().getAppDefinition(appName) - }) - .then(function (appDef) { - existingAppDefinition = appDef - - return dataStore - .getAppsDataStore() - .updateAppDefinitionInDb( - appName, - projectId, - description, - instanceCount, - captainDefinitionRelativeFilePath, - envVars, - volumes, - tags, - nodeId, - notExposeAsWebApp, - containerHttpPort, - httpAuth, - forceSsl, - ports, - repoInfo, - self.authenticator, - customNginxConfig, - redirectDomain, - preDeployFunction, - serviceUpdateOverride, - websocketSupport, - appDeployTokenConfig - ) - }) - .then(function () { - return self.ensureServiceInitedAndUpdated(appName) - }) - .catch(function (error) { - if ( - error && - error.captainErrorType === - ApiStatusCodes.STATUS_ERROR_NGINX_VALIDATION_FAILED - ) { - // Revert back to the old definition because the nginx config is invalid - if (existingAppDefinition) { - Logger.d( - `nginx validation failed, reverting configs for: ${appName}` - ) - return dataStore - .getAppsDataStore() - .updateAppDefinitionInDb( - appName, - existingAppDefinition.projectId, - existingAppDefinition.description, - existingAppDefinition.instanceCount, - existingAppDefinition.captainDefinitionRelativeFilePath, - existingAppDefinition.envVars, - existingAppDefinition.volumes, - existingAppDefinition.tags || [], - existingAppDefinition.nodeId || '', - existingAppDefinition.notExposeAsWebApp, - existingAppDefinition.containerHttpPort || 80, - existingAppDefinition.httpAuth, - existingAppDefinition.forceSsl, - existingAppDefinition.ports, - existingAppDefinition.appPushWebhook - ?.repoInfo || { - repo: '', - branch: '', - user: '', - password: '', - }, - self.authenticator, - existingAppDefinition.customNginxConfig || '', - existingAppDefinition.redirectDomain || '', - existingAppDefinition.preDeployFunction || '', - existingAppDefinition.serviceUpdateOverride || - '', - existingAppDefinition.websocketSupport, - existingAppDefinition.appDeployTokenConfig || { - enabled: false, - } - ) - .then(function () { - self.reloadLoadBalancer() - }) - .then(function () { - throw error - }) - } - } - - throw error - }) - } - - isAppBuilding(appName: string) { - return !!this.activeOrScheduledBuilds[appName] - } - - /** - * - * @returns the active build that it finds - */ - isAnyBuildRunning() { - const activeBuilds = this.activeOrScheduledBuilds - - for (const appName in activeBuilds) { - if (activeBuilds[appName]) { - return appName - } - } - - return undefined - } - - getBuildStatus(appName: string) { - const self = this - - return { - isAppBuilding: self.isAppBuilding(appName), - logs: self.buildLogsManager.getAppBuildLogs(appName).getLogs(), - isBuildFailed: - self.buildLogsManager.getAppBuildLogs(appName).isBuildFailed, - } - } - - logBuildFailed(appName: string, error: string) { - const self = this - error = (error || '') + '' - - self.eventLogger.trackEvent( - CapRoverEventFactory.create(CapRoverEventType.AppBuildFailed, { - appName, - error: error.substring(0, 1000), - }) - ) - this.buildLogsManager.getAppBuildLogs(appName).onBuildFailed(error) - } - - getAppLogs(appName: string, encoding: string) { - const dockerApi = this.dockerApi - const dataStore = this.dataStore - let serviceName: string - - return Promise.resolve() // - .then(function () { - return dataStore.getAppsDataStore().getAppDefinition(appName) - }) - .then(function (appDef) { - serviceName = dataStore - .getAppsDataStore() - .getServiceName(appName, !!appDef.isLegacyAppName) - return dockerApi.getLogForService( - serviceName, - CaptainConstants.configs.appLogSize, - encoding - ) - }) - } - - ensureServiceInitedAndUpdated(appName: string) { - Logger.d(`Ensure service inited and Updated for: ${appName}`) - const self = this - - let imageName: string | undefined - const dockerApi = this.dockerApi - const dataStore = this.dataStore - let app: IAppDef - let dockerAuthObject: DockerAuthObj | undefined - let serviceName: string - - return Promise.resolve() // - .then(function () { - return dataStore.getAppsDataStore().getAppDefinition(appName) - }) - .then(function (appFound) { - app = appFound - serviceName = dataStore - .getAppsDataStore() - .getServiceName(appName, !!app.isLegacyAppName) - - Logger.d(`Check if service is running: ${serviceName}`) - return dockerApi.isServiceRunningByName(serviceName) - }) - .then(function (isRunning) { - for (let i = 0; i < app.versions.length; i++) { - const element = app.versions[i] - if (element.version === app.deployedVersion) { - imageName = element.deployedImageName - break - } - } - - if (!imageName) { - throw ApiStatusCodes.createError( - ApiStatusCodes.ILLEGAL_PARAMETER, - 'ImageName for deployed version is not available, this version was probably failed due to an unsuccessful build!' - ) - } - - if (isRunning) { - Logger.d(`Service is already running: ${serviceName}`) - return true - } else { - Logger.d( - `Creating service ${serviceName} with default image, we will update image later` - ) - - // if we pass in networks here. Almost always it results in a delayed update which causes - // update errors if they happen right away! - return dockerApi - .createServiceOnNodeId( - CaptainConstants.configs.appPlaceholderImageName, - serviceName, - undefined, - undefined, - undefined, - undefined, - undefined - ) - .then(() => true) - } - }) - .then(function () { - return self.dockerRegistryHelper.getDockerAuthObjectForImageName( - imageName! - ) - }) - .then(function (data) { - dockerAuthObject = data - }) - .then(function () { - return self.createPreDeployFunctionIfExist(app) - }) - .then(function (preDeployFunction) { - Logger.d( - `Updating service ${serviceName} with image ${imageName}` - ) - - return dockerApi.updateService( - serviceName, - imageName, - app.volumes, - app.networks, - app.envVars, - undefined, - dockerAuthObject, - Number(app.instanceCount), - app.nodeId, - dataStore.getNameSpace(), - app.ports, - app, - IDockerUpdateOrders.AUTO, - Utils.convertYamlOrJsonToObject(app.serviceUpdateOverride), - preDeployFunction - ) - }) - .then(function () { - return new Promise(function (resolve) { - // Waiting 2 extra seconds for docker DNS to pickup the service name - setTimeout(resolve, 2000) + ApiStatusCodes.STATUS_ERROR_GENERIC, + "Cannot find the service. Try again in a minute...", + ); + } + return dockerApi.getNodeIdByServiceName(serviceName, 0); }) - }) - .then(function () { - return self.reloadLoadBalancer() - }) + .then(function (nodeIdRunningService: string) { + if (!nodeIdRunningService) { + throw ApiStatusCodes.createError( + ApiStatusCodes.STATUS_ERROR_GENERIC, + "No NodeId was found. Try again in a minute...", + ); + } + + nodeId = nodeIdRunningService; + }); + } + } + } else { + if (volumes && volumes.length) { + throw ApiStatusCodes.createError( + ApiStatusCodes.ILLEGAL_OPERATION, + "Cannot set volumes for a non-persistent container!", + ); + } + + if (nodeId) { + return checkIfNodeIdExists(nodeId); + } + } + }) + .then(function () { + serviceUpdateOverride = serviceUpdateOverride + ? `${serviceUpdateOverride}`.trim() + : ""; + if (!serviceUpdateOverride) { + // no override! + return; + } + + if (!Utils.convertYamlOrJsonToObject(serviceUpdateOverride)) { + throw ApiStatusCodes.createError( + ApiStatusCodes.ILLEGAL_PARAMETER, + "serviceUpdateOverride must be either a valid JSON object starting with { or an equivalent yaml", + ); + } + }) + .then(function () { + return dataStore.getAppsDataStore().getAppDefinition(appName); + }) + .then(function (appDef) { + existingAppDefinition = appDef; + + return dataStore + .getAppsDataStore() + .updateAppDefinitionInDb( + appName, + projectId, + description, + instanceCount, + captainDefinitionRelativeFilePath, + envVars, + volumes, + tags, + nodeId, + notExposeAsWebApp, + containerHttpPort, + httpAuth, + forceSsl, + ports, + repoInfo, + self.authenticator, + customNginxConfig, + redirectDomain, + preDeployFunction, + serviceUpdateOverride, + websocketSupport, + appDeployTokenConfig, + ); + }) + .then(function () { + return self.ensureServiceInitedAndUpdated(appName); + }) + .catch(function (error) { + if ( + error && + error.captainErrorType === + ApiStatusCodes.STATUS_ERROR_NGINX_VALIDATION_FAILED + ) { + // Revert back to the old definition because the nginx config is invalid + if (existingAppDefinition) { + Logger.d( + `nginx validation failed, reverting configs for: ${appName}`, + ); + return dataStore + .getAppsDataStore() + .updateAppDefinitionInDb( + appName, + existingAppDefinition.projectId, + existingAppDefinition.description, + existingAppDefinition.instanceCount, + existingAppDefinition.captainDefinitionRelativeFilePath, + existingAppDefinition.envVars, + existingAppDefinition.volumes, + existingAppDefinition.tags || [], + existingAppDefinition.nodeId || "", + existingAppDefinition.notExposeAsWebApp, + existingAppDefinition.containerHttpPort || 80, + existingAppDefinition.httpAuth, + existingAppDefinition.forceSsl, + existingAppDefinition.ports, + existingAppDefinition.appPushWebhook?.repoInfo || { + repo: "", + branch: "", + user: "", + password: "", + }, + self.authenticator, + existingAppDefinition.customNginxConfig || "", + existingAppDefinition.redirectDomain || "", + existingAppDefinition.preDeployFunction || "", + existingAppDefinition.serviceUpdateOverride || "", + existingAppDefinition.websocketSupport, + existingAppDefinition.appDeployTokenConfig || { + enabled: false, + }, + ) + .then(function () { + self.reloadLoadBalancer(); + }) + .then(function () { + throw error; + }); + } + } + + throw error; + }); + } + + isAppBuilding(appName: string) { + return !!this.activeOrScheduledBuilds[appName]; + } + + /** + * + * @returns the active build that it finds + */ + isAnyBuildRunning() { + const activeBuilds = this.activeOrScheduledBuilds; + + for (const appName in activeBuilds) { + if (activeBuilds[appName]) { + return appName; + } } - reloadLoadBalancer() { - Logger.d('Updating Load Balancer - ServiceManager') - const self = this - return self.loadBalancerManager.rePopulateNginxConfigFile() - } + return undefined; + } + + getBuildStatus(appName: string) { + const self = this; + + return { + isAppBuilding: self.isAppBuilding(appName), + logs: self.buildLogsManager.getAppBuildLogs(appName).getLogs(), + isBuildFailed: + self.buildLogsManager.getAppBuildLogs(appName).isBuildFailed, + }; + } + + logBuildFailed(appName: string, error: string) { + const self = this; + error = (error || "") + ""; + + self.eventLogger.trackEvent( + CapRoverEventFactory.create(CapRoverEventType.AppBuildFailed, { + appName, + error: error.substring(0, 1000), + }), + ); + this.buildLogsManager.getAppBuildLogs(appName).onBuildFailed(error); + } + + getAppLogs(appName: string, encoding: string) { + const dockerApi = this.dockerApi; + const dataStore = this.dataStore; + let serviceName: string; + + return Promise.resolve() // + .then(function () { + return dataStore.getAppsDataStore().getAppDefinition(appName); + }) + .then(function (appDef) { + serviceName = dataStore + .getAppsDataStore() + .getServiceName(appName, !!appDef.isLegacyAppName); + return dockerApi.getLogForService( + serviceName, + CaptainConstants.configs.appLogSize, + encoding, + ); + }); + } + + ensureServiceInitedAndUpdated(appName: string) { + Logger.d(`Ensure service inited and Updated for: ${appName}`); + const self = this; + + let imageName: string | undefined; + const dockerApi = this.dockerApi; + const dataStore = this.dataStore; + let app: IAppDef; + let dockerAuthObject: DockerAuthObj | undefined; + let serviceName: string; + + return Promise.resolve() // + .then(function () { + return dataStore.getAppsDataStore().getAppDefinition(appName); + }) + .then(function (appFound) { + app = appFound; + serviceName = dataStore + .getAppsDataStore() + .getServiceName(appName, !!app.isLegacyAppName); + + Logger.d(`Check if service is running: ${serviceName}`); + return dockerApi.isServiceRunningByName(serviceName); + }) + .then(function (isRunning) { + for (let i = 0; i < app.versions.length; i++) { + const element = app.versions[i]; + if (element.version === app.deployedVersion) { + imageName = element.deployedImageName; + break; + } + } + + if (!imageName) { + throw ApiStatusCodes.createError( + ApiStatusCodes.ILLEGAL_PARAMETER, + "ImageName for deployed version is not available, this version was probably failed due to an unsuccessful build!", + ); + } + + if (isRunning) { + Logger.d(`Service is already running: ${serviceName}`); + return true; + } else { + Logger.d( + `Creating service ${serviceName} with default image, we will update image later`, + ); + + // if we pass in networks here. Almost always it results in a delayed update which causes + // update errors if they happen right away! + return dockerApi + .createServiceOnNodeId( + CaptainConstants.configs.appPlaceholderImageName, + serviceName, + undefined, + undefined, + undefined, + undefined, + undefined, + ) + .then(() => true); + } + }) + .then(function () { + return self.dockerRegistryHelper.getDockerAuthObjectForImageName( + imageName!, + ); + }) + .then(function (data) { + dockerAuthObject = data; + }) + .then(function () { + return self.createPreDeployFunctionIfExist(app); + }) + .then(function (preDeployFunction) { + Logger.d(`Updating service ${serviceName} with image ${imageName}`); + + return dockerApi.updateService( + serviceName, + imageName, + app.volumes, + app.networks, + app.envVars, + undefined, + dockerAuthObject, + Number(app.instanceCount), + app.nodeId, + dataStore.getNameSpace(), + app.ports, + app, + IDockerUpdateOrders.AUTO, + Utils.convertYamlOrJsonToObject(app.serviceUpdateOverride), + preDeployFunction, + ); + }) + .then(function () { + return new Promise(function (resolve) { + // Waiting 2 extra seconds for docker DNS to pickup the service name + setTimeout(resolve, 2000); + }); + }) + .then(function () { + return self.reloadLoadBalancer(); + }); + } + + reloadLoadBalancer() { + Logger.d("Updating Load Balancer - ServiceManager"); + const self = this; + return self.loadBalancerManager.rePopulateNginxConfigFile(); + } } -export default ServiceManager +export default ServiceManager;