Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/1275 add metadata option to deploy #1283

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 54 additions & 21 deletions lib/Deployer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import auth from './util/auth.js';
* @typedef {import('../types/mcdev.d.js').SoapRequestParams} SoapRequestParams
* @typedef {import('../types/mcdev.d.js').TemplateMap} TemplateMap
* @typedef {import('../types/mcdev.d.js').MultiMetadataTypeMap} MultiMetadataTypeMap
* @typedef {import('../types/mcdev.d.js').TypeKeyCombo} TypeKeyCombo
*/

/**
Expand Down Expand Up @@ -57,7 +58,7 @@ class Deployer {
* Deploys all metadata located in the 'deploy' directory to the specified business unit
*
* @param {string} businessUnit references credentials from properties.json
* @param {string[]} [selectedTypesArr] limit deployment to given metadata type
* @param {string[] | TypeKeyCombo} [selectedTypesArr] limit deployment to given metadata type
* @param {string[]} [keyArr] limit deployment to given metadata keys
* @returns {Promise.<Object.<string, MultiMetadataTypeMap>>} deployed metadata per BU (first key: bu name, second key: metadata type)
*/
Expand All @@ -73,25 +74,45 @@ class Deployer {
if (Util.OPTIONS.fromRetrieve) {
properties.directories.deploy = properties.directories.retrieve;
}
if (Array.isArray(selectedTypesArr)) {
// types and keys can be provided but for each type all provided keys are applied as filter
for (const selectedType of selectedTypesArr) {
if (selectedTypesArr) {
for (const selectedType of Array.isArray(selectedTypesArr)
? selectedTypesArr
: Object.keys(selectedTypesArr)) {
if (!Util._isValidType(selectedType)) {
return;
}
}
}
if (
Util.OPTIONS.fromRetrieve &&
(!selectedTypesArr ||
!Array.isArray(selectedTypesArr) ||
!selectedTypesArr.length ||
!keyArr ||
!Array.isArray(keyArr) ||
!keyArr.length)
) {
Util.logger.error('type & key need to be defined to deploy from retrieve folder');
return;
if (Util.OPTIONS.fromRetrieve) {
// check if either type & key or typeKeyCombo including keys was supplied
// we dont want to allow deploying without key from the retrieve dir for safety reasons
let keysFound = false;
if (
Array.isArray(selectedTypesArr) &&
selectedTypesArr.length &&
Array.isArray(keyArr) &&
keyArr.length
) {
// check legacy way of passing in type(s) and key(s)
keysFound = true;
} else if (
selectedTypesArr &&
!Array.isArray(selectedTypesArr) &&
Object.values(selectedTypesArr).length
) {
// TypeKeyCombo - a single null value (== no keys for one type) should lead to the error
keysFound = true;
for (const keys of Object.values(selectedTypesArr)) {
if (keys === null) {
keysFound = false;
break;
}
}
}
if (!keysFound) {
Util.logger.error('type & key need to be defined to deploy from retrieve folder');
return;
}
}
let counter_credBu = 0;
if (businessUnit === '*') {
Expand Down Expand Up @@ -201,7 +222,7 @@ class Deployer {
* @param {string} cred name of Credential
* @param {string} bu name of BU
* @param {Mcdevrc} properties General configuration to be used in retrieve
* @param {string[]} [typeArr] limit deployment to given metadata type
* @param {string[] | TypeKeyCombo} [typeArr] limit deployment to given metadata type
* @param {string[]} [keyArr] limit deployment to given metadata keys
* @returns {Promise.<MultiMetadataTypeMap>} ensure that BUs are worked on sequentially
*/
Expand All @@ -227,20 +248,32 @@ class Deployer {
/**
* Deploy all metadata that is located in the deployDir
*
* @param {string[]} [typeArr] limit deployment to given metadata type (can include subtype)
* @param {string[] | TypeKeyCombo} [types] limit deployment to given metadata type (can include subtype)
* @param {string[]} [keyArr] limit deployment to given metadata keys
* @returns {Promise.<MultiMetadataTypeMap>} Promise of all deployed metadata
*/
async _deploy(typeArr, keyArr) {
async _deploy(types, keyArr) {
const typeArr = !types || Array.isArray(types) ? types : Object.keys(types);
const typeKeyCombo = Array.isArray(types)
? Util.createTypeKeyCombo(typeArr, keyArr, true)
: types;
if (await File.pathExists(this.deployDir)) {
/** @type {MultiMetadataTypeMap} */
this.metadata = Deployer.readBUMetadata(this.deployDir, typeArr);

// filter found metadata by key if given
if (typeArr && Array.isArray(keyArr)) {
if (typeArr && Array.isArray(typeArr)) {
for (const selectedType of typeArr) {
const type = selectedType.split('-')[0];
this.metadata[type] = Util.filterObjByKeys(this.metadata[type], keyArr);
this.metadata[type] = Util.filterObjByKeys(
this.metadata[type],
typeKeyCombo[selectedType]
);
if (!Object.keys(this.metadata[type]).length) {
Util.logger.warn(
`No deployable metadata found for type ${type} for selected keys`
);
delete this.metadata[type];
}
}
}
} else {
Expand Down
19 changes: 4 additions & 15 deletions lib/Retriever.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,11 @@ class Retriever {
* @type {MultiMetadataTypeList}
*/
const retrieveChangelog = {};
if (!namesOrKeys || (Array.isArray(namesOrKeys) && !namesOrKeys.length)) {
// no keys were provided, ensure we retrieve all
namesOrKeys = [null];
}
/** @type {TypeKeyCombo} */
let typeKeyMap = {};
if (Array.isArray(namesOrKeys)) {
// no keys or array of keys was provided (likely called via CLI or to retrieve all)
// transform into TypeKeyCombo to iterate over it
for (const type of metadataTypes) {
typeKeyMap[type] = namesOrKeys;
}
} else {
// assuming TypeKeyCombo was provided
typeKeyMap = namesOrKeys;
}
const typeKeyMap =
!namesOrKeys || Array.isArray(namesOrKeys)
? Util.createTypeKeyCombo(metadataTypes, namesOrKeys)
: namesOrKeys;
// ensure we know which real dependencies we have to ensure we cache those completely
const dependencies = this._getTypeDependencies(metadataTypes);
const deployOrder = Util.getMetadataHierachy(metadataTypes);
Expand Down
110 changes: 21 additions & 89 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,23 @@ yargs(hideBin(process.argv))
type: 'string',
describe: 'metadata keys that shall be exclusively downloaded',
})
.option('like', {
.option('metadata', {
type: 'string',
alias: 'm',
group: 'Options for retrieve:',
describe:
'filter metadata components (can include % as wildcard or _ for a single character)',
'type or type:key or type:i:id or type:n:name to retrieve; if not provided, all metadata will be retrieved',
})
.option('metadata', {
.option('like', {
type: 'string',
alias: 'm',
group: 'Options for retrieve:',
describe:
'type or type:key or type:i:id or type:n:name to retrieve; if not provided, all metadata will be retrieved',
'filter metadata components (can include % as wildcard or _ for a single character)',
});
},
handler: (argv) => {
Mcdev.setOptions(argv);
const typeKeyCombo = metadataToTypeKey(argv.metadata);
const typeKeyCombo = Mcdev.metadataToTypeKey(argv.metadata);
if ('undefined' === typeof typeKeyCombo) {
Mcdev.retrieve(argv.BU, csvToArray(argv.TYPE), csvToArray(argv.KEY));
} else {
Expand Down Expand Up @@ -80,6 +80,13 @@ yargs(hideBin(process.argv))
type: 'string',
describe: 'metadata key that shall be exclusively uploaded',
})
.option('metadata', {
type: 'string',
alias: 'm',
group: 'Options for deploy:',
describe:
'type or type:key or type:i:id or type:n:name to deploy; if not provided, all metadata will be deploy',
})
.option('changeKeyField', {
type: 'string',
group: 'Options for deploy:',
Expand Down Expand Up @@ -125,7 +132,12 @@ yargs(hideBin(process.argv))
handler: (argv) => {
Mcdev.setOptions(argv);

Mcdev.deploy(argv.BU, csvToArray(argv.TYPE), csvToArray(argv.KEY));
const typeKeyCombo = Mcdev.metadataToTypeKey(argv.metadata);
if ('undefined' === typeof typeKeyCombo) {
Mcdev.deploy(argv.BU, csvToArray(argv.TYPE), csvToArray(argv.KEY));
} else {
Mcdev.deploy(argv.BU, typeKeyCombo);
}
},
})
// @ts-expect-error
Expand Down Expand Up @@ -236,7 +248,7 @@ yargs(hideBin(process.argv))
},
handler: (argv) => {
Mcdev.setOptions(argv);
const typeKeyCombo = metadataToTypeKey(argv.metadata, ['key', 'id'], true);
const typeKeyCombo = Mcdev.metadataToTypeKey(argv.metadata, ['key', 'id'], true);
if ('undefined' === typeof typeKeyCombo) {
if (argv.TYPE && argv.KEY) {
Mcdev.deleteByKey(argv.BU, argv.TYPE, argv.KEY);
Expand Down Expand Up @@ -269,7 +281,7 @@ yargs(hideBin(process.argv))
})
.option('json', {
type: 'boolean',
group: 'Options for explainTypes:',
group: 'Options for resolveId:',
describe: 'optionaly return info in json format',
});
// TODO: add option --metadata
Expand Down Expand Up @@ -723,83 +735,3 @@ function csvToArray(csv) {
.filter(Boolean)
: [csv.trim()].filter(Boolean);
}
/**
* helper to convert CSVs into an array. if only one value was given, it's also returned as an array
*
* @param {string|string[]} metadataOption potentially comma-separated value or null
* @param {string[]} [allowedIdentifiers] 'key', 'id', 'name'
* @param {boolean} [firstOnly] removes all but the first entry if enabled
* @returns {void|TypeKeyCombo} values split into an array.
*/
function metadataToTypeKey(
metadataOption,
allowedIdentifiers = ['key', 'id', 'name'],
firstOnly = false
) {
if (!metadataOption) {
return;
} else if (!Array.isArray(metadataOption)) {
metadataOption = [metadataOption];
}
if (firstOnly) {
// delete everything but the first entry
metadataOption.length = 1;
}
const metadataOptionMap = metadataOption.map((item) => {
const itemArr = item.split(':');
switch (itemArr.length) {
case 1: {
return { type: itemArr[0] };
}
case 2: {
if (allowedIdentifiers.includes('key')) {
return { type: itemArr[0], key: itemArr[1] };
}
break;
}
case 3: {
switch (itemArr[1]) {
case 'key':
case 'k': {
if (allowedIdentifiers.includes('key')) {
return { type: itemArr[0], key: itemArr[2] };
}
break;
}
case 'id':
case 'i': {
if (allowedIdentifiers.includes('id')) {
return { type: itemArr[0], id: itemArr[2] };
}
break;
}
case 'name':
case 'n': {
if (allowedIdentifiers.includes('name')) {
return { type: itemArr[0], name: itemArr[2] };
}
}
}
}
}
});
const response = {};
for (const item of metadataOptionMap) {
if (item) {
if (item.key || item.id || item.name) {
if (!response[item.type]) {
response[item.type] = [];
}
response[item.type].push(
item.key || (item.id ? 'id:' + item.id : item.name ? 'name:' + item.name : null)
);
} else {
if (!response[item.type]) {
response[item.type] = null;
}
}
}
}

return Object.keys(response).length >= 1 ? response : undefined;
}
Loading
Loading