mirror of
https://github.com/caprover/caprover
synced 2025-10-30 01:57:03 +00:00
Compare commits
7 Commits
80fe613b0f
...
0fa8643fae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fa8643fae | ||
|
|
a0247e068e | ||
|
|
c3abec0367 | ||
|
|
6a35863cd3 | ||
|
|
b592ebed6e | ||
|
|
ef3a4db51f | ||
|
|
c07a202523 |
3
src/handlers/BaseHandlerResult.ts
Normal file
3
src/handlers/BaseHandlerResult.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface BaseHandlerResult {
|
||||
message: string
|
||||
}
|
||||
46
src/handlers/users/ProjectHandler.ts
Normal file
46
src/handlers/users/ProjectHandler.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import DataStore from '../../datastore/DataStore'
|
||||
import { ProjectDefinition } from '../../models/ProjectDefinition'
|
||||
import Logger from '../../utils/Logger'
|
||||
import { BaseHandlerResult } from '../BaseHandlerResult'
|
||||
|
||||
export interface RegisterProjectParams {
|
||||
name: string
|
||||
parentProjectId?: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface RegisterProjectResult extends BaseHandlerResult {
|
||||
data: ProjectDefinition
|
||||
}
|
||||
|
||||
export async function registerProject(
|
||||
params: RegisterProjectParams,
|
||||
dataStore: DataStore
|
||||
): Promise<RegisterProjectResult> {
|
||||
const { name, parentProjectId, description } = params
|
||||
|
||||
Logger.d(`Creating project: ${name}`)
|
||||
|
||||
try {
|
||||
const projectId = uuid()
|
||||
const project = await dataStore
|
||||
.getProjectsDataStore()
|
||||
.saveProject(projectId, {
|
||||
id: projectId,
|
||||
name: name,
|
||||
parentProjectId: parentProjectId,
|
||||
description: description,
|
||||
})
|
||||
|
||||
Logger.d(`Project created: ${name}`)
|
||||
|
||||
return {
|
||||
message: `Project created: ${name}`,
|
||||
data: project,
|
||||
}
|
||||
} catch (error: any) {
|
||||
Logger.e(`Failed to create project: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
69
src/handlers/users/apps/appdata/AppDataHandler.ts
Normal file
69
src/handlers/users/apps/appdata/AppDataHandler.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import ServiceManager from '../../../../user/ServiceManager'
|
||||
import Logger from '../../../../utils/Logger'
|
||||
import { BaseHandlerResult } from '../../../BaseHandlerResult'
|
||||
|
||||
export interface UploadCaptainDefinitionContentParams {
|
||||
appName: string
|
||||
isDetachedBuild: boolean
|
||||
captainDefinitionContent?: string
|
||||
gitHash?: string
|
||||
uploadedTarPathSource?: string
|
||||
}
|
||||
|
||||
export async function uploadCaptainDefinitionContent(
|
||||
params: UploadCaptainDefinitionContentParams,
|
||||
serviceManager: ServiceManager
|
||||
): Promise<BaseHandlerResult> {
|
||||
const {
|
||||
appName,
|
||||
isDetachedBuild,
|
||||
captainDefinitionContent,
|
||||
gitHash,
|
||||
uploadedTarPathSource,
|
||||
} = params
|
||||
|
||||
const hasTar = !!uploadedTarPathSource
|
||||
const hasCaptainDef = !!captainDefinitionContent
|
||||
|
||||
if (hasTar === hasCaptainDef) {
|
||||
throw new Error(
|
||||
'Either tarballfile or captainDefinitionContent should be present.'
|
||||
)
|
||||
}
|
||||
|
||||
const promiseToDeployNewVer = serviceManager.scheduleDeployNewVersion(
|
||||
appName,
|
||||
{
|
||||
uploadedTarPathSource: hasTar
|
||||
? {
|
||||
uploadedTarPath: uploadedTarPathSource as string,
|
||||
gitHash: `${gitHash || ''}`,
|
||||
}
|
||||
: undefined,
|
||||
captainDefinitionContentSource: hasCaptainDef
|
||||
? {
|
||||
captainDefinitionContent:
|
||||
captainDefinitionContent as string,
|
||||
gitHash: `${gitHash || ''}`,
|
||||
}
|
||||
: undefined,
|
||||
}
|
||||
)
|
||||
|
||||
if (isDetachedBuild) {
|
||||
// Avoid unhandled promise rejection
|
||||
promiseToDeployNewVer.catch(function (err: any) {
|
||||
Logger.e(err)
|
||||
})
|
||||
|
||||
return {
|
||||
message: 'Deploy is started',
|
||||
}
|
||||
}
|
||||
|
||||
await promiseToDeployNewVer
|
||||
|
||||
return {
|
||||
message: 'Deploy is done',
|
||||
}
|
||||
}
|
||||
305
src/handlers/users/apps/appdefinition/AppDefinitionHandler.ts
Normal file
305
src/handlers/users/apps/appdefinition/AppDefinitionHandler.ts
Normal file
@@ -0,0 +1,305 @@
|
||||
import DataStore from '../../../../datastore/DataStore'
|
||||
import { ICaptainDefinition } from '../../../../models/ICaptainDefinition'
|
||||
import ServiceManager from '../../../../user/ServiceManager'
|
||||
import CaptainConstants from '../../../../utils/CaptainConstants'
|
||||
import Logger from '../../../../utils/Logger'
|
||||
|
||||
import ApiStatusCodes from '../../../../api/ApiStatusCodes'
|
||||
import {
|
||||
AppDeployTokenConfig,
|
||||
IAppEnvVar,
|
||||
IAppPort,
|
||||
IAppTag,
|
||||
IAppVolume,
|
||||
IHttpAuth,
|
||||
RepoInfo,
|
||||
} from '../../../../models/AppDefinition'
|
||||
import { BaseHandlerResult } from '../../../BaseHandlerResult'
|
||||
|
||||
export interface RegisterAppDefinitionParams {
|
||||
appName: string
|
||||
projectId: string
|
||||
hasPersistentData: boolean
|
||||
isDetachedBuild: boolean
|
||||
}
|
||||
|
||||
export async function registerAppDefinition(
|
||||
params: RegisterAppDefinitionParams,
|
||||
dataStore: DataStore,
|
||||
serviceManager: ServiceManager
|
||||
): Promise<BaseHandlerResult> {
|
||||
const { appName, projectId, hasPersistentData, isDetachedBuild } = params
|
||||
let appCreated = false
|
||||
|
||||
Logger.d(`Registering app started: ${appName}`)
|
||||
|
||||
try {
|
||||
// Validate project if projectId is provided
|
||||
if (projectId) {
|
||||
await dataStore.getProjectsDataStore().getProject(projectId)
|
||||
// if project is not found, it will throw an error
|
||||
}
|
||||
|
||||
// Register the app definition
|
||||
await dataStore
|
||||
.getAppsDataStore()
|
||||
.registerAppDefinition(appName, projectId, hasPersistentData)
|
||||
|
||||
appCreated = true
|
||||
|
||||
// Create captain definition content
|
||||
const captainDefinitionContent: ICaptainDefinition = {
|
||||
schemaVersion: 2,
|
||||
imageName: CaptainConstants.configs.appPlaceholderImageName,
|
||||
}
|
||||
|
||||
// Schedule deployment (unless detached build)
|
||||
const promiseToIgnore = serviceManager
|
||||
.scheduleDeployNewVersion(appName, {
|
||||
captainDefinitionContentSource: {
|
||||
captainDefinitionContent: JSON.stringify(
|
||||
captainDefinitionContent
|
||||
),
|
||||
gitHash: '',
|
||||
},
|
||||
})
|
||||
.catch(function (error) {
|
||||
Logger.e(error)
|
||||
})
|
||||
|
||||
if (!isDetachedBuild) {
|
||||
await promiseToIgnore
|
||||
}
|
||||
|
||||
Logger.d(`AppName is saved: ${appName}`)
|
||||
|
||||
return {
|
||||
message: 'App Definition Saved',
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Cleanup if app was created but something failed
|
||||
if (appCreated) {
|
||||
try {
|
||||
await dataStore.getAppsDataStore().deleteAppDefinition(appName)
|
||||
} catch (cleanupError) {
|
||||
Logger.e(
|
||||
`Failed to cleanup app definition after error: ${cleanupError}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Re-throw the error
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export interface GetAllAppDefinitionsResult extends BaseHandlerResult {
|
||||
data: {
|
||||
appDefinitions: any[]
|
||||
rootDomain: string
|
||||
captainSubDomain: string
|
||||
defaultNginxConfig: any
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllAppDefinitions(
|
||||
dataStore: DataStore,
|
||||
serviceManager: ServiceManager
|
||||
): Promise<GetAllAppDefinitionsResult> {
|
||||
Logger.d('Getting all app definitions started')
|
||||
|
||||
try {
|
||||
const apps = await dataStore.getAppsDataStore().getAppDefinitions()
|
||||
const appsArray: any[] = []
|
||||
|
||||
Object.keys(apps).forEach(function (key) {
|
||||
const app = apps[key]
|
||||
app.appName = key
|
||||
app.isAppBuilding = serviceManager.isAppBuilding(key)
|
||||
app.appPushWebhook = app.appPushWebhook || undefined
|
||||
appsArray.push(app)
|
||||
})
|
||||
|
||||
const defaultNginxConfig = await dataStore.getDefaultAppNginxConfig()
|
||||
|
||||
Logger.d(`App definitions retrieved: ${appsArray.length} apps`)
|
||||
|
||||
return {
|
||||
message: 'App definitions are retrieved.',
|
||||
data: {
|
||||
appDefinitions: appsArray,
|
||||
rootDomain: dataStore.getRootDomain(),
|
||||
captainSubDomain: CaptainConstants.configs.captainSubDomain,
|
||||
defaultNginxConfig: defaultNginxConfig,
|
||||
},
|
||||
}
|
||||
} catch (error: any) {
|
||||
Logger.e(`Failed to get app definitions: ${error}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export interface UpdateAppDefinitionParams {
|
||||
appName: string
|
||||
projectId?: string
|
||||
description?: string
|
||||
instanceCount?: number | string
|
||||
captainDefinitionRelativeFilePath?: string
|
||||
envVars?: IAppEnvVar[]
|
||||
volumes?: IAppVolume[]
|
||||
tags?: IAppTag[]
|
||||
nodeId?: string
|
||||
notExposeAsWebApp?: boolean
|
||||
containerHttpPort?: number | string
|
||||
httpAuth?: any
|
||||
forceSsl?: boolean
|
||||
ports?: IAppPort[]
|
||||
repoInfo?: RepoInfo | any
|
||||
customNginxConfig?: string
|
||||
redirectDomain?: string
|
||||
preDeployFunction?: string
|
||||
serviceUpdateOverride?: string
|
||||
websocketSupport?: boolean
|
||||
appDeployTokenConfig?: AppDeployTokenConfig
|
||||
}
|
||||
|
||||
export async function updateAppDefinition(
|
||||
params: UpdateAppDefinitionParams,
|
||||
serviceManager: ServiceManager
|
||||
): Promise<BaseHandlerResult> {
|
||||
const {
|
||||
appName,
|
||||
projectId,
|
||||
description,
|
||||
instanceCount,
|
||||
captainDefinitionRelativeFilePath,
|
||||
envVars,
|
||||
volumes,
|
||||
tags,
|
||||
nodeId,
|
||||
notExposeAsWebApp,
|
||||
containerHttpPort,
|
||||
httpAuth,
|
||||
forceSsl,
|
||||
ports,
|
||||
repoInfo: inputRepoInfo,
|
||||
customNginxConfig,
|
||||
redirectDomain,
|
||||
preDeployFunction,
|
||||
serviceUpdateOverride,
|
||||
websocketSupport,
|
||||
appDeployTokenConfig,
|
||||
} = params
|
||||
|
||||
// Defaults & normalization
|
||||
const normalizedDescription = `${description || ''}`
|
||||
const instanceCountNum = Number(instanceCount ?? 0)
|
||||
const containerHttpPortNum = Number(containerHttpPort ?? 80)
|
||||
const normalizedEnvVars = envVars || []
|
||||
const normalizedVolumes = volumes || []
|
||||
const normalizedTags = tags || []
|
||||
const normalizedPorts = ports || []
|
||||
const normalizedNotExposeAsWebApp = !!notExposeAsWebApp
|
||||
const normalizedForceSsl = !!forceSsl
|
||||
const normalizedWebsocketSupport = !!websocketSupport
|
||||
const normalizedRedirectDomain = `${redirectDomain || ''}`
|
||||
const normalizedPreDeployFunction = `${preDeployFunction || ''}`
|
||||
const normalizedServiceUpdateOverride = `${serviceUpdateOverride || ''}`
|
||||
|
||||
let normalizedDeployTokenConfig: AppDeployTokenConfig | undefined
|
||||
if (!appDeployTokenConfig) {
|
||||
normalizedDeployTokenConfig = { enabled: false }
|
||||
} else {
|
||||
normalizedDeployTokenConfig = {
|
||||
enabled: !!appDeployTokenConfig.enabled,
|
||||
appDeployToken: `${
|
||||
appDeployTokenConfig.appDeployToken
|
||||
? appDeployTokenConfig.appDeployToken
|
||||
: ''
|
||||
}`.trim(),
|
||||
}
|
||||
}
|
||||
|
||||
const repoInfo: any = inputRepoInfo || {}
|
||||
|
||||
if (repoInfo.user) {
|
||||
repoInfo.user = repoInfo.user.trim()
|
||||
}
|
||||
if (repoInfo.repo) {
|
||||
repoInfo.repo = repoInfo.repo.trim()
|
||||
}
|
||||
if (repoInfo.branch) {
|
||||
repoInfo.branch = repoInfo.branch.trim()
|
||||
}
|
||||
|
||||
if (
|
||||
(repoInfo.branch ||
|
||||
repoInfo.user ||
|
||||
repoInfo.repo ||
|
||||
repoInfo.password ||
|
||||
repoInfo.sshKey) &&
|
||||
(!repoInfo.branch ||
|
||||
!repoInfo.repo ||
|
||||
(!repoInfo.sshKey && !repoInfo.user && !repoInfo.password) ||
|
||||
(repoInfo.password && !repoInfo.user) ||
|
||||
(repoInfo.user && !repoInfo.password))
|
||||
) {
|
||||
throw ApiStatusCodes.createError(
|
||||
ApiStatusCodes.ILLEGAL_PARAMETER,
|
||||
'Missing required Github/BitBucket/Gitlab field'
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
repoInfo &&
|
||||
repoInfo.sshKey &&
|
||||
repoInfo.sshKey.indexOf('ENCRYPTED') > 0 &&
|
||||
!CaptainConstants.configs.disableEncryptedCheck
|
||||
) {
|
||||
throw ApiStatusCodes.createError(
|
||||
ApiStatusCodes.ILLEGAL_PARAMETER,
|
||||
'You cannot use encrypted SSH keys'
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
repoInfo &&
|
||||
repoInfo.sshKey &&
|
||||
repoInfo.sshKey.indexOf('END OPENSSH PRIVATE KEY-----') > 0
|
||||
) {
|
||||
repoInfo.sshKey = repoInfo.sshKey.trim()
|
||||
repoInfo.sshKey = repoInfo.sshKey + '\n'
|
||||
}
|
||||
|
||||
Logger.d(`Updating app started: ${appName}`)
|
||||
|
||||
await serviceManager.updateAppDefinition(
|
||||
appName,
|
||||
`${projectId || ''}`,
|
||||
normalizedDescription,
|
||||
instanceCountNum,
|
||||
`${captainDefinitionRelativeFilePath || ''}`,
|
||||
normalizedEnvVars,
|
||||
normalizedVolumes,
|
||||
normalizedTags,
|
||||
`${nodeId || ''}`,
|
||||
normalizedNotExposeAsWebApp,
|
||||
containerHttpPortNum,
|
||||
httpAuth as IHttpAuth,
|
||||
normalizedForceSsl,
|
||||
normalizedPorts,
|
||||
repoInfo,
|
||||
`${customNginxConfig || ''}`,
|
||||
normalizedRedirectDomain,
|
||||
normalizedPreDeployFunction,
|
||||
normalizedServiceUpdateOverride,
|
||||
normalizedWebsocketSupport,
|
||||
normalizedDeployTokenConfig
|
||||
)
|
||||
|
||||
Logger.d(`AppName is updated: ${appName}`)
|
||||
|
||||
return {
|
||||
message: 'Updated App Definition Saved',
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import express = require('express')
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import ApiStatusCodes from '../../api/ApiStatusCodes'
|
||||
import BaseApi from '../../api/BaseApi'
|
||||
import { registerProject } from '../../handlers/users/ProjectHandler'
|
||||
import InjectionExtractor from '../../injection/InjectionExtractor'
|
||||
import { ProjectDefinition } from '../../models/ProjectDefinition'
|
||||
import Logger from '../../utils/Logger'
|
||||
@@ -16,23 +16,15 @@ router.post('/register/', function (req, res, next) {
|
||||
const parentProjectId = `${req.body.parentProjectId || ''}`.trim()
|
||||
const description = `${req.body.description || ''}`.trim()
|
||||
|
||||
Promise.resolve()
|
||||
.then(function () {
|
||||
const projectId = uuid()
|
||||
return dataStore.getProjectsDataStore().saveProject(projectId, {
|
||||
id: projectId,
|
||||
name: projectName,
|
||||
parentProjectId: parentProjectId,
|
||||
description: description,
|
||||
})
|
||||
})
|
||||
.then(function (project) {
|
||||
Logger.d(`Project created: ${projectName}`)
|
||||
const resp = new BaseApi(
|
||||
ApiStatusCodes.STATUS_OK,
|
||||
`Project created: ${projectName}`
|
||||
)
|
||||
resp.data = project
|
||||
return registerProject(
|
||||
{ name: projectName, parentProjectId, description },
|
||||
dataStore
|
||||
)
|
||||
.then(function (result) {
|
||||
const resp = new BaseApi(ApiStatusCodes.STATUS_OK, result.message)
|
||||
if (result.data) {
|
||||
resp.data = result.data
|
||||
}
|
||||
res.send(resp)
|
||||
})
|
||||
.catch(ApiStatusCodes.createCatcher(res))
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import express = require('express')
|
||||
import ApiStatusCodes from '../../../../api/ApiStatusCodes'
|
||||
import BaseApi from '../../../../api/BaseApi'
|
||||
import { uploadCaptainDefinitionContent as uploadCaptainDefinitionContentHandler } from '../../../../handlers/users/apps/appdata/AppDataHandler'
|
||||
import InjectionExtractor from '../../../../injection/InjectionExtractor'
|
||||
import Logger from '../../../../utils/Logger'
|
||||
import multer = require('multer')
|
||||
|
||||
const TEMP_UPLOAD = 'temp_upload/'
|
||||
@@ -70,6 +70,7 @@ router.post('/:appName/', function (req, res, next) {
|
||||
.catch(ApiStatusCodes.createCatcher(res))
|
||||
})
|
||||
|
||||
// uploadCaptainDefinitionContent
|
||||
router.post(
|
||||
'/:appName/',
|
||||
upload.single('sourceFile'),
|
||||
@@ -79,63 +80,27 @@ router.post(
|
||||
|
||||
const appName = req.params.appName
|
||||
const isDetachedBuild = !!req.query.detached
|
||||
const captainDefinitionContent =
|
||||
(req.body.captainDefinitionContent || '') + ''
|
||||
const gitHash = (req.body.gitHash || '') + ''
|
||||
const captainDefinitionContent = `${req.body.captainDefinitionContent || ''}`
|
||||
const gitHash = `${req.body.gitHash || ''}`
|
||||
const tarballSourceFilePath: string = req.file ? req.file.path : ''
|
||||
|
||||
if (!!tarballSourceFilePath === !!captainDefinitionContent) {
|
||||
res.send(
|
||||
new BaseApi(
|
||||
ApiStatusCodes.ILLEGAL_OPERATION,
|
||||
'Either tarballfile or captainDefinitionContent should be present.'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
return Promise.resolve().then(function () {
|
||||
const promiseToDeployNewVer =
|
||||
serviceManager.scheduleDeployNewVersion(appName, {
|
||||
uploadedTarPathSource: tarballSourceFilePath
|
||||
? {
|
||||
uploadedTarPath: tarballSourceFilePath,
|
||||
gitHash,
|
||||
}
|
||||
: undefined,
|
||||
captainDefinitionContentSource: captainDefinitionContent
|
||||
? {
|
||||
captainDefinitionContent,
|
||||
gitHash,
|
||||
}
|
||||
: undefined,
|
||||
})
|
||||
|
||||
if (isDetachedBuild) {
|
||||
res.send(
|
||||
new BaseApi(
|
||||
ApiStatusCodes.STATUS_OK_DEPLOY_STARTED,
|
||||
'Deploy is started'
|
||||
)
|
||||
)
|
||||
|
||||
// To avoid unhandled promise error
|
||||
promiseToDeployNewVer.catch(function (err) {
|
||||
Logger.e(err)
|
||||
})
|
||||
} else {
|
||||
promiseToDeployNewVer
|
||||
.then(function () {
|
||||
res.send(
|
||||
new BaseApi(
|
||||
ApiStatusCodes.STATUS_OK,
|
||||
'Deploy is done'
|
||||
)
|
||||
)
|
||||
})
|
||||
.catch(ApiStatusCodes.createCatcher(res))
|
||||
}
|
||||
})
|
||||
return uploadCaptainDefinitionContentHandler(
|
||||
{
|
||||
appName,
|
||||
isDetachedBuild,
|
||||
captainDefinitionContent: captainDefinitionContent || undefined,
|
||||
gitHash: gitHash || undefined,
|
||||
uploadedTarPathSource: tarballSourceFilePath || undefined,
|
||||
},
|
||||
serviceManager
|
||||
)
|
||||
.then(function (result) {
|
||||
const status = isDetachedBuild
|
||||
? ApiStatusCodes.STATUS_OK_DEPLOY_STARTED
|
||||
: ApiStatusCodes.STATUS_OK
|
||||
res.send(new BaseApi(status, result.message))
|
||||
})
|
||||
.catch(ApiStatusCodes.createCatcher(res))
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import express = require('express')
|
||||
import ApiStatusCodes from '../../../../api/ApiStatusCodes'
|
||||
import BaseApi from '../../../../api/BaseApi'
|
||||
import {
|
||||
getAllAppDefinitions,
|
||||
registerAppDefinition,
|
||||
updateAppDefinition,
|
||||
} from '../../../../handlers/users/apps/appdefinition/AppDefinitionHandler'
|
||||
import InjectionExtractor from '../../../../injection/InjectionExtractor'
|
||||
import { AppDeployTokenConfig, IAppDef } from '../../../../models/AppDefinition'
|
||||
import { ICaptainDefinition } from '../../../../models/ICaptainDefinition'
|
||||
import { CaptainError } from '../../../../models/OtherTypes'
|
||||
import { AppDeployTokenConfig } from '../../../../models/AppDefinition'
|
||||
import CaptainManager from '../../../../user/system/CaptainManager'
|
||||
import CaptainConstants from '../../../../utils/CaptainConstants'
|
||||
import Logger from '../../../../utils/Logger'
|
||||
import Utils from '../../../../utils/Utils'
|
||||
|
||||
@@ -60,39 +62,14 @@ router.get('/', function (req, res, next) {
|
||||
InjectionExtractor.extractUserFromInjected(res).user.dataStore
|
||||
const serviceManager =
|
||||
InjectionExtractor.extractUserFromInjected(res).user.serviceManager
|
||||
const appsArray: IAppDef[] = []
|
||||
|
||||
return dataStore
|
||||
.getAppsDataStore()
|
||||
.getAppDefinitions()
|
||||
.then(function (apps) {
|
||||
const promises: Promise<void>[] = []
|
||||
|
||||
Object.keys(apps).forEach(function (key, index) {
|
||||
const app = apps[key]
|
||||
app.appName = key
|
||||
app.isAppBuilding = serviceManager.isAppBuilding(key)
|
||||
app.appPushWebhook = app.appPushWebhook || undefined
|
||||
appsArray.push(app)
|
||||
})
|
||||
|
||||
return Promise.all(promises)
|
||||
})
|
||||
.then(function () {
|
||||
return dataStore.getDefaultAppNginxConfig()
|
||||
})
|
||||
.then(function (defaultNginxConfig) {
|
||||
return getAllAppDefinitions(dataStore, serviceManager)
|
||||
.then(function (result) {
|
||||
const baseApi = new BaseApi(
|
||||
ApiStatusCodes.STATUS_OK,
|
||||
'App definitions are retrieved.'
|
||||
result.message
|
||||
)
|
||||
baseApi.data = {
|
||||
appDefinitions: appsArray,
|
||||
rootDomain: dataStore.getRootDomain(),
|
||||
captainSubDomain: CaptainConstants.configs.captainSubDomain,
|
||||
defaultNginxConfig: defaultNginxConfig,
|
||||
}
|
||||
|
||||
baseApi.data = result.data
|
||||
res.send(baseApi)
|
||||
})
|
||||
.catch(ApiStatusCodes.createCatcher(res))
|
||||
@@ -190,69 +167,13 @@ router.post('/register/', function (req, res, next) {
|
||||
const hasPersistentData = !!req.body.hasPersistentData
|
||||
const isDetachedBuild = !!req.query.detached
|
||||
|
||||
let appCreated = false
|
||||
|
||||
Logger.d(`Registering app started: ${appName}`)
|
||||
|
||||
return Promise.resolve()
|
||||
.then(function () {
|
||||
if (projectId) {
|
||||
return dataStore.getProjectsDataStore().getProject(projectId)
|
||||
// if project is not found, it will throw an error
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
return dataStore
|
||||
.getAppsDataStore()
|
||||
.registerAppDefinition(appName, projectId, hasPersistentData)
|
||||
})
|
||||
.then(function () {
|
||||
appCreated = true
|
||||
})
|
||||
.then(function () {
|
||||
const captainDefinitionContent: ICaptainDefinition = {
|
||||
schemaVersion: 2,
|
||||
imageName: CaptainConstants.configs.appPlaceholderImageName,
|
||||
}
|
||||
|
||||
const promiseToIgnore = serviceManager
|
||||
.scheduleDeployNewVersion(appName, {
|
||||
captainDefinitionContentSource: {
|
||||
captainDefinitionContent: JSON.stringify(
|
||||
captainDefinitionContent
|
||||
),
|
||||
gitHash: '',
|
||||
},
|
||||
})
|
||||
.catch(function (error) {
|
||||
Logger.e(error)
|
||||
})
|
||||
|
||||
if (!isDetachedBuild) return promiseToIgnore
|
||||
})
|
||||
.then(function () {
|
||||
Logger.d(`AppName is saved: ${appName}`)
|
||||
res.send(
|
||||
new BaseApi(ApiStatusCodes.STATUS_OK, 'App Definition Saved')
|
||||
)
|
||||
})
|
||||
.catch(function (error: CaptainError) {
|
||||
function createRejectionPromise() {
|
||||
return new Promise<void>(function (resolve, reject) {
|
||||
reject(error)
|
||||
})
|
||||
}
|
||||
|
||||
if (appCreated) {
|
||||
return dataStore
|
||||
.getAppsDataStore()
|
||||
.deleteAppDefinition(appName)
|
||||
.then(function () {
|
||||
return createRejectionPromise()
|
||||
})
|
||||
} else {
|
||||
return createRejectionPromise()
|
||||
}
|
||||
return registerAppDefinition(
|
||||
{ appName, projectId, hasPersistentData, isDetachedBuild },
|
||||
dataStore,
|
||||
serviceManager
|
||||
)
|
||||
.then(function (result) {
|
||||
res.send(new BaseApi(ApiStatusCodes.STATUS_OK, result.message))
|
||||
})
|
||||
.catch(ApiStatusCodes.createCatcher(res))
|
||||
})
|
||||
@@ -329,6 +250,7 @@ router.post('/rename/', function (req, res, next) {
|
||||
.catch(ApiStatusCodes.createCatcher(res))
|
||||
})
|
||||
|
||||
// Update app configs
|
||||
router.post('/update/', function (req, res, next) {
|
||||
const serviceManager =
|
||||
InjectionExtractor.extractUserFromInjected(res).user.serviceManager
|
||||
@@ -339,103 +261,33 @@ router.post('/update/', function (req, res, next) {
|
||||
const captainDefinitionRelativeFilePath =
|
||||
req.body.captainDefinitionRelativeFilePath
|
||||
const notExposeAsWebApp = req.body.notExposeAsWebApp
|
||||
const tags = req.body.tags || []
|
||||
const tags = req.body.tags
|
||||
const customNginxConfig = req.body.customNginxConfig
|
||||
const forceSsl = !!req.body.forceSsl
|
||||
const websocketSupport = !!req.body.websocketSupport
|
||||
const forceSsl = req.body.forceSsl
|
||||
const websocketSupport = req.body.websocketSupport
|
||||
const repoInfo = req.body.appPushWebhook
|
||||
? req.body.appPushWebhook.repoInfo || {}
|
||||
: {}
|
||||
const envVars = req.body.envVars || []
|
||||
const volumes = req.body.volumes || []
|
||||
const ports = req.body.ports || []
|
||||
const instanceCount = req.body.instanceCount || '0'
|
||||
const redirectDomain = req.body.redirectDomain || ''
|
||||
const preDeployFunction = req.body.preDeployFunction || ''
|
||||
const serviceUpdateOverride = req.body.serviceUpdateOverride || ''
|
||||
const containerHttpPort = Number(req.body.containerHttpPort) || 80
|
||||
? req.body.appPushWebhook.repoInfo
|
||||
: undefined
|
||||
const envVars = req.body.envVars
|
||||
const volumes = req.body.volumes
|
||||
const ports = req.body.ports
|
||||
const instanceCount = req.body.instanceCount
|
||||
const redirectDomain = req.body.redirectDomain
|
||||
const preDeployFunction = req.body.preDeployFunction
|
||||
const serviceUpdateOverride = req.body.serviceUpdateOverride
|
||||
const containerHttpPort = req.body.containerHttpPort
|
||||
const httpAuth = req.body.httpAuth
|
||||
let appDeployTokenConfig = req.body.appDeployTokenConfig as
|
||||
const appDeployTokenConfig = req.body.appDeployTokenConfig as
|
||||
| AppDeployTokenConfig
|
||||
| undefined
|
||||
const description = req.body.description || ''
|
||||
const description = req.body.description
|
||||
|
||||
if (!appDeployTokenConfig) {
|
||||
appDeployTokenConfig = { enabled: false }
|
||||
} else {
|
||||
appDeployTokenConfig = {
|
||||
enabled: !!appDeployTokenConfig.enabled,
|
||||
appDeployToken: `${
|
||||
appDeployTokenConfig.appDeployToken
|
||||
? appDeployTokenConfig.appDeployToken
|
||||
: ''
|
||||
}`.trim(),
|
||||
}
|
||||
}
|
||||
|
||||
if (repoInfo.user) {
|
||||
repoInfo.user = repoInfo.user.trim()
|
||||
}
|
||||
if (repoInfo.repo) {
|
||||
repoInfo.repo = repoInfo.repo.trim()
|
||||
}
|
||||
if (repoInfo.branch) {
|
||||
repoInfo.branch = repoInfo.branch.trim()
|
||||
}
|
||||
|
||||
if (
|
||||
(repoInfo.branch ||
|
||||
repoInfo.user ||
|
||||
repoInfo.repo ||
|
||||
repoInfo.password ||
|
||||
repoInfo.sshKey) &&
|
||||
(!repoInfo.branch ||
|
||||
!repoInfo.repo ||
|
||||
(!repoInfo.sshKey && !repoInfo.user && !repoInfo.password) ||
|
||||
(repoInfo.password && !repoInfo.user) ||
|
||||
(repoInfo.user && !repoInfo.password))
|
||||
) {
|
||||
res.send(
|
||||
new BaseApi(
|
||||
ApiStatusCodes.ILLEGAL_PARAMETER,
|
||||
'Missing required Github/BitBucket/Gitlab field'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
repoInfo &&
|
||||
repoInfo.sshKey &&
|
||||
repoInfo.sshKey.indexOf('ENCRYPTED') > 0 &&
|
||||
!CaptainConstants.configs.disableEncryptedCheck
|
||||
) {
|
||||
res.send(
|
||||
new BaseApi(
|
||||
ApiStatusCodes.ILLEGAL_PARAMETER,
|
||||
'You cannot use encrypted SSH keys'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
repoInfo &&
|
||||
repoInfo.sshKey &&
|
||||
repoInfo.sshKey.indexOf('END OPENSSH PRIVATE KEY-----') > 0
|
||||
) {
|
||||
repoInfo.sshKey = repoInfo.sshKey.trim()
|
||||
repoInfo.sshKey = repoInfo.sshKey + '\n'
|
||||
}
|
||||
|
||||
Logger.d(`Updating app started: ${appName}`)
|
||||
|
||||
return serviceManager
|
||||
.updateAppDefinition(
|
||||
return updateAppDefinition(
|
||||
{
|
||||
appName,
|
||||
projectId,
|
||||
description,
|
||||
Number(instanceCount),
|
||||
instanceCount,
|
||||
captainDefinitionRelativeFilePath,
|
||||
envVars,
|
||||
volumes,
|
||||
@@ -452,16 +304,12 @@ router.post('/update/', function (req, res, next) {
|
||||
preDeployFunction,
|
||||
serviceUpdateOverride,
|
||||
websocketSupport,
|
||||
appDeployTokenConfig
|
||||
)
|
||||
.then(function () {
|
||||
Logger.d(`AppName is updated: ${appName}`)
|
||||
res.send(
|
||||
new BaseApi(
|
||||
ApiStatusCodes.STATUS_OK,
|
||||
'Updated App Definition Saved'
|
||||
)
|
||||
)
|
||||
appDeployTokenConfig,
|
||||
},
|
||||
serviceManager
|
||||
)
|
||||
.then(function (result) {
|
||||
res.send(new BaseApi(ApiStatusCodes.STATUS_OK, result.message))
|
||||
})
|
||||
.catch(ApiStatusCodes.createCatcher(res))
|
||||
})
|
||||
|
||||
@@ -272,6 +272,11 @@ router.get('/template/app', function (req, res, next) {
|
||||
})
|
||||
|
||||
router.post('/deploy', function (req, res, next) {
|
||||
const dataStore =
|
||||
InjectionExtractor.extractUserFromInjected(res).user.dataStore
|
||||
const serviceManager =
|
||||
InjectionExtractor.extractUserFromInjected(res).user.serviceManager
|
||||
|
||||
const template = req.body.template
|
||||
const values = req.body.values
|
||||
const deploymentJobRegistry = OneClickDeploymentJobRegistry.getInstance()
|
||||
@@ -291,13 +296,20 @@ router.post('/deploy', function (req, res, next) {
|
||||
Logger.dev(`Template: ${JSON.stringify(template, null, 2)}`)
|
||||
Logger.dev(`Values: ${JSON.stringify(values, null, 2)}`)
|
||||
|
||||
new OneClickAppDeployManager((deploymentState) => {
|
||||
deploymentJobRegistry.updateJobProgress(jobId, deploymentState)
|
||||
Logger.dev(`Deployment state updated for jobId: ${jobId}`)
|
||||
Logger.dev(
|
||||
`Deployment state: ${JSON.stringify(deploymentState, null, 2)}`
|
||||
)
|
||||
}).startDeployProcess(template, values)
|
||||
new OneClickAppDeployManager(
|
||||
dataStore,
|
||||
serviceManager,
|
||||
(deploymentState) => {
|
||||
deploymentJobRegistry.updateJobProgress(
|
||||
jobId,
|
||||
deploymentState
|
||||
)
|
||||
Logger.dev(`Deployment state updated for jobId: ${jobId}`)
|
||||
Logger.dev(
|
||||
`Deployment state: ${JSON.stringify(deploymentState, null, 2)}`
|
||||
)
|
||||
}
|
||||
).startDeployProcess(template, values)
|
||||
|
||||
const baseApi = new BaseApi(
|
||||
ApiStatusCodes.STATUS_OK,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import DataStore from '../../datastore/DataStore'
|
||||
import { IHashMapGeneric } from '../../models/ICacheGeneric'
|
||||
import {
|
||||
IDockerComposeService,
|
||||
IOneClickTemplate,
|
||||
} from '../../models/IOneClickAppModels'
|
||||
import Utils from '../../utils/Utils'
|
||||
import ServiceManager from '../ServiceManager'
|
||||
import OneClickAppDeploymentHelper from './OneClickAppDeploymentHelper'
|
||||
export const ONE_CLICK_APP_NAME_VAR_NAME = '$$cap_appname'
|
||||
|
||||
@@ -28,15 +30,19 @@ function replaceWith(
|
||||
}
|
||||
|
||||
export default class OneClickAppDeployManager {
|
||||
private deploymentHelper: OneClickAppDeploymentHelper =
|
||||
new OneClickAppDeploymentHelper()
|
||||
private deploymentHelper: OneClickAppDeploymentHelper
|
||||
private template: IOneClickTemplate | undefined
|
||||
constructor(
|
||||
dataStore: DataStore,
|
||||
serviceManager: ServiceManager,
|
||||
private onDeploymentStateChanged: (
|
||||
deploymentState: IDeploymentState
|
||||
) => void
|
||||
) {
|
||||
//
|
||||
this.deploymentHelper = new OneClickAppDeploymentHelper(
|
||||
dataStore,
|
||||
serviceManager
|
||||
)
|
||||
}
|
||||
|
||||
startDeployProcess(
|
||||
|
||||
@@ -1,52 +1,79 @@
|
||||
import DataStore from '../../datastore/DataStore'
|
||||
import { uploadCaptainDefinitionContent as uploadCaptainDefinitionContentHandler } from '../../handlers/users/apps/appdata/AppDataHandler'
|
||||
import {
|
||||
getAllAppDefinitions,
|
||||
registerAppDefinition,
|
||||
updateAppDefinition,
|
||||
} from '../../handlers/users/apps/appdefinition/AppDefinitionHandler'
|
||||
import { registerProject } from '../../handlers/users/ProjectHandler'
|
||||
import { IAppDef } from '../../models/AppDefinition'
|
||||
import { ICaptainDefinition } from '../../models/ICaptainDefinition'
|
||||
import { IDockerComposeService } from '../../models/IOneClickAppModels'
|
||||
import { ProjectDefinition } from '../../models/ProjectDefinition'
|
||||
import DockerComposeToServiceOverride from '../../utils/DockerComposeToServiceOverride'
|
||||
import Utils from '../../utils/Utils'
|
||||
|
||||
// TODO - Replace with actual API implementation
|
||||
// Step 1- find the endpoint using the mapping here:
|
||||
// https://github.com/caprover/caprover-api/blob/master/src/api/ApiManager.ts
|
||||
// Step 2- Lookup the implementation based on the path found above
|
||||
// Step 3- Replace the mock implementation below with the implementation found in step 2
|
||||
import ServiceManager from '../ServiceManager'
|
||||
|
||||
class ApiManager {
|
||||
constructor(
|
||||
private dataStore: DataStore,
|
||||
private serviceManager: ServiceManager
|
||||
) {
|
||||
// Initialize if needed
|
||||
}
|
||||
registerNewApp(
|
||||
appName: string,
|
||||
projectId: string,
|
||||
hasVolume: boolean,
|
||||
skipIfExists: boolean
|
||||
hasPersistentData: boolean,
|
||||
isDetachedBuild: boolean
|
||||
): Promise<any> {
|
||||
// Mock implementation
|
||||
return Promise.resolve({ success: true })
|
||||
return registerAppDefinition(
|
||||
{
|
||||
appName,
|
||||
projectId,
|
||||
hasPersistentData,
|
||||
isDetachedBuild,
|
||||
},
|
||||
this.dataStore,
|
||||
this.serviceManager
|
||||
)
|
||||
}
|
||||
|
||||
registerProject(projectDef: ProjectDefinition): Promise<any> {
|
||||
// Mock implementation
|
||||
return Promise.resolve({ id: 'mock-project-id' })
|
||||
return registerProject(projectDef, this.dataStore)
|
||||
}
|
||||
getAllApps(): Promise<any> {
|
||||
// Mock implementation
|
||||
return Promise.resolve({ appDefinitions: [] })
|
||||
return getAllAppDefinitions(this.dataStore, this.serviceManager)
|
||||
}
|
||||
updateConfigAndSave(appName: string, appDef: IAppDef): Promise<any> {
|
||||
// Mock implementation
|
||||
return Promise.resolve({ success: true })
|
||||
return updateAppDefinition({ appName, ...appDef }, this.serviceManager)
|
||||
}
|
||||
uploadCaptainDefinitionContent(
|
||||
appName: string,
|
||||
captainDefinition: ICaptainDefinition,
|
||||
tarFileBase64: string,
|
||||
skipIfExists: boolean
|
||||
gitHash: string,
|
||||
isDetachedBuild: boolean
|
||||
): Promise<any> {
|
||||
// Mock implementation
|
||||
return Promise.resolve({ success: true })
|
||||
const captainDefinitionContent = JSON.stringify(captainDefinition)
|
||||
|
||||
return uploadCaptainDefinitionContentHandler(
|
||||
{
|
||||
appName,
|
||||
isDetachedBuild,
|
||||
captainDefinitionContent,
|
||||
gitHash,
|
||||
},
|
||||
this.serviceManager
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default class OneClickAppDeploymentHelper {
|
||||
private apiManager: ApiManager = new ApiManager()
|
||||
private apiManager: ApiManager
|
||||
|
||||
constructor(dataStore: DataStore, serviceManager: ServiceManager) {
|
||||
this.apiManager = new ApiManager(dataStore, serviceManager)
|
||||
}
|
||||
|
||||
createRegisterPromise(
|
||||
appName: string,
|
||||
|
||||
Reference in New Issue
Block a user