From 2d761a4ad5713baf1c540569f4f9799adedd6449 Mon Sep 17 00:00:00 2001 From: jvallexm Date: Wed, 28 Feb 2024 12:15:31 -0500 Subject: [PATCH 1/3] Issue 1585: secrets manager plan (#1587) * feat: power vs v2 enhancements * json to iac * feat: secrets manager plan * feat: secrets manager * feat: secrets manager * feat: secrets manager * mv: schema functions * mv: invalid tag list * rm: tgw vpc filter * mv: networking rules --- CHANGELOG.md | 12 + client/package-lock.json | 4 +- client/package.json | 2 +- client/src/components/pages/CraigForms.js | 3 + client/src/lib/docs/release-notes.json | 6 + .../lib/forms/dynamic-form-fields/select.js | 2 +- client/src/lib/forms/filters.js | 28 +- client/src/lib/forms/index.js | 2 - client/src/lib/forms/invalid-callbacks.js | 19 - client/src/lib/index.js | 2 - client/src/lib/json-to-iac/secrets-manager.js | 2 +- client/src/lib/state/clusters.js | 3 +- client/src/lib/state/index.js | 2 - client/src/lib/state/options.js | 4 +- client/src/lib/state/reusable-fields.js | 269 +++++++ client/src/lib/state/secrets-manager.js | 29 +- client/src/lib/state/security-groups.js | 12 +- client/src/lib/state/utils.js | 329 +------- client/src/lib/state/vpc/vpc.js | 12 +- package-lock.json | 4 +- package.json | 2 +- .../disable-save/secrets-manager.test.js | 59 -- unit-tests/forms/filters.test.js | 26 - unit-tests/forms/invalid-callbacks.test.js | 9 - unit-tests/forms/wizard.test.js | 24 +- .../json-to-iac/secrets-manager.test.js | 73 ++ unit-tests/state/power-affinity.test.js | 754 ------------------ unit-tests/state/power-vs-instances.test.js | 752 +++++++++++++++++ unit-tests/state/reusable-fields.test.js | 152 ++++ unit-tests/state/secrets-manager.test.js | 92 +++ unit-tests/state/utils.test.js | 145 +--- 31 files changed, 1474 insertions(+), 1360 deletions(-) create mode 100644 client/src/lib/state/reusable-fields.js delete mode 100644 unit-tests/forms/disable-save/secrets-manager.test.js delete mode 100644 unit-tests/forms/filters.test.js delete mode 100644 unit-tests/state/power-affinity.test.js create mode 100644 unit-tests/state/reusable-fields.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f254e18..0265e717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. +## 1.12.1 + +### Upgrade Notes + + +### Features + +- Users can now use the `Trial` plan for secrets manager + +### Fixes + + ## 1.12.0 ### Upgrade Notes diff --git a/client/package-lock.json b/client/package-lock.json index 2fe9f596..1c1f469f 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "craig", - "version": "1.12.0", + "version": "1.12.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "craig", - "version": "1.12.0", + "version": "1.12.1", "license": "Apache-2.0", "dependencies": { "@apollo/client": "^3.4.10", diff --git a/client/package.json b/client/package.json index 9c1e40e6..6c879880 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "craig", - "version": "1.12.0", + "version": "1.12.1", "private": true, "license": "Apache-2.0", "scripts": { diff --git a/client/src/components/pages/CraigForms.js b/client/src/components/pages/CraigForms.js index db719d18..c98ef3c5 100644 --- a/client/src/components/pages/CraigForms.js +++ b/client/src/components/pages/CraigForms.js @@ -1148,6 +1148,9 @@ function craigForms(craig) { { name: craig.secrets_manager.name, resource_group: craig.secrets_manager.resource_group, + }, + { + plan: craig.secrets_manager.plan, encryption_key: craig.secrets_manager.encryption_key, }, ], diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index ae386083..08421b3b 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -1,4 +1,10 @@ [ + { + "version": "1.12.1", + "features": ["Users can now use the `Trial` plan for secrets manager"], + "fixes": [], + "upgrade_notes": [] + }, { "version": "1.12.0", "features": [ diff --git a/client/src/lib/forms/dynamic-form-fields/select.js b/client/src/lib/forms/dynamic-form-fields/select.js index 48f4b83b..69e8da2c 100644 --- a/client/src/lib/forms/dynamic-form-fields/select.js +++ b/client/src/lib/forms/dynamic-form-fields/select.js @@ -62,7 +62,7 @@ function dynamicSelectProps(props, isMounted, stateData) { // state value let stateValue = props.field.onRender - ? props.field.onRender(props.parentState) + ? props.field.onRender(props.parentState, props.parentProps) : props.parentState[props.name]; let groups = groupsEvaluatesToArrayCheck( diff --git a/client/src/lib/forms/filters.js b/client/src/lib/forms/filters.js index c3d3a10b..49dc6288 100644 --- a/client/src/lib/forms/filters.js +++ b/client/src/lib/forms/filters.js @@ -1,4 +1,4 @@ -const { distinct, contains, isEmpty } = require("lazy-z"); +const { contains, isEmpty } = require("lazy-z"); /** * Filters docs obj to render defaults for specific template only. @@ -40,28 +40,4 @@ function filterDocs(template, field, docs) { return doc; } -/* - * filter vpcs with connections to extant tgws - * @param {*} craig - * @returns {Array} list of vpcs not currently - */ -function tgwVpcFilter(craig) { - let unconnectedVpcs = []; - let allTgwVpcs = []; - craig.store.json.transit_gateways.forEach((tgw) => { - let connectionVpcs = []; - tgw.connections.forEach((connection) => { - if (connection.vpc) { - // not using splat to avoid picking up `null` for crn connections - connectionVpcs.push(connection.vpc); - } - }); - allTgwVpcs = distinct(allTgwVpcs.concat(connectionVpcs)); - }); - craig.store.vpcList.forEach((vpc) => { - if (!contains(allTgwVpcs, vpc)) unconnectedVpcs.push(vpc); - }); - return unconnectedVpcs; -} - -module.exports = { filterDocs, tgwVpcFilter }; +module.exports = { filterDocs }; diff --git a/client/src/lib/forms/index.js b/client/src/lib/forms/index.js index 55acdd6b..37eade38 100644 --- a/client/src/lib/forms/index.js +++ b/client/src/lib/forms/index.js @@ -9,7 +9,6 @@ const { const { invalidName, validSshKey, - invalidTagList, invalidCrnList, invalidSubnetTierName, invalidNewResourceName, @@ -62,7 +61,6 @@ module.exports = { disableSave, invalidNameText, validSshKey, - invalidTagList, invalidSubnetTierName, invalidSubnetTierText, formatConfig, diff --git a/client/src/lib/forms/invalid-callbacks.js b/client/src/lib/forms/invalid-callbacks.js index 4c885000..fa2eef22 100644 --- a/client/src/lib/forms/invalid-callbacks.js +++ b/client/src/lib/forms/invalid-callbacks.js @@ -1,6 +1,4 @@ const { - splat, - getObjectFromArray, isNullOrEmptyString, contains, containsKeys, @@ -32,22 +30,6 @@ function invalidNewResourceName(str) { return str ? str.match(newResourceNameExp) === null : true; } -/** - * invalid tags - * @param {Array} tags list of tags - * @returns {boolean} true if any tags in list are invalid - */ -function invalidTagList(tags) { - if (!tags || tags.length === 0) return false; - let invalid = false; - tags.forEach((tag) => { - if (tag.match(newResourceNameExp) === null || tag.length > 128) { - invalid = true; - } - }); - return invalid; -} - /** * create invalid bool for resource * @param {string} field json field name @@ -492,7 +474,6 @@ function invalidCrns(stateData) { module.exports = { invalidName, invalidNewResourceName, - invalidTagList, invalidCrnList, validSshKey, invalidSubnetTierName, diff --git a/client/src/lib/index.js b/client/src/lib/index.js index 8062f4e4..ac9e6dc4 100644 --- a/client/src/lib/index.js +++ b/client/src/lib/index.js @@ -6,7 +6,6 @@ const { disableSave, invalidNameText, validSshKey, - invalidTagList, invalidSubnetTierName, invalidSubnetTierText, formatConfig, @@ -194,7 +193,6 @@ module.exports = { disableSave, invalidNameText, validSshKey, - invalidTagList, invalidSubnetTierName, invalidSubnetTierText, formatConfig, diff --git a/client/src/lib/json-to-iac/secrets-manager.js b/client/src/lib/json-to-iac/secrets-manager.js index 0a0e38de..35433c70 100644 --- a/client/src/lib/json-to-iac/secrets-manager.js +++ b/client/src/lib/json-to-iac/secrets-manager.js @@ -115,7 +115,7 @@ function ibmResourceInstanceSecretsManager(secretsManager, config) { : { name: kebabName([secretsManager.name]), location: varDotRegion, - plan: "standard", + plan: secretsManager.plan ? secretsManager.plan : "standard", service: "secrets-manager", resource_group_id: rgIdRef(secretsManager.resource_group, config), parameters: { diff --git a/client/src/lib/state/clusters.js b/client/src/lib/state/clusters.js index a0c68481..62a59f1a 100644 --- a/client/src/lib/state/clusters.js +++ b/client/src/lib/state/clusters.js @@ -30,8 +30,9 @@ const { encryptionKeyGroups, onArrayInputChange, fieldIsNotWholeNumber, + invalidTagList, } = require("./utils"); -const { invalidName, invalidNameText, invalidTagList } = require("../forms"); +const { invalidName, invalidNameText } = require("../forms"); const { invalidDescription } = require("../forms/invalid-callbacks"); /** diff --git a/client/src/lib/state/index.js b/client/src/lib/state/index.js index 29095f32..941789ab 100644 --- a/client/src/lib/state/index.js +++ b/client/src/lib/state/index.js @@ -1,7 +1,5 @@ const state = require("./state"); -const { storageChangeDisabledCallback } = require("./utils"); module.exports = { state, - storageChangeDisabledCallback, }; diff --git a/client/src/lib/state/options.js b/client/src/lib/state/options.js index ad00f182..43167929 100644 --- a/client/src/lib/state/options.js +++ b/client/src/lib/state/options.js @@ -5,11 +5,10 @@ const { buildNumberDropdownList, titleCase, contains, - isNullOrEmptyString, } = require("lazy-z"); const { subnetTierSave } = require("./vpc/vpc"); const { RegexButWithWords } = require("regex-but-with-words"); -const { invalidNewResourceName, invalidTagList } = require("../forms"); +const { invalidNewResourceName } = require("../forms"); const releaseNotes = require("../docs/release-notes.json"); const { shouldDisableComponentSave, @@ -18,6 +17,7 @@ const { selectInvalidText, kebabCaseInput, onArrayInputChange, + invalidTagList, } = require("./utils"); const powerVsZones = [ diff --git a/client/src/lib/state/reusable-fields.js b/client/src/lib/state/reusable-fields.js new file mode 100644 index 00000000..72fbfa05 --- /dev/null +++ b/client/src/lib/state/reusable-fields.js @@ -0,0 +1,269 @@ +const { contains, allFieldsNull, validPortRange } = require("lazy-z"); +const { + fieldIsNullOrEmptyString, + selectInvalidText, + unconditionalInvalidText, +} = require("./utils"); + +/** + * invalid icmp code or type callback + * @param {*} stateData + * @returns {boolean} true if invalid + */ +function invalidIcmpCodeOrType(stateData) { + return contains(["all", "tcp", "udp"], stateData.ruleProtocol) + ? false + : invalidPort(stateData); +} + +/** + * hide when rule has protocol is not tcp or udp + * @param {*} stateData + * @returns {boolean} true if should be hidden + */ +function hideWhenNotAllIcmp(stateData) { + return stateData.ruleProtocol === "all" + ? true + : stateData.ruleProtocol === "icmp"; +} + +/** + * hide when rule has protocol is tcp or udp + * @param {*} stateData + * @returns {boolean} true if should be hidden + */ +function hideWhenTcpOrUdp(stateData) { + return stateData.ruleProtocol === "all" + ? true + : contains(["tcp", "udp"], stateData.ruleProtocol); +} + +/** + * test if a rule has an invalid port + * @param {*} rule + * @param {boolean=} isSecurityGroup + * @returns {boolean} true if port is invalid + */ +function invalidPort(rule, isSecurityGroup) { + let hasInvalidPort = false; + if (rule.ruleProtocol !== "all") { + (rule.ruleProtocol === "icmp" + ? ["type", "code"] + : isSecurityGroup + ? ["port_min", "port_max"] + : ["port_min", "port_max", "source_port_min", "source_port_max"] + ).forEach((type) => { + // check for rule[type] for craig form rule[rule] + let value = rule.rule ? rule.rule[type] : rule[type]; + if (value && !hasInvalidPort) { + hasInvalidPort = + value.match && value.match(/\D/g) !== null + ? true + : !validPortRange(type, value); + } + }); + } + return hasInvalidPort; +} + +/** + * invalid tcp or udp callback function + * @param {*} stateData + * @returns {boolean} true if invalid + */ +function invalidTcpOrUdpPort(stateData) { + return contains(["all", "icmp"], stateData.ruleProtocol) + ? false + : invalidPort(stateData); +} + +/** + * get which rule protocol is being used + * @param {string} rule + * @returns {string} protocol + */ +function getRuleProtocol(rule) { + let protocol = "all"; + // for each possible protocol + ["icmp", "tcp", "udp"].forEach((field) => { + // set protocol to that field if not all fields are null + if (rule[field] && allFieldsNull(rule[field]) === false) { + protocol = field; + } + }); + return protocol; +} + +/** + * handle input change for rule field + * @param {*} stateData + */ +function onRuleFieldInputChange(field) { + return function (stateData) { + if (!stateData.rule) { + stateData.rule = { + port_max: null, + port_min: null, + source_port_max: null, + source_port_min: null, + type: null, + code: null, + }; + } + stateData.rule[field] = stateData[field]; + return stateData[field]; + }; +} + +/** + * networking rule protocol field + * @returns {Object} form field object + */ +function networkingRuleProtocolField(isAcl) { + return { + size: "small", + type: "select", + default: "", + groups: ["All", "TCP", "UDP", "ICMP"], + invalid: fieldIsNullOrEmptyString("ruleProtocol"), + invalidText: selectInvalidText("protocol"), + onInputChange: function (stateData) { + stateData.rule = { + port_max: null, + port_min: null, + source_port_max: null, + source_port_min: null, + type: null, + code: null, + }; + stateData.tcp = { + port_min: null, + port_max: null, + }; + stateData.udp = { + port_min: null, + port_max: null, + }; + stateData.icmp = { + type: null, + code: null, + }; + if (isAcl) { + stateData.tcp.source_port_max = null; + stateData.tcp.source_port_min = null; + stateData.udp.source_port_max = null; + stateData.udp.source_port_min = null; + } + return stateData.ruleProtocol.toLowerCase(); + }, + onRender: function (stateData) { + return contains(["icmp", "udp", "tcp", "all"], stateData.ruleProtocol) + ? stateData.ruleProtocol.toUpperCase() + : ""; + }, + }; +} + +/** + * build networking form port field + * @param {boolean} max true if port max + * @param {boolean} source true if source port + * @returns {Object} form field object + */ +function networkingRulePortField(max, source) { + return { + default: "", + invalid: invalidTcpOrUdpPort, + size: "small", + invalidText: unconditionalInvalidText( + "Enter a whole number between 1 and 65536" + ), + hideWhen: hideWhenNotAllIcmp, + onInputChange: onRuleFieldInputChange( + max && source + ? "source_port_max" + : source + ? "source_port_min" + : max + ? "port_max" + : "port_min" + ), + helperText: unconditionalInvalidText(""), + onRender: function (stateData) { + let ruleType = getRuleProtocol(stateData); + let ruleField = + max && source + ? "source_port_max" + : source + ? "source_port_min" + : max + ? "port_max" + : "port_min"; + if ( + stateData[ruleType] && + stateData[ruleType][ruleField] && + !stateData[ruleField] + ) { + return stateData[ruleType][ruleField]; + } else return stateData[ruleField]; + }, + }; +} + +/** + * build networking form type field + * @returns {Object} form field object + */ +function networkingRuleTypeField() { + return { + size: "small", + default: "", + invalid: invalidIcmpCodeOrType, + invalidText: unconditionalInvalidText( + "Enter a while number between 0 and 254" + ), + hideWhen: hideWhenTcpOrUdp, + onInputChange: onRuleFieldInputChange("type"), + helperText: unconditionalInvalidText(""), + onRender: function (stateData) { + if (stateData.icmp && stateData.icmp.type && !stateData.type) { + return stateData.icmp.type === "null" ? "" : stateData.icmp.type; + } else return stateData.type; + }, + }; +} + +/** + * build networking form code field + * @returns {Object} form field object + */ +function networkingRuleCodeField() { + return { + size: "small", + default: "", + invalid: invalidIcmpCodeOrType, + invalidText: unconditionalInvalidText( + "Enter a while number between 0 and 255" + ), + hideWhen: hideWhenTcpOrUdp, + onInputChange: onRuleFieldInputChange("code"), + helperText: unconditionalInvalidText(""), + onRender: function (stateData) { + if (stateData.icmp && stateData.icmp.code && !stateData.code) { + return stateData.icmp.code === "null" ? "" : stateData.icmp.code; + } else return stateData.code; + }, + }; +} + +module.exports = { + networkingRuleProtocolField, + networkingRulePortField, + networkingRuleTypeField, + networkingRuleCodeField, + getRuleProtocol, + hideWhenTcpOrUdp, + hideWhenNotAllIcmp, + onRuleFieldInputChange, + invalidPort, +}; diff --git a/client/src/lib/state/secrets-manager.js b/client/src/lib/state/secrets-manager.js index e5c20ec7..fc253b10 100644 --- a/client/src/lib/state/secrets-manager.js +++ b/client/src/lib/state/secrets-manager.js @@ -8,6 +8,8 @@ const { encryptionKeyGroups, hideWhenUseData, selectInvalidText, + kebabCaseInput, + titleCaseRender, } = require("./utils"); /** @@ -94,15 +96,36 @@ function initSecretsManagerStore(store) { default: "", invalid: invalidName("secrets_manager"), invalidText: invalidNameText("secrets_manager"), - size: "small", }, - resource_group: resourceGroupsField(true), + resource_group: resourceGroupsField(), + plan: { + type: "select", + default: "standard", + invalid: fieldIsNullOrEmptyString("plan"), + invalidText: selectInvalidText("plan"), + hideWhen: hideWhenUseData, + groups: ["Standard", "Trial"], + onRender: function (stateData, componentProps) { + if (!componentProps?.data?.plan) { + // add to plan to component props prevent button from highlighting + // when no plan is selected + if (componentProps.data) componentProps.data.plan = stateData.plan; + } + return titleCaseRender("plan")(stateData); + }, + onInputChange: kebabCaseInput("plan"), + tooltip: { + content: + "You can have one Trial instance provisioned in your account at any time. After your 30 day trial expires, functionality is removed but your instance remains available to upgrade for an additional 30 days.", + align: "right", + alignModal: "right", + }, + }, encryption_key: { type: "select", default: "", invalid: fieldIsNullOrEmptyString("encryption_key"), invalidText: selectInvalidText("encryption key"), - size: "small", groups: encryptionKeyGroups, hideWhen: hideWhenUseData, }, diff --git a/client/src/lib/state/security-groups.js b/client/src/lib/state/security-groups.js index af7e2213..683dfa9c 100644 --- a/client/src/lib/state/security-groups.js +++ b/client/src/lib/state/security-groups.js @@ -26,11 +26,6 @@ const { unconditionalInvalidText, kebabCaseInput, titleCaseRender, - networkingRuleProtocolField, - networkingRulePortField, - networkingRuleTypeField, - networkingRuleCodeField, - getRuleProtocol, } = require("./utils"); const { invalidName, @@ -41,6 +36,13 @@ const { duplicateNameCallback, genericNameCallback, } = require("../forms/text-callbacks"); +const { + networkingRuleProtocolField, + networkingRulePortField, + networkingRuleTypeField, + networkingRuleCodeField, + getRuleProtocol, +} = require("./reusable-fields"); /** * intialize security groups diff --git a/client/src/lib/state/utils.js b/client/src/lib/state/utils.js index accdc72d..8d5a3677 100644 --- a/client/src/lib/state/utils.js +++ b/client/src/lib/state/utils.js @@ -2,7 +2,6 @@ const { eachKey, transpose, contains, - allFieldsNull, splatContains, arraySplatIndex, carve, @@ -10,7 +9,6 @@ const { isNullOrEmptyString, isEmpty, splat, - validPortRange, isIpv4CidrOrAddress, isWholeNumber, titleCase, @@ -21,37 +19,10 @@ const { capitalize, containsKeys, } = require("lazy-z"); -const { commaSeparatedIpListExp } = require("../constants"); +const { commaSeparatedIpListExp, newResourceNameExp } = require("../constants"); const { invalidName, validSshKey } = require("../forms/invalid-callbacks"); const { invalidNameText } = require("../forms/text-callbacks"); -/** - * check to see if storage for a power vs instance or volume should be disabled - * @param {*} stateData - * @param {*} componentProps - * @returns {boolean} true if disabled - */ -function storageChangeDisabledCallback(stateData, componentProps) { - if (componentProps.isModal) return false; - let isInUse = false; - ["power_instances", "power_volumes"].forEach((field) => { - (componentProps[field] - ? componentProps[field] - : // store is for refactored items - componentProps.craig.store.json[field] - ).forEach((item) => { - // get test items, for instance tests check for network field - let testItems = containsKeys(stateData, "network") - ? [item.pi_anti_affinity_instance, item.pi_affinity_instance] - : [item.pi_affinity_volume, item.pi_anti_affinity_volume]; - if (contains(testItems, componentProps.data.name)) { - isInUse = true; - } - }); - }); - return isInUse; -} - /** * set kms from encryption key on store update * @param {*} instance @@ -519,34 +490,6 @@ function resourceGroupsField(small, options) { }; } -/** - * test if a rule has an invalid port - * @param {*} rule - * @param {boolean=} isSecurityGroup - * @returns {boolean} true if port is invalid - */ -function invalidPort(rule, isSecurityGroup) { - let hasInvalidPort = false; - if (rule.ruleProtocol !== "all") { - (rule.ruleProtocol === "icmp" - ? ["type", "code"] - : isSecurityGroup - ? ["port_min", "port_max"] - : ["port_min", "port_max", "source_port_min", "source_port_max"] - ).forEach((type) => { - // check for rule[type] for craig form rule[rule] - let value = rule.rule ? rule.rule[type] : rule[type]; - if (value && !hasInvalidPort) { - hasInvalidPort = - value.match && value.match(/\D/g) !== null - ? true - : !validPortRange(type, value); - } - }); - } - return hasInvalidPort; -} - /** * test for invalid range * @param {*} value @@ -563,28 +506,6 @@ function isRangeInvalid(value, min, max) { return false; } -/** - * invalid tcp or udp callback function - * @param {*} stateData - * @returns {boolean} true if invalid - */ -function invalidTcpOrUdpPort(stateData) { - return contains(["all", "icmp"], stateData.ruleProtocol) - ? false - : invalidPort(stateData); -} - -/** - * invalid icmp code or type callback - * @param {*} stateData - * @returns {boolean} true if invalid - */ -function invalidIcmpCodeOrType(stateData) { - return contains(["all", "tcp", "udp"], stateData.ruleProtocol) - ? false - : invalidPort(stateData); -} - /** * return validation function if value is not a valid IPV4 IP address * @param {*} field @@ -1028,7 +949,35 @@ function powerVsStorageOptions(isVolume) { default: "", type: "select", groups: ["None", "Storage Pool", "Affinity", "Anti-Affinity"], - disabled: storageChangeDisabledCallback, + /** + * check to see if storage for a power vs instance or volume should be disabled + * @param {*} stateData + * @param {*} componentProps + * @returns {boolean} true if disabled + */ + disabled: function storageChangeDisabledCallback( + stateData, + componentProps + ) { + if (componentProps.isModal) return false; + let isInUse = false; + ["power_instances", "power_volumes"].forEach((field) => { + (componentProps[field] + ? componentProps[field] + : // store is for refactored items + componentProps.craig.store.json[field] + ).forEach((item) => { + // get test items, for instance tests check for network field + let testItems = containsKeys(stateData, "network") + ? [item.pi_anti_affinity_instance, item.pi_affinity_instance] + : [item.pi_affinity_volume, item.pi_anti_affinity_volume]; + if (contains(testItems, componentProps.data.name)) { + isInUse = true; + } + }); + }); + return isInUse; + }, invalid: function (stateData) { return ( isNullOrEmptyString(stateData.storage_option, true) || @@ -1366,210 +1315,23 @@ function cbrSaveType(field) { } /** - * hide when rule has protocol is tcp or udp - * @param {*} stateData - * @returns {boolean} true if should be hidden - */ -function hideWhenTcpOrUdp(stateData) { - return stateData.ruleProtocol === "all" - ? true - : contains(["tcp", "udp"], stateData.ruleProtocol); -} - -/** - * hide when rule has protocol is not tcp or udp - * @param {*} stateData - * @returns {boolean} true if should be hidden - */ -function hideWhenNotAllIcmp(stateData) { - return stateData.ruleProtocol === "all" - ? true - : stateData.ruleProtocol === "icmp"; -} - -/** - * handle input change for rule field - * @param {*} stateData - */ -function onRuleFieldInputChange(field) { - return function (stateData) { - if (!stateData.rule) { - stateData.rule = { - port_max: null, - port_min: null, - source_port_max: null, - source_port_min: null, - type: null, - code: null, - }; - } - stateData.rule[field] = stateData[field]; - return stateData[field]; - }; -} - -/** - * networking rule protocol field - * @returns {Object} form field object - */ -function networkingRuleProtocolField(isAcl) { - return { - size: "small", - type: "select", - default: "", - groups: ["All", "TCP", "UDP", "ICMP"], - invalid: fieldIsNullOrEmptyString("ruleProtocol"), - invalidText: selectInvalidText("protocol"), - onInputChange: function (stateData) { - stateData.rule = { - port_max: null, - port_min: null, - source_port_max: null, - source_port_min: null, - type: null, - code: null, - }; - stateData.tcp = { - port_min: null, - port_max: null, - }; - stateData.udp = { - port_min: null, - port_max: null, - }; - stateData.icmp = { - type: null, - code: null, - }; - if (isAcl) { - stateData.tcp.source_port_max = null; - stateData.tcp.source_port_min = null; - stateData.udp.source_port_max = null; - stateData.udp.source_port_min = null; - } - return stateData.ruleProtocol.toLowerCase(); - }, - onRender: function (stateData) { - return contains(["icmp", "udp", "tcp", "all"], stateData.ruleProtocol) - ? stateData.ruleProtocol.toUpperCase() - : ""; - }, - }; -} - -/** - * build networking form port field - * @param {boolean} max true if port max - * @param {boolean} source true if source port - * @returns {Object} form field object - */ -function networkingRulePortField(max, source) { - return { - default: "", - invalid: invalidTcpOrUdpPort, - size: "small", - invalidText: unconditionalInvalidText( - "Enter a whole number between 1 and 65536" - ), - hideWhen: hideWhenNotAllIcmp, - onInputChange: onRuleFieldInputChange( - max && source - ? "source_port_max" - : source - ? "source_port_min" - : max - ? "port_max" - : "port_min" - ), - helperText: unconditionalInvalidText(""), - onRender: function (stateData) { - let ruleType = getRuleProtocol(stateData); - let ruleField = - max && source - ? "source_port_max" - : source - ? "source_port_min" - : max - ? "port_max" - : "port_min"; - if ( - stateData[ruleType] && - stateData[ruleType][ruleField] && - !stateData[ruleField] - ) { - return stateData[ruleType][ruleField]; - } else return stateData[ruleField]; - }, - }; -} - -/** - * build networking form type field - * @returns {Object} form field object - */ -function networkingRuleTypeField() { - return { - size: "small", - default: "", - invalid: invalidIcmpCodeOrType, - invalidText: unconditionalInvalidText( - "Enter a while number between 0 and 254" - ), - hideWhen: hideWhenTcpOrUdp, - onInputChange: onRuleFieldInputChange("type"), - helperText: unconditionalInvalidText(""), - onRender: function (stateData) { - if (stateData.icmp && stateData.icmp.type && !stateData.type) { - return stateData.icmp.type === "null" ? "" : stateData.icmp.type; - } else return stateData.type; - }, - }; -} - -/** - * build networking form code field - * @returns {Object} form field object - */ -function networkingRuleCodeField() { - return { - size: "small", - default: "", - invalid: invalidIcmpCodeOrType, - invalidText: unconditionalInvalidText( - "Enter a while number between 0 and 255" - ), - hideWhen: hideWhenTcpOrUdp, - onInputChange: onRuleFieldInputChange("code"), - helperText: unconditionalInvalidText(""), - onRender: function (stateData) { - if (stateData.icmp && stateData.icmp.code && !stateData.code) { - return stateData.icmp.code === "null" ? "" : stateData.icmp.code; - } else return stateData.code; - }, - }; -} - -/** - * get which rule protocol is being used - * @param {string} rule - * @returns {string} protocol + * invalid tags + * @param {Array} tags list of tags + * @returns {boolean} true if any tags in list are invalid */ -function getRuleProtocol(rule) { - let protocol = "all"; - // for each possible protocol - ["icmp", "tcp", "udp"].forEach((field) => { - // set protocol to that field if not all fields are null - if (rule[field] && allFieldsNull(rule[field]) === false) { - protocol = field; +function invalidTagList(tags) { + if (!tags || tags.length === 0) return false; + let invalid = false; + tags.forEach((tag) => { + if (tag.match(newResourceNameExp) === null || tag.length > 128) { + invalid = true; } }); - return protocol; + return invalid; } module.exports = { - onRuleFieldInputChange, - hideWhenNotAllIcmp, - hideWhenTcpOrUdp, + invalidTagList, hideHelperText, encryptionKeyGroups, invalidIpv4Address, @@ -1589,9 +1351,6 @@ module.exports = { selectInvalidText, resourceGroupsField, nameField, - invalidTcpOrUdpPort, - invalidIcmpCodeOrType, - invalidPort, wholeNumberField, wholeNumberText, titleCaseRender, @@ -1621,11 +1380,5 @@ module.exports = { cbrValuePlaceholder, cbrTitleCase, cbrSaveType, - storageChangeDisabledCallback, powerAffinityInvalid, - networkingRuleProtocolField, - networkingRulePortField, - networkingRuleTypeField, - networkingRuleCodeField, - getRuleProtocol, }; diff --git a/client/src/lib/state/vpc/vpc.js b/client/src/lib/state/vpc/vpc.js index 8c2ec3e2..d133abdf 100644 --- a/client/src/lib/state/vpc/vpc.js +++ b/client/src/lib/state/vpc/vpc.js @@ -40,11 +40,6 @@ const { unconditionalInvalidText, kebabCaseInput, titleCaseRender, - networkingRuleProtocolField, - networkingRulePortField, - networkingRuleTypeField, - networkingRuleCodeField, - getRuleProtocol, } = require("../utils"); const { calculateNeededSubnetIps, getNextCidr } = require("../../json-to-iac"); const { @@ -66,6 +61,13 @@ const { } = require("../../forms/text-callbacks"); const { vpcSchema } = require("./vpc-schema"); const { newSubnetTierSave } = require("./subnets"); +const { + networkingRuleProtocolField, + networkingRulePortField, + networkingRuleTypeField, + networkingRuleCodeField, + getRuleProtocol, +} = require("../reusable-fields"); /** * read only when diff --git a/package-lock.json b/package-lock.json index 38e0451b..00b9f391 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "craig", - "version": "1.12.0", + "version": "1.12.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "craig", - "version": "1.12.0", + "version": "1.12.1", "license": "ISC", "dependencies": { "axios": "^1.6.3", diff --git a/package.json b/package.json index 9c4cff2c..0c4e5d2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "craig", - "version": "1.12.0", + "version": "1.12.1", "description": "gui for generating ibm cloud infrastructure resources", "main": "index.js", "scripts": { diff --git a/unit-tests/forms/disable-save/secrets-manager.test.js b/unit-tests/forms/disable-save/secrets-manager.test.js deleted file mode 100644 index 14e3fda9..00000000 --- a/unit-tests/forms/disable-save/secrets-manager.test.js +++ /dev/null @@ -1,59 +0,0 @@ -const { assert } = require("chai"); -const { disableSave, state } = require("../../../client/src/lib"); - -describe("secrets manager", () => { - it("should return true if a secrets manager instance has an invalid name", () => { - assert.isTrue( - disableSave( - "secrets_manager", - { - name: "@@@", - resource_group: "managment-rg", - encryption_key: "key", - }, - { - craig: state(), - data: { - name: "frog", - }, - } - ), - "it should be true" - ); - }); - it("should return true if a secrets manager instance has an invalid resource group", () => { - assert.isTrue( - disableSave( - "secrets_manager", - { name: "frog", resource_group: null, use_data: false }, - { - craig: state(), - data: { - name: "test", - }, - } - ), - "it should be false" - ); - }); - it("should return true if a secrets manager instance has an invalid encryption key", () => { - assert.isTrue( - disableSave( - "secrets_manager", - { - name: "frog2", - resource_group: "management-rg", - encryption_key: null, - use_data: false, - }, - { - craig: state(), - data: { - name: "test", - }, - } - ), - "it should be false" - ); - }); -}); diff --git a/unit-tests/forms/filters.test.js b/unit-tests/forms/filters.test.js deleted file mode 100644 index edaddd27..00000000 --- a/unit-tests/forms/filters.test.js +++ /dev/null @@ -1,26 +0,0 @@ -const { state } = require("../../client/src/lib"); -const { tgwVpcFilter } = require("../../client/src/lib/forms/filters"); -const { assert } = require("chai"); -const craig = state(); -describe("filters", () => { - describe("tgwVpcFilter", () => { - it("should return list of vpcs not connected via transit gateway", () => { - let actualData = tgwVpcFilter(craig); - assert.deepEqual(actualData, [], "it should return empty array"); - }); - it("should return list of vpcs not connected via transit gateway", () => { - craig.setUpdateCallback(() => {}); - craig.vpcs.create( - { - name: "hi", - }, - {} - ); - craig.store.json.transit_gateways[0].connections.push({ - crn: "crn", - }); - let actualData = tgwVpcFilter(craig); - assert.deepEqual(actualData, ["hi"], "it should return unconnected vpcs"); - }); - }); -}); diff --git a/unit-tests/forms/invalid-callbacks.test.js b/unit-tests/forms/invalid-callbacks.test.js index 4e740d56..a6bef905 100644 --- a/unit-tests/forms/invalid-callbacks.test.js +++ b/unit-tests/forms/invalid-callbacks.test.js @@ -1,7 +1,6 @@ const { assert } = require("chai"); const { invalidName, - invalidTagList, invalidCrnList, invalidIpCommaList, isValidUrl, @@ -699,14 +698,6 @@ describe("invalid callbacks", () => { ); assert.isTrue(actualData, "it should be true"); }); - describe("invalidTagList", () => { - it("should return true when invalid tag list", () => { - assert.isTrue(invalidTagList(["hi", "2@@@2"])); - }); - it("should return false when no tags", () => { - assert.isFalse(invalidTagList([])); - }); - }); describe("invalidCrnList", () => { it("should return true when invalid crn in list", () => { assert.isTrue( diff --git a/unit-tests/forms/wizard.test.js b/unit-tests/forms/wizard.test.js index ce92f8c9..182884f2 100644 --- a/unit-tests/forms/wizard.test.js +++ b/unit-tests/forms/wizard.test.js @@ -57,7 +57,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.0", + craig_version: "1.12.1", power_vs_high_availability: false, }, resource_groups: [ @@ -971,7 +971,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.0", + craig_version: "1.12.1", power_vs_high_availability: false, }, resource_groups: [ @@ -1884,7 +1884,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.0", + craig_version: "1.12.1", power_vs_high_availability: false, }, resource_groups: [ @@ -2391,7 +2391,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.0", + craig_version: "1.12.1", power_vs_high_availability: false, }, resource_groups: [ @@ -2889,7 +2889,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.0", + craig_version: "1.12.1", power_vs_high_availability: false, }, resource_groups: [ @@ -3464,7 +3464,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.0", + craig_version: "1.12.1", }, resource_groups: [ { use_prefix: true, name: "service-rg", use_data: false }, @@ -3630,7 +3630,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.0", + craig_version: "1.12.1", power_vs_high_availability: false, }, resource_groups: [ @@ -4197,7 +4197,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.0", + craig_version: "1.12.1", power_vs_high_availability: false, }, resource_groups: [ @@ -4738,7 +4738,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.0", + craig_version: "1.12.1", power_vs_high_availability: false, }, resource_groups: [ @@ -5284,7 +5284,7 @@ describe("setup wizard", () => { enable_classic: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.0", + craig_version: "1.12.1", }, resource_groups: [ { use_prefix: true, name: "service-rg", use_data: false }, @@ -5772,7 +5772,7 @@ describe("setup wizard", () => { enable_power_vs: true, enable_classic: false, power_vs_zones: ["dal10"], - craig_version: "1.12.0", + craig_version: "1.12.1", power_vs_high_availability: false, }, resource_groups: [ @@ -6270,7 +6270,7 @@ describe("setup wizard", () => { enable_power_vs: true, enable_classic: false, power_vs_zones: ["dal10"], - craig_version: "1.12.0", + craig_version: "1.12.1", power_vs_high_availability: false, }, resource_groups: [ diff --git a/unit-tests/json-to-iac/secrets-manager.test.js b/unit-tests/json-to-iac/secrets-manager.test.js index bae8b791..559d305d 100644 --- a/unit-tests/json-to-iac/secrets-manager.test.js +++ b/unit-tests/json-to-iac/secrets-manager.test.js @@ -237,6 +237,79 @@ resource "ibm_resource_instance" "secrets_manager_secrets_manager" { ibm_iam_authorization_policy.secrets_manager_to_kms_kms_policy ] } +`; + assert.deepEqual( + actualData, + expectedData, + "it should return auth policy tf" + ); + }); + it("should return correct data for secrets manager with trial plan", () => { + let actualData = formatSecretsManagerInstance( + { + name: "secrets-manager", + resource_group: "slz-service-rg", + kms: "kms", + encryption_key: "key", + plan: "trial", + }, + { + _options: { + region: "us-south", + tags: ["hello", "world"], + prefix: "iac", + }, + resource_groups: [ + { + use_data: false, + name: "slz-service-rg", + }, + ], + key_management: [ + { + name: "kms", + service: "kms", + resource_group: "slz-service-rg", + authorize_vpc_reader_role: true, + use_data: false, + use_hs_crypto: false, + keys: [ + { + name: "key", + root_key: true, + key_ring: "test", + force_delete: true, + endpoint: "private", + rotation: 12, + dual_auth_delete: true, + }, + ], + }, + ], + } + ); + let expectedData = ` +resource "ibm_resource_instance" "secrets_manager_secrets_manager" { + name = "\${var.prefix}-secrets-manager" + location = var.region + plan = "trial" + service = "secrets-manager" + resource_group_id = ibm_resource_group.slz_service_rg.id + parameters = { + kms_key = ibm_kms_key.kms_key_key.crn + } + timeouts { + create = "1h" + delete = "1h" + } + tags = [ + "hello", + "world" + ] + depends_on = [ + ibm_iam_authorization_policy.secrets_manager_to_kms_kms_policy + ] +} `; assert.deepEqual( actualData, diff --git a/unit-tests/state/power-affinity.test.js b/unit-tests/state/power-affinity.test.js deleted file mode 100644 index 6fda7741..00000000 --- a/unit-tests/state/power-affinity.test.js +++ /dev/null @@ -1,754 +0,0 @@ -const { assert } = require("chai"); -const { - state, - storageChangeDisabledCallback, -} = require("../../client/src/lib/state"); - -describe("storageChangeDisabledCallback", () => { - it("should be false when is modal", () => { - assert.isFalse( - storageChangeDisabledCallback({}, { isModal: true }), - "it should be false for modal forms" - ); - }); - it("should be true for an instance when it is used by another instance for affinity", () => { - let actualData = storageChangeDisabledCallback( - { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - { - power_volumes: [], - data: { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - power_instances: [ - { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - { - name: "oracle-2", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: null, - ssh_key: "power-ssh", - pi_sys_type: "s922", - pi_processors: "2", - pi_memory: "32", - storage_option: "Affinity", - affinity_type: "Instance", - pi_storage_pool_affinity: true, - pi_storage_pool: null, - pi_anti_affinity_volume: null, - pi_anti_affinity_instance: null, - pi_affinity_policy: "affinity", - pi_affinity_instance: "oracle-1", - }, - ], - } - ); - assert.isTrue(actualData, "it should be true"); - }); - it("should be true for an instance when it is used by another instance for anti-affinity", () => { - let craig = state(); - let actualData = storageChangeDisabledCallback( - { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - { - craig: craig, - data: { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - power_instances: [ - { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - { - name: "oracle-2", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: null, - ssh_key: "power-ssh", - pi_sys_type: "s922", - pi_processors: "2", - pi_memory: "32", - storage_option: "Anti-Affinity", - affinity_type: "Instance", - pi_storage_pool_affinity: true, - pi_storage_pool: null, - pi_anti_affinity_volume: null, - pi_anti_affinity_instance: "oracle-1", - pi_affinity_policy: "anti-affinity", - pi_affinity_instance: null, - pi_affinity_volume: null, - }, - ], - } - ); - assert.isTrue(actualData, "it should be true"); - }); - it("should be true for an instance when it is used by a volume for affinity", () => { - let actualData = storageChangeDisabledCallback( - { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - { - data: { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - power_instances: [ - { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - ], - power_volumes: [ - { - name: "oracle-1-db-1", - workspace: "oracle-template", - pi_volume_shareable: false, - pi_replication_enabled: false, - pi_volume_type: null, - attachments: ["oracle-1"], - zone: "dal12", - pi_volume_size: "90", - storage_option: "Affinity", - affinity_type: "Instance", - pi_storage_pool: null, - pi_anti_affinity_volume: null, - pi_anti_affinity_instance: null, - pi_affinity_policy: "affinity", - pi_affinity_instance: "oracle-1", - }, - ], - } - ); - assert.isTrue(actualData, "it should be true"); - }); - it("should be true for an instance when it is used by a volume for anti-affinity", () => { - let actualData = storageChangeDisabledCallback( - { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - { - data: { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - power_instances: [ - { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: "tier1", - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Storage Type", - affinity_type: null, - pi_storage_pool_affinity: true, - }, - ], - power_volumes: [ - { - name: "oracle-1-db-1", - workspace: "oracle-template", - pi_volume_shareable: false, - pi_replication_enabled: false, - pi_volume_type: null, - attachments: ["oracle-1"], - zone: "dal12", - pi_volume_size: "90", - storage_option: "Affinity", - affinity_type: "Instance", - pi_storage_pool: null, - pi_anti_affinity_volume: null, - pi_affinity_instance: null, - pi_affinity_policy: "affinity", - pi_anti_affinity_instance: "oracle-1", - }, - ], - } - ); - assert.isTrue(actualData, "it should be true"); - }); - it("should be true for a volume when it is used by an instance for affinity", () => { - let actualData = storageChangeDisabledCallback( - { - name: "redo-1", - workspace: "oracle-template", - pi_volume_shareable: true, - pi_replication_enabled: false, - pi_volume_type: "tier1", - attachments: ["oracle-1", "oracle-2"], - zone: "dal12", - pi_volume_size: "50", - storage_option: "Storage Type", - affinity_type: null, - }, - { - power_volumes: [], - data: { - name: "redo-1", - workspace: "oracle-template", - pi_volume_shareable: true, - pi_replication_enabled: false, - pi_volume_type: "tier1", - attachments: ["oracle-1", "oracle-2"], - zone: "dal12", - pi_volume_size: "50", - storage_option: "Storage Type", - affinity_type: null, - }, - power_instances: [ - { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: null, - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Affinity", - affinity_type: "Volume", - pi_storage_pool_affinity: true, - pi_storage_pool: null, - pi_anti_affinity_volume: null, - pi_anti_affinity_instance: null, - pi_affinity_policy: "affinity", - pi_affinity_volume: "redo-1", - }, - ], - } - ); - assert.isTrue(actualData, "it should be true"); - }); - it("should be true for a volume when it is used by an instance for anti-affinity", () => { - let actualData = storageChangeDisabledCallback( - { - name: "redo-1", - workspace: "oracle-template", - pi_volume_shareable: true, - pi_replication_enabled: false, - pi_volume_type: "tier1", - attachments: ["oracle-1", "oracle-2"], - zone: "dal12", - pi_volume_size: "50", - storage_option: "Storage Type", - affinity_type: null, - }, - { - power_volumes: [], - data: { - name: "redo-1", - workspace: "oracle-template", - pi_volume_shareable: true, - pi_replication_enabled: false, - pi_volume_type: "tier1", - attachments: ["oracle-1", "oracle-2"], - zone: "dal12", - pi_volume_size: "50", - storage_option: "Storage Type", - affinity_type: null, - }, - power_instances: [ - { - name: "oracle-1", - workspace: "oracle-template", - image: "7300-00-01", - network: [ - { - name: "oracle-private-1", - ip_address: "", - }, - { - name: "oracle-private-2", - ip_address: "", - }, - { - name: "oracle-public", - ip_address: "", - }, - ], - zone: "dal12", - pi_health_status: "OK", - pi_proc_type: "shared", - pi_storage_type: null, - ssh_key: "power-ssh", - pi_processors: "2", - pi_memory: "32", - pi_sys_type: "s922", - storage_option: "Affinity", - affinity_type: "Volume", - pi_storage_pool_affinity: true, - pi_storage_pool: null, - pi_anti_affinity_volume: "redo-1", - pi_anti_affinity_instance: null, - pi_affinity_policy: "affinity", - pi_affinity_volume: null, - }, - ], - } - ); - assert.isTrue(actualData, "it should be true"); - }); - it("should be true for a volume when it is used by another volume for anti-affinity", () => { - let actualData = storageChangeDisabledCallback( - { - name: "redo-1", - workspace: "oracle-template", - pi_volume_shareable: true, - pi_replication_enabled: false, - pi_volume_type: "tier1", - attachments: ["oracle-1", "oracle-2"], - zone: "dal12", - pi_volume_size: "50", - storage_option: "Storage Type", - affinity_type: null, - }, - { - power_volumes: [ - { - name: "dev", - workspace: "oracle-template", - pi_volume_shareable: true, - pi_replication_enabled: false, - pi_volume_type: "tier1", - attachments: ["oracle-1", "oracle-2"], - zone: "dal12", - pi_volume_size: "50", - storage_option: "Storage Type", - affinity_type: null, - pi_anti_affinity_volume: "redo-1", - }, - ], - data: { - name: "redo-1", - workspace: "oracle-template", - pi_volume_shareable: true, - pi_replication_enabled: false, - pi_volume_type: "tier1", - attachments: ["oracle-1", "oracle-2"], - zone: "dal12", - pi_volume_size: "50", - storage_option: "Storage Type", - affinity_type: null, - }, - power_instances: [], - } - ); - assert.isTrue(actualData, "it should be true"); - }); - it("should be true for a volume when it is used by another volume for affinity", () => { - let actualData = storageChangeDisabledCallback( - { - name: "redo-1", - workspace: "oracle-template", - pi_volume_shareable: true, - pi_replication_enabled: false, - pi_volume_type: "tier1", - attachments: ["oracle-1", "oracle-2"], - zone: "dal12", - pi_volume_size: "50", - storage_option: "Storage Type", - affinity_type: null, - }, - { - power_volumes: [ - { - name: "dev", - workspace: "oracle-template", - pi_volume_shareable: true, - pi_replication_enabled: false, - pi_volume_type: "tier1", - attachments: ["oracle-1", "oracle-2"], - zone: "dal12", - pi_volume_size: "50", - storage_option: "Storage Type", - affinity_type: null, - pi_affinity_volume: "redo-1", - }, - ], - data: { - name: "redo-1", - workspace: "oracle-template", - pi_volume_shareable: true, - pi_replication_enabled: false, - pi_volume_type: "tier1", - attachments: ["oracle-1", "oracle-2"], - zone: "dal12", - pi_volume_size: "50", - storage_option: "Storage Type", - affinity_type: null, - }, - power_instances: [], - } - ); - assert.isTrue(actualData, "it should be true"); - }); -}); diff --git a/unit-tests/state/power-vs-instances.test.js b/unit-tests/state/power-vs-instances.test.js index 3a9b6f8a..b8453b31 100644 --- a/unit-tests/state/power-vs-instances.test.js +++ b/unit-tests/state/power-vs-instances.test.js @@ -1553,6 +1553,758 @@ describe("power_instances", () => { }); }); describe("power_instances.schema", () => { + let craig; + beforeEach(() => { + craig = newState(); + }); + describe("storage_option", () => { + it("should be false when is modal", () => { + assert.isFalse( + craig.power_instances.storage_option.disabled({}, { isModal: true }), + "it should be false for modal forms" + ); + }); + it("should be true for an instance when it is used by another instance for anti-affinity", () => { + let craig = state(); + let actualData = craig.power_instances.storage_option.disabled( + { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + { + craig: craig, + data: { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + power_instances: [ + { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + { + name: "oracle-2", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: null, + ssh_key: "power-ssh", + pi_sys_type: "s922", + pi_processors: "2", + pi_memory: "32", + storage_option: "Anti-Affinity", + affinity_type: "Instance", + pi_storage_pool_affinity: true, + pi_storage_pool: null, + pi_anti_affinity_volume: null, + pi_anti_affinity_instance: "oracle-1", + pi_affinity_policy: "anti-affinity", + pi_affinity_instance: null, + pi_affinity_volume: null, + }, + ], + } + ); + assert.isTrue(actualData, "it should be true"); + }); + it("should be true for an instance when it is used by a volume for affinity", () => { + let actualData = craig.power_instances.storage_option.disabled( + { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + { + data: { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + power_instances: [ + { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + ], + power_volumes: [ + { + name: "oracle-1-db-1", + workspace: "oracle-template", + pi_volume_shareable: false, + pi_replication_enabled: false, + pi_volume_type: null, + attachments: ["oracle-1"], + zone: "dal12", + pi_volume_size: "90", + storage_option: "Affinity", + affinity_type: "Instance", + pi_storage_pool: null, + pi_anti_affinity_volume: null, + pi_anti_affinity_instance: null, + pi_affinity_policy: "affinity", + pi_affinity_instance: "oracle-1", + }, + ], + } + ); + assert.isTrue(actualData, "it should be true"); + }); + it("should be true for an instance when it is used by a volume for anti-affinity", () => { + let actualData = craig.power_instances.storage_option.disabled( + { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + { + data: { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + power_instances: [ + { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + ], + power_volumes: [ + { + name: "oracle-1-db-1", + workspace: "oracle-template", + pi_volume_shareable: false, + pi_replication_enabled: false, + pi_volume_type: null, + attachments: ["oracle-1"], + zone: "dal12", + pi_volume_size: "90", + storage_option: "Affinity", + affinity_type: "Instance", + pi_storage_pool: null, + pi_anti_affinity_volume: null, + pi_affinity_instance: null, + pi_affinity_policy: "affinity", + pi_anti_affinity_instance: "oracle-1", + }, + ], + } + ); + assert.isTrue(actualData, "it should be true"); + }); + it("should be true for a volume when it is used by an instance for affinity", () => { + let actualData = craig.power_instances.storage_option.disabled( + { + name: "redo-1", + workspace: "oracle-template", + pi_volume_shareable: true, + pi_replication_enabled: false, + pi_volume_type: "tier1", + attachments: ["oracle-1", "oracle-2"], + zone: "dal12", + pi_volume_size: "50", + storage_option: "Storage Type", + affinity_type: null, + }, + { + power_volumes: [], + data: { + name: "redo-1", + workspace: "oracle-template", + pi_volume_shareable: true, + pi_replication_enabled: false, + pi_volume_type: "tier1", + attachments: ["oracle-1", "oracle-2"], + zone: "dal12", + pi_volume_size: "50", + storage_option: "Storage Type", + affinity_type: null, + }, + power_instances: [ + { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: null, + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Affinity", + affinity_type: "Volume", + pi_storage_pool_affinity: true, + pi_storage_pool: null, + pi_anti_affinity_volume: null, + pi_anti_affinity_instance: null, + pi_affinity_policy: "affinity", + pi_affinity_volume: "redo-1", + }, + ], + } + ); + assert.isTrue(actualData, "it should be true"); + }); + it("should be true for a volume when it is used by an instance for anti-affinity", () => { + let actualData = craig.power_instances.storage_option.disabled( + { + name: "redo-1", + workspace: "oracle-template", + pi_volume_shareable: true, + pi_replication_enabled: false, + pi_volume_type: "tier1", + attachments: ["oracle-1", "oracle-2"], + zone: "dal12", + pi_volume_size: "50", + storage_option: "Storage Type", + affinity_type: null, + }, + { + power_volumes: [], + data: { + name: "redo-1", + workspace: "oracle-template", + pi_volume_shareable: true, + pi_replication_enabled: false, + pi_volume_type: "tier1", + attachments: ["oracle-1", "oracle-2"], + zone: "dal12", + pi_volume_size: "50", + storage_option: "Storage Type", + affinity_type: null, + }, + power_instances: [ + { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: null, + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Affinity", + affinity_type: "Volume", + pi_storage_pool_affinity: true, + pi_storage_pool: null, + pi_anti_affinity_volume: "redo-1", + pi_anti_affinity_instance: null, + pi_affinity_policy: "affinity", + pi_affinity_volume: null, + }, + ], + } + ); + assert.isTrue(actualData, "it should be true"); + }); + it("should be true for a volume when it is used by another volume for anti-affinity", () => { + let actualData = craig.power_instances.storage_option.disabled( + { + name: "redo-1", + workspace: "oracle-template", + pi_volume_shareable: true, + pi_replication_enabled: false, + pi_volume_type: "tier1", + attachments: ["oracle-1", "oracle-2"], + zone: "dal12", + pi_volume_size: "50", + storage_option: "Storage Type", + affinity_type: null, + }, + { + power_volumes: [ + { + name: "dev", + workspace: "oracle-template", + pi_volume_shareable: true, + pi_replication_enabled: false, + pi_volume_type: "tier1", + attachments: ["oracle-1", "oracle-2"], + zone: "dal12", + pi_volume_size: "50", + storage_option: "Storage Type", + affinity_type: null, + pi_anti_affinity_volume: "redo-1", + }, + ], + data: { + name: "redo-1", + workspace: "oracle-template", + pi_volume_shareable: true, + pi_replication_enabled: false, + pi_volume_type: "tier1", + attachments: ["oracle-1", "oracle-2"], + zone: "dal12", + pi_volume_size: "50", + storage_option: "Storage Type", + affinity_type: null, + }, + power_instances: [], + } + ); + assert.isTrue(actualData, "it should be true"); + }); + it("should be true for a volume when it is used by another volume for affinity", () => { + let actualData = craig.power_instances.storage_option.disabled( + { + name: "redo-1", + workspace: "oracle-template", + pi_volume_shareable: true, + pi_replication_enabled: false, + pi_volume_type: "tier1", + attachments: ["oracle-1", "oracle-2"], + zone: "dal12", + pi_volume_size: "50", + storage_option: "Storage Type", + affinity_type: null, + }, + { + power_volumes: [ + { + name: "dev", + workspace: "oracle-template", + pi_volume_shareable: true, + pi_replication_enabled: false, + pi_volume_type: "tier1", + attachments: ["oracle-1", "oracle-2"], + zone: "dal12", + pi_volume_size: "50", + storage_option: "Storage Type", + affinity_type: null, + pi_affinity_volume: "redo-1", + }, + ], + data: { + name: "redo-1", + workspace: "oracle-template", + pi_volume_shareable: true, + pi_replication_enabled: false, + pi_volume_type: "tier1", + attachments: ["oracle-1", "oracle-2"], + zone: "dal12", + pi_volume_size: "50", + storage_option: "Storage Type", + affinity_type: null, + }, + power_instances: [], + } + ); + assert.isTrue(actualData, "it should be true"); + }); + it("should be true for an instance when it is used by another instance for affinity", () => { + let actualData = craig.power_instances.storage_option.disabled( + { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + { + power_volumes: [], + data: { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + power_instances: [ + { + name: "oracle-1", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: "tier1", + ssh_key: "power-ssh", + pi_processors: "2", + pi_memory: "32", + pi_sys_type: "s922", + storage_option: "Storage Type", + affinity_type: null, + pi_storage_pool_affinity: true, + }, + { + name: "oracle-2", + workspace: "oracle-template", + image: "7300-00-01", + network: [ + { + name: "oracle-private-1", + ip_address: "", + }, + { + name: "oracle-private-2", + ip_address: "", + }, + { + name: "oracle-public", + ip_address: "", + }, + ], + zone: "dal12", + pi_health_status: "OK", + pi_proc_type: "shared", + pi_storage_type: null, + ssh_key: "power-ssh", + pi_sys_type: "s922", + pi_processors: "2", + pi_memory: "32", + storage_option: "Affinity", + affinity_type: "Instance", + pi_storage_pool_affinity: true, + pi_storage_pool: null, + pi_anti_affinity_volume: null, + pi_anti_affinity_instance: null, + pi_affinity_policy: "affinity", + pi_affinity_instance: "oracle-1", + }, + ], + } + ); + assert.isTrue(actualData, "it should be true"); + }); + }); describe("power_instances.sap_profile", () => { describe("power_instances.schema.sap_profile.hideWhen", () => { it("should be true when is not sap", () => { diff --git a/unit-tests/state/reusable-fields.test.js b/unit-tests/state/reusable-fields.test.js new file mode 100644 index 00000000..d4d4e53e --- /dev/null +++ b/unit-tests/state/reusable-fields.test.js @@ -0,0 +1,152 @@ +const { assert } = require("chai"); +const { + hideWhenTcpOrUdp, + hideWhenNotAllIcmp, + onRuleFieldInputChange, + invalidPort, +} = require("../../client/src/lib/state/reusable-fields"); + +describe("reusable fields", () => { + describe("utility functions", () => { + describe("onRuleFieldInputChange", () => { + it("should set rule when no rule is found", () => { + let task = onRuleFieldInputChange("port_max"); + let data = { + port_max: "1", + }; + assert.deepEqual(task(data), "1", "it should return correct data"); + assert.deepEqual( + data, + { + port_max: "1", + rule: { + port_max: "1", + port_min: null, + source_port_max: null, + source_port_min: null, + type: null, + code: null, + }, + }, + "it should send correct data" + ); + }); + it("should set rule when no rule found", () => { + let task = onRuleFieldInputChange("port_max"); + let data = { + port_max: "2", + rule: { + port_max: "1", + port_min: null, + source_port_max: null, + source_port_min: null, + type: null, + code: null, + }, + }; + assert.deepEqual(task(data), "2", "it should return correct data"); + assert.deepEqual( + data, + { + port_max: "2", + rule: { + port_max: "2", + port_min: null, + source_port_max: null, + source_port_min: null, + type: null, + code: null, + }, + }, + "it should send correct data" + ); + }); + }); + describe("hideWhenTcpOrUdp", () => { + it("should be true when all", () => { + assert.isTrue( + hideWhenTcpOrUdp({ ruleProtocol: "all" }), + "it should be hidden" + ); + }); + it("should be true when icmp", () => { + assert.isTrue( + hideWhenTcpOrUdp({ ruleProtocol: "udp" }), + "it should be hidden" + ); + }); + }); + describe("hideWhenNotAllIcmp", () => { + it("should be true when all", () => { + assert.isTrue( + hideWhenNotAllIcmp({ ruleProtocol: "all" }), + "it should be hidden" + ); + }); + it("should be true when icmp", () => { + assert.isTrue( + hideWhenNotAllIcmp({ ruleProtocol: "icmp" }), + "it should be hidden" + ); + }); + }); + describe("invalidPort", () => { + it("should return false if rule protocol all", () => { + assert.isFalse( + invalidPort({ + ruleProtocol: "all", + }), + "it should be false" + ); + }); + it("should return true if rule protocol is icmp and invalid field", () => { + assert.isTrue( + invalidPort({ + ruleProtocol: "icmp", + rule: { + code: 10000, + }, + }), + "it should be false" + ); + }); + it("should return true if rule protocol is not icmp and invalid field", () => { + assert.isTrue( + invalidPort({ + ruleProtocol: "udp", + rule: { + port_min: 1000000, + }, + }), + "it should be false" + ); + }); + it("should return true if rule protocol is not icmp and invalid field and security group", () => { + assert.isTrue( + invalidPort( + { + ruleProtocol: "udp", + rule: { + port_min: 1000000, + }, + }, + true + ), + "it should be false" + ); + }); + it("should return true if rule protocol is not icmp and invalid field and security group when no rule object", () => { + assert.isTrue( + invalidPort( + { + ruleProtocol: "udp", + port_min: 1000000, + }, + true + ), + "it should be false" + ); + }); + }); + }); +}); diff --git a/unit-tests/state/secrets-manager.test.js b/unit-tests/state/secrets-manager.test.js index 73b8189a..14339181 100644 --- a/unit-tests/state/secrets-manager.test.js +++ b/unit-tests/state/secrets-manager.test.js @@ -154,4 +154,96 @@ describe("secrets_manager", () => { ); }); }); + describe("schema", () => { + let craig; + beforeEach(() => { + craig = newState(); + }); + describe("name", () => { + it("should return true if a secrets manager instance has an invalid name", () => { + assert.isTrue( + craig.secrets_manager.name.invalid( + { + name: "@@@", + resource_group: "managment-rg", + encryption_key: "key", + }, + { + craig: craig, + data: { + name: "frog", + }, + } + ), + "it should be true" + ); + }); + }); + describe("resource_group", () => { + it("should return true if a secrets manager instance has an invalid resource group", () => { + assert.isTrue( + craig.secrets_manager.resource_group.invalid( + { name: "frog", resource_group: null, use_data: false }, + { + craig: state(), + data: { + name: "test", + }, + } + ), + "it should be false" + ); + }); + }); + describe("encryption_key", () => { + it("should return true if a secrets manager instance has an invalid encryption key", () => { + assert.isTrue( + craig.secrets_manager.encryption_key.invalid( + { + name: "frog2", + resource_group: "management-rg", + encryption_key: null, + use_data: false, + }, + { + craig: state(), + data: { + name: "test", + }, + } + ), + "it should be false" + ); + }); + }); + describe("plan", () => { + it("should return correct data on render when no plan in data", () => { + assert.deepEqual( + craig.secrets_manager.plan.onRender({ plan: "standard" }, {}), + "Standard", + "it should return correct plan" + ); + }); + it("should return correct data on render when no plan in data", () => { + assert.deepEqual( + craig.secrets_manager.plan.onRender( + { plan: "standard" }, + { data: {} } + ), + "Standard", + "it should return correct plan" + ); + }); + it("should return correct data on render when no plan in data", () => { + assert.deepEqual( + craig.secrets_manager.plan.onRender( + { plan: "standard" }, + { data: { plan: "standard" } } + ), + "Standard", + "it should return correct plan" + ); + }); + }); + }); }); diff --git a/unit-tests/state/utils.test.js b/unit-tests/state/utils.test.js index 75964040..bd4b795f 100644 --- a/unit-tests/state/utils.test.js +++ b/unit-tests/state/utils.test.js @@ -11,6 +11,7 @@ const { onRuleFieldInputChange, hideWhenNotAllIcmp, hideWhenTcpOrUdp, + invalidTagList, } = require("../../client/src/lib/state/utils"); describe("utils", () => { @@ -444,64 +445,7 @@ describe("utils", () => { ); }); }); - describe("invalidPort", () => { - it("should return false if rule protocol all", () => { - assert.isFalse( - invalidPort({ - ruleProtocol: "all", - }), - "it should be false" - ); - }); - it("should return true if rule protocol is icmp and invalid field", () => { - assert.isTrue( - invalidPort({ - ruleProtocol: "icmp", - rule: { - code: 10000, - }, - }), - "it should be false" - ); - }); - it("should return true if rule protocol is not icmp and invalid field", () => { - assert.isTrue( - invalidPort({ - ruleProtocol: "udp", - rule: { - port_min: 1000000, - }, - }), - "it should be false" - ); - }); - it("should return true if rule protocol is not icmp and invalid field and security group", () => { - assert.isTrue( - invalidPort( - { - ruleProtocol: "udp", - rule: { - port_min: 1000000, - }, - }, - true - ), - "it should be false" - ); - }); - it("should return true if rule protocol is not icmp and invalid field and security group when no rule object", () => { - assert.isTrue( - invalidPort( - { - ruleProtocol: "udp", - port_min: 1000000, - }, - true - ), - "it should be false" - ); - }); - }); + describe("isRangeInvalid", () => { it("should return false if range is valid", () => { assert.isFalse(isRangeInvalid(5, 1, 10)); @@ -578,86 +522,13 @@ describe("utils", () => { ); }); }); - describe("onRuleFieldInputChange", () => { - it("should set rule when no rule is found", () => { - let task = onRuleFieldInputChange("port_max"); - let data = { - port_max: "1", - }; - assert.deepEqual(task(data), "1", "it should return correct data"); - assert.deepEqual( - data, - { - port_max: "1", - rule: { - port_max: "1", - port_min: null, - source_port_max: null, - source_port_min: null, - type: null, - code: null, - }, - }, - "it should send correct data" - ); - }); - it("should set rule when no rule found", () => { - let task = onRuleFieldInputChange("port_max"); - let data = { - port_max: "2", - rule: { - port_max: "1", - port_min: null, - source_port_max: null, - source_port_min: null, - type: null, - code: null, - }, - }; - assert.deepEqual(task(data), "2", "it should return correct data"); - assert.deepEqual( - data, - { - port_max: "2", - rule: { - port_max: "2", - port_min: null, - source_port_max: null, - source_port_min: null, - type: null, - code: null, - }, - }, - "it should send correct data" - ); - }); - }); - describe("hideWhenNotAllIcmp", () => { - it("should be true when all", () => { - assert.isTrue( - hideWhenNotAllIcmp({ ruleProtocol: "all" }), - "it should be hidden" - ); - }); - it("should be true when icmp", () => { - assert.isTrue( - hideWhenNotAllIcmp({ ruleProtocol: "icmp" }), - "it should be hidden" - ); - }); - }); - describe("hideWhenTcpOrUdp", () => { - it("should be true when all", () => { - assert.isTrue( - hideWhenTcpOrUdp({ ruleProtocol: "all" }), - "it should be hidden" - ); + + describe("invalidTagList", () => { + it("should return true when invalid tag list", () => { + assert.isTrue(invalidTagList(["hi", "2@@@2"])); }); - it("should be true when icmp", () => { - assert.isTrue( - hideWhenTcpOrUdp({ ruleProtocol: "udp" }), - "it should be hidden" - ); + it("should return false when no tags", () => { + assert.isFalse(invalidTagList([])); }); }); }); From e3d0c93ed1e816cc9e0ff880049527de1aad8f48 Mon Sep 17 00:00:00 2001 From: Samuel Matzek Date: Wed, 28 Feb 2024 12:27:35 -0600 Subject: [PATCH 2/3] Add script to generate an env file for existing workspaces (#1576) Add script to generate an env file for existing workspaces --- .docs/craig-code-engine.md | 27 +++++++++++-- .docs/power-vs-workspace-deployment.md | 18 +++++---- README.md | 7 ++-- generate-env.sh | 53 ++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 15 deletions(-) create mode 100755 generate-env.sh diff --git a/.docs/craig-code-engine.md b/.docs/craig-code-engine.md index 5a5de66d..84b81a9d 100644 --- a/.docs/craig-code-engine.md +++ b/.docs/craig-code-engine.md @@ -37,12 +37,33 @@ The Code Engine image build and application settings can be used to manage the C ### Background You can bring your own existing Power VS workspace into CRAIG which allows you to choose custom images for Power VSIs. -The IBM Code Engine deployment script will automatically create Power VS workspaces for CRAIG use when using the `-z` parameter. The script also allows specifying environment variables with the workspace IDs in a file with the `-e` parameter. In both cases the Power VS workspace zones and corresponding workspace IDs are placed in a Code Engine configmap. +The IBM Code Engine deployment script will automatically create Power VS workspaces in every Power VS zone worldwide for CRAIG's use when using the `-z` parameter. + +If you do not want Power VS workspaces created in every zone, you can create the Power VS Workspaces in your chosen zone(s) using the Clould console, CLI, or other means. The [generate-env.sh](../generate-env.sh) script can generate an environment file that can be used with the `deploy.sh` script to configure CRAIG to use the workspaces. + +#### generate-env.sh prerequisites +- [jq](https://jqlang.github.io/jq/) v1.7 or higher +- ibmcloud CLI +- Bash version 4 or higher + +To generate an env containing all of the workspaces in your account, you can run the following command: + +``` +./generate-env.sh env +``` + +The `env` file should then be modified to remove or comment out any workspaces that CRAIG should not use, and to ensure it contains only one workspace per zone. + +The `env` file can then be used on the `deploy.sh` script: + +``` +./deploy.sh -e env +``` ### Modifying the configmap If you want to bring your own workspace after CRAIG deployment in Code Engine you can update the configmap with the GUID of your workspace. -To find the GUIDs and locations of your workspaces, the following IBM Cloud CLI command can be run in a terminal window or an IBM Cloud Shell: +To find the GUIDs and locations of your workspaces, the following IBM Cloud CLI command can be run in a terminal window or an IBM Cloud Shell: ``` ibmcloud resource service-instances --service-name power-iaas --output json | jq -r '.[]? | "\(.guid), \(.name), \(.region_id)"' @@ -50,4 +71,4 @@ ibmcloud resource service-instances --service-name power-iaas --output json | jq To modify the configmap to add your workspace GUID, click on `Secrets and configmaps` on left navigation pane of the Code Engine project. Click on the `craig-env` Configmap. Find the key that matches your workspace's zone and set your workspace's GUID as the value for the key. Click the `Save` button. The CRAIG instance can then be [redeployed](#redeploying-the-craig-instance) to pick up the configmap change. -If CRAIG was deployed without specifying `-z` or `-e`, a configmap can be manually created and set with the correct key-value for the zone. See the [.example.env](../.example.env) for the possible keys and the [IBM Code Engine documentation](https://cloud.ibm.com/docs/codeengine?topic=codeengine-configmap) for how to create the configmap and add the reference to the `craig` application. +If CRAIG was deployed without specifying `-z` or `-e`, a configmap can be manually created and set with the correct key-value for the zone. See the [.env.example](../.env.example) for the possible keys and the [IBM Code Engine documentation](https://cloud.ibm.com/docs/codeengine?topic=codeengine-configmap) for how to create the configmap and add the reference to the `craig` application. diff --git a/.docs/power-vs-workspace-deployment.md b/.docs/power-vs-workspace-deployment.md index 5e8931c9..fa6e4a80 100644 --- a/.docs/power-vs-workspace-deployment.md +++ b/.docs/power-vs-workspace-deployment.md @@ -15,7 +15,7 @@ To dynamically fetch Power VS images and storage pools within CRAIG, the IBM Pow ## Automated Deployment -The `terraform.sh` script found in the `/deploy` folder of the CRAIG root directory provisions a Power VS Workspace in each zone and sets the needed environment variables with the format of `POWER_WORKSPACE_=`. +The `terraform.sh` script found in the `/deploy` folder of the CRAIG root directory provisions a Power VS Workspace in each zone worldwide and sets the needed environment variables with the format of `POWER_WORKSPACE_=`. Use the following command to run the script: ```shell @@ -26,16 +26,18 @@ This will produce a file named `.env` that can be passed to the `deploy.sh` scri #### Bring Your Own Workspace -To bring your own Power VS Workspace into CRAIG to fetch images, you will need to set a field in your `.env` with the following format. To see an example, see [.env.example](../.env.example) +If you do not want Power VS workspaces created in every zone or if you want to use custom images you can bring your own Power VS Workspace into CRAIG. The [generate-env.sh](../generate-env.sh) script can generate a `.env` environment file containing all of the workspaces in your account. -``` -POWER_WORKSPACE_= -``` +#### generate-env.sh prerequisites +- [jq](https://jqlang.github.io/jq/) v1.7 or higher +- [IBM Cloud CLI](https://cloud.ibm.com/docs/cli?topic=cli-getting-started) +- Bash version 4 or higher -To find the GUIDs and locations of your workspaces, the following IBM Cloud CLI command can be run in a terminal window or an IBM Cloud Shell: +To generate a `.env` file containing all of the workspaces in your account, you can run the following command: ``` -ibmcloud resource service-instances --service-name power-iaas --output json | jq -r '.[]? | "\(.guid), \(.name), \(.region_id)"' +./generate-env.sh .env ``` -*For instructions on how to install the IBM Cloud CLI, click [here](https://cloud.ibm.com/docs/cli?topic=cli-getting-started)* +The `.env` file should then be modified to remove or comment out any workspaces that CRAIG should not use, and to ensure it contains only one workspace per zone. The `API_KEY` key and value should also be added to the file, see [.env.example](../.env.example) for more information. + diff --git a/README.md b/README.md index 80f9ef63..2a51f2ef 100644 --- a/README.md +++ b/README.md @@ -108,20 +108,19 @@ chmod 755 deploy.sh By default the script will securely prompt you for your API key. It may also be read from an environment variable or specified as a command line argument. See the `deploy.sh -h` usage for more information. - If CRAIG is used for Power VS configuration, the Power VS workspaces must be created. The deploy script can create the Power Virtual Server workspaces and automatically integrate them with the CRAIG deployment. The deploy script uses a Schematics workspace and Terraform to drive the creation and deletion of the Power Virtual Server workspaces. Specify the `-z` parameter to automatically create the Power Virtual Server workspaces: + If CRAIG is used for Power VS configuration, Power VS workspaces must exist in the zones that CRAIG projects will use. The deploy script can create the Power Virtual Server workspaces in every Power VS zone worldwide and automatically integrate them with the CRAIG deployment. The deploy script uses a Schematics workspace and Terraform to drive the creation and deletion of the Power Virtual Server workspaces. Specify the `-z` parameter to automatically create the Power Virtual Server workspaces: ```bash ./deploy.sh -z ``` +If CRAIG is used for Power VS configuration and you do not want Power VS workspaces created in every zone, you can bring your own existing Power VS workspace into CRAIG. This also allows you to choose custom images for Power VSIs. See [Bring Your Own Power VS Workspace](.docs/craig-code-engine.md#bring-your-own-power-vs-workspace) for more information. + If CRAIG will not be used for Power VS configuration, `deploy.sh` can be run without parameters to deploy CRAIG into Code Engine: ```bash ./deploy.sh ``` - -You can bring your own existing Power VS workspace into CRAIG which allows you to choose custom images for Power VSIs. To bring your own workspace you can update the Code Engine configuration after deployment. See [Bring Your Own Power VS Workspace](.docs/craig-code-engine.md#bring-your-own-power-vs-workspace) for more information. - For the full list of parameters which allows full customization of the IBM Code Engine deployment, specify the `-h` parameter: ``` diff --git a/generate-env.sh b/generate-env.sh new file mode 100755 index 00000000..b8ca157d --- /dev/null +++ b/generate-env.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + + +fatal() { + echo "FATAL: $*" + exit 1 +} + +check_runtime_prereqs() { + echo "checking prereqs" + [[ "${BASH_VERSINFO:-0}" -lt 4 ]] && fatal "This script requires bash version 4 or higher. Version ${BASH_VERSINFO} is in use." + [[ -z "$(which jq)" ]] && fatal "jq is not installed. See https://stedolan.github.io/jq/" + [[ -z "$(which ibmcloud)" ]] && fatal "ibmcloud is not installed" +} + +install_ibmcloud_plugin () { + [[ -z "$1" ]] && fatal "install_ibmcloud_plugin requries one parameter which contains the name of a plugin to install" + # Install the IBM Cloud plugin + + if ! ibmcloud plugin show $1 &> /dev/null ; then + echo "Installing ibmcloud plugin $1" + ibmcloud plugin install $1 || fatal "Failed to install ibmcloud plugin: $1" + fi +} + +check_runtime_prereqs + +# Install ibmcloud plugins if necessary +install_ibmcloud_plugin power-iaas + +outputfile=$1 +echo "# Comment out or remove workspaces that you do not want to use with CRAIG." > $outputfile +echo "# You should have a max of one workspace listed per zone." >> $outputfile + +echo "" >> $outputfile + +echo "Fetching workspace information" +# Look up all Power VS workspaces and get their name, region (zone), and ID +workspaces=$(ibmcloud pi wss --json | jq -r '.[]? | "\(.name) \(.location.region) \(.id)"') + +# Cycle through all the workspaces +while read name region id; +do + echo "# Workspace: ${name}" >> $outputfile + # the ${region^^} makes all characters in the region upper case + echo "POWER_WORKSPACE_${region^^}=$id" >> $outputfile + echo "" >> $outputfile + +done <<< "$workspaces" + +echo "Generated file ${outputfile}:" +echo "" +cat $outputfile \ No newline at end of file From b5a83d94bf9fac83a58eddc8aaa94732aa10366f Mon Sep 17 00:00:00 2001 From: jvallexm Date: Wed, 28 Feb 2024 14:16:18 -0500 Subject: [PATCH 3/3] Issue 1590 (#1591) * feat: power vs v2 enhancements * fix: vpn servers * fix --- CHANGELOG.md | 1 + client/src/lib/docs/release-notes.json | 4 +++- client/src/lib/state/vpn-servers.js | 2 +- unit-tests/state/vpn-servers.test.js | 7 +++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0265e717..28d01718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file. ### Fixes +- Fixed an issue causing the VPN Server page to crash when opening the creation modal ## 1.12.0 diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index 08421b3b..72abf9eb 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -2,7 +2,9 @@ { "version": "1.12.1", "features": ["Users can now use the `Trial` plan for secrets manager"], - "fixes": [], + "fixes": [ + "Fixed an issue causing the VPN Server page to crash when opening the creation modal" + ], "upgrade_notes": [] }, { diff --git a/client/src/lib/state/vpn-servers.js b/client/src/lib/state/vpn-servers.js index 879feaac..de5a6827 100644 --- a/client/src/lib/state/vpn-servers.js +++ b/client/src/lib/state/vpn-servers.js @@ -356,7 +356,7 @@ function initVpnState(store) { invalidText: selectInvalidText("protocol"), groups: ["TCP", "UDP"], onRender: function (stateData) { - return stateData.protocol.toUpperCase(); + return (stateData.protocol || "").toUpperCase(); }, onInputChange: function (stateData) { return stateData.protocol.toLowerCase(); diff --git a/unit-tests/state/vpn-servers.test.js b/unit-tests/state/vpn-servers.test.js index f55b34f5..cadc729f 100644 --- a/unit-tests/state/vpn-servers.test.js +++ b/unit-tests/state/vpn-servers.test.js @@ -326,6 +326,13 @@ describe("vpn_servers", () => { "it should render correctly" ); }); + it("should return protocol null on render", () => { + assert.deepEqual( + craig.vpn_servers.protocol.onRender({}), + "", + "it should render correctly" + ); + }); describe("vpnServersWorkspaceHelperText", () => { it("should return correct helper text", () => { assert.deepEqual(