Merge pull request #798 from caprover/master

Release 1.8.0
This commit is contained in:
Kasra Bigdeli
2020-08-16 13:57:46 -04:00
committed by GitHub
33 changed files with 1502 additions and 872 deletions
+34
View File
@@ -0,0 +1,34 @@
name: Build and push the edge image
on:
push:
branches:
- master
jobs:
run-pre-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: 14
- run: |
sudo mkdir /captain
npm ci
npm run build
npm run tslint
npm run formatter
sudo npm run test
build-publish-docker-hub:
needs: run-pre-checks
runs-on: ubuntu-latest
steps:
- uses: azure/docker-login@v1
with:
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- uses: actions/checkout@v1
- name: Build and Push Edge to DockerHub
shell: bash
run: ./dev-scripts/build_and_push_edge.sh
+20
View File
@@ -1,5 +1,25 @@
## [Next Version - available as `edge`]
- TBD
## [1.8.0] - 2020-08-16
- New: Update Docker API to v1.40 - Make sure to have Docker v19.03 or above. [Issue-797](https://github.com/caprover/caprover/issues/797)
- New: Change one-click app schema to be more compatible with Docker compose. [Issue-786](https://github.com/caprover/caprover/issues/786)
- New: Added hostname support for one-click apps which was necessary for some apps. [Issue-404](https://github.com/caprover/caprover/issues/404)
- New: Added ability to override docker service configs that are not present in CapRover (read-only volumes, CPU/RAM limitation and reservation and many more. [See docs](https://caprover.com/docs/service-update-override.html)
- New: Allow logs timestamp to be disabled for services. [Issue-602](https://github.com/caprover/caprover/issues/602)
- New: Added --advertise-addr for Docker swarm join. [Issue-572](https://github.com/caprover/caprover/issues/572)
- New: Added deletion capability to selfhost Docker Registry. [Issue-580](https://github.com/caprover/caprover/issues/580)
- New: Remove "Exposed Webapp" in apps table in favor of adding last deployed time. [Issue-47](https://github.com/caprover/caprover-frontend/issues/47)
- New: Default to a Apps tab if the instance is fully set up. [Issue-48](https://github.com/caprover/caprover-frontend/issues/48)
- Bugfix: Fixed a edge case where dhparam file is empty [Issue-745](https://github.com/caprover/caprover/issues/745)
- Bugfix: Fixed an issue with app renaming where the app was deleted [Issue-701](https://github.com/caprover/caprover/issues/701)
- Bugfix: Fixed an issue with HTTPS redirection on query parameters [PR-788](https://github.com/caprover/caprover/pull/788)
- Security: Updated Node dependencies
## [1.7.1] - 2020-07-03
- New: Improved TLS security settings for the built-in docker registry (https://github.com/caprover/caprover/pull/595)
- New: Allowing custom ports for git remote repositories. (https://github.com/caprover/caprover/issues/606)
- New: Default to http2 (https://github.com/caprover/caprover/pull/667)
+39 -5
View File
@@ -1,6 +1,40 @@
#!/bin/sh
#!/bin/bash
docker build -t caprover/caprover-edge:latest -f dockerfile-captain.edge .
docker tag caprover/caprover-edge:latest caprover/caprover-edge:0.0.1
docker push caprover/caprover-edge:latest
docker push caprover/caprover-edge:0.0.1
# Exit early if any command fails
set -e
# Print all commands
set -x
pwd
CAPROVER_VERSION=0.0.1
IMAGE_NAME=caprover/caprover-edge
if [ ! -f ./package-lock.json ]; then
echo "package-lock.json not found!"
exit 1;
fi
# BRANCH=$(git rev-parse --abbrev-ref HEAD)
# On Github the line above does not work, instead:
BRANCH=${GITHUB_REF##*/}
echo "on branch $BRANCH"
if [[ "$BRANCH" != "master" ]]; then
echo 'Not on master branch! Aborting script!';
exit 1;
fi
docker build -t $IMAGE_NAME:$CAPROVER_VERSION -t $IMAGE_NAME:latest -f dockerfile-captain.edge .
docker push $IMAGE_NAME:$CAPROVER_VERSION
docker push $IMAGE_NAME:latest
+22
View File
@@ -0,0 +1,22 @@
/*jshint esversion: 6 */
const fs = require('fs-extra');
const CONFIG_FILE_PATH = '/captain/data/config-captain.json';
const fileContent = JSON.parse(fs.readFileSync(CONFIG_FILE_PATH, {
encoding: 'utf-8'
}));
fs.writeFileSync(CONFIG_FILE_PATH + '.backup', JSON.stringify(fileContent));
fileContent.nginxBaseConfig = '';
fileContent.nginxCaptainConfig = '';
const apps = fileContent.appDefinitions || {};
Object.keys(apps).forEach(app => {
apps[app].customNginxConfig = '';
});
fs.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(fileContent));
+17 -32
View File
@@ -2,15 +2,14 @@
# https://github.com/digitalocean/marketplace-partners/blob/master/marketplace_docs/build-an-image.md#cleaning-up-your-build-droplet
apt -y update
apt -y upgrade
apt -y update && apt -y full-upgrade
############################ WAIT
docker pull caprover/caprover:1.4.0
docker pull caprover/caprover:1.7.1
cd /var/lib/cloud/scripts/per-instance
@@ -24,7 +23,7 @@ cat <<EOT >> z_01_caprover_installation.sh
ufw allow 80,443,3000,996,7946,4789,2377/tcp; ufw allow 7946,4789,2377/udp;
docker run -p 80:80 -p 443:443 -p 3000:3000 -v /var/run/docker.sock:/var/run/docker.sock -v /captain:/captain caprover/caprover:1.4.0
docker run -p 80:80 -p 443:443 -p 3000:3000 -v /var/run/docker.sock:/var/run/docker.sock -v /captain:/captain caprover/caprover:1.7.1
EOT
@@ -49,8 +48,9 @@ cat <<EOF
Welcome to CapRover, Open Source PaaS for developers!
Follow the guide here to finish the setup https://caprover.com/docs/get-started.html#step-3-install-caprover-cli
You can access the dashboard from http://<IP-ADDRESS>:3000
Or you can access the dashboard from http://<IP-ADDRESS>:3000
The default password is captain42
@@ -70,47 +70,32 @@ EOT
chmod +x 99-caprover-readme
############################ WAIT
apt -y update
apt -y upgrade
rm -rf /tmp/* /var/tmp/*
history -c
cat /dev/null > /root/.bash_history
unset HISTFILE
apt -y autoremove
apt -y autoclean
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
rm -rf /var/log/*.gz /var/log/*.[0-9] /var/log/*-????????
rm -rf /var/lib/cloud/instances/*
rm -f /root/.ssh/authorized_keys /etc/ssh/*key*
dd if=/dev/zero of=/zerofile; sync; rm /zerofile; sync
cat /dev/null > /var/log/lastlog; cat /dev/null > /var/log/wtmp
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
############################ WAIT
apt -y update && apt -y full-upgrade
cd /etc
wget https://raw.githubusercontent.com/digitalocean/marketplace-partners/master/scripts/cleanup.sh
chmod +x cleanup.sh
./cleanup.sh
############################ WAIT
cd /etc
wget https://raw.githubusercontent.com/digitalocean/marketplace-partners/master/marketplace_validation/img_check.sh
wget https://raw.githubusercontent.com/digitalocean/marketplace-partners/master/scripts/img_check.sh
chmod +x img_check.sh
./img_check.sh
cat /var/lib/cloud/scripts/per-instance/z_01_caprover_installation.sh
cat /etc/update-motd.d/99-caprover-readme
history -c
-1
View File
@@ -7,7 +7,6 @@ RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
# Build backend code
COPY . /usr/src/app
+1 -1
View File
@@ -15,7 +15,7 @@ RUN npm install --production && \
# Build frontend code using a fixed hash commit.
ENV FRONTEND_COMMIT_HASH 08960a9da2e7fbdfc04e1ed79890c6fa9ab1d41b
ENV FRONTEND_COMMIT_HASH e95fc5b8ee11d15da9017c5ef47ddb75956cf2a6
RUN mkdir -p /usr/src/app-frontend && cd /usr/src/app-frontend && \
git clone https://github.com/githubsaturn/caprover-frontend.git && \
+4
View File
@@ -1,3 +1,7 @@
# FROM node:14-alpine
# Uncomment the line above if you want to use a Dockerfile instead of templateId
RUN apk update && apk upgrade && \
apk add --no-cache git
RUN mkdir -p /usr/src/app
+3 -1
View File
@@ -1,2 +1,4 @@
# FROM php:7.3-apache
# Uncomment the line above if you want to use a Dockerfile instead of templateId
COPY ./ /var/www/html/
# COPY config/php.ini /usr/local/etc/php/
+4
View File
@@ -1,3 +1,7 @@
# FROM python:3.8.3-alpine
# Uncomment the line above if you want to use a Dockerfile instead of templateId
RUN apk update && apk upgrade && \
apk add --no-cache make g++ bash git openssh postgresql-dev curl
+4
View File
@@ -1,3 +1,7 @@
# FROM ruby:2.7.1-alpine
# Uncomment the line above if you want to use a Dockerfile instead of templateId
RUN apk update && apk upgrade && \
apk add --no-cache make g++ git postgresql-dev
+1055 -781
View File
File diff suppressed because it is too large Load Diff
+19 -18
View File
@@ -16,23 +16,23 @@
"@types/bcryptjs": "^2.4.2",
"@types/configstore": "^4.0.0",
"@types/cookie-parser": "^1.4.2",
"@types/dockerode": "^2.5.33",
"@types/dockerode": "^2.5.34",
"@types/ejs": "^3.0.4",
"@types/express": "^4.17.6",
"@types/express": "^4.17.7",
"@types/fs-extra": "^9.0.1",
"@types/http-proxy": "^1.17.4",
"@types/jest": "^26.0.3",
"@types/js-base64": "^2.3.2",
"@types/jest": "^26.0.10",
"@types/js-base64": "^3.0.0",
"@types/jsonwebtoken": "^8.5.0",
"@types/morgan": "^1.9.1",
"@types/multer": "^1.4.3",
"@types/multer": "^1.4.4",
"@types/on-finished": "^2.3.1",
"@types/request": "^2.48.5",
"@types/require-from-string": "^1.2.0",
"@types/serve-favicon": "^2.5.0",
"@types/ssh2": "^0.5.43",
"@types/ssh2": "^0.5.44",
"@types/tar": "^4.0.3",
"@types/uuid": "^8.0.0",
"@types/uuid": "^8.3.0",
"@types/validator": "^13.1.0",
"axios": "^0.19.2",
"bcryptjs": "^2.4.3",
@@ -40,13 +40,13 @@
"configstore": "^5.0.1",
"cookie-parser": "~1.4.5",
"debug": "~4.1.1",
"dockerode": "^3.2.0",
"dockerode": "^3.2.1",
"ejs": "^3.1.3",
"express": "^4.17.1",
"fs-extra": "^9.0.1",
"http-proxy": "^1.18.1",
"is-valid-path": "^0.1.1",
"js-base64": "^2.6.2",
"js-base64": "^3.4.5",
"jsonwebtoken": "^8.5.1",
"madge": "^3.9.2",
"moment": "^2.27.0",
@@ -54,21 +54,22 @@
"multer": "^1.4.2",
"on-finished": "^2.3.0",
"prettier": "^2.0.5",
"public-ip": "^4.0.1",
"public-ip": "^4.0.2",
"recursive-readdir": "^2.2.2",
"request": "^2.88.2",
"require-from-string": "^2.0.2",
"serve-favicon": "~2.5.0",
"simple-git": "^2.11.0",
"simple-git": "^2.17.0",
"ssh2": "^0.8.9",
"tar": "^6.0.2",
"tslint": "^6.1.2",
"typescript": "^3.9.6",
"uuid": "^8.2.0",
"validator": "^13.1.1"
"tar": "^6.0.5",
"tslint": "^6.1.3",
"typescript": "^3.9.7",
"uuid": "^8.3.0",
"validator": "^13.1.1",
"yaml": "^1.10.0"
},
"devDependencies": {
"jest": "^26.1.0",
"ts-jest": "^26.1.1"
"jest": "^26.4.0",
"ts-jest": "^26.2.0"
}
}
+6 -4
View File
@@ -246,11 +246,11 @@ class AppsDataStore {
.then(function (appData) {
if (appData.appName) appData.appName = newAppName
appData.hasDefaultSubDomainSsl = false
self.data.delete(`${APP_DEFINITIONS}.${oldAppName}`)
self.saveApp(newAppName, appData)
return self.saveApp(newAppName, appData)
})
.then(function () {
Utils.getDelayedPromise(2000)
self.data.delete(`${APP_DEFINITIONS}.${oldAppName}`)
return Utils.getDelayedPromise(2000)
})
}
@@ -616,6 +616,7 @@ class AppsDataStore {
authenticator: Authenticator,
customNginxConfig: string,
preDeployFunction: string,
serviceUpdateOverride: string,
websocketSupport: boolean
) {
const self = this
@@ -688,6 +689,7 @@ class AppsDataStore {
appObj.nodeId = nodeId
appObj.customNginxConfig = customNginxConfig
appObj.preDeployFunction = preDeployFunction
appObj.serviceUpdateOverride = serviceUpdateOverride
appObj.description = description
if (httpAuth && httpAuth.user) {
@@ -806,7 +808,7 @@ class AppsDataStore {
self.data.delete(`${APP_DEFINITIONS}.${appName}`)
resolve()
}).then(function () {
Utils.getDelayedPromise(2000)
return Utils.getDelayedPromise(2000)
})
}
+29 -14
View File
@@ -163,8 +163,12 @@ class DockerApi {
})
}
createJoinCommand(captainIpAddress: string, token: string) {
return `docker swarm join --token ${token} ${captainIpAddress}:2377`
createJoinCommand(
captainIpAddress: string,
token: string,
workerIp: string
) {
return `docker swarm join --token ${token} ${captainIpAddress}:2377 --advertise-addr ${workerIp}:2377`
}
getNodesInfo() {
@@ -437,12 +441,7 @@ class DockerApi {
})
})
.then(function () {
Logger.d('Pruning containers...')
return self.pruneContainers().catch(function (err) {
Logger.d('Prune Containers Failed!')
Logger.e(err)
})
return self.pruneContainers()
})
.then(function () {
Logger.d(`Disconnecting from network: ${nameOrId}`)
@@ -1017,6 +1016,7 @@ class DockerApi {
undefined,
undefined,
undefined,
undefined,
undefined
)
.then(function () {
@@ -1133,6 +1133,7 @@ class DockerApi {
undefined,
undefined,
undefined,
undefined,
undefined
)
})
@@ -1218,6 +1219,7 @@ class DockerApi {
ports: IAppPort[] | undefined,
appObject: IAppDef | undefined,
updateOrder: IDockerUpdateOrder | undefined,
serviceUpdateOverride: any | undefined,
preDeployFunction: Function | undefined
) {
const self = this
@@ -1463,6 +1465,9 @@ class DockerApi {
updatedData.Mode.Replicated.Replicas = instanceCount
}
return Utils.mergeObjects(updatedData, serviceUpdateOverride)
})
.then(function (updatedData) {
if (preDeployFunction) {
Logger.d('Running preDeployFunction')
return preDeployFunction(appObject, updatedData)
@@ -1479,10 +1484,7 @@ class DockerApi {
// give some time such that the new container is updated.
// also we don't want to fail the update just because prune failed.
setTimeout(function () {
self.pruneContainers().catch(function (err) {
Logger.d('Prune Containers Failed!')
Logger.e(err)
})
self.pruneContainers()
}, 5000)
return serviceData
@@ -1490,8 +1492,20 @@ class DockerApi {
}
pruneContainers() {
Logger.d('Pruning containers...')
const self = this
return self.dockerode.pruneContainers()
return self.dockerode
.pruneContainers() //
.catch(function (error) {
// Error: (HTTP code 409) unexpected - a prune operation is already running
if (error && error.statusCode === 409) {
Logger.d('Skipping prune due to a minor error: ' + error)
return
}
Logger.d('Prune Containers Failed!')
Logger.e(error)
})
}
isNodeManager(nodeId: string) {
@@ -1513,7 +1527,8 @@ class DockerApi {
.logs({
tail: tailCount,
follow: false,
timestamps: true,
timestamps: !!CaptainConstants.configs
.enableDockerLogsTimestamp,
stdout: true,
stderr: true,
})
+2 -1
View File
@@ -40,7 +40,8 @@ export default class DockerUtils {
CaptainConstants.disableFirewallCommand
} ${dockerApi.createJoinCommand(
captainIpAddress,
token
token,
remoteNodeIpAddress
)}`,
function (err, stream) {
if (err) {
+1
View File
@@ -61,6 +61,7 @@ interface IAppDefinitionBase {
nodeId?: string
instanceCount: number
preDeployFunction?: string
serviceUpdateOverride?: string
customNginxConfig?: string
networks: string[]
customDomain: IAppCustomDomain[]
@@ -325,6 +325,7 @@ router.post('/update/', function (req, res, next) {
let ports = req.body.ports || []
let instanceCount = req.body.instanceCount || '0'
let preDeployFunction = req.body.preDeployFunction || ''
let serviceUpdateOverride = req.body.serviceUpdateOverride || ''
let containerHttpPort = Number(req.body.containerHttpPort) || 80
let httpAuth = req.body.httpAuth
let description = req.body.description || ''
@@ -379,6 +380,7 @@ router.post('/update/', function (req, res, next) {
repoInfo,
customNginxConfig,
preDeployFunction,
serviceUpdateOverride,
websocketSupport
)
.then(function () {
@@ -8,10 +8,11 @@ import Logger from '../../../utils/Logger'
const router = express.Router()
const DEFAULT_ONE_CLICK_BASE_URL = 'https://oneclickapps.caprover.com'
const VERSION = `v3`
const VERSION = `v4`
interface IOneClickAppIdentifier {
baseUrl: string
isOfficial: boolean
name: string
displayName: string
description: string
@@ -145,6 +146,9 @@ router.get('/template/list', function (req, res, next) {
baseUrl: apiBaseUrl,
name: element.name,
displayName: `${element.displayName}`,
isOfficial:
(element.isOfficial + '').toLowerCase() ===
'true',
description: `${element.description}`,
logoUrl:
element.logoUrl &&
+20
View File
@@ -4,6 +4,7 @@ import DataStore from '../datastore/DataStore'
import DockerApi, { IDockerUpdateOrders } from '../docker/DockerApi'
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'
@@ -649,6 +650,7 @@ class ServiceManager {
repoInfo: RepoInfo,
customNginxConfig: string,
preDeployFunction: string,
serviceUpdateOverride: string,
websocketSupport: boolean
) {
const self = this
@@ -731,6 +733,22 @@ class ServiceManager {
}
}
})
.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()
@@ -751,6 +769,7 @@ class ServiceManager {
self.authenticator,
customNginxConfig,
preDeployFunction,
serviceUpdateOverride,
websocketSupport
)
})
@@ -906,6 +925,7 @@ class ServiceManager {
app.ports,
app,
IDockerUpdateOrders.AUTO,
Utils.convertYamlOrJsonToObject(app.serviceUpdateOverride),
preDeployFunction
)
})
+1 -1
View File
@@ -251,7 +251,7 @@ export default class BackupManager {
Logger.d(
'waiting 20 seconds for all services to settle'
)
Utils.getDelayedPromise(20000)
return Utils.getDelayedPromise(20000)
})
.then(function () {
return fs.remove(CaptainConstants.restoreDirectoryPath)
+1
View File
@@ -828,6 +828,7 @@ class CaptainManager {
undefined,
undefined,
undefined,
undefined,
undefined
)
}, 2000)
+1
View File
@@ -399,6 +399,7 @@ class CertbotManager {
undefined,
undefined,
undefined,
undefined,
undefined
)
})
+31 -5
View File
@@ -484,11 +484,13 @@ class LoadBalancerManager {
return ejs.render(baseConfigTemplate, {
base: {
dhparamsFilePath: fs.existsSync(
DH_PARAMS_FILE_PATH_ON_HOST
)
? DH_PARAMS_FILE_PATH_ON_NGINX
: '',
dhparamsFilePath:
fs.existsSync(DH_PARAMS_FILE_PATH_ON_HOST) &&
fs
.readFileSync(DH_PARAMS_FILE_PATH_ON_HOST)
.toString().length > 10 // making sure it's not an buggy file
? DH_PARAMS_FILE_PATH_ON_NGINX
: '',
},
})
})
@@ -504,6 +506,29 @@ class LoadBalancerManager {
const self = this
return fs
.pathExists(DH_PARAMS_FILE_PATH_ON_HOST) //
.then(function (dhParamExists) {
if (!dhParamExists) {
return false
}
const dhFileContent = fs
.readFileSync(DH_PARAMS_FILE_PATH_ON_HOST)
.toString()
const contentValid =
dhFileContent.indexOf('END DH PARAMETERS') > 0
if (contentValid) {
return true
}
Logger.d(
`Invalid dh param content - size of: ${dhFileContent.length}`
)
fs.removeSync(DH_PARAMS_FILE_PATH_ON_HOST)
return false
})
.then(function (dhParamExists) {
if (dhParamExists) {
return
@@ -733,6 +758,7 @@ class LoadBalancerManager {
undefined,
undefined,
undefined,
undefined,
undefined
)
})
+11 -1
View File
@@ -96,7 +96,12 @@ class SelfHostedDockerRegistry {
undefined,
nodeId,
undefined,
undefined,
[
{
key: 'REGISTRY_STORAGE_DELETE_ENABLED',
value: 'true',
},
],
undefined
)
.then(function () {
@@ -222,6 +227,10 @@ class SelfHostedDockerRegistry {
key: 'REGISTRY_AUTH_HTPASSWD_PATH',
value: '/etc/auth',
},
{
key: 'REGISTRY_STORAGE_DELETE_ENABLED',
value: 'true',
},
],
undefined,
undefined,
@@ -238,6 +247,7 @@ class SelfHostedDockerRegistry {
],
undefined,
undefined,
undefined,
undefined
)
})
+1
View File
@@ -165,6 +165,7 @@ class VersionManager {
undefined,
undefined,
undefined,
undefined,
undefined
)
})
+3 -1
View File
@@ -29,9 +29,11 @@ const configs = {
skipVerifyingDomains: false,
enableDockerLogsTimestamp: true,
registrySubDomainPort: 996,
dockerApiVersion: 'v1.30',
dockerApiVersion: 'v1.40',
netDataImageName: 'titpetric/netdata:1.8',
+1
View File
@@ -334,6 +334,7 @@ export default class MigrateCaptainDuckDuck {
self.authenticator,
app.customNginxConfig,
app.preDeployFunction,
'',
false
)
})
+52
View File
@@ -1,4 +1,6 @@
import { remove } from 'fs-extra'
import * as yaml from 'yaml'
import Logger from './Logger'
export default class Utils {
static removeHttpHttps(input: string) {
@@ -14,6 +16,56 @@ export default class Utils {
)
}
static mergeObjects(object1: any, object2: any) {
const newObject = object1 || {}
object2 = object2 || {}
Object.keys(object2).forEach((k) => {
if (
!newObject[k] ||
Array.isArray(newObject[k]) ||
Array.isArray(object2[k])
) {
newObject[k] = object2[k]
} else {
if (
typeof object2[k] === 'object' &&
typeof newObject[k] === 'object'
) {
newObject[k] = this.mergeObjects(newObject[k], object2[k])
} else {
newObject[k] = object2[k]
}
}
})
return newObject
}
static convertYamlOrJsonToObject(raw: string | undefined) {
raw = !!raw ? `${raw}`.trim() : ''
if (!raw.length) {
return undefined
}
let returnValue = undefined as any
if (raw.startsWith('{') || raw.startsWith('[')) {
try {
returnValue = JSON.parse(raw)
} catch (error) {
Logger.e(error)
}
} else {
try {
returnValue = yaml.parse(raw)
} catch (error) {
Logger.e(error)
}
}
return returnValue
}
static deleteFileQuietly(absFileOrDirPath: string) {
return remove(absFileOrDirPath).catch(function (error) {
// nom nom
+1 -1
View File
@@ -18,7 +18,7 @@ if (s.forceSsl) {
}
location / {
return 302 https://$http_host$request_uri$is_args$query_string;
return 302 https://$http_host$request_uri;
}
}
<%
+2 -4
View File
@@ -30,7 +30,6 @@ test('Testing dropFirstElements - smaller', () => {
.toBe('A0,A1')
})
test('Testing dropFirstElements - smaller (1)', () => {
const originalArray = []
for (let index = 0; index < 1; index++) {
@@ -41,7 +40,6 @@ test('Testing dropFirstElements - smaller (1)', () => {
.toBe('A0')
})
test('Testing dropFirstElements - smaller (0)', () => {
const originalArray = []
for (let index = 0; index < 0; index++) {
@@ -71,7 +69,7 @@ function createTestArray() {
test('Testing filter in place - remove 1st', () => {
const originalArray = createTestArray()
Utils.filterInPlace(originalArray, v => v.val1 !== 'e-1-1')
Utils.filterInPlace(originalArray, (v) => v.val1 !== 'e-1-1')
expect(originalArray.length).toBe(1)
expect(originalArray[0].val1).toBe('e-1-2')
expect(originalArray[0].val2).toBe('e-2-2')
@@ -80,7 +78,7 @@ test('Testing filter in place - remove 1st', () => {
test('Testing filter in place - remove 2nd', () => {
const originalArray = createTestArray()
Utils.filterInPlace(originalArray, v => v.val1 !== 'e-1-2')
Utils.filterInPlace(originalArray, (v) => v.val1 !== 'e-1-2')
expect(originalArray.length).toBe(1)
expect(originalArray[0].val1).toBe('e-1-1')
expect(originalArray[0].val2).toBe('e-2-1')
+69
View File
@@ -0,0 +1,69 @@
import Utils from '../src/utils/Utils'
test('Testing JSON/YAML', () => {
const badYaml = `
version: "3.3"
s
`
const badJSON = `
{ something:
`
const goodJSON = `
{
"version": "3.3",
"services": {
"$$cap_appname": {
"image": "adminer:$$cap_adminer_version",
"restart": "always",
"environment": {
"ADMINER_PLUGINS": "$$cap_adminer_plugins",
"ADMINER_DESIGN": "$$cap_adminer_design"
},
"caproverExtra": {
"containerHttpPort": "8080"
}
}
},
"captainVersion": 4,
"caproverOneClickApp": {
"variables": [
{
"id": "$$cap_adminer_version"
}
]
}
}
`
const goodYAML = `
version: "3.3"
services:
$$cap_appname:
image: 'adminer:$$cap_adminer_version'
restart: always
environment:
ADMINER_PLUGINS: $$cap_adminer_plugins
ADMINER_DESIGN: $$cap_adminer_design
caproverExtra:
containerHttpPort: '8080'
### ================================
### CAPROVER ONE CLICK APP SPECIFICS
### ================================
captainVersion: 4
caproverOneClickApp:
variables:
- id: $$cap_adminer_version
`
expect(Utils.convertYamlOrJsonToObject(badYaml)).toBeUndefined()
expect(Utils.convertYamlOrJsonToObject(badJSON)).toBeUndefined()
expect(Utils.convertYamlOrJsonToObject(goodJSON)).toStrictEqual(
JSON.parse(goodJSON)
)
expect(Utils.convertYamlOrJsonToObject(goodYAML)).toStrictEqual(
JSON.parse(goodJSON)
)
})
+41
View File
@@ -0,0 +1,41 @@
import Utils from '../src/utils/Utils'
test('Testing merge objects', () => {
expect(Utils.mergeObjects({ k1: 'v1' }, {})) //
.toStrictEqual({ k1: 'v1' })
expect(Utils.mergeObjects({ k1: 'v1' }, { k2: 'v2' })) //
.toStrictEqual({ k1: 'v1', k2: 'v2' })
expect(Utils.mergeObjects({ k1: 'v1', k2: 'v1' }, { k2: 'v2' })) //
.toStrictEqual({ k1: 'v1', k2: 'v2' })
expect(
Utils.mergeObjects(
{
k1: 'v1',
k2: {
k21: 'v21',
k22: 'v22',
k23: ['v23a', 'v23b'],
k24: ['v24a', 'v24b'],
},
},
{ k2: { k22: 'v22New', k23: ['v23c'] } }
)
) //
.toStrictEqual({
k1: 'v1',
k2: {
k21: 'v21',
k22: 'v22New',
k23: ['v23c'],
k24: ['v24a', 'v24b'],
},
})
expect(
Utils.mergeObjects({ k1: 'v1', k2: ['t1', 't2'] }, { k2: ['t3', 't4'] })
) //
.toStrictEqual({ k1: 'v1', k2: ['t3', 't4'] })
})