"use strict";
const CloudControllerBase = require("./CloudControllerBase");
const HttpUtils = require("../../utils/HttpUtils");
const fs = require("fs");
/**
* AppsDeployment — deployment, runtime info, and service-binding operations.
* Methods: upload, getStats, getInstances, getAppRoutes, associateRoute,
* getServiceBindings, removeServiceBindings, getEnvironmentVariables,
* setEnvironmentVariables, restage, getDroplets, getPackages, getProcesses
*
* @class
*/
class AppsDeployment extends CloudControllerBase {
constructor(endPoint, options = {}) {
super(endPoint, options);
}
/**
* Upload application source code.
* @param {String} appGuid
* @param {String} filePath - Local file path to zip
* @param {Boolean} [async=false]
* @return {Promise}
*/
upload(appGuid, filePath, async) {
const stats = fs.statSync(filePath);
const fileSizeInBytes = stats.size;
const asyncFlag = typeof async === "boolean" && async;
if (this.isUsingV3()) {
return this._uploadV3(appGuid, filePath, fileSizeInBytes, asyncFlag);
}
return this._uploadV2(appGuid, filePath, fileSizeInBytes, asyncFlag);
}
/** @private */
_uploadV2(appGuid, filePath, fileSizeInBytes, asyncFlag) {
const url = `${this.API_URL}/v2/apps/${appGuid}/bits`;
const options = {
multipart: true,
accessToken: this.UAA_TOKEN.access_token,
query: { guid: appGuid, async: asyncFlag },
data: {
resources: JSON.stringify([]),
application: HttpUtils.file(filePath, "application/zip", fileSizeInBytes)
}
};
return this.REST.upload(url, options, this.HttpStatus.CREATED, false);
}
/** @private */
_uploadV3(appGuid, filePath, fileSizeInBytes, asyncFlag) {
const self = this;
const token = this.getAuthorizationHeader();
// Step 1: Create a package of type "bits" linked to the app
const createOpts = {
method: "POST",
url: `${this.API_URL}/v3/packages`,
headers: { Authorization: token, "Content-Type": "application/json" },
body: JSON.stringify({
type: "bits",
relationships: { app: { data: { guid: appGuid } } }
})
};
return this.REST.request(createOpts, this.HttpStatus.CREATED, true)
.then(function (pkg) {
// Step 2: Upload bits to the newly created package
const uploadUrl = `${self.API_URL}/v3/packages/${pkg.guid}/upload`;
const uploadOpts = {
method: "POST",
multipart: true,
accessToken: self.UAA_TOKEN.access_token,
query: { async: asyncFlag },
data: {
bits: HttpUtils.file(filePath, "application/zip", fileSizeInBytes)
}
};
return self.REST.upload(uploadUrl, uploadOpts, self.HttpStatus.OK, true);
});
}
/**
* Get detailed stats for a started app.
* @param {String} appGuid
* @return {Promise}
*/
getStats(appGuid) {
const token = this.getAuthorizationHeader();
// v3: stats are per-process; use the "web" process shortcut
const url = this.isUsingV3()
? `${this.API_URL}/v3/apps/${appGuid}/processes/web/stats`
: `${this.API_URL}/v2/apps/${appGuid}/stats`;
const options = { method: "GET", url, headers: { Authorization: token } };
return this.REST.request(options, this.HttpStatus.OK, true);
}
/**
* Get instance information.
* @param {String} appGuid
* @return {Promise}
*/
getInstances(appGuid) {
const token = this.getAuthorizationHeader();
const url = this.isUsingV3()
? `${this.API_URL}/v3/apps/${appGuid}/processes`
: `${this.API_URL}/v2/apps/${appGuid}/instances`;
const options = { method: "GET", url, headers: { Authorization: token } };
return this.REST.request(options, this.HttpStatus.OK, true);
}
/**
* Get routes for an app.
* @param {String} appGuid
* @return {Promise}
*/
getAppRoutes(appGuid) {
const token = this.getAuthorizationHeader();
const url = this.isUsingV3()
? `${this.API_URL}/v3/apps/${appGuid}/routes`
: `${this.API_URL}/v2/apps/${appGuid}/routes`;
const options = { method: "GET", url, headers: { Authorization: token } };
return this.REST.request(options, this.HttpStatus.OK, true);
}
/**
* Associate a route with an app.
* @param {String} appGuid
* @param {String} routeGuid
* @return {Promise}
*/
associateRoute(appGuid, routeGuid) {
const token = this.getAuthorizationHeader();
if (this.isUsingV3()) {
// v3: map app to route via destinations endpoint
const options = {
method: "POST",
url: `${this.API_URL}/v3/routes/${routeGuid}/destinations`,
headers: { Authorization: token, "Content-Type": "application/json" },
body: JSON.stringify({ destinations: [{ app: { guid: appGuid } }] })
};
return this.REST.request(options, this.HttpStatus.OK, true);
}
const options = {
method: "PUT",
url: `${this.API_URL}/v2/apps/${appGuid}/routes/${routeGuid}`,
headers: { Authorization: token }
};
return this.REST.request(options, this.HttpStatus.CREATED, true);
}
/**
* Get service bindings for an app.
* @param {String} appGuid
* @param {Object} [filter]
* @return {Promise}
*/
getServiceBindings(appGuid, filter) {
const token = this.getAuthorizationHeader();
if (this.isUsingV3()) {
// v3: no nested route; use top-level endpoint with app_guids filter
const qs = Object.assign({ app_guids: appGuid }, filter || {});
const options = {
method: "GET",
url: `${this.API_URL}/v3/service_credential_bindings`,
headers: { Authorization: token, "Content-Type": "application/json" },
qs: qs
};
return this.REST.request(options, this.HttpStatus.OK, true);
}
const url = `${this.API_URL}/v2/apps/${appGuid}/service_bindings`;
const options = { method: "GET", url, headers: { Authorization: token }, qs: filter || {} };
return this.REST.request(options, this.HttpStatus.OK, true);
}
/**
* Remove a service binding from an app.
* v3: DELETE /v3/service_credential_bindings/:guid → 202 (managed, async) or 204 (key/UPS, sync)
* v2: DELETE /v2/apps/:guid/service_bindings/:guid → 204
* @param {String} appGuid
* @param {String} serviceBindingGuid
* @return {Promise}
*/
removeServiceBindings(appGuid, serviceBindingGuid) {
const token = this.getAuthorizationHeader();
if (this.isUsingV3()) {
const options = {
method: "DELETE",
url: `${this.API_URL}/v3/service_credential_bindings/${serviceBindingGuid}`,
headers: { "Content-Type": "application/json", Authorization: token }
};
// Managed bindings return 202 (async job); key/UPS bindings return 204
return this.REST.request(options, [this.HttpStatus.ACCEPTED, this.HttpStatus.NO_CONTENT], false);
}
const options = {
method: "DELETE",
url: `${this.API_URL}/v2/apps/${appGuid}/service_bindings/${serviceBindingGuid}`,
headers: { "Content-Type": "application/x-www-form-urlencoded", Authorization: token }
};
return this.REST.request(options, this.HttpStatus.NO_CONTENT, false);
}
/**
* Get environment variables for an app.
* @param {String} appGuid
* @return {Promise}
*/
getEnvironmentVariables(appGuid) {
const token = this.getAuthorizationHeader();
const url = this.isUsingV3()
? `${this.API_URL}/v3/apps/${appGuid}/environment_variables`
: `${this.API_URL}/v2/apps/${appGuid}/env`;
const options = { method: "GET", url, headers: { Authorization: token } };
return this.REST.request(options, this.HttpStatus.OK, true);
}
/**
* Set environment variables for an app.
* NOTE: v2 path calls this.update() — only works when mixed into Apps facade.
* @param {String} appGuid
* @param {Object} variables
* @return {Promise}
*/
setEnvironmentVariables(appGuid, variables) {
const token = this.getAuthorizationHeader();
if (this.isUsingV3()) {
const options = {
method: "PATCH",
url: `${this.API_URL}/v3/apps/${appGuid}/environment_variables`,
headers: { "Content-Type": "application/json", Authorization: token },
body: JSON.stringify({ var: variables })
};
return this.REST.request(options, this.HttpStatus.OK, true);
}
// v2 path — delegates to update() which comes from AppsCore via Apps facade
if (typeof this.update !== "function") {
throw new Error(
"setEnvironmentVariables() v2 requires the Apps facade (mixin with AppsCore). " +
"Use the Apps class instead, or switch to v3 mode."
);
}
return this.update(appGuid, { environment_json: variables });
}
/**
* Restage an application (v2 only).
* @param {String} appGuid
* @return {Promise}
*/
restage(appGuid) {
if (this.isUsingV3()) {
throw new Error("Restage is not directly supported in v3 API. Use package/droplet endpoints instead.");
}
const token = this.getAuthorizationHeader();
const options = {
method: "POST",
url: `${this.API_URL}/v2/apps/${appGuid}/restage`,
headers: { Authorization: token }
};
return this.REST.request(options, this.HttpStatus.CREATED, true);
}
/**
* Get droplets for an app (v3 only).
* @param {String} appGuid
* @return {Promise}
*/
getDroplets(appGuid) {
if (!this.isUsingV3()) throw new Error("getDroplets is only available in v3 API");
const token = this.getAuthorizationHeader();
const options = {
method: "GET",
url: `${this.API_URL}/v3/apps/${appGuid}/droplets`,
headers: { Authorization: token }
};
return this.REST.request(options, this.HttpStatus.OK, true);
}
/**
* Get packages for an app (v3 only).
* @param {String} appGuid
* @return {Promise}
*/
getPackages(appGuid) {
if (!this.isUsingV3()) throw new Error("getPackages is only available in v3 API");
const token = this.getAuthorizationHeader();
const options = {
method: "GET",
url: `${this.API_URL}/v3/apps/${appGuid}/packages`,
headers: { Authorization: token }
};
return this.REST.request(options, this.HttpStatus.OK, true);
}
/**
* Get processes for an app (v3 only).
* @param {String} appGuid
* @return {Promise}
*/
getProcesses(appGuid) {
if (!this.isUsingV3()) throw new Error("getProcesses is only available in v3 API");
const token = this.getAuthorizationHeader();
const options = {
method: "GET",
url: `${this.API_URL}/v3/apps/${appGuid}/processes`,
headers: { Authorization: token }
};
return this.REST.request(options, this.HttpStatus.OK, true);
}
}
module.exports = AppsDeployment;