@coalescesoftware/coa 1.0.113 → 1.0.114
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/CLIProfile.js +20 -10
- package/bin/CommonCLI.js +4 -5
- package/bin/Deploy.js +1 -1
- package/bin/Package.js +731 -0
- package/bin/Plan.js +2 -2
- package/bin/index.js +21 -8
- package/package.json +2 -2
package/bin/CLIProfile.js
CHANGED
|
@@ -55,6 +55,7 @@ exports.GetDefaultLocationForCoaConfigFile = GetDefaultLocationForCoaConfigFile;
|
|
|
55
55
|
exports.ICLIProfileExample = {
|
|
56
56
|
profile: "Profile To Use",
|
|
57
57
|
environmentID: "Environment ID",
|
|
58
|
+
parameters: "Parameters",
|
|
58
59
|
snowflakeAccount: "Snowflake Account To Use",
|
|
59
60
|
snowflakeAuthType: "Snowflake Auth Type (Basic, KeyPair)",
|
|
60
61
|
snowflakeKeyPairPath: "Snowflake Key Pair Path",
|
|
@@ -68,21 +69,29 @@ exports.ICLIProfileExample = {
|
|
|
68
69
|
exclude: "Coalesce Node Selector"
|
|
69
70
|
};
|
|
70
71
|
const ReadCLIProfiles = (filePath) => {
|
|
71
|
-
|
|
72
|
+
let filePathToUse;
|
|
73
|
+
if (!filePath) {
|
|
74
|
+
filePathToUse = (0, exports.GetDefaultLocationForCoaConfigFile)();
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
filePathToUse = filePath;
|
|
78
|
+
}
|
|
79
|
+
return fs.promises.readFile(filePathToUse, 'utf-8')
|
|
80
|
+
.then((file) => {
|
|
72
81
|
return ini.parse(file);
|
|
73
|
-
})
|
|
82
|
+
})
|
|
83
|
+
.catch((err) => {
|
|
74
84
|
CLILogger.error("unable to read cli profile file filePath:", filePath, err);
|
|
75
|
-
|
|
85
|
+
if (!filePath) //if no filepath was specified, silently proceed
|
|
86
|
+
return {};
|
|
87
|
+
else //unable to proceed couldnt read file
|
|
88
|
+
throw new Error(`unable to read cli profile:${filePathToUse}`);
|
|
76
89
|
});
|
|
77
90
|
};
|
|
78
91
|
const GetFinalCLIProfile = (commandLineOverrides, configFileLocation) => {
|
|
79
92
|
let profileToUseOverride = commandLineOverrides.profile ? commandLineOverrides.profile : null;
|
|
80
|
-
return ReadCLIProfiles(configFileLocation)
|
|
81
|
-
|
|
82
|
-
}).catch((err) => {
|
|
83
|
-
//unable to read cli profiles
|
|
84
|
-
return {};
|
|
85
|
-
}).then((cliProfiles) => {
|
|
93
|
+
return ReadCLIProfiles(configFileLocation)
|
|
94
|
+
.then((cliProfiles) => {
|
|
86
95
|
const defaultCLIProfile = cliProfiles.default;
|
|
87
96
|
let finalCLIProfile = {};
|
|
88
97
|
//if theres a default cli profile, start with that
|
|
@@ -124,7 +133,8 @@ const GetCLIConfig = (commandLineOverrides, configFileLocation) => {
|
|
|
124
133
|
snowflakeRole: cliProfile.snowflakeRole,
|
|
125
134
|
snowflakeUsername: cliProfile.snowflakeUsername,
|
|
126
135
|
snowflakeWarehouse: cliProfile.snowflakeWarehouse
|
|
127
|
-
}
|
|
136
|
+
},
|
|
137
|
+
runtimeParameters: cliProfile.parameters,
|
|
128
138
|
};
|
|
129
139
|
Shared.Common.CleanupUndefinedValuesFromObject(cliConfig);
|
|
130
140
|
return cliConfig;
|
package/bin/CommonCLI.js
CHANGED
|
@@ -76,21 +76,20 @@ const GetKeyPairKey = (keyPairPath) => {
|
|
|
76
76
|
};
|
|
77
77
|
const GetUserConnectionForCLI = (userID, runInfo) => {
|
|
78
78
|
return new Promise((resolve, reject) => {
|
|
79
|
-
var _a, _b, _c, _d, _e, _f
|
|
79
|
+
var _a, _b, _c, _d, _e, _f;
|
|
80
80
|
const output = {
|
|
81
81
|
connectionDetails: {
|
|
82
82
|
userID,
|
|
83
83
|
user: (_a = runInfo.userCredentials) === null || _a === void 0 ? void 0 : _a.snowflakeUsername,
|
|
84
84
|
role: (_b = runInfo.userCredentials) === null || _b === void 0 ? void 0 : _b.snowflakeRole,
|
|
85
85
|
warehouse: (_c = runInfo.userCredentials) === null || _c === void 0 ? void 0 : _c.snowflakeWarehouse,
|
|
86
|
-
authenticator: (_d = runInfo.userCredentials) === null || _d === void 0 ? void 0 : _d.snowflakeAuthType
|
|
87
86
|
},
|
|
88
|
-
connectionType: (
|
|
87
|
+
connectionType: (_d = runInfo.userCredentials) === null || _d === void 0 ? void 0 : _d.snowflakeAuthType
|
|
89
88
|
};
|
|
90
|
-
if (!((
|
|
89
|
+
if (!((_e = runInfo.userCredentials) === null || _e === void 0 ? void 0 : _e.snowflakeAuthType)) {
|
|
91
90
|
reject(new Error("ERROR (GetUserConnectionForCLI): no auth type provided"));
|
|
92
91
|
}
|
|
93
|
-
else if (((
|
|
92
|
+
else if (((_f = runInfo.userCredentials) === null || _f === void 0 ? void 0 : _f.snowflakeAuthType) === Shared.ConnectionOperations.EUserConnectionTypes.keyPair) {
|
|
94
93
|
GetKeyPairKey(Shared.Common.getValueSafe(runInfo, ["userCredentials", "snowflakeKeyPairPath"], ""))
|
|
95
94
|
.then((keyPair) => {
|
|
96
95
|
output.connectionDetails.keyPair = keyPair;
|
package/bin/Deploy.js
CHANGED
|
@@ -50,7 +50,7 @@ const DeployWithCLI = (plan, config, token) => {
|
|
|
50
50
|
logContext = Shared.Logging.CreateLogContext(teamID, environmentID, userID);
|
|
51
51
|
const connectionCache = new Shared.Snowflake.ConnectionStorageClass();
|
|
52
52
|
RunSQL = Shared.SQLExecutorCreators.CreateRunSQLWithoutScheduler(teamDetails, connectionCache);
|
|
53
|
-
runInfo = Shared.DeployOperations.CreateDeployRequestObject(plan.plan, plan.environmentState, teamInfo, plan.gitInfo, plan.targetEnvironment,
|
|
53
|
+
runInfo = Shared.DeployOperations.CreateDeployRequestObject(plan.plan, plan.environmentState, teamInfo, plan.gitInfo, plan.targetEnvironment, plan.runtimeParameters);
|
|
54
54
|
runInfo.userCredentials = config.userCredentials;
|
|
55
55
|
return CommonCLI.GetUserConnectionForCLI(userID, runInfo);
|
|
56
56
|
})
|
package/bin/Package.js
ADDED
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
11
|
+
}) : function(o, v) {
|
|
12
|
+
o["default"] = v;
|
|
13
|
+
});
|
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
15
|
+
if (mod && mod.__esModule) return mod;
|
|
16
|
+
var result = {};
|
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
18
|
+
__setModuleDefault(result, mod);
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
22
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.GetPackageSupportedEntityTypes = exports.RemoveNamespacedWorkspaceData_Testing = exports.NamespaceWorkspaceData_Testing = exports.UninstallPackage = exports.InstallPackage = exports.PackageProvider = exports.GetPackageListMessageCLI = exports.InitializePackageContext = exports.localInstallKeyword = void 0;
|
|
26
|
+
/* eslint-disable multiline-comment-style */
|
|
27
|
+
const Shared = __importStar(require("@coalescesoftware/shared"));
|
|
28
|
+
const fs_1 = __importDefault(require("fs"));
|
|
29
|
+
const os = require("os");
|
|
30
|
+
/**
|
|
31
|
+
* coa package uses the same config file that should be setup to leverage coa.
|
|
32
|
+
*
|
|
33
|
+
* project - the user's ws data
|
|
34
|
+
* package - the targetted repo being applied to the project
|
|
35
|
+
* dependency - any package installed to a project/another package
|
|
36
|
+
*
|
|
37
|
+
* coa add works at a high level in these steps:
|
|
38
|
+
* 1 - collects ws data from a targetted repo (local or remote)
|
|
39
|
+
* 2 - namespaces that ws data w/package ID
|
|
40
|
+
* 3 - adds to firestore packages collection the info for the package being installed (dependency information) w/status: 'adding'
|
|
41
|
+
* 4 - updates project ws data on firestore with namespaced ws data from package
|
|
42
|
+
* 5 - updates firestore package dependency information w/status: 'added' and a timestamp
|
|
43
|
+
* 6 - in case of error, will clean up any added package information including dependency info from project ws data on FS
|
|
44
|
+
*
|
|
45
|
+
* coa remove works similarly
|
|
46
|
+
*/
|
|
47
|
+
exports.localInstallKeyword = "file:";
|
|
48
|
+
const PackagesLogger = Shared.Logging.GetLogger(Shared.Logging.LoggingArea.CLI);
|
|
49
|
+
var EPackageStatus;
|
|
50
|
+
(function (EPackageStatus) {
|
|
51
|
+
EPackageStatus["adding"] = "adding";
|
|
52
|
+
EPackageStatus["added"] = "added";
|
|
53
|
+
EPackageStatus["removing"] = "removing";
|
|
54
|
+
EPackageStatus["error"] = "error";
|
|
55
|
+
})(EPackageStatus || (EPackageStatus = {}));
|
|
56
|
+
var EVersionType;
|
|
57
|
+
(function (EVersionType) {
|
|
58
|
+
EVersionType["file"] = "file";
|
|
59
|
+
EVersionType["url"] = "url";
|
|
60
|
+
})(EVersionType || (EVersionType = {}));
|
|
61
|
+
////////////////////////////////////////////////////////////////////
|
|
62
|
+
//////////////////// reusable namespacing helper functions
|
|
63
|
+
////////////////////////////////////////////////////////////////////
|
|
64
|
+
/**
|
|
65
|
+
* Recieves a string or a number that will be namespaced (NAMESPACEVALUE::STRINGorNUMBER)
|
|
66
|
+
* @param value
|
|
67
|
+
* @param namespace
|
|
68
|
+
* @returns
|
|
69
|
+
*/
|
|
70
|
+
const UpdateValueWithNamespace = (value, namespace) => {
|
|
71
|
+
Shared.Common.assert(Shared.Logging.LoggingArea.Packages, typeof namespace === "string", `Namespace was not a string, but was type ${typeof namespace}`);
|
|
72
|
+
Shared.Common.assert(Shared.Logging.LoggingArea.Packages, !!namespace.trim(), `Namespace cannot be an empty string`);
|
|
73
|
+
Shared.Common.assert(Shared.Logging.LoggingArea.Packages, typeof value === "string" || typeof value === "number", `Value was not a string, but was type ${typeof value}`);
|
|
74
|
+
const stringValue = String(value);
|
|
75
|
+
Shared.Common.assert(Shared.Logging.LoggingArea.Packages, stringValue.indexOf("::") === -1, `value to namespace contained invalid character ":"`);
|
|
76
|
+
Shared.Common.assert(Shared.Logging.LoggingArea.Packages, namespace.indexOf("::") === -1, `namespace contained invalid character ":"`);
|
|
77
|
+
if (stringValue.trim() === "")
|
|
78
|
+
return stringValue; //empty string should not be namespaced but returned as is
|
|
79
|
+
return `${namespace}::${value}`;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Updates originalObject field values (fieldsToUpdate) using fieldValueUpdateCB and returns original value for non selected fields
|
|
83
|
+
*
|
|
84
|
+
* @param entity object with field values to update
|
|
85
|
+
* @param entityFieldsToUpdate an array of fields to update, or null to update all fields
|
|
86
|
+
* @param namespacerCB CB to call on each field to update
|
|
87
|
+
* @returns entity with field values that have been namespaced or updated with CB
|
|
88
|
+
*/
|
|
89
|
+
const EntityNamespacer = (entity, entityFieldsToUpdate, namespacerCB) => {
|
|
90
|
+
const updatedEntity = Object.assign({}, entity);
|
|
91
|
+
if (!entityFieldsToUpdate) {
|
|
92
|
+
// update all
|
|
93
|
+
Object.keys(entity).forEach(field => {
|
|
94
|
+
updatedEntity[field] = namespacerCB(updatedEntity[field]);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
Object.keys(entity).forEach(key => {
|
|
99
|
+
// if key included in keypath array, update, otherwise return original value
|
|
100
|
+
if (entityFieldsToUpdate.includes(key)) {
|
|
101
|
+
updatedEntity[key] = namespacerCB(updatedEntity[key]);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
updatedEntity[key] = entity[key];
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return updatedEntity;
|
|
109
|
+
};
|
|
110
|
+
////////////////////////////////////////////////////////////////////
|
|
111
|
+
///////////////// Parent namespacing functions
|
|
112
|
+
////////////////////////////////////////////////////////////////////
|
|
113
|
+
/**
|
|
114
|
+
* internal functions that will add or remove namespaced package data
|
|
115
|
+
* to/from a project's ws data and return the modified workspace data
|
|
116
|
+
*/
|
|
117
|
+
/**
|
|
118
|
+
* function adds namespaced package data to workspace data and return the modified workspace data
|
|
119
|
+
*
|
|
120
|
+
* @param allWorkspaceData
|
|
121
|
+
* @param packageInfo
|
|
122
|
+
* @param packageContext
|
|
123
|
+
* @returns AllWorkspaceData with added namespaced data
|
|
124
|
+
*/
|
|
125
|
+
const GetNamespacedWorkspaceData = (allWorkspaceData, packageInfo, packageContext) => {
|
|
126
|
+
const { id, version } = packageInfo;
|
|
127
|
+
const namespacedWorkspaceData = {};
|
|
128
|
+
Object.keys(allWorkspaceData).forEach(wsKey => {
|
|
129
|
+
try {
|
|
130
|
+
if (WorkspaceEntityNamespaceFunctionLookup[wsKey]) {
|
|
131
|
+
PackagesLogger.info(`Preparing workspace data for import: ${wsKey}`);
|
|
132
|
+
namespacedWorkspaceData[wsKey] = WorkspaceEntityNamespaceFunctionLookup[wsKey].add(id, version, allWorkspaceData[wsKey]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
const message = `error when preparing workspace data for import: ${wsKey}`;
|
|
137
|
+
if (error instanceof Error) {
|
|
138
|
+
PackagesLogger.errorContext(packageContext.loggerContext, message, error.message, error.name, error.stack, error);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
PackagesLogger.errorContext(packageContext.loggerContext, message, error);
|
|
142
|
+
}
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return namespacedWorkspaceData;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* will remove namespaced package workspace data from allWorkspaceData and return the updated object
|
|
150
|
+
*
|
|
151
|
+
* @param allWorkspaceData
|
|
152
|
+
* @param packageInfo
|
|
153
|
+
* @param packageContext
|
|
154
|
+
* @returns AllWorkspaceData with added namespaced data
|
|
155
|
+
*/
|
|
156
|
+
const RemoveNamespacedWorkspaceData = (allWorkspaceData, packageInfo, packageContext) => {
|
|
157
|
+
const prunedWorkspaceData = {};
|
|
158
|
+
const { id } = packageInfo;
|
|
159
|
+
Object.keys(allWorkspaceData).forEach(wsKey => {
|
|
160
|
+
try {
|
|
161
|
+
// if included in entities that should be imported, then add with namespace
|
|
162
|
+
if (WorkspaceEntityNamespaceFunctionLookup[wsKey]) {
|
|
163
|
+
PackagesLogger.info(`Pruning workspace data: ${wsKey}`);
|
|
164
|
+
prunedWorkspaceData[wsKey] = WorkspaceEntityNamespaceFunctionLookup[wsKey].remove(id, allWorkspaceData[wsKey]);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// else just add back the original data
|
|
168
|
+
prunedWorkspaceData[wsKey] = allWorkspaceData[wsKey];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
const message = `error when pruning workspace data for: ${wsKey}`;
|
|
173
|
+
if (error instanceof Error) {
|
|
174
|
+
PackagesLogger.errorContext(packageContext.loggerContext, message, error.message, error.name, error.stack, error);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
PackagesLogger.errorContext(packageContext.loggerContext, message, error);
|
|
178
|
+
}
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
return prunedWorkspaceData;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Takes a parentEntityObject (ie 'stepTypes') that contains nested entity objects (ie 'stepType-1') that will be namespaced
|
|
186
|
+
* a) all nested entity object keys will be namespaced
|
|
187
|
+
* b) all nested entity object field values (fieldsToUpdate) will be namespaced
|
|
188
|
+
* c) (optional) additional work can be done on the newly namespaced nested entity object using the nestedObjectUpdateCB
|
|
189
|
+
*
|
|
190
|
+
* @param parentEntityObject object containing nested entity objects which will be iterated through and namespaced
|
|
191
|
+
* @param packageID used as new namespace
|
|
192
|
+
* @param fieldsToUpdate array of fields whose values will be namespaced for each nested entity object, or null to update all fields
|
|
193
|
+
* @param nestedObjectUpdateCB (optional) any additional work that should be done to namespaced nested entity object
|
|
194
|
+
* @param entity the entity (singular) being updated, ie. "step type" or "folder", etc. used for info logger
|
|
195
|
+
* @returns
|
|
196
|
+
*/
|
|
197
|
+
const NamespaceEntity = (parentEntityObject, packageID, packageVersion, fieldsToUpdate, nestedObjectUpdateCB, entity) => {
|
|
198
|
+
const namespacedDataObject = {};
|
|
199
|
+
Object.keys(parentEntityObject).forEach(key => {
|
|
200
|
+
const isPackageImportedObject = !!Shared.Common.getValueSafe(parentEntityObject[key], ["packageInfo", "id"], null);
|
|
201
|
+
if (isPackageImportedObject)
|
|
202
|
+
return; // do not namespace already namespaced data
|
|
203
|
+
const namespacedKey = UpdateValueWithNamespace(key, packageID);
|
|
204
|
+
PackagesLogger.info(`Adding ${entity}: ${key} as ${namespacedKey}`);
|
|
205
|
+
namespacedDataObject[namespacedKey] = Object.assign({}, EntityNamespacer(parentEntityObject[key], fieldsToUpdate, (fieldValueToUpdate) => UpdateValueWithNamespace(fieldValueToUpdate, packageID)));
|
|
206
|
+
// do additional work if needed on newly namespaced nested object
|
|
207
|
+
if (nestedObjectUpdateCB) {
|
|
208
|
+
nestedObjectUpdateCB(namespacedDataObject[namespacedKey], key);
|
|
209
|
+
}
|
|
210
|
+
// add package information to top level of entity
|
|
211
|
+
namespacedDataObject[namespacedKey]["packageInfo"] = BuildPackageInfoForEntity(packageID, packageVersion);
|
|
212
|
+
});
|
|
213
|
+
return namespacedDataObject;
|
|
214
|
+
};
|
|
215
|
+
////////////////////////////////////////////////////////////////////
|
|
216
|
+
///////////////// Namespacing function dictionary
|
|
217
|
+
////////////////////////////////////////////////////////////////////
|
|
218
|
+
// to start packages will support import/removal of macros and steptypes
|
|
219
|
+
const WorkspaceEntityNamespaceFunctionLookup = {
|
|
220
|
+
macros: {
|
|
221
|
+
add: (packageID, packageVersion, macros) => MacrosNamespacer(packageID, macros, packageVersion),
|
|
222
|
+
remove: (packageID, macros) => MacrosNamespaceRemover(packageID, macros)
|
|
223
|
+
},
|
|
224
|
+
stepTypes: {
|
|
225
|
+
add: (packageID, packageVersion, stepTypes) => StepTypesNamespacer(packageID, stepTypes, packageVersion),
|
|
226
|
+
remove: (packageID, stepTypes) => StepTypesNamespaceRemover(packageID, stepTypes)
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
////////////////////////////////////////////////////////////////////
|
|
230
|
+
///////////////// Workspace Data Namespacing functions by entity (adding)
|
|
231
|
+
////////////////////////////////////////////////////////////////////
|
|
232
|
+
const MacrosNamespacer = (packageID, macros, packageVersion) => {
|
|
233
|
+
return NamespaceEntity(macros, packageID, packageVersion, ["id"], null, "macro");
|
|
234
|
+
};
|
|
235
|
+
const StepTypesNamespacer = (packageID, stepTypes, packageVersion) => {
|
|
236
|
+
const filteredStepTypes = Object.keys(stepTypes)
|
|
237
|
+
.filter(stepTypeID => !Shared.StepTypes.isDefaultStepType(stepTypeID)) // remove default steptypes
|
|
238
|
+
.reduce((acc, id) => {
|
|
239
|
+
acc[id] = stepTypes[id];
|
|
240
|
+
return acc;
|
|
241
|
+
}, {});
|
|
242
|
+
const updateStepTypeCB = (namespacedStepTypeData) => {
|
|
243
|
+
namespacedStepTypeData.metadata["defaultStorageLocation"] = null;
|
|
244
|
+
};
|
|
245
|
+
return NamespaceEntity(filteredStepTypes, packageID, packageVersion, ["id"], updateStepTypeCB, "stepType");
|
|
246
|
+
};
|
|
247
|
+
////////////////////////////////////////////////////////////////////
|
|
248
|
+
///////////////// Workspace Data Namespace Remover functions by entity (remove)
|
|
249
|
+
////////////////////////////////////////////////////////////////////
|
|
250
|
+
const EntityRemover = (packageID, parentEntityObject, entityName) => {
|
|
251
|
+
const prunedEntityObject = {};
|
|
252
|
+
Object.keys(parentEntityObject)
|
|
253
|
+
.filter(entityKey => {
|
|
254
|
+
const entityPackageInfo = Shared.Common.getValueSafe(parentEntityObject[entityKey], ["packageInfo"], null);
|
|
255
|
+
// if no info, then not imported, if info doesn't match, then not imported from this package
|
|
256
|
+
if (!entityPackageInfo || (!!entityPackageInfo && entityPackageInfo.id !== packageID)) {
|
|
257
|
+
return entityKey;
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
PackagesLogger.info(`Removing ${entityName}: ${entityKey}`);
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
.forEach(prunedEntityKey => prunedEntityObject[prunedEntityKey] = parentEntityObject[prunedEntityKey]);
|
|
264
|
+
return prunedEntityObject;
|
|
265
|
+
};
|
|
266
|
+
const MacrosNamespaceRemover = (packageID, macros) => {
|
|
267
|
+
return EntityRemover(packageID, macros, "macro");
|
|
268
|
+
};
|
|
269
|
+
const StepTypesNamespaceRemover = (packageID, stepTypes) => {
|
|
270
|
+
return EntityRemover(packageID, stepTypes, "step type");
|
|
271
|
+
};
|
|
272
|
+
////////////////////////////////////////////////////////////////////
|
|
273
|
+
///////////////// Package/Dependency getters/builders
|
|
274
|
+
////////////////////////////////////////////////////////////////////
|
|
275
|
+
const BuildPackageInfoForEntity = (packageID, version) => {
|
|
276
|
+
return {
|
|
277
|
+
id: packageID,
|
|
278
|
+
version
|
|
279
|
+
};
|
|
280
|
+
};
|
|
281
|
+
const InitializePackageDependencyInfoForPackage = (id, versionInfo, version, userID) => {
|
|
282
|
+
return {
|
|
283
|
+
id,
|
|
284
|
+
version,
|
|
285
|
+
versionInfo,
|
|
286
|
+
status: EPackageStatus.adding,
|
|
287
|
+
createdAt: null,
|
|
288
|
+
addedBy: userID
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
////////////////////////////////////////////////////////////////////
|
|
292
|
+
///////////////// Workspace Data Collection functions
|
|
293
|
+
////////////////////////////////////////////////////////////////////
|
|
294
|
+
const GetRepoNameFromURL = (repoURL) => {
|
|
295
|
+
try {
|
|
296
|
+
const nameIndex = repoURL.lastIndexOf("/") + 1;
|
|
297
|
+
const gitIndex = repoURL.lastIndexOf(".git");
|
|
298
|
+
return repoURL.substring(nameIndex, gitIndex);
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
PackagesLogger.error(`Error when reading package dependency URL: ${repoURL}`, error);
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
const GetAllWorkspaceDataAndCommitHashFromRemoteRepository = (location) => {
|
|
306
|
+
return new Promise((resolve, reject) => {
|
|
307
|
+
// ensure a unique temp folder name
|
|
308
|
+
const tempRepoFolder = `COA_TEMP_${GetRepoNameFromURL(location)}_${Math.random()}`;
|
|
309
|
+
const tempFolderPath = os.tmpdir();
|
|
310
|
+
let commitHashToReturn;
|
|
311
|
+
let commitOrBranch = null;
|
|
312
|
+
let locationToUse = location;
|
|
313
|
+
const indexOfHash = location.indexOf("#");
|
|
314
|
+
// if hash indicator, then grab commit and remove #info from URL
|
|
315
|
+
if (indexOfHash > -1) {
|
|
316
|
+
commitOrBranch = location.slice(indexOfHash + 1);
|
|
317
|
+
locationToUse = location.substring(0, indexOfHash);
|
|
318
|
+
}
|
|
319
|
+
const toExecute = commitOrBranch ? `&& git checkout ${commitOrBranch} && git log -n 1 ${commitOrBranch}` : `&& git log -n 1`;
|
|
320
|
+
PackagesLogger.info(`Cloning Coalesce package from repo URL: ${locationToUse}`);
|
|
321
|
+
return Shared.Common.executeCommand(`cd ${tempFolderPath} && git clone ${locationToUse} ${tempRepoFolder} && ls && cd ${tempRepoFolder} && ls ${toExecute}`)
|
|
322
|
+
.then(res => {
|
|
323
|
+
const fileSystemSettings = { fs: fs_1.default, dir: `${tempFolderPath}/${tempRepoFolder}` };
|
|
324
|
+
const getCommitFromGitLogRegex = new RegExp(/commit ([\s\S]*?)\n/);
|
|
325
|
+
const regexResult = getCommitFromGitLogRegex.exec(res[0]);
|
|
326
|
+
if (!regexResult) {
|
|
327
|
+
const message = `No commit was found for ${commitOrBranch}`;
|
|
328
|
+
throw new Error(message);
|
|
329
|
+
}
|
|
330
|
+
commitHashToReturn = regexResult[1];
|
|
331
|
+
return GetAllWorkspaceDataFromFileSystem(fileSystemSettings);
|
|
332
|
+
})
|
|
333
|
+
.then((workspaceData) => {
|
|
334
|
+
const additionalLogInfo = commitOrBranch ? ` at commit or branch: "${commitOrBranch}"` : "";
|
|
335
|
+
PackagesLogger.info(`Reading data from Coalesce package ${workspaceData.projectName || GetRepoNameFromURL(locationToUse)}${additionalLogInfo}...`);
|
|
336
|
+
resolve({
|
|
337
|
+
workspaceData,
|
|
338
|
+
hashFromRemote: commitHashToReturn
|
|
339
|
+
});
|
|
340
|
+
})
|
|
341
|
+
.catch(error => {
|
|
342
|
+
PackagesLogger.error(`Error while collecting workspace data for repo ${GetRepoNameFromURL(locationToUse)}`, error);
|
|
343
|
+
reject(error);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
};
|
|
347
|
+
const GetAllWorkspaceDataFromFileSystem = (fileSystemSettings) => {
|
|
348
|
+
return Shared.Workspaces.GetAllWorkspaceDataFromGitCommit(fileSystemSettings, null, null);
|
|
349
|
+
};
|
|
350
|
+
////////////////////////////////////////////////////////////////////
|
|
351
|
+
///////////////// Other helpers
|
|
352
|
+
////////////////////////////////////////////////////////////////////
|
|
353
|
+
/**
|
|
354
|
+
* this function will remove any ws data associated with packageInfo from projectWorkspaceData
|
|
355
|
+
* before reinstalling to ensure up-to-date package information
|
|
356
|
+
*
|
|
357
|
+
* @param packageInfo
|
|
358
|
+
* @param packageWorkspaceData
|
|
359
|
+
* @param projectWorkspaceData
|
|
360
|
+
* @param packageContext
|
|
361
|
+
* @returns {
|
|
362
|
+
* desiredState: what FS should look like
|
|
363
|
+
* stateToReplace: what FS looks like now
|
|
364
|
+
* }
|
|
365
|
+
*/
|
|
366
|
+
const GetNamespacedDataAndDesiredStateForFSFlush = (packageInfo, packageWorkspaceData, projectWorkspaceData, packageContext) => {
|
|
367
|
+
if (!packageInfo.id) {
|
|
368
|
+
const message = `PackageID was ${packageInfo.id} when it should be a string`;
|
|
369
|
+
Shared.Common.assert(Shared.Logging.LoggingArea.Packages, false, message);
|
|
370
|
+
throw new Error(message);
|
|
371
|
+
}
|
|
372
|
+
PackagesLogger.info(`Cleaning up any stale package data in your project...`);
|
|
373
|
+
// remove entities that were installed by current package
|
|
374
|
+
const prunedProjectWorkspaceData = RemoveNamespacedWorkspaceData(projectWorkspaceData, packageInfo, packageContext);
|
|
375
|
+
// get namespaced data for current package
|
|
376
|
+
const namespacedDataFromPackage = GetNamespacedWorkspaceData(packageWorkspaceData, packageInfo, packageContext);
|
|
377
|
+
// add namespaced data to project data
|
|
378
|
+
const projectWorkspaceDataWithUpdates = CombineWorkspaceDataByEntities(namespacedDataFromPackage, prunedProjectWorkspaceData);
|
|
379
|
+
return {
|
|
380
|
+
desiredState: projectWorkspaceDataWithUpdates,
|
|
381
|
+
namespacedData: namespacedDataFromPackage
|
|
382
|
+
};
|
|
383
|
+
};
|
|
384
|
+
// get a package context object utilized by much of the functionality in this file
|
|
385
|
+
const InitializePackageContext = (authInfo, environmentID) => {
|
|
386
|
+
return {
|
|
387
|
+
firestore: authInfo.firebase.firestore(),
|
|
388
|
+
timestamp: Shared.Firebase.serverTimestamp,
|
|
389
|
+
teamID: authInfo.teamInfo.fbTeamID,
|
|
390
|
+
environmentID,
|
|
391
|
+
loggerContext: { orgID: authInfo.teamInfo.fbTeamID, userID: authInfo.teamInfo.fbUserID }
|
|
392
|
+
};
|
|
393
|
+
};
|
|
394
|
+
exports.InitializePackageContext = InitializePackageContext;
|
|
395
|
+
const CombineWorkspaceDataByEntities = (namespacedWorkspaceData, projectWorkspaceData) => {
|
|
396
|
+
let updatedProjectWorkspaceData = {};
|
|
397
|
+
Object.keys(projectWorkspaceData).forEach(entityKey => {
|
|
398
|
+
updatedProjectWorkspaceData[entityKey] = projectWorkspaceData[entityKey];
|
|
399
|
+
});
|
|
400
|
+
Object.keys(namespacedWorkspaceData).forEach(entityKey => {
|
|
401
|
+
updatedProjectWorkspaceData[entityKey] = Object.assign(Object.assign({}, updatedProjectWorkspaceData[entityKey]), namespacedWorkspaceData[entityKey]);
|
|
402
|
+
});
|
|
403
|
+
return updatedProjectWorkspaceData;
|
|
404
|
+
};
|
|
405
|
+
const InitializeVersionInfo = (commitOrBranch, repoLocation) => {
|
|
406
|
+
if (!!commitOrBranch) {
|
|
407
|
+
return {
|
|
408
|
+
commit: commitOrBranch,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
return {
|
|
413
|
+
filePath: repoLocation,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
const BuildManifest = (workspaceData) => {
|
|
418
|
+
const manifest = {};
|
|
419
|
+
const getEntityNameFromPackageManifest = (entityType, entityID) => {
|
|
420
|
+
return Shared.Common.getValueSafe(workspaceData, [entityType, entityID, "name"], entityID);
|
|
421
|
+
};
|
|
422
|
+
Object.keys(workspaceData).forEach(entityType => {
|
|
423
|
+
const entityImportedData = Object.keys(workspaceData[entityType]);
|
|
424
|
+
manifest[entityType] = {};
|
|
425
|
+
entityImportedData.forEach(namedEntityKey => {
|
|
426
|
+
debugger;
|
|
427
|
+
manifest[entityType][namedEntityKey] = {
|
|
428
|
+
id: namedEntityKey,
|
|
429
|
+
name: getEntityNameFromPackageManifest(entityType, namedEntityKey)
|
|
430
|
+
};
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
return manifest;
|
|
434
|
+
};
|
|
435
|
+
////////////////////////////////////////////////////
|
|
436
|
+
///////////// Firestore read/write funcs
|
|
437
|
+
////////////////////////////////////////////////////
|
|
438
|
+
const GetPackageDocRefAdmin = (packageInfo, packageContext) => {
|
|
439
|
+
const { environmentID, teamID, firestore } = packageContext;
|
|
440
|
+
return Shared.CommonOperations.getOrgConfigDocRefAdmin(firestore, teamID).collection("workspaces").doc(environmentID.toString()).collection("packages").doc(packageInfo.id);
|
|
441
|
+
};
|
|
442
|
+
const UpdatePackageInformationFS = (packageInfo, status, packageContext, setTimestamp) => {
|
|
443
|
+
// example: setting timestamp once package has completed install
|
|
444
|
+
const createdAt = setTimestamp ? packageContext.timestamp : packageInfo.createdAt;
|
|
445
|
+
const update = Object.assign({}, Object.assign(Object.assign({}, packageInfo), { status, createdAt }));
|
|
446
|
+
return GetPackageDocRefAdmin(packageInfo, packageContext).set(update)
|
|
447
|
+
.then(() => {
|
|
448
|
+
return { status, id: packageInfo.id };
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
const RemovePackageInformationFS = (packageInfo, packageContext) => {
|
|
452
|
+
const { environmentID, teamID, firestore } = packageContext;
|
|
453
|
+
return Shared.CommonOperations.getOrgConfigDocRefAdmin(firestore, teamID).collection("workspaces").doc(environmentID.toString()).collection("packages").doc(packageInfo.id).delete();
|
|
454
|
+
};
|
|
455
|
+
const GetPackageListMessageCLI = (packageContext) => {
|
|
456
|
+
const { environmentID, teamID, firestore } = packageContext;
|
|
457
|
+
return Shared.Workspaces.getAllWorkspaceDataFromFirebase(firestore, teamID, environmentID)
|
|
458
|
+
.then(wsData => {
|
|
459
|
+
const packages = Shared.Common.getValueSafe(wsData, ["packages"], null);
|
|
460
|
+
if (packages && Object.keys(packages).length) {
|
|
461
|
+
const listForCLI = Object.keys(packages).map(packageID => `${packageID} \n`);
|
|
462
|
+
listForCLI.unshift(`\nPackages added to environment ${environmentID}: \n`);
|
|
463
|
+
return listForCLI.join("");
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
return `No packages added to environment ${environmentID}`;
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
};
|
|
470
|
+
exports.GetPackageListMessageCLI = GetPackageListMessageCLI;
|
|
471
|
+
//////////////////////////////////////////////////////////
|
|
472
|
+
///////////// install/remove kickoff functions and classes
|
|
473
|
+
//////////////////////////////////////////////////////////
|
|
474
|
+
class PackageProvider {
|
|
475
|
+
constructor(dirOrURL, loggerContext) {
|
|
476
|
+
this.version = dirOrURL;
|
|
477
|
+
this.loggerContext = loggerContext;
|
|
478
|
+
// these vars will be null until getWorkspaceDataFS is called
|
|
479
|
+
this.commitHash = null;
|
|
480
|
+
this.id = null;
|
|
481
|
+
this.workspaceData = null;
|
|
482
|
+
this.versionInfo = null;
|
|
483
|
+
// init logic
|
|
484
|
+
if (!(typeof this.version === "string")) {
|
|
485
|
+
const message = `Location should be a string, but is of type ${typeof this.version}`;
|
|
486
|
+
PackagesLogger.errorContext(this.loggerContext, message);
|
|
487
|
+
throw new Error(`Invalid Package package location: ${message}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
_getVersionType() {
|
|
491
|
+
const fileKeywordIndex = this.version.indexOf(exports.localInstallKeyword);
|
|
492
|
+
const httpIndex = this.version.indexOf("http");
|
|
493
|
+
if (fileKeywordIndex === 0) {
|
|
494
|
+
return EVersionType.file;
|
|
495
|
+
}
|
|
496
|
+
else if (httpIndex === 0) {
|
|
497
|
+
return EVersionType.url;
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
const message = `Error with URL or filepath: "${this.version}" - filepath missing prefix "filepath:" or URL missing "http://" or "https://"`;
|
|
501
|
+
PackagesLogger.errorContext(this.loggerContext, message);
|
|
502
|
+
throw new Error(message);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
_isProjectNameValid(name) {
|
|
506
|
+
let isValid = true;
|
|
507
|
+
const indexOfNamespace = name.indexOf("::");
|
|
508
|
+
// projectName should not be only whitespace
|
|
509
|
+
if (indexOfNamespace > -1) {
|
|
510
|
+
isValid = false;
|
|
511
|
+
}
|
|
512
|
+
return isValid;
|
|
513
|
+
}
|
|
514
|
+
getWorkspaceDataFS() {
|
|
515
|
+
const versionType = this._getVersionType();
|
|
516
|
+
let wsDataFunc;
|
|
517
|
+
switch (versionType) {
|
|
518
|
+
case EVersionType.file: {
|
|
519
|
+
const dirWithoutKeyword = this.version.substring(exports.localInstallKeyword.length);
|
|
520
|
+
const fsSettings = { fs: fs_1.default, dir: dirWithoutKeyword };
|
|
521
|
+
this.commitHash = null;
|
|
522
|
+
wsDataFunc = GetAllWorkspaceDataFromFileSystem(fsSettings);
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
case EVersionType.url: {
|
|
526
|
+
wsDataFunc = GetAllWorkspaceDataAndCommitHashFromRemoteRepository(this.version)
|
|
527
|
+
.then((response) => {
|
|
528
|
+
const { workspaceData, hashFromRemote } = response;
|
|
529
|
+
this.commitHash = hashFromRemote;
|
|
530
|
+
return workspaceData;
|
|
531
|
+
});
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
default: {
|
|
535
|
+
const message = `versionType was invariant: ${versionType}`;
|
|
536
|
+
Shared.Common.assert(Shared.Logging.LoggingArea.Packages, false, message);
|
|
537
|
+
throw new Error(message);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return wsDataFunc
|
|
541
|
+
.then((workspaceData) => {
|
|
542
|
+
// ensure package ID
|
|
543
|
+
if (!workspaceData.projectName) {
|
|
544
|
+
PackagesLogger.errorContext(this.loggerContext, `Invalid Package: Missing project name`);
|
|
545
|
+
PackagesLogger.errorContext(this.loggerContext, `Package owner must add a project name in Coalesce git settings before deploying commit for Package`);
|
|
546
|
+
throw new Error(`Invalid Package: Missing package ID; Package owner must add a package name in Coalesce git settings before deploying commit for Package`);
|
|
547
|
+
}
|
|
548
|
+
// validate project name
|
|
549
|
+
if (!this._isProjectNameValid(workspaceData.projectName)) {
|
|
550
|
+
PackagesLogger.errorContext(this.loggerContext, `Invalid Package: Project name contains reserved namespacing characters "::"`);
|
|
551
|
+
throw new Error(`${workspaceData.projectName} contains "::" and is not a valid project name`);
|
|
552
|
+
}
|
|
553
|
+
this.id = workspaceData.projectName.toUpperCase(); // should be uppercase
|
|
554
|
+
this.workspaceData = workspaceData;
|
|
555
|
+
this.versionInfo = InitializeVersionInfo(this.commitHash, this.version);
|
|
556
|
+
return this.workspaceData;
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
exports.PackageProvider = PackageProvider;
|
|
561
|
+
const InstallPackage = (packageProvider, packageContext) => {
|
|
562
|
+
const { firestore, teamID, environmentID, loggerContext } = packageContext;
|
|
563
|
+
return new Promise((resolve, reject) => {
|
|
564
|
+
let projectWorkspaceData;
|
|
565
|
+
let packageDependencyInfo;
|
|
566
|
+
let manifest;
|
|
567
|
+
PackagesLogger.infoContext(loggerContext, `Gathering your project workspace data for environment: ${environmentID}`);
|
|
568
|
+
return Shared.Workspaces.getAllWorkspaceDataFromFirebase(firestore, teamID, environmentID)
|
|
569
|
+
.then((projectWorkspaceDataFromFirestore) => {
|
|
570
|
+
projectWorkspaceData = projectWorkspaceDataFromFirestore;
|
|
571
|
+
return packageProvider.getWorkspaceDataFS();
|
|
572
|
+
})
|
|
573
|
+
.then(() => {
|
|
574
|
+
const { id, versionInfo } = packageProvider; // at this point these vars have been defined
|
|
575
|
+
if (!versionInfo) {
|
|
576
|
+
const message = `versionInfo was ${versionInfo}`;
|
|
577
|
+
Shared.Common.assert(Shared.Logging.LoggingArea.Packages, false, message);
|
|
578
|
+
throw new Error(message);
|
|
579
|
+
}
|
|
580
|
+
PackagesLogger.infoContext(loggerContext, `Installing package using namespace "${id}::"`);
|
|
581
|
+
packageDependencyInfo = InitializePackageDependencyInfoForPackage(id, versionInfo, packageProvider.version, loggerContext.userID || "unknown");
|
|
582
|
+
return UpdatePackageInformationFS(packageDependencyInfo, EPackageStatus.adding, packageContext, false);
|
|
583
|
+
})
|
|
584
|
+
.then((packageInfoFS) => {
|
|
585
|
+
const { status, id } = packageInfoFS;
|
|
586
|
+
PackagesLogger.infoContext(loggerContext, `${status} ${id}...building firestore updates payload...`);
|
|
587
|
+
const { desiredState, namespacedData } = GetNamespacedDataAndDesiredStateForFSFlush(packageDependencyInfo, packageProvider.workspaceData, projectWorkspaceData, packageContext);
|
|
588
|
+
manifest = BuildManifest(namespacedData);
|
|
589
|
+
PackagesLogger.infoContext(loggerContext, "Applying updates to firestore...");
|
|
590
|
+
return Shared.Workspaces.FlushFirestoreWorkspaceWithAllWorkspaceData(firestore, teamID, environmentID, desiredState, projectWorkspaceData);
|
|
591
|
+
})
|
|
592
|
+
.then(() => {
|
|
593
|
+
return UpdatePackageInformationFS(Object.assign(Object.assign({}, packageDependencyInfo), { manifest }), EPackageStatus.added, packageContext, true);
|
|
594
|
+
})
|
|
595
|
+
.then((packageInfoFS) => {
|
|
596
|
+
const { status, id } = packageInfoFS;
|
|
597
|
+
PackagesLogger.infoContext(loggerContext, `${status} ${id}...update successful!`);
|
|
598
|
+
resolve();
|
|
599
|
+
})
|
|
600
|
+
.catch(error => {
|
|
601
|
+
if (!!packageDependencyInfo) {
|
|
602
|
+
return UpdatePackageInformationFS(packageDependencyInfo, EPackageStatus.error, packageContext, false)
|
|
603
|
+
.then((packageInfo) => {
|
|
604
|
+
const { status, id } = packageInfo;
|
|
605
|
+
PackagesLogger.errorContext(loggerContext, `Update failed for ${id}. Package ${id} status: ${status}, removing package information from project: `, error);
|
|
606
|
+
PackagesLogger.errorContext(loggerContext, error);
|
|
607
|
+
return (0, exports.UninstallPackage)(id, packageContext);
|
|
608
|
+
})
|
|
609
|
+
.then(() => {
|
|
610
|
+
const message = `All data from package ${packageDependencyInfo.id} removed from project after error during installation.`;
|
|
611
|
+
PackagesLogger.infoContext(loggerContext, message);
|
|
612
|
+
resolve();
|
|
613
|
+
})
|
|
614
|
+
.catch(error => reject(error));
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
PackagesLogger.errorContext(loggerContext, `Update failed when installing package from ${packageProvider.version}`, error);
|
|
618
|
+
reject(error);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
};
|
|
623
|
+
exports.InstallPackage = InstallPackage;
|
|
624
|
+
const UninstallPackage = (packageID, packageContext) => {
|
|
625
|
+
const { firestore, teamID, environmentID, loggerContext } = packageContext;
|
|
626
|
+
return new Promise((resolve, reject) => {
|
|
627
|
+
let projectWorkspaceData;
|
|
628
|
+
let prunedWorkspaceData;
|
|
629
|
+
PackagesLogger.infoContext(loggerContext, `Gathering your project workspace data for environment: ${environmentID}`);
|
|
630
|
+
return Shared.Workspaces.getAllWorkspaceDataFromFirebase(firestore, teamID, environmentID)
|
|
631
|
+
.then((projectWorkspaceDataFromFirestore) => {
|
|
632
|
+
projectWorkspaceData = projectWorkspaceDataFromFirestore;
|
|
633
|
+
const allProjectDependencies = Shared.Common.getValueSafe(projectWorkspaceDataFromFirestore, ["packages"], {});
|
|
634
|
+
const thisDependencyInfo = allProjectDependencies[packageID];
|
|
635
|
+
if (!thisDependencyInfo) {
|
|
636
|
+
const depKeys = Object.keys(allProjectDependencies);
|
|
637
|
+
reject(new Error(`Environment ${environmentID} has ${depKeys.length} packages installed: ${depKeys}. Package "${packageID}" is not installed to this environment.`));
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
PackagesLogger.infoContext(loggerContext, `Removing package workspace data from your project`);
|
|
641
|
+
prunedWorkspaceData = RemoveNamespacedWorkspaceData(projectWorkspaceData, thisDependencyInfo, packageContext);
|
|
642
|
+
return UpdatePackageInformationFS(thisDependencyInfo, EPackageStatus.removing, packageContext, false)
|
|
643
|
+
.then(() => {
|
|
644
|
+
PackagesLogger.infoContext(loggerContext, `Applying updates to firestore...`);
|
|
645
|
+
return Shared.Workspaces.FlushFirestoreWorkspaceWithAllWorkspaceData(firestore, teamID, environmentID, prunedWorkspaceData, projectWorkspaceData);
|
|
646
|
+
})
|
|
647
|
+
.then(() => {
|
|
648
|
+
return RemovePackageInformationFS(thisDependencyInfo, packageContext);
|
|
649
|
+
})
|
|
650
|
+
.then(() => {
|
|
651
|
+
PackagesLogger.infoContext(loggerContext, `Removed ${packageID} workspace data from your project`);
|
|
652
|
+
resolve();
|
|
653
|
+
})
|
|
654
|
+
.catch((error) => {
|
|
655
|
+
if (!!thisDependencyInfo) {
|
|
656
|
+
return UpdatePackageInformationFS(thisDependencyInfo, EPackageStatus.error, packageContext, false)
|
|
657
|
+
.then(() => {
|
|
658
|
+
PackagesLogger.errorContext(loggerContext, `Encountered a problem when removing ${thisDependencyInfo.id}, removal aborted. Please try again.`);
|
|
659
|
+
reject(error);
|
|
660
|
+
})
|
|
661
|
+
.catch(error => reject(error));
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
PackagesLogger.errorContext(loggerContext, `Update failed when removing package`, error);
|
|
665
|
+
reject(error);
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
};
|
|
671
|
+
exports.UninstallPackage = UninstallPackage;
|
|
672
|
+
////////////////////////////////////////////////////
|
|
673
|
+
///////////// helper Jest test funcs
|
|
674
|
+
////////////////////////////////////////////////////
|
|
675
|
+
const NamespaceWorkspaceData_Testing = (allWorkspaceData, packageInfo) => {
|
|
676
|
+
const { id, version } = packageInfo;
|
|
677
|
+
const namespacedWorkspaceData = {};
|
|
678
|
+
Object.keys(allWorkspaceData).forEach(wsKey => {
|
|
679
|
+
try {
|
|
680
|
+
if (WorkspaceEntityNamespaceFunctionLookup[wsKey]) {
|
|
681
|
+
PackagesLogger.info(`JEST TEST: Preparing workspace data for import: ${wsKey}`);
|
|
682
|
+
namespacedWorkspaceData[wsKey] = WorkspaceEntityNamespaceFunctionLookup[wsKey].add(id, version, allWorkspaceData[wsKey]);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
const message = `JEST TEST: error when preparing workspace data for import: ${wsKey}`;
|
|
687
|
+
if (error instanceof Error) {
|
|
688
|
+
PackagesLogger.error(message, error.message, error.name, error.stack, error);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
PackagesLogger.error(message, error);
|
|
692
|
+
}
|
|
693
|
+
throw error;
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
return namespacedWorkspaceData;
|
|
697
|
+
};
|
|
698
|
+
exports.NamespaceWorkspaceData_Testing = NamespaceWorkspaceData_Testing;
|
|
699
|
+
// will remove namespaced package workspace data from allWorkspaceData and return the updated object
|
|
700
|
+
const RemoveNamespacedWorkspaceData_Testing = (allWorkspaceData, packageInfo) => {
|
|
701
|
+
const prunedWorkspaceData = {};
|
|
702
|
+
const { id } = packageInfo;
|
|
703
|
+
Object.keys(allWorkspaceData).forEach(wsKey => {
|
|
704
|
+
try {
|
|
705
|
+
if (WorkspaceEntityNamespaceFunctionLookup[wsKey]) {
|
|
706
|
+
PackagesLogger.info(`JEST TEST: Pruning workspace data: ${wsKey}`);
|
|
707
|
+
prunedWorkspaceData[wsKey] = WorkspaceEntityNamespaceFunctionLookup[wsKey].remove(id, allWorkspaceData[wsKey]);
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
prunedWorkspaceData[wsKey] = allWorkspaceData[wsKey];
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
const message = `JEST TEST: error when pruning workspace data for: ${wsKey}`;
|
|
715
|
+
if (error instanceof Error) {
|
|
716
|
+
PackagesLogger.error(message, error.message, error.name, error.stack, error);
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
PackagesLogger.error(message, error);
|
|
720
|
+
}
|
|
721
|
+
throw error;
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
return prunedWorkspaceData;
|
|
725
|
+
};
|
|
726
|
+
exports.RemoveNamespacedWorkspaceData_Testing = RemoveNamespacedWorkspaceData_Testing;
|
|
727
|
+
const GetPackageSupportedEntityTypes = () => {
|
|
728
|
+
return Object.keys(WorkspaceEntityNamespaceFunctionLookup);
|
|
729
|
+
};
|
|
730
|
+
exports.GetPackageSupportedEntityTypes = GetPackageSupportedEntityTypes;
|
|
731
|
+
//# sourceMappingURL=Package.js.map
|
package/bin/Plan.js
CHANGED
|
@@ -25,12 +25,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
25
25
|
exports.CreatePlan = void 0;
|
|
26
26
|
const Shared = __importStar(require("@coalescesoftware/shared"));
|
|
27
27
|
const fs_1 = __importDefault(require("fs"));
|
|
28
|
-
const CreatePlan = (path, environmentID, token, message) => {
|
|
28
|
+
const CreatePlan = (path, environmentID, token, message, runtimeParameters) => {
|
|
29
29
|
const fsSettings = {
|
|
30
30
|
fs: fs_1.default,
|
|
31
31
|
dir: path,
|
|
32
32
|
};
|
|
33
|
-
return Shared.DeployOperations.CreatePlanCLI(environmentID, fsSettings, token, Shared.Templates.PlatformType.snowflake)
|
|
33
|
+
return Shared.DeployOperations.CreatePlanCLI(environmentID, fsSettings, token, Shared.Templates.PlatformType.snowflake, runtimeParameters)
|
|
34
34
|
.then(result => {
|
|
35
35
|
const { plan } = result;
|
|
36
36
|
plan.gitInfo = { commit: { message: message }, oid: "" };
|
package/bin/index.js
CHANGED
|
@@ -40,7 +40,7 @@ const LogCLIInternal = Shared.Logging.GetLogger(Shared.Logging.LoggingArea.CLI_I
|
|
|
40
40
|
const program = new commander_1.Command();
|
|
41
41
|
program.version(require("../package.json").version, "-v, --version");
|
|
42
42
|
program.option("-b, --debug", "Output extra debugging", false);
|
|
43
|
-
program.option("--config <coa-config-location>", "coa config file location"
|
|
43
|
+
program.option("--config <coa-config-location>", "coa config file location");
|
|
44
44
|
const AddOverridesToCommand = (command) => {
|
|
45
45
|
Object.keys(CLIProfile.ICLIProfileExample).forEach((key) => {
|
|
46
46
|
command.option(`--${key} <value>`, CLIProfile.ICLIProfileExample[key], "");
|
|
@@ -94,10 +94,17 @@ AddOverridesToCommand(program.command(Shared.CLIOperations.ECLICommands.Plan))
|
|
|
94
94
|
.then((configResult) => {
|
|
95
95
|
config = configResult;
|
|
96
96
|
return Shared.SchedulerOperations.AuthenticateFirebaseTokenAndRetrieveTeamInfoForCLI(config.token, undefined);
|
|
97
|
-
})
|
|
97
|
+
})
|
|
98
|
+
.then(({ teamInfo, firebase }) => {
|
|
99
|
+
const firestore = firebase.firestore();
|
|
98
100
|
Shared.Logging.SetBackendGlobalContext({ userID: teamInfo.fbUserID, orgID: teamInfo.fbTeamID });
|
|
101
|
+
const teamID = teamInfo.fbTeamID;
|
|
102
|
+
const environmentID = config.runDetails.environmentID;
|
|
103
|
+
return Shared.SchedulerOperations.GetRuntimeParametersObjectForRun(firestore, teamID, environmentID, config.runtimeParameters);
|
|
104
|
+
})
|
|
105
|
+
.then((runtimeParametersObject) => {
|
|
99
106
|
const envID = config.runDetails.environmentID;
|
|
100
|
-
return Plan.CreatePlan(directoryPath, envID, config.token, `Deploy to environment: ${envID}
|
|
107
|
+
return Plan.CreatePlan(directoryPath, envID, config.token, `Deploy to environment: ${envID}`, JSON.stringify(runtimeParametersObject));
|
|
101
108
|
}).then(result => {
|
|
102
109
|
const { plan, logContext } = result;
|
|
103
110
|
logContextToUse = logContext;
|
|
@@ -155,7 +162,7 @@ AddOverridesToCommand(program.command(Shared.CLIOperations.ECLICommands.Deploy))
|
|
|
155
162
|
environmentID: config.runDetails.environmentID
|
|
156
163
|
},
|
|
157
164
|
userCredentials: config.userCredentials,
|
|
158
|
-
runTimeParameters: {}
|
|
165
|
+
runTimeParameters: plan.runtimeParameters || {},
|
|
159
166
|
};
|
|
160
167
|
Shared.Common.assert(Shared.Logging.LoggingArea.CLI, !!runInfo, "runinfo must exist to continue");
|
|
161
168
|
return Deploy.DeployWithCLI(plan, runInfo, config.token)
|
|
@@ -212,8 +219,14 @@ AddOverridesToCommand(program.command(Shared.CLIOperations.ECLICommands.Refresh)
|
|
|
212
219
|
config = configResult;
|
|
213
220
|
return Shared.SchedulerOperations.AuthenticateFirebaseTokenAndRetrieveTeamInfoForCLI(config.token, undefined);
|
|
214
221
|
})
|
|
215
|
-
.then(({ teamInfo }) => {
|
|
222
|
+
.then(({ teamInfo, firebase }) => {
|
|
223
|
+
const firestore = firebase.firestore();
|
|
216
224
|
Shared.Logging.SetBackendGlobalContext({ userID: teamInfo.fbUserID, orgID: teamInfo.fbTeamID });
|
|
225
|
+
const teamID = teamInfo.fbTeamID;
|
|
226
|
+
const environmentID = config.runDetails.environmentID;
|
|
227
|
+
return Shared.SchedulerOperations.GetRuntimeParametersObjectForRun(firestore, teamID, environmentID, config.runtimeParameters);
|
|
228
|
+
})
|
|
229
|
+
.then((runtimeParametersObject) => {
|
|
217
230
|
runInfo = {
|
|
218
231
|
runType: Shared.Runner.ERunType.refresh,
|
|
219
232
|
runStatus: Shared.Runner.ERunStatus.running,
|
|
@@ -224,12 +237,12 @@ AddOverridesToCommand(program.command(Shared.CLIOperations.ECLICommands.Refresh)
|
|
|
224
237
|
excludeNodesSelector: config.runDetails.excludeNodesSelector
|
|
225
238
|
},
|
|
226
239
|
userCredentials: config.userCredentials,
|
|
227
|
-
runTimeParameters:
|
|
240
|
+
runTimeParameters: runtimeParametersObject,
|
|
228
241
|
};
|
|
229
242
|
Shared.Common.CleanupUndefinedValuesFromObject(runInfo);
|
|
230
243
|
LogCLIInternal.infoContext(logContextToUse, "cliOverrides", cliOverrides);
|
|
231
|
-
LogCLIInternal.infoContext(logContextToUse, "got the cli config", config)
|
|
232
|
-
LogCLIInternal.infoContext(logContextToUse, "got the run info", config)
|
|
244
|
+
//LogCLIInternal.infoContext(logContextToUse, "got the cli config", config)
|
|
245
|
+
//LogCLIInternal.infoContext(logContextToUse, "got the run info", config)
|
|
233
246
|
Shared.Common.assert(Shared.Logging.LoggingArea.CLI, !!runInfo, "runinfo must exist to continue");
|
|
234
247
|
return Refresh.RefreshWithCLI(config.token, runInfo)
|
|
235
248
|
.then((result) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coalescesoftware/coa",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.114",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"author": "Coalesce Automation, Inc.",
|
|
6
6
|
"main": "index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"start-cli-debug": "yarn run start --debug"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@coalescesoftware/shared": "^1.0.
|
|
15
|
+
"@coalescesoftware/shared": "^1.0.114",
|
|
16
16
|
"chalk": "^4.1.2",
|
|
17
17
|
"commander": "^9.2.0",
|
|
18
18
|
"firebase": "8.2.0",
|