Add endpoint to revert client app version

This commit is contained in:
Andrew Kingston 2021-07-08 12:56:54 +01:00
parent 4eb6d1e624
commit 0ce553eaf2
5 changed files with 185 additions and 38 deletions

View File

@ -34,9 +34,10 @@ const {
const { clientLibraryPath } = require("../../utilities") const { clientLibraryPath } = require("../../utilities")
const { getAllLocks } = require("../../utilities/redis") const { getAllLocks } = require("../../utilities/redis")
const { const {
uploadClientLibrary, updateClientLibrary,
downloadLibraries, backupClientLibrary,
} = require("../../utilities/fileSystem/newApp") revertClientLibrary,
} = require("../../utilities/fileSystem/clientLibrary")
const URL_REGEX_SLASH = /\/|\\/g const URL_REGEX_SLASH = /\/|\\/g
@ -247,7 +248,8 @@ exports.updateClient = async function (ctx) {
const currentVersion = application.version const currentVersion = application.version
// Update client library and manifest // Update client library and manifest
await uploadClientLibrary(ctx.params.appId) await backupClientLibrary(ctx.params.appId)
await updateClientLibrary(ctx.params.appId)
// Update versions in app package // Update versions in app package
const appPackageUpdates = { const appPackageUpdates = {
@ -259,6 +261,27 @@ exports.updateClient = async function (ctx) {
ctx.body = data ctx.body = data
} }
exports.revertClient = async function (ctx) {
// Check app can be reverted
const db = new CouchDB(ctx.params.appId)
const application = await db.get(DocumentTypes.APP_METADATA)
if (!application.revertableVersion) {
ctx.throw(400, "There is no version to revert to")
}
// Update client library and manifest
await revertClientLibrary(ctx.params.appId)
// Update versions in app package
const appPackageUpdates = {
version: application.revertableVersion,
revertableVersion: null,
}
const data = await updateAppPackage(ctx, appPackageUpdates, ctx.params.appId)
ctx.status = 200
ctx.body = data
}
exports.delete = async function (ctx) { exports.delete = async function (ctx) {
const db = new CouchDB(ctx.params.appId) const db = new CouchDB(ctx.params.appId)
@ -290,10 +313,7 @@ const updateAppPackage = async (ctx, appPackage, appId) => {
delete newAppPackage.lockedBy delete newAppPackage.lockedBy
} }
const response = await db.put(newAppPackage) return await db.put(newAppPackage)
console.log(response)
return response
} }
const createEmptyAppPackage = async (ctx, app) => { const createEmptyAppPackage = async (ctx, app) => {

View File

@ -16,6 +16,11 @@ router
authorized(BUILDER), authorized(BUILDER),
controller.updateClient controller.updateClient
) )
.post(
"/api/applications/:appId/client/revert",
authorized(BUILDER),
controller.revertClient
)
.delete("/api/applications/:appId", authorized(BUILDER), controller.delete) .delete("/api/applications/:appId", authorized(BUILDER), controller.delete)
module.exports = router module.exports = router

View File

@ -0,0 +1,149 @@
const { join } = require("path")
const { ObjectStoreBuckets } = require("../../constants")
const fs = require("fs")
const { upload, retrieveToTmp, streamUpload } = require("./utilities")
/**
* Client library paths in the object store:
* Previously, the entire standard-components package was downloaded from NPM
* as a tarball and extracted to the object store, even though only the manifest
* was ever needed. Therefore we need to support old apps which may still have
* the manifest at this location for the first update.
*
* The new paths for the in-use version are:
* {appId}/manifest.json
* {appId}/budibase-client.js
*
* The paths for the backups are:
* {appId}/manifest.json.bak
* {appId}/budibase-client.js.bak
*
* We don't rely on NPM at all any more, as when updating to the latest version
* we pull both the manifest and client bundle from the server's dependencies
* in the local file system.
*/
/**
* Backs up the current client library version by copying both the manifest
* and client bundle to .bak extensions in the object store. Only the one
* previous version is stored as a backup, which can be reverted to.
* @param appId The app ID to backup
* @returns {Promise<void>}
*/
exports.backupClientLibrary = async appId => {
let tmpManifestPath
let tmpClientPath
// Copy existing manifest to tmp
try {
// Try to load the manifest from the new file location
tmpManifestPath = await retrieveToTmp(
ObjectStoreBuckets.APPS,
join(appId, "manifest.json")
)
} catch (error) {
// Fallback to loading it from the old location for old apps
tmpManifestPath = await retrieveToTmp(
ObjectStoreBuckets.APPS,
join(
appId,
"node_modules",
"budibase",
"standard-components",
"package",
"manifest.json"
)
)
}
// Copy existing client lib to tmp
tmpClientPath = await retrieveToTmp(
ObjectStoreBuckets.APPS,
join(appId, "budibase-client.js")
)
// Upload manifest as backup
await upload({
bucket: ObjectStoreBuckets.APPS,
filename: join(appId, "manifest.json.bak"),
path: tmpManifestPath,
type: "application/json",
})
// Upload client library as backup
await upload({
bucket: ObjectStoreBuckets.APPS,
filename: join(appId, "budibase-client.js.bak"),
path: tmpClientPath,
type: "application/javascript",
})
}
/**
* Uploads the latest version of the component manifest and the client library
* to the object store, overwriting the existing version.
* @param appId The app ID to update
* @returns {Promise<void>}
*/
exports.updateClientLibrary = async appId => {
// Upload latest component manifest
await streamUpload(
ObjectStoreBuckets.APPS,
join(appId, "manifest.json"),
fs.createReadStream(
require.resolve("@budibase/standard-components/manifest.json")
),
{
ContentType: "application/json",
}
)
// Upload latest component library
await streamUpload(
ObjectStoreBuckets.APPS,
join(appId, "budibase-client.js"),
fs.createReadStream(require.resolve("@budibase/client")),
{
ContentType: "application/javascript",
}
)
}
/**
* Reverts the version of the client library and manifest to the previously
* used version for an app.
* @param appId The app ID to revert
* @returns {Promise<void>}
*/
exports.revertClientLibrary = async appId => {
let tmpManifestPath
let tmpClientPath
// Copy backup manifest to tmp
tmpManifestPath = await retrieveToTmp(
ObjectStoreBuckets.APPS,
join(appId, "manifest.json.bak")
)
// Copy backup client lib to tmp
tmpClientPath = await retrieveToTmp(
ObjectStoreBuckets.APPS,
join(appId, "budibase-client.js.bak")
)
// Upload manifest backup
await upload({
bucket: ObjectStoreBuckets.APPS,
filename: join(appId, "manifest.json"),
path: tmpManifestPath,
type: "application/json",
})
// Upload client library backup
await upload({
bucket: ObjectStoreBuckets.APPS,
filename: join(appId, "budibase-client.js"),
path: tmpClientPath,
type: "application/javascript",
})
}

View File

@ -13,7 +13,7 @@ const {
deleteFolder, deleteFolder,
downloadTarball, downloadTarball,
} = require("./utilities") } = require("./utilities")
const { uploadClientLibrary } = require("./newApp") const { updateClientLibrary } = require("./clientLibrary")
const download = require("download") const download = require("download")
const env = require("../../environment") const env = require("../../environment")
const { homedir } = require("os") const { homedir } = require("os")
@ -139,12 +139,12 @@ exports.performBackup = async (appId, backupName) => {
} }
/** /**
* Downloads required libraries and creates a new path in the object store. * Uploads the latest client library to the object store.
* @param {string} appId The ID of the app which is being created. * @param {string} appId The ID of the app which is being created.
* @return {Promise<void>} once promise completes app resources should be ready in object store. * @return {Promise<void>} once promise completes app resources should be ready in object store.
*/ */
exports.createApp = async appId => { exports.createApp = async appId => {
await uploadClientLibrary(appId) await updateClientLibrary(appId)
} }
/** /**

View File

@ -1,27 +0,0 @@
const { join } = require("path")
const { ObjectStoreBuckets } = require("../../constants")
const { streamUpload } = require("./utilities")
const fs = require("fs")
const BUCKET_NAME = ObjectStoreBuckets.APPS
exports.uploadClientLibrary = async appId => {
await streamUpload(
BUCKET_NAME,
join(appId, "budibase-client.js"),
fs.createReadStream(require.resolve("@budibase/client")),
{
ContentType: "application/javascript",
}
)
await streamUpload(
BUCKET_NAME,
join(appId, "manifest.json"),
fs.createReadStream(
require.resolve("@budibase/standard-components/manifest.json")
),
{
ContentType: "application/javascript",
}
)
}