From 89d51384e16d782adbb506e4d8866680abaa53fd Mon Sep 17 00:00:00 2001 From: Pseusco Date: Mon, 8 Apr 2024 14:00:28 -0400 Subject: [PATCH 01/17] Issue 1785: Vsi api refactor (#1834) * vsi-api refactor test added Signed-off-by: Lucas-Franke * pretty Signed-off-by: Lucas-Franke * finished vsi api refactor Signed-off-by: Lucas-Franke --------- Signed-off-by: Lucas-Franke --- unit-tests/api-refactor/vsi-api.test.js | 271 ++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 unit-tests/api-refactor/vsi-api.test.js diff --git a/unit-tests/api-refactor/vsi-api.test.js b/unit-tests/api-refactor/vsi-api.test.js new file mode 100644 index 00000000..a81515d4 --- /dev/null +++ b/unit-tests/api-refactor/vsi-api.test.js @@ -0,0 +1,271 @@ +const { assert } = require("chai"); +const sinon = require("sinon"); +const controller = require("../../express-controllers/controller"); +const res = require("../mocks/response.mock"); +const { initMockAxios } = require("lazy-z"); +const { initRecursiveMockAxios } = require("../mocks/recursive-axios.mock"); + +describe("vsi api", () => { + let spyFns; + beforeEach(() => { + res.send = new sinon.spy(); + spyFns = { + sendDataOnTokenValid: (res, field, callback) => { + return callback(); + }, + getBearerToken: () => { + return new Promise((resolve) => resolve("token")); + }, + }; + }); + afterEach(() => { + delete process.env.CRAIG_PROD; + }); + + describe("/api/vsi/us-south/instanceProfiles", () => { + it("should respond with the correct data", () => { + let { axios } = initMockAxios({ + profiles: [ + { name: "bx2-2x8" }, + { name: "bx2-4x16" }, + { name: "bx2-8x32" }, + ], + }); + let testController = new controller(axios); + testController.sendDataOnTokenValid = new sinon.spy( + spyFns, + "sendDataOnTokenValid" + ); + testController.getBearerToken = new sinon.spy(spyFns, "getBearerToken"); + return testController + .vsiInstanceProfiles({ params: { region: "us-south" } }, res) + .then(() => { + assert.isTrue( + res.send.calledOnceWith(["bx2-2x8", "bx2-4x16", "bx2-8x32"]) + ); + assert.isTrue( + testController.getBearerToken.calledOnce, + "should be true" + ); + assert.isTrue( + testController.sendDataOnTokenValid.calledOnce, + "should be true" + ); + }); + }); + it("should respond with error", () => { + let { axios } = initMockAxios( + { + profiles: [ + { name: "bx2-2x8" }, + { name: "bx2-4x16" }, + { name: "bx2-8x32" }, + ], + response: "response", + }, + true + ); + let testController = new controller(axios); + testController.sendDataOnTokenValid = new sinon.spy( + spyFns, + "sendDataOnTokenValid" + ); + testController.getBearerToken = new sinon.spy(spyFns, "getBearerToken"); + return testController + .vsiInstanceProfiles({ params: { region: "us-south" } }, res) + .then(() => { + assert.isTrue(res.send.calledOnceWith("response")); + assert.isTrue( + testController.getBearerToken.calledOnce, + "should be true" + ); + assert.isTrue( + testController.sendDataOnTokenValid.calledOnce, + "should be true" + ); + }); + }); + }); + describe("/api/vsi/us-south/images", () => { + let data; + beforeEach(() => { + data = [ + { + images: [ + { + name: "windows-2016-amd64", + operating_system: { + display_name: "Windows Server 2016 Standard Edition (amd64)", + }, + }, + { + name: "debian-9-amd64", + operating_system: { + display_name: + "Debian GNU/Linux 9.x Stretch/Stable - Minimal Install (amd64)", + }, + }, + { + name: "my-image", + operating_system: { + display_name: + "Ubuntu Linux 16.04 LTS Xenial Xerus Minimal Install (amd64)", + }, + }, + ], + limit: 50, + total_count: 101, + next: { + href: "https://us-south.iaas.cloud.ibm.com/v1/images?limit=100&start=123456", + }, + }, + { + images: [ + { + name: "windows-2016-amd64", + operating_system: { + display_name: "Windows Server 2016 Standard Edition (amd64) 2", + }, + }, + { + name: "debian-9-amd64", + operating_system: { + display_name: + "Debian GNU/Linux 9.x Stretch/Stable - Minimal Install (amd64) 2", + }, + }, + { + name: "my-image", + operating_system: { + display_name: + "Ubuntu Linux 16.04 LTS Xenial Xerus Minimal Install (amd64) 2", + }, + }, + ], + limit: 50, + total_count: 1, + }, + ]; + }); + it("should respond with the correct data", () => { + let { axios } = initRecursiveMockAxios(data, false, true); + let testController = new controller(axios); + testController.getBearerToken = new sinon.spy(spyFns, "getBearerToken"); + testController.sendDataOnTokenValid = new sinon.spy( + spyFns, + "sendDataOnTokenValid" + ); + return testController + .vsiImages({ params: { region: "us-south" } }, res) + .then(() => { + assert.isTrue(res.send.calledOnce); + assert.deepEqual( + res.send.lastCall.args, + [ + [ + "Debian GNU/Linux 9.x Stretch/Stable - Minimal Install (amd64) 2 [debian-9-amd64]", + "Debian GNU/Linux 9.x Stretch/Stable - Minimal Install (amd64) [debian-9-amd64]", + "Ubuntu Linux 16.04 LTS Xenial Xerus Minimal Install (amd64) 2 [my-image]", + "Ubuntu Linux 16.04 LTS Xenial Xerus Minimal Install (amd64) [my-image]", + "Windows Server 2016 Standard Edition (amd64) 2 [windows-2016-amd64]", + "Windows Server 2016 Standard Edition (amd64) [windows-2016-amd64]", + ], + ], + "it should get images" + ); + assert.isTrue( + testController.getBearerToken.calledTwice, + "should be true" + ); + assert.isTrue(testController.sendDataOnTokenValid.calledOnce); + }); + }); + it("should respond with error", () => { + let { axios } = initMockAxios( + { + images: [ + { + name: "windows-2016-amd64", + operating_system: { + display_name: "Windows Server 2016 Standard Edition (amd64)", + }, + }, + { + name: "debian-9-amd64", + operating_system: { + display_name: + "Debian GNU/Linux 9.x Stretch/Stable - Minimal Install (amd64)", + }, + }, + { + name: "my-image", + operating_system: { + display_name: + "Ubuntu Linux 16.04 LTS Xenial Xerus Minimal Install (amd64)", + }, + }, + ], + limit: 50, + total_count: 101, + next: { + href: "https://us-south.iaas.cloud.ibm.com/v1/images?limit=100&start=123456", + }, + data: "should be returned on err", + }, + true, + true + ); + let testController = new controller(axios); + testController.getBearerToken = new sinon.spy(spyFns, "getBearerToken"); + testController.sendDataOnTokenValid = new sinon.spy( + spyFns, + "sendDataOnTokenValid" + ); + return testController + .vsiImages({ params: { region: "us-south" } }, res) + .then(() => { + assert.isTrue(res.send.calledOnceWith("should be returned on err")); + assert.isTrue( + testController.getBearerToken.calledOnce, + "should be true" + ); + assert.isTrue(testController.sendDataOnTokenValid.calledOnce); + }); + }); + }); + describe("/api/vsi/us-south/snapshots", () => { + let data; + beforeEach(() => { + data = [{ name: "frog" }, { name: "toad" }]; + }); + it("should respond with the correct data", () => { + let { axios } = initMockAxios({ snapshots: data }); + let testController = new controller(axios); + testController.getBearerToken = new sinon.spy(spyFns, "getBearerToken"); + return testController + .vsiSnapShots({ params: { region: "us-south" } }, res) + .then(() => { + assert.isTrue(res.send.calledOnce); + assert.isTrue( + res.send.calledOnceWith(["frog", "toad"]), + "it should get images" + ); + assert.isTrue(testController.getBearerToken.calledOnce); + }); + }); + it("should respond with error", () => { + let { axios } = initMockAxios( + { data: "should return this on err" }, + true + ); + let testController = new controller(axios); + testController.getBearerToken = new sinon.spy(spyFns, "getBearerToken"); + return testController + .vsiSnapShots({ params: { region: "us-south" } }, res) + .then(() => { + assert.isTrue(res.send.calledOnceWith("should return this on err")); + assert.isTrue(testController.getBearerToken.calledOnce); + }); + }); + }); +}); From 4302a84bedc7eca9436df523c53f75c667bc36ea Mon Sep 17 00:00:00 2001 From: Pseusco Date: Tue, 9 Apr 2024 09:08:12 -0400 Subject: [PATCH 02/17] cluster api refactor (#1839) Signed-off-by: Lucas-Franke --- unit-tests/api-refactor/cluster-api.test.js | 180 ++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 unit-tests/api-refactor/cluster-api.test.js diff --git a/unit-tests/api-refactor/cluster-api.test.js b/unit-tests/api-refactor/cluster-api.test.js new file mode 100644 index 00000000..9a982be8 --- /dev/null +++ b/unit-tests/api-refactor/cluster-api.test.js @@ -0,0 +1,180 @@ +const { assert } = require("chai"); +const sinon = require("sinon"); +const controller = require("../../express-controllers/controller"); +const res = require("../mocks/response.mock"); +const { initMockAxios } = require("lazy-z"); + +describe("cluster api", () => { + let sendDataOnTokenValid = (res, field, callback) => { + return callback(); + }; + beforeEach(() => { + res.send = new sinon.spy(); + sendDataOnTokenValid = new sinon.spy(sendDataOnTokenValid); + }); + afterEach(() => { + delete process.env.CRAIG_PROD; + }); + describe("/api/cluster/us-south/flavors", () => { + it("should respond with the correct data", () => { + let { axios } = initMockAxios([ + { name: "bx2.16x64" }, + { name: "bx2.2x8" }, + ]); + let testController = new controller(axios); + testController.sendDataOnTokenValid = sendDataOnTokenValid; + return testController + .clusterFlavors({ params: { region: "us-south" } }, res) + .then(() => { + assert.isTrue(res.send.calledOnceWith(["bx2.16x64", "bx2.2x8"])); + assert.isTrue(testController.sendDataOnTokenValid.calledOnce); + }); + }); + it("should respond with the correct data and store in flavors", () => { + let { axios } = initMockAxios([ + { name: "bx2.16x64" }, + { name: "bx2.2x8" }, + ]); + let testController = new controller(axios); + testController.sendDataOnTokenValid = sendDataOnTokenValid; + return testController + .clusterFlavors({ params: { region: "us-south" } }, res) + .then(() => { + assert.deepEqual( + testController.flavors, + ["bx2.16x64", "bx2.2x8"], + "it should save flavors" + ); + assert.isTrue(testController.sendDataOnTokenValid.calledOnce); + }); + }); + it("should send flavors in controller constructor if there are flavors and the token is not expired", () => { + let { axios } = initMockAxios([ + { name: "bx2.16x64" }, + { name: "bx2.2x8" }, + ]); + let testController = new controller(axios); + let mockExpiration = Math.floor(Date.now() / 1000) + 100; + testController.expiration = mockExpiration; + testController.flavors = ["1234"]; + testController.token = "1234"; + testController.sendDataOnTokenValid = new sinon.spy( + testController.sendDataOnTokenValid + ); + return testController + .clusterFlavors({ params: { region: "us-south" } }, res) + .then(() => { + assert.isTrue(res.send.calledOnceWith(["1234"]), "it should be true"); + assert.isTrue(testController.sendDataOnTokenValid.calledOnce); + }); + }); + it("should respond with error", () => { + let { axios } = initMockAxios( + { response: "should return this when err" }, + true + ); + let testController = new controller(axios); + testController.sendDataOnTokenValid = sendDataOnTokenValid; + return testController + .clusterFlavors({ params: { region: "us-south" } }, res) + .then(() => { + assert.isTrue(res.send.calledOnceWith("should return this when err")); + assert.isTrue(testController.sendDataOnTokenValid.calledOnce); + }); + }); + }); + describe("/api/cluster/versions", () => { + let data = { + kubernetes: [ + { + major: 1, + minor: 23, + patch: 15, + default: false, + end_of_service: "", + }, + { + major: 1, + minor: 24, + patch: 9, + default: true, + end_of_service: "", + }, + ], + openshift: [ + { + major: 4, + minor: 10, + patch: 43, + default: true, + end_of_service: "", + }, + { + major: 4, + minor: 11, + patch: 17, + default: false, + end_of_service: "", + }, + ], + }; + it("should return the correct data", () => { + let { axios } = initMockAxios(data); + let testController = new controller(axios); + testController.sendDataOnTokenValid = sendDataOnTokenValid; + return testController.clusterVersions({}, res).then(() => { + assert.isTrue( + res.send.calledOnceWith([ + "4.10.43_openshift (Default)", + "1.24.9 (Default)", + "1.23.15", + "4.11.17_openshift", + ]) + ); + assert.isTrue(testController.sendDataOnTokenValid.calledOnce); + }); + }); + it("should set versions in controller constructor", () => { + let { axios } = initMockAxios(data); + let testController = new controller(axios); + testController.sendDataOnTokenValid = sendDataOnTokenValid; + return testController.clusterVersions({}, res).then(() => { + assert.deepEqual( + testController.versions, + [ + "4.10.43_openshift (Default)", + "1.24.9 (Default)", + "1.23.15", + "4.11.17_openshift", + ], + "it should have versions" + ); + assert.isTrue(testController.sendDataOnTokenValid.calledOnce); + }); + }); + it("should send versions in controller constructor if there are versions and the token is not expired", () => { + let { axios } = initMockAxios(data); + let testController = new controller(axios); + let mockExpiration = Math.floor(Date.now() / 1000) + 100; + testController.expiration = mockExpiration; + testController.versions = ["1234"]; + testController.token = "1234"; + testController.sendDataOnTokenValid = new sinon.spy( + testController.sendDataOnTokenValid + ); + return testController.clusterVersions({}, res).then(() => { + assert.isTrue(res.send.calledOnceWith(["1234"])); + assert.isTrue(testController.sendDataOnTokenValid.calledOnce); + }); + }); + it("should respond with error", () => { + let { axios } = initMockAxios(data, true); + let testController = new controller(axios); + testController.sendDataOnTokenValid = sendDataOnTokenValid; + return testController.clusterVersions({}, res).then(() => { + assert.isTrue(res.send.calledOnce); + assert.isTrue(testController.sendDataOnTokenValid.calledOnce); + }); + }); + }); +}); From 9b2e88c02e322bd59c8d90b53c2aa7a33b62d583 Mon Sep 17 00:00:00 2001 From: jvallexm Date: Tue, 9 Apr 2024 11:10:51 -0400 Subject: [PATCH 03/17] Issue 1809: routing table enhancements (#1836) * feat: rt route enhancements * feat: rt route enhancements * feat: rt enhandements * prune: cluster disable save * prune: power volumes disablesave * fixes * fix: kms key ring * cl * feat: advertise --- CHANGELOG.md | 11 + client/package-lock.json | 4 +- client/package.json | 2 +- client/src/components/pages/CraigForms.js | 5 + client/src/lib/docs/release-notes.json | 11 + .../docs/templates/power-poc-quick-start.json | 3 +- .../lib/docs/templates/vpn-as-a-service.json | 6 +- client/src/lib/json-to-iac/key-management.js | 14 +- client/src/lib/json-to-iac/routing-tables.js | 8 + client/src/lib/state/clusters.js | 2 +- client/src/lib/state/reusable-fields.js | 1 + client/src/lib/state/routing-tables.js | 49 ++ package-lock.json | 4 +- package.json | 2 +- unit-tests/forms/disable-save.test.js | 54 ++ .../forms/disable-save/clusters.test.js | 519 ------------------ .../forms/disable-save/power-volumes.test.js | 74 --- unit-tests/forms/wizard.test.js | 24 +- unit-tests/json-to-iac/key-management.test.js | 94 ++++ unit-tests/json-to-iac/routing-table.test.js | 73 +++ unit-tests/state/clusters.test.js | 50 ++ unit-tests/state/power-vs-volumes.test.js | 8 + unit-tests/state/reusable-fields.test.js | 6 + unit-tests/state/routing-tables.test.js | 51 ++ unit-tests/state/schema.test.js | 10 + 25 files changed, 464 insertions(+), 621 deletions(-) delete mode 100644 unit-tests/forms/disable-save/clusters.test.js delete mode 100644 unit-tests/forms/disable-save/power-volumes.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 21e51526..49910753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this project will be documented in this file. +## 1.15.0 + +### Features + +- VPC Routing Tables can now be advertised to Transit Gateway, and Direct Link +- VPC Routing Table routes can now be assigned a Priority value and can advertise to the parent table's sources by using the `Advertise` toggle + +### Fixes + +- Fixed an issue causing encryption keys with no Key Ring value to populate with a `null` key ring + ## 1.14.1 ### Upgrade Notes diff --git a/client/package-lock.json b/client/package-lock.json index 932fd484..4b1b6c69 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "craig", - "version": "1.14.1", + "version": "1.15.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "craig", - "version": "1.14.1", + "version": "1.15.0", "license": "Apache-2.0", "dependencies": { "@apollo/client": "^3.4.10", diff --git a/client/package.json b/client/package.json index 394830f6..4341f721 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "craig", - "version": "1.14.1", + "version": "1.15.0", "private": true, "license": "Apache-2.0", "scripts": { diff --git a/client/src/components/pages/CraigForms.js b/client/src/components/pages/CraigForms.js index d268e7ae..ae71224c 100644 --- a/client/src/components/pages/CraigForms.js +++ b/client/src/components/pages/CraigForms.js @@ -1197,6 +1197,7 @@ function craigForms(craig) { { accept_routes_from_resource_type: craig.routing_tables.accept_routes_from_resource_type, + advertise_routes_to: craig.routing_tables.advertise_routes_to, }, ], subForms: [ @@ -1214,6 +1215,10 @@ function craigForms(craig) { { action: craig.routing_tables.routes.action, next_hop: craig.routing_tables.routes.next_hop, + priority: craig.routing_tables.routes.priority, + }, + { + advertise: craig.routing_tables.routes.advertise, }, ], }, diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index 3fbe82ba..aee88c3a 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -1,4 +1,15 @@ [ + { + "version": "1.15.0", + "features": [ + "VPC Routing Tables can now be advertised to Transit Gateway, and Direct Link", + "VPC Routing Table routes can now be assigned a Priority value and can advertise to the parent table's sources by using the `Advertise` toggle" + ], + "fixes": [ + "Fixed an issue causing encryption keys with no Key Ring value to populate with a `null` key ring" + ], + "upgrade_notes": [] + }, { "version": "1.14.1", "features": [ diff --git a/client/src/lib/docs/templates/power-poc-quick-start.json b/client/src/lib/docs/templates/power-poc-quick-start.json index 6841cf09..e4a2cad8 100644 --- a/client/src/lib/docs/templates/power-poc-quick-start.json +++ b/client/src/lib/docs/templates/power-poc-quick-start.json @@ -568,7 +568,8 @@ "direct_link_ingress": false, "route_direct_link_ingress": false, "route_vpc_zone_ingress": false, - "accept_routes_from_resource_type": ["vpn_gateway", "vpn_server"] + "accept_routes_from_resource_type": ["vpn_gateway", "vpn_server"], + "advertise_routes_to": [] } ], "scc": { diff --git a/client/src/lib/docs/templates/vpn-as-a-service.json b/client/src/lib/docs/templates/vpn-as-a-service.json index 788cd3dd..c3cc5fb1 100644 --- a/client/src/lib/docs/templates/vpn-as-a-service.json +++ b/client/src/lib/docs/templates/vpn-as-a-service.json @@ -151,7 +151,8 @@ "direct_link_ingress": false, "accept_routes_from_resource_type": ["vpn_gateway", "vpn_server"], "route_direct_link_ingress": false, - "route_vpc_zone_ingress": false + "route_vpc_zone_ingress": false, + "advertise_routes_to": [] }, { "routes": [], @@ -163,7 +164,8 @@ "direct_link_ingress": false, "accept_routes_from_resource_type": ["vpn_server"], "route_direct_link_ingress": false, - "route_vpc_zone_ingress": false + "route_vpc_zone_ingress": false, + "advertise_routes_to": [] } ], "scc": { diff --git a/client/src/lib/json-to-iac/key-management.js b/client/src/lib/json-to-iac/key-management.js index 62f5dff0..112608cd 100644 --- a/client/src/lib/json-to-iac/key-management.js +++ b/client/src/lib/json-to-iac/key-management.js @@ -160,11 +160,13 @@ function ibmKmsKey(key, kms, config) { instance_id: composedKmsId(kms), key_name: kebabName([kms.name, key.name]), standard_key: !key.root_key, - key_ring_id: tfRef( - "ibm_kms_key_rings", - `${kms.name} ${key.key_ring} ring`, - "key_ring_id" - ), + key_ring_id: key.key_ring + ? tfRef( + "ibm_kms_key_rings", + `${kms.name} ${key.key_ring} ring`, + "key_ring_id" + ) + : undefined, force_delete: key.force_delete, endpoint_type: key.endpoint ? key.endpoint : config._options.endpoints, }; @@ -278,7 +280,7 @@ function kmsInstanceTf(kms, config) { instanceTf += formatKmsAuthPolicy(kms) + formatKmsAuthPolicy(kms, true); } keyRings.forEach((ring) => { - instanceTf += formatKeyRing(ring, kms, config); + if (ring) instanceTf += formatKeyRing(ring, kms, config); }); kms.keys.forEach((key) => { instanceTf += diff --git a/client/src/lib/json-to-iac/routing-tables.js b/client/src/lib/json-to-iac/routing-tables.js index bdf9ecef..cd4d3339 100644 --- a/client/src/lib/json-to-iac/routing-tables.js +++ b/client/src/lib/json-to-iac/routing-tables.js @@ -40,6 +40,9 @@ function ibmIsVpcRoutingTable(table) { */ function formatRoutingTable(table, config) { let data = ibmIsVpcRoutingTable(table, config); + if (table.advertise_routes_to) { + data.data.advertise_routes_to = table.advertise_routes_to; + } return jsonToTfPrint( "resource", "ibm_is_vpc_routing_table", @@ -99,6 +102,11 @@ function ibmIsVpcRoutingTableRoute(route, config) { */ function formatRoutingTableRoute(route, config) { let data = ibmIsVpcRoutingTableRoute(route, config); + ["advertise", "priority"].forEach((item) => { + if (route[item]) { + data.data[item] = route[item]; + } + }); return jsonToTfPrint( "resource", "ibm_is_vpc_routing_table_route", diff --git a/client/src/lib/state/clusters.js b/client/src/lib/state/clusters.js index d4145ff8..e610d03f 100644 --- a/client/src/lib/state/clusters.js +++ b/client/src/lib/state/clusters.js @@ -615,7 +615,7 @@ function initClusterStore(store) { type: "date", default: null, invalid: function (stateData) { - return !stateData.expiration_date; + return isNullOrEmptyString(stateData.expiration_date, true); }, invalidText: unconditionalInvalidText("Select a date"), }, diff --git a/client/src/lib/state/reusable-fields.js b/client/src/lib/state/reusable-fields.js index b77488ff..6e0448c8 100644 --- a/client/src/lib/state/reusable-fields.js +++ b/client/src/lib/state/reusable-fields.js @@ -914,4 +914,5 @@ module.exports = { classicPrivateVlan, classicPublicVlan, classicPrivateNetworkOnly, + invalidNewResourceName, }; diff --git a/client/src/lib/state/routing-tables.js b/client/src/lib/state/routing-tables.js index d8a9a4c7..a49248c8 100644 --- a/client/src/lib/state/routing-tables.js +++ b/client/src/lib/state/routing-tables.js @@ -161,6 +161,9 @@ function initRoutingTable(store) { alignModal: "bottom-left", }, labelText: "Direct Link Ingress", + disabled: function (stateData) { + return contains(stateData.advertise_routes_to, "direct_link"); + }, }, transit_gateway_ingress: { default: false, @@ -172,6 +175,9 @@ function initRoutingTable(store) { align: "bottom-left", alignModal: "bottom-left", }, + disabled: function (stateData) { + return contains(stateData.advertise_routes_to, "transit_gateway"); + }, }, route_vpc_zone_ingress: { default: false, @@ -184,6 +190,28 @@ function initRoutingTable(store) { alignModal: "bottom-left", }, }, + advertise_routes_to: { + default: [], + type: "multiselect", + labelText: "Advertise Routes to Service", + groups: function (stateData, componentProps) { + return [] + .concat(stateData.route_direct_link_ingress ? ["Direct Link"] : []) + .concat( + stateData.transit_gateway_ingress ? ["Transit Gateway"] : [] + ); + }, + onRender: function (stateData) { + return stateData.advertise_routes_to.map((type) => { + return titleCase(type).replace(/Vpn/g, "VPN"); + }); + }, + onInputChange: function (stateData) { + return stateData.advertise_routes_to.map((type) => { + return snakeCase(type); + }); + }, + }, accept_routes_from_resource_type: { default: [], type: "multiselect", @@ -270,6 +298,27 @@ function initRoutingTable(store) { placeholder: "X.X.X.X/X", size: "small", }, + advertise: { + size: "small", + labelText: "Advertise", + type: "toggle", + default: false, + tooltip: { + content: + "Indicates whether this route will be advertised to the ingress sources of the parent route", + align: "right", + alignModal: "right", + }, + }, + priority: { + size: "small", + type: "select", + default: "", + groups: ["0", "1", "2", "3", "4"], + tooltip: { + content: "Smaller values have higher priority", + }, + }, }, }, }, diff --git a/package-lock.json b/package-lock.json index acedfea0..1d36953a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "craig", - "version": "1.14.1", + "version": "1.15.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "craig", - "version": "1.14.1", + "version": "1.15.0", "license": "ISC", "dependencies": { "axios": "^1.6.3", diff --git a/package.json b/package.json index c96f32e4..b32903c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "craig", - "version": "1.14.1", + "version": "1.15.0", "description": "gui for generating ibm cloud infrastructure resources", "main": "index.js", "scripts": { diff --git a/unit-tests/forms/disable-save.test.js b/unit-tests/forms/disable-save.test.js index 6d027f49..590e94c8 100644 --- a/unit-tests/forms/disable-save.test.js +++ b/unit-tests/forms/disable-save.test.js @@ -11,6 +11,60 @@ describe("disableSave", () => { it("should otherwise return false", () => { assert.isFalse(disableSave("pretend_field", {}, {}), "it should be false"); }); + describe("clusters", () => { + it("should return true if a cluster worker pool has an invalid name", () => { + assert.isTrue( + disableSave( + "worker_pools", + { + name: "a--", + }, + { + craig: state(), + data: { + name: "mm", + }, + } + ), + "it should be true" + ); + }); + it("should return true if a secrets group is an invalid duplicate name", () => { + let tempCraig = state(); + tempCraig.store = { + json: { + clusters: [ + { + name: "frog", + opaque_secrets: [ + { + name: "a", + secrets_group: "duplicate", + }, + ], + }, + ], + }, + }; + + assert.isTrue( + disableSave( + "opaque_secrets", + { + name: "frog", + secrets_group: "duplicate", + }, + { + craig: tempCraig, + data: { + name: "mm", + }, + } + ), + "it should be true" + ); + }); + }); describe("security groups", () => { describe("rules", () => { it("should return true if security group rule has invalid name", () => { diff --git a/unit-tests/forms/disable-save/clusters.test.js b/unit-tests/forms/disable-save/clusters.test.js deleted file mode 100644 index aee48f14..00000000 --- a/unit-tests/forms/disable-save/clusters.test.js +++ /dev/null @@ -1,519 +0,0 @@ -const { assert } = require("chai"); -const { disableSave, state } = require("../../../client/src/lib"); -const craig = state(); - -describe("clusters", () => { - it("should return true if a cluster has an invalid name", () => { - assert.isTrue( - disableSave( - "clusters", - { - name: "@@@", - }, - { - craig: craig, - data: { - name: "frog", - }, - } - ), - "it should be true" - ); - }); - it("should return true if a cluster has an invalid duplicate name", () => { - let tempCraig = state(); - tempCraig.setUpdateCallback(() => {}); - tempCraig.clusters.create({ name: "toad", subnets: [], worker_pools: [] }); - assert.isTrue( - disableSave( - "clusters", - { - name: "toad", - }, - { - craig: tempCraig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if a cluster is openshift and has no cos", () => { - assert.isTrue( - disableSave( - "clusters", - { - name: "toad2", - kube_type: "openshift", - cos: null, - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if a cluster is openshift and has cos but invalid subnets", () => { - assert.isTrue( - disableSave( - "clusters", - { - name: "toad2", - kube_type: "openshift", - cos: "cos", - workers_per_subnet: 1, - subnets: [], - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - describe("worker_pools", () => { - it("should return true if a cluster worker pool has an invalid name", () => { - assert.isTrue( - disableSave( - "worker_pools", - { - name: "a--", - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if a cluster worker pool has no flavor", () => { - assert.isTrue( - disableSave( - "worker_pools", - { - name: "aaaa", - flavor: "", - }, - { - craig: craig, - data: { - name: "aaaa", - }, - } - ), - "it should be true" - ); - }); - it("should return true if a cluster worker pool has no subnets", () => { - assert.isTrue( - disableSave( - "worker_pools", - { - name: "toad", - flavor: "spicy", - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if a cluster worker pool has empty subnets", () => { - assert.isTrue( - disableSave( - "worker_pools", - { - name: "aaa", - flavor: "spicy", - subnets: [], - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - }); - describe("opaque_secrets", () => { - it("should return true if a cluster opaque secret has an invalid duplicate name", () => { - let tempCraig = state(); - tempCraig.store = { - json: { - clusters: [ - { - name: "frog", - opaque_secrets: [ - { - name: "a", - }, - ], - }, - { - name: "toad", - opaque_secrets: [ - { - name: "duplicate", - }, - ], - }, - ], - }, - }; - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "duplicate", - }, - { - craig: tempCraig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if a cluster opaque secret has an invalid name", () => { - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "AAAAA", - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if a cluster secrets group has an invalid name", () => { - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "a", - secrets_group: "AAAAAA", - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if a secrets group is an invalid duplicate name", () => { - let tempCraig = state(); - tempCraig.store = { - json: { - clusters: [ - { - name: "frog", - opaque_secrets: [ - { - name: "a", - secrets_group: "duplicate", - }, - ], - }, - ], - }, - }; - - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "frog", - secrets_group: "duplicate", - }, - { - craig: tempCraig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if arbitrary secret is an invalid duplicate name", () => { - let tempCraig = state(); - tempCraig.store = { - json: { - secrets_manager: [], - clusters: [ - { - name: "frog", - opaque_secrets: [ - { - name: "a", - secrets_group: "a", - arbitrary_secret_name: "duplicate", - }, - ], - }, - ], - }, - }; - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "frog", - secrets_group: "frog", - arbitrary_secret_name: "duplicate", - }, - { - craig: tempCraig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if username password secret is an invalid duplicate name", () => { - let tempCraig = state(); - tempCraig.store = { - json: { - secrets_manager: [], - clusters: [ - { - name: "frog", - opaque_secrets: [ - { - name: "a", - secrets_group: "a", - arbitrary_secret_name: "a", - username_password_secret_name: "duplicate", - }, - ], - }, - ], - }, - }; - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "frog", - secrets_group: "frog", - arbitrary_secret_name: "frog", - username_password_secret_name: "duplicate", - labels: [], - }, - { - craig: tempCraig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if secrets manager is empty", () => { - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "frog", - secrets_group: "frog", - arbitrary_secret_name: "frog", - username_password_secret_name: "frog", - secrets_manager: "", - labels: [], - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if labels are empty", () => { - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "frog", - secrets_group: "frog", - arbitrary_secret_name: "frog", - username_password_secret_name: "frog", - secrets_manager: "frog", - labels: ["2@@@@2"], - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if a secret label is invalid", () => { - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "frog", - labels: ["label", "invalid-label-"], - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if no expiration date is selected", () => { - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "frog", - secrets_group: "frog", - secrets_manager: "frog", - username_password_secret_name: "frog", - arbitrary_secret_name: "frog", - arbitrary_secred_data: "frog", - username_password_secret_name: "frog", - username_password_secret_username: "frog", - username_password_secret_password: "frog", - labels: [], - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return false if user_pass secret and arbitrary secret have diff names", () => { - assert.isFalse( - disableSave( - "opaque_secrets", - { - name: "frog", - secrets_group: "frog", - secrets_manager: "frog", - username_password_secret_name: "frog", - arbitrary_secret_name: "tadpole", - arbitrary_secret_data: "frog", - username_password_secret_username: "frog", - username_password_secret_password: "frog", - expiration_date: "never", - arbitrary_secret_description: "frog", - username_password_secret_description: "frog", - labels: [], - auto_rotate: false, - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be false" - ); - }); - it("should return true if arbitrary secret description is invalid", () => { - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "frog", - secrets_group: "frog", - secrets_manager: "frog", - username_password_secret_name: "frog", - arbitrary_secret_name: "frog", - arbitrary_secret_data: "frog", - arbitrary_secret_description: "@@@", - username_password_secret_description: "frog", - username_password_secret_username: "frog", - username_password_secret_password: "frog", - expiration_date: "never", - labels: [], - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - it("should return true if username password secret description is invalid", () => { - assert.isTrue( - disableSave( - "opaque_secrets", - { - name: "frog", - secrets_group: "frog", - secrets_manager: "frog", - username_password_secret_name: "frog", - arbitrary_secret_name: "frog", - arbitrary_secret_data: "frog", - arbitrary_secret_description: "frog", - username_password_secret_description: "@@@@", - username_password_secret_username: "frog", - username_password_secret_password: "frog", - expiration_date: "never", - labels: [], - }, - { - craig: craig, - data: { - name: "mm", - }, - } - ), - "it should be true" - ); - }); - }); -}); diff --git a/unit-tests/forms/disable-save/power-volumes.test.js b/unit-tests/forms/disable-save/power-volumes.test.js deleted file mode 100644 index 371d85ed..00000000 --- a/unit-tests/forms/disable-save/power-volumes.test.js +++ /dev/null @@ -1,74 +0,0 @@ -const { assert } = require("chai"); -const { disableSave, state } = require("../../../client/src/lib"); - -describe("power vs volumes", () => { - it("should disable save for volume with an invalid name", () => { - assert.isTrue( - disableSave( - "power_volumes", - { - name: "@@@@", - }, - { - data: { - name: "test", - }, - craig: state(), - } - ), - "it should be disabled" - ); - }); - it("should be disabled when valid name and no workspace", () => { - let actualData = disableSave( - "power_volumes", - { - name: "toad", - workspace: "", - }, - { - data: { - name: "egg", - }, - craig: state(), - } - ); - assert.isTrue(actualData, "it should be disabled"); - }); - it("should disable save for volume with an invalid capacity", () => { - assert.isTrue( - disableSave( - "power_volumes", - { - name: "frog", - pi_volume_size: 0, - }, - { - data: { - name: "test", - }, - craig: state(), - } - ), - "it should be disabled" - ); - }); - it("should disable save for volume with an invalid capacity", () => { - assert.isTrue( - disableSave( - "power_volumes", - { - name: "frog", - pi_volume_size: "", - }, - { - data: { - name: "test", - }, - craig: state(), - } - ), - "it should be disabled" - ); - }); -}); diff --git a/unit-tests/forms/wizard.test.js b/unit-tests/forms/wizard.test.js index 6911059f..85422aa8 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.14.1", + craig_version: "1.15.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -975,7 +975,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.14.1", + craig_version: "1.15.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -1892,7 +1892,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.14.1", + craig_version: "1.15.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -2403,7 +2403,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.14.1", + craig_version: "1.15.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -2905,7 +2905,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.14.1", + craig_version: "1.15.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -3484,7 +3484,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.14.1", + craig_version: "1.15.0", no_vpn_secrets_manager_auth: false, }, resource_groups: [ @@ -3654,7 +3654,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.14.1", + craig_version: "1.15.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -4225,7 +4225,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.14.1", + craig_version: "1.15.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -4770,7 +4770,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.14.1", + craig_version: "1.15.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -5320,7 +5320,7 @@ describe("setup wizard", () => { enable_classic: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.14.1", + craig_version: "1.15.0", no_vpn_secrets_manager_auth: false, }, resource_groups: [ @@ -5812,7 +5812,7 @@ describe("setup wizard", () => { enable_power_vs: true, enable_classic: false, power_vs_zones: ["dal10"], - craig_version: "1.14.1", + craig_version: "1.15.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -6314,7 +6314,7 @@ describe("setup wizard", () => { enable_power_vs: true, enable_classic: false, power_vs_zones: ["dal10"], - craig_version: "1.14.1", + craig_version: "1.15.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, diff --git a/unit-tests/json-to-iac/key-management.test.js b/unit-tests/json-to-iac/key-management.test.js index b6f4445b..4ef5071a 100644 --- a/unit-tests/json-to-iac/key-management.test.js +++ b/unit-tests/json-to-iac/key-management.test.js @@ -867,6 +867,100 @@ resource "ibm_kms_key" "kms_key_key" { ] } +############################################################################## +`; + assert.deepEqual( + actualData, + expectedData, + "it should return terraform code" + ); + }); + it("should create code for one instance from configuration file with no rotation for standard keys and no key ring", () => { + let actualData = kmsTf({ + _options: { + region: "us-south", + tags: ["hello", "world"], + prefix: "iac", + endpoints: "private", + }, + 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: false, + key_ring: null, + force_delete: false, + endpoint: null, + rotation: 1, + dual_auth_delete: false, + }, + ], + }, + ], + }); + let expectedData = `############################################################################## +# Key Management Instance Kms +############################################################################## + +resource "ibm_resource_instance" "kms" { + name = "\${var.prefix}-kms" + resource_group_id = ibm_resource_group.slz_service_rg.id + service = "kms" + plan = "tiered-pricing" + location = var.region + tags = [ + "hello", + "world" + ] +} + +resource "ibm_iam_authorization_policy" "kms_server_protect_policy" { + source_service_name = "server-protect" + target_service_name = "kms" + target_resource_instance_id = ibm_resource_instance.kms.guid + description = "Allow block storage volumes to be encrypted by Key Management instance." + roles = [ + "Reader" + ] +} + +resource "ibm_iam_authorization_policy" "kms_block_storage_policy" { + source_service_name = "is" + target_service_name = "kms" + target_resource_instance_id = ibm_resource_instance.kms.guid + description = "Allow block storage volumes to be encrypted by Key Management instance." + source_resource_type = "share" + roles = [ + "Reader", + "Authorization Delegator" + ] +} + +resource "ibm_kms_key" "kms_key_key" { + instance_id = ibm_resource_instance.kms.guid + key_name = "\${var.prefix}-kms-key" + standard_key = true + force_delete = false + endpoint_type = "private" + depends_on = [ + ibm_iam_authorization_policy.kms_server_protect_policy, + ibm_iam_authorization_policy.kms_block_storage_policy + ] +} + ############################################################################## `; assert.deepEqual( diff --git a/unit-tests/json-to-iac/routing-table.test.js b/unit-tests/json-to-iac/routing-table.test.js index 839deac1..60f265e5 100644 --- a/unit-tests/json-to-iac/routing-table.test.js +++ b/unit-tests/json-to-iac/routing-table.test.js @@ -30,6 +30,41 @@ resource "ibm_is_vpc_routing_table" "management_vpc_routing_table_table" { route_transit_gateway_ingress = true route_vpc_zone_ingress = true } +`; + + assert.deepEqual( + actualData, + expectedData, + "it should return correct terraform" + ); + }); + it("should format a routing table with advertised routes", () => { + let actualData = formatRoutingTable( + { + vpc: "management", + name: "routing-table", + route_direct_link_ingress: true, + transit_gateway_ingress: true, + route_vpc_zone_ingress: true, + advertise_routes_to: ["vpn_server"], + }, + { + _options: { + prefix: "iac", + }, + } + ); + let expectedData = ` +resource "ibm_is_vpc_routing_table" "management_vpc_routing_table_table" { + name = "\${var.prefix}-management-vpc-routing-table-table" + vpc = ibm_is_vpc.management_vpc.id + route_direct_link_ingress = true + route_transit_gateway_ingress = true + route_vpc_zone_ingress = true + advertise_routes_to = [ + "vpn_server" + ] +} `; assert.deepEqual( @@ -67,6 +102,44 @@ resource "ibm_is_vpc_routing_table_route" "management_vpc_routing_table_table_te action = "delegate" next_hop = "0.0.0.0" } +`; + assert.deepEqual( + actualData, + expectedData, + "it should return correct terraform" + ); + }); + it("should format routing table route with advertise and priority", () => { + let actualData = formatRoutingTableRoute( + { + vpc: "management", + routing_table: "routing-table", + name: "test-route", + zone: 1, + destination: "1.2.3.4/5", + action: "delegate", + advertise: true, + priority: "0", + }, + { + _options: { + region: "us-south", + prefix: "iac", + }, + } + ); + let expectedData = ` +resource "ibm_is_vpc_routing_table_route" "management_vpc_routing_table_table_test_route_route" { + vpc = ibm_is_vpc.management_vpc.id + routing_table = ibm_is_vpc_routing_table.management_vpc_routing_table_table.routing_table + zone = "\${var.region}-1" + name = "\${var.prefix}-management-routing-table-test-route-route" + destination = "1.2.3.4/5" + action = "delegate" + next_hop = "0.0.0.0" + advertise = true + priority = "0" +} `; assert.deepEqual( actualData, diff --git a/unit-tests/state/clusters.test.js b/unit-tests/state/clusters.test.js index 76c47a89..69135b58 100644 --- a/unit-tests/state/clusters.test.js +++ b/unit-tests/state/clusters.test.js @@ -433,6 +433,20 @@ describe("clusters", () => { "it should return correct data" ); }); + it("should be not have invalid cos when openshift", () => { + assert.isFalse( + craig.clusters.cos.invalid({}), + "it should not be invalid" + ); + }); + it("should be have invalid cos when openshift and not selected", () => { + assert.isFalse( + craig.clusters.cos.invalid({ + kube_type: "openshift", + }), + "it should not be invalid" + ); + }); it("should return correct groups for cos", () => { assert.deepEqual( craig.clusters.cos.groups({}, { craig: craig }), @@ -665,6 +679,42 @@ describe("clusters", () => { }); }); describe("clusters.opaque_secrets.schema", () => { + it("should have invalid expiration date", () => { + assert.isTrue( + craig.clusters.opaque_secrets.expiration_date.invalid({}), + "it should be invalid" + ); + }); + it("should have invalid username_password_secret_description", () => { + assert.isTrue( + craig.clusters.opaque_secrets.username_password_secret_description.invalid( + { + username_password_secret_description: "@@@", + } + ), + "it should be invalid" + ); + }); + it("should have invalid arbitrary_secret_description", () => { + assert.isTrue( + craig.clusters.opaque_secrets.arbitrary_secret_description.invalid({ + arbitrary_secret_description: "@@@", + }), + "it should be invalid" + ); + }); + it("should have correct invalid values", () => { + assert.isTrue( + craig.clusters.opaque_secrets.labels.invalid({}), + "it should be invalid" + ); + assert.isTrue( + craig.clusters.opaque_secrets.labels.invalid({ + labels: ["@!@@"], + }), + "it should be invalid" + ); + }); it("should return true if arbitrary_secret_name has empty string as name", () => { let actualData = craig.clusters.opaque_secrets.arbitrary_secret_name.invalid( diff --git a/unit-tests/state/power-vs-volumes.test.js b/unit-tests/state/power-vs-volumes.test.js index e6d9c053..9df435e2 100644 --- a/unit-tests/state/power-vs-volumes.test.js +++ b/unit-tests/state/power-vs-volumes.test.js @@ -382,6 +382,14 @@ describe("power_volumes", () => { "it should update attachments" ); }); + it("should have invalid pi_volume_size when out of range", () => { + assert.isTrue( + craig.power_volumes.pi_volume_size.invalid({ + pi_volume_size: -1, + }), + "it should be invalid" + ); + }); it("should not disable volume size when sap and storage log", () => { assert.isFalse( craig.power_volumes.pi_volume_size.disabled({ diff --git a/unit-tests/state/reusable-fields.test.js b/unit-tests/state/reusable-fields.test.js index d4d4e53e..2fed3559 100644 --- a/unit-tests/state/reusable-fields.test.js +++ b/unit-tests/state/reusable-fields.test.js @@ -4,10 +4,16 @@ const { hideWhenNotAllIcmp, onRuleFieldInputChange, invalidPort, + invalidNewResourceName, } = require("../../client/src/lib/state/reusable-fields"); describe("reusable fields", () => { describe("utility functions", () => { + describe("invalidNewResourceName", () => { + it("should be true if no value", () => { + assert.isTrue(invalidNewResourceName(), "it should be false"); + }); + }); describe("onRuleFieldInputChange", () => { it("should set rule when no rule is found", () => { let task = onRuleFieldInputChange("port_max"); diff --git a/unit-tests/state/routing-tables.test.js b/unit-tests/state/routing-tables.test.js index ce3c5f14..296a7489 100644 --- a/unit-tests/state/routing-tables.test.js +++ b/unit-tests/state/routing-tables.test.js @@ -122,6 +122,57 @@ describe("routing_tables", () => { "it should return groups" ); }); + it("should return correct advertise groups", () => { + assert.deepEqual( + craig.routing_tables.advertise_routes_to.groups({}), + [], + "it should return groups" + ); + assert.deepEqual( + craig.routing_tables.advertise_routes_to.groups({ + route_direct_link_ingress: true, + }), + ["Direct Link"], + "it should return groups" + ); + assert.isTrue( + craig.routing_tables.route_direct_link_ingress.disabled({ + advertise_routes_to: ["direct_link"], + }), + "it should be disabled" + ); + assert.isTrue( + craig.routing_tables.transit_gateway_ingress.disabled({ + advertise_routes_to: ["transit_gateway"], + }), + "it should be disabled" + ); + assert.deepEqual( + craig.routing_tables.advertise_routes_to.groups({ + transit_gateway_ingress: true, + }), + ["Transit Gateway"], + "it should return groups" + ); + }); + it("should return list of routes on render", () => { + assert.deepEqual( + craig.routing_tables.advertise_routes_to.onRender({ + advertise_routes_to: ["vpn_server", "vpn_gateway"], + }), + ["VPN Server", "VPN Gateway"], + "it should return groups" + ); + }); + it("should return list of routes on input change", () => { + assert.deepEqual( + craig.routing_tables.advertise_routes_to.onInputChange({ + advertise_routes_to: ["VPN Server", "VPN Gateway"], + }), + ["vpn_server", "vpn_gateway"], + "it should return groups" + ); + }); }); describe("routing_tables.routes", () => { beforeEach(() => { diff --git a/unit-tests/state/schema.test.js b/unit-tests/state/schema.test.js index b2bbe230..e766a3e4 100644 --- a/unit-tests/state/schema.test.js +++ b/unit-tests/state/schema.test.js @@ -1544,6 +1544,7 @@ describe("automate schema generation", () => { transit_gateway_ingress: { type: "boolean", default: false }, route_vpc_zone_ingress: { type: "boolean", default: false }, accept_routes_from_resource_type: { type: "Array", default: [] }, + advertise_routes_to: { type: "Array", default: [] }, routes: { Array: { name: { type: "string", default: null }, @@ -1557,8 +1558,17 @@ describe("automate schema generation", () => { default: null, groups: ["delegate", "deliver", "delegate_vpc", "drop"], }, + advertise: { + default: false, + type: "boolean", + }, next_hop: { type: "string", default: null }, destination: { type: "string", default: null }, + priority: { + default: null, + groups: ["0", "1", "2", "3", "4"], + type: "string", + }, }, }, }, From d20ec6695bd7f6ae3d6329847f0b596af600c99b Mon Sep 17 00:00:00 2001 From: terechc Date: Tue, 9 Apr 2024 12:23:07 -0400 Subject: [PATCH 04/17] Issue 1790: Option to use existing workspace (#1838) * get ws data * typo * quotes * quotes * pause * delete ws * rm power for testing * ws id * vars comments * pause description * print template id * more debug * rm debug * fix when clause * print ids * mv debug to role * move debug * use sepeare vars for ws * ternary * pause debug * move pause * rm second pause * addd back variablestore pause * rm quotes from booleans * use quotes for bool * rm ternary * quotes aroung true/false * always pause before downlaod tar * quotes for if statement * quotes for upload_tar vars * ternary * rm bool uotes upload tar * wait for tar quotes * rm ternary or upload tar * add region to action * rm ternary * fix action url * rm print url * clean up * revert power override * fix Ibm i image * fix Ibm i image again * smh --- ansible/template-test/main.yml | 13 ++++- .../template-test/roles/action/tasks/main.yml | 2 +- .../tasks/main.yml | 6 ++- .../tasks/main.yml | 2 +- .../roles/get_workspace_data/README.md | 38 ++++++++++++++ .../get_workspace_data/defaults/main.yml | 2 + .../get_workspace_data/handlers/main.yml | 2 + .../roles/get_workspace_data/meta/main.yml | 52 +++++++++++++++++++ .../roles/get_workspace_data/tasks/main.yml | 12 +++++ .../roles/get_workspace_data/tests/inventory | 2 + .../roles/get_workspace_data/tests/test.yml | 5 ++ .../roles/get_workspace_data/vars/main.yml | 2 + .../template-test/roles/pause/tasks/main.yml | 4 +- .../update_workspace_variables/tasks/main.yml | 2 +- .../roles/upload_tar/tasks/main.yml | 4 +- ansible/template-test/test-inside-action.yml | 6 ++- .../template_override_vars/from-scratch.yml | 0 .../template_override_vars/power-vs-poc.yml | 2 +- ansible/template-test/vars/vars.template.yml | 9 +++- 19 files changed, 152 insertions(+), 13 deletions(-) create mode 100644 ansible/template-test/roles/get_workspace_data/README.md create mode 100644 ansible/template-test/roles/get_workspace_data/defaults/main.yml create mode 100644 ansible/template-test/roles/get_workspace_data/handlers/main.yml create mode 100644 ansible/template-test/roles/get_workspace_data/meta/main.yml create mode 100644 ansible/template-test/roles/get_workspace_data/tasks/main.yml create mode 100644 ansible/template-test/roles/get_workspace_data/tests/inventory create mode 100644 ansible/template-test/roles/get_workspace_data/tests/test.yml create mode 100644 ansible/template-test/roles/get_workspace_data/vars/main.yml create mode 100644 ansible/template-test/vars/template_override_vars/from-scratch.yml diff --git a/ansible/template-test/main.yml b/ansible/template-test/main.yml index 7d55d038..e4012723 100644 --- a/ansible/template-test/main.yml +++ b/ansible/template-test/main.yml @@ -12,8 +12,14 @@ - ./vars/template_override_vars/{{template}}.yml roles: - role: get_iam_token + - role: pause + vars: + pause_time: 1 - role: download_tar + - role: get_workspace_data + when: use_existing_workspace == "true" - role: create_schematics_workspace + when: use_existing_workspace == "false" vars: description: Automated CRAIG Testing Workspace - role: upload_tar @@ -22,6 +28,9 @@ vars_files: ./vars/vars.template.yml roles: - role: update_variablestore + - role: pause + vars: + pause_time: 1 - role: update_workspace_variables - name: Deploy CRAIG terraform template hosts: localhost @@ -78,4 +87,6 @@ roles: - role: get_iam_token - role: delete_schematics_workspace - when: job_status != "job_failed" \ No newline at end of file + when: + - job_status != "job_failed" + - use_existing_workspace == "false" \ No newline at end of file diff --git a/ansible/template-test/roles/action/tasks/main.yml b/ansible/template-test/roles/action/tasks/main.yml index 661382e8..d846f271 100644 --- a/ansible/template-test/roles/action/tasks/main.yml +++ b/ansible/template-test/roles/action/tasks/main.yml @@ -2,7 +2,7 @@ # tasks file for action - name: Start {{ action_type }} action uri: - url: https://schematics.cloud.ibm.com/v1/workspaces/{{ workspace.json.id }}/{{ action_type }} + url: "https://schematics.cloud.ibm.com/v1/workspaces/{{ existing_workspace_id if use_existing_workspace == 'true' else new_workspace_id }}/{{ action_type }}" method: "{{ 'POST' if action_type == 'plan' else 'PUT' }}" body_format: json headers: diff --git a/ansible/template-test/roles/create_schematics_workspace/tasks/main.yml b/ansible/template-test/roles/create_schematics_workspace/tasks/main.yml index 3e286dca..dc22315a 100644 --- a/ansible/template-test/roles/create_schematics_workspace/tasks/main.yml +++ b/ansible/template-test/roles/create_schematics_workspace/tasks/main.yml @@ -16,4 +16,8 @@ "tags": ["craig"] "template_data": [{ "type": "terraform_v1.5"}] status_code: 201 - register: workspace \ No newline at end of file + register: workspace +- name: Store Workspace IDs + set_fact: + new_workspace_id: "{{ workspace.json.id }}" + template_id: "{{ workspace.json.template_data[0].id }}" \ No newline at end of file diff --git a/ansible/template-test/roles/delete_schematics_workspace/tasks/main.yml b/ansible/template-test/roles/delete_schematics_workspace/tasks/main.yml index 33db1ffd..95ba5547 100644 --- a/ansible/template-test/roles/delete_schematics_workspace/tasks/main.yml +++ b/ansible/template-test/roles/delete_schematics_workspace/tasks/main.yml @@ -2,7 +2,7 @@ # tasks file for delete_schematics_workspace - name: Delete Schematics Workspace uri: - url: https://schematics.cloud.ibm.com/v1/workspaces/{{workspace.json.id}} + url: https://schematics.cloud.ibm.com/v1/workspaces/{{ new_workspace_id }} method: DELETE headers: Authorization: Bearer {{token.json.access_token}} diff --git a/ansible/template-test/roles/get_workspace_data/README.md b/ansible/template-test/roles/get_workspace_data/README.md new file mode 100644 index 00000000..225dd44b --- /dev/null +++ b/ansible/template-test/roles/get_workspace_data/README.md @@ -0,0 +1,38 @@ +Role Name +========= + +A brief description of the role goes here. + +Requirements +------------ + +Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. + +Role Variables +-------------- + +A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. + +Dependencies +------------ + +A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. + +Example Playbook +---------------- + +Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: + + - hosts: servers + roles: + - { role: username.rolename, x: 42 } + +License +------- + +BSD + +Author Information +------------------ + +An optional section for the role authors to include contact information, or a website (HTML is not allowed). diff --git a/ansible/template-test/roles/get_workspace_data/defaults/main.yml b/ansible/template-test/roles/get_workspace_data/defaults/main.yml new file mode 100644 index 00000000..a6fd895e --- /dev/null +++ b/ansible/template-test/roles/get_workspace_data/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for get_workspace_data diff --git a/ansible/template-test/roles/get_workspace_data/handlers/main.yml b/ansible/template-test/roles/get_workspace_data/handlers/main.yml new file mode 100644 index 00000000..d8cdb732 --- /dev/null +++ b/ansible/template-test/roles/get_workspace_data/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for get_workspace_data diff --git a/ansible/template-test/roles/get_workspace_data/meta/main.yml b/ansible/template-test/roles/get_workspace_data/meta/main.yml new file mode 100644 index 00000000..c572acc9 --- /dev/null +++ b/ansible/template-test/roles/get_workspace_data/meta/main.yml @@ -0,0 +1,52 @@ +galaxy_info: + author: your name + description: your role description + company: your company (optional) + + # If the issue tracker for your role is not on github, uncomment the + # next line and provide a value + # issue_tracker_url: http://example.com/issue/tracker + + # Choose a valid license ID from https://spdx.org - some suggested licenses: + # - BSD-3-Clause (default) + # - MIT + # - GPL-2.0-or-later + # - GPL-3.0-only + # - Apache-2.0 + # - CC-BY-4.0 + license: license (GPL-2.0-or-later, MIT, etc) + + min_ansible_version: 2.1 + + # If this a Container Enabled role, provide the minimum Ansible Container version. + # min_ansible_container_version: + + # + # Provide a list of supported platforms, and for each platform a list of versions. + # If you don't wish to enumerate all versions for a particular platform, use 'all'. + # To view available platforms and versions (or releases), visit: + # https://galaxy.ansible.com/api/v1/platforms/ + # + # platforms: + # - name: Fedora + # versions: + # - all + # - 25 + # - name: SomePlatform + # versions: + # - all + # - 1.0 + # - 7 + # - 99.99 + + galaxy_tags: [] + # List tags for your role here, one per line. A tag is a keyword that describes + # and categorizes the role. Users find roles by searching for tags. Be sure to + # remove the '[]' above, if you add tags to this list. + # + # NOTE: A tag is limited to a single word comprised of alphanumeric characters. + # Maximum 20 tags per role. + +dependencies: [] + # List your role dependencies here, one per line. Be sure to remove the '[]' above, + # if you add dependencies to this list. diff --git a/ansible/template-test/roles/get_workspace_data/tasks/main.yml b/ansible/template-test/roles/get_workspace_data/tasks/main.yml new file mode 100644 index 00000000..05dd1337 --- /dev/null +++ b/ansible/template-test/roles/get_workspace_data/tasks/main.yml @@ -0,0 +1,12 @@ +--- +# tasks file for get_workspace_data +- name: Get workspace data + uri: + url: "https://schematics.cloud.ibm.com/v1/workspaces/{{existing_workspace_id}}" + method: GET + headers: + Authorization: Bearer {{token.json.access_token}} + register: workspace_data +- name: Store template_id from workspace + set_fact: + template_id: "{{workspace_data.json.template_data[0].id}}" diff --git a/ansible/template-test/roles/get_workspace_data/tests/inventory b/ansible/template-test/roles/get_workspace_data/tests/inventory new file mode 100644 index 00000000..878877b0 --- /dev/null +++ b/ansible/template-test/roles/get_workspace_data/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/ansible/template-test/roles/get_workspace_data/tests/test.yml b/ansible/template-test/roles/get_workspace_data/tests/test.yml new file mode 100644 index 00000000..54820e40 --- /dev/null +++ b/ansible/template-test/roles/get_workspace_data/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - get_workspace_data diff --git a/ansible/template-test/roles/get_workspace_data/vars/main.yml b/ansible/template-test/roles/get_workspace_data/vars/main.yml new file mode 100644 index 00000000..c5cd5670 --- /dev/null +++ b/ansible/template-test/roles/get_workspace_data/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for get_workspace_data diff --git a/ansible/template-test/roles/pause/tasks/main.yml b/ansible/template-test/roles/pause/tasks/main.yml index ce695ae1..9d45c93d 100644 --- a/ansible/template-test/roles/pause/tasks/main.yml +++ b/ansible/template-test/roles/pause/tasks/main.yml @@ -1,4 +1,4 @@ --- -- name: Pause playbook for 5 minutes +- name: Pause playbook for "{{ pause_time }}" minute(s) ansible.builtin.pause: - minutes: "{{ pause_time }}" + minutes: "{{ pause_time }}" \ No newline at end of file diff --git a/ansible/template-test/roles/update_workspace_variables/tasks/main.yml b/ansible/template-test/roles/update_workspace_variables/tasks/main.yml index 24620002..77cf18ff 100644 --- a/ansible/template-test/roles/update_workspace_variables/tasks/main.yml +++ b/ansible/template-test/roles/update_workspace_variables/tasks/main.yml @@ -2,7 +2,7 @@ # tasks file for update_workspace_variables - name: Update Workspace Variables uri: - url: https://schematics.cloud.ibm.com/v1/workspaces/{{ workspace.json.id }}/template_data/{{ workspace.json.template_data[0].id }}/values + url: "https://schematics.cloud.ibm.com/v1/workspaces/{{ existing_workspace_id if use_existing_workspace == 'true' else new_workspace_id }}/template_data/{{ template_id }}/values" method: PUT headers: Authorization: Bearer {{token.json.access_token}} diff --git a/ansible/template-test/roles/upload_tar/tasks/main.yml b/ansible/template-test/roles/upload_tar/tasks/main.yml index dc25fd1a..21dc9ac3 100644 --- a/ansible/template-test/roles/upload_tar/tasks/main.yml +++ b/ansible/template-test/roles/upload_tar/tasks/main.yml @@ -2,13 +2,13 @@ # tasks file for upload_tar - name: Upload {{template}}.tar to Schematics Workspace ansible.builtin.shell: "curl -s --request PUT \ - --url 'https://schematics.cloud.ibm.com/v1/workspaces/{{ workspace.json.id }}/template_data/{{ workspace.json.template_data[0].id }}/template_repo_upload' \ + --url 'https://schematics.cloud.ibm.com/v1/workspaces/{{ existing_workspace_id if use_existing_workspace == 'true' else new_workspace_id }}/template_data/{{ template_id }}/template_repo_upload' \ -H 'Authorization: Bearer {{ token.json.access_token }}' \ -H 'Content-Type: multipart/form-data' \ --form 'file=@{{playbook_dir}}/{{template}}.tar'" - name: Wait until {{template}}.tar has been successfully uploaded uri: - url: https://schematics.cloud.ibm.com/v1/workspaces/{{ workspace.json.id }} + url: "https://schematics.cloud.ibm.com/v1/workspaces/{{ existing_workspace_id if use_existing_workspace == 'true' else new_workspace_id }}" method: GET body_format: json headers: diff --git a/ansible/template-test/test-inside-action.yml b/ansible/template-test/test-inside-action.yml index 467a5299..afcee45f 100644 --- a/ansible/template-test/test-inside-action.yml +++ b/ansible/template-test/test-inside-action.yml @@ -34,5 +34,9 @@ metadata: secure: true - name: tf_var_ssh_key - value: "{{ tf_var_ssh_key }}" + value: "{{ tf_var_ssh_key }}" + - name: use_existing_workspace + value: "{{ use_existing_workspace }}" + - name: existing_workspace_id + value: "{{ existing_workspace_id }}" - role: run_schematics_action \ No newline at end of file diff --git a/ansible/template-test/vars/template_override_vars/from-scratch.yml b/ansible/template-test/vars/template_override_vars/from-scratch.yml new file mode 100644 index 00000000..e69de29b diff --git a/ansible/template-test/vars/template_override_vars/power-vs-poc.yml b/ansible/template-test/vars/template_override_vars/power-vs-poc.yml index f982ae31..ac6c2405 100644 --- a/ansible/template-test/vars/template_override_vars/power-vs-poc.yml +++ b/ansible/template-test/vars/template_override_vars/power-vs-poc.yml @@ -85,7 +85,7 @@ override_craig: - name: "pvm" ip_address: "" ssh_key: "powervs-ssh-key" - image: "IBMi-75-01-2924-2" + image: "IBMi-75-03-2984-1" pi_sys_type: "s922" pi_proc_type: "shared" pi_processors: "0.25" diff --git a/ansible/template-test/vars/vars.template.yml b/ansible/template-test/vars/vars.template.yml index dfcc7c79..2aa767a9 100644 --- a/ansible/template-test/vars/vars.template.yml +++ b/ansible/template-test/vars/vars.template.yml @@ -2,14 +2,19 @@ # To run this playbook , copy this file to `vars.yml` and fill in your data # tf_var_preshared_key is only needed for the POC template +# Terrafrom variabes tf_var_api_key: "" tf_var_ssh_key: "" tf_var_prefix: "" tf_var_preshared_key: "" # 6-128 characters -workspace_name: "" -template: "