From 062b55d15ff7c4a47b337c36e114047d6bbff293 Mon Sep 17 00:00:00 2001 From: jvallexm Date: Mon, 11 Mar 2024 13:50:51 -0400 Subject: [PATCH 01/29] Issue 1637: VTL Tile (#1645) * feat: power vs v2 enhancements * fix: changelog * feat: vtl tile * fix: display on v2 --- CHANGELOG.md | 12 +++++ client/src/components/forms/DynamicForm.js | 6 ++- .../forms/dynamic-form/PowerInterfaces.js | 28 ++++++++++- client/src/components/pages/CraigForms.js | 28 ++++++++--- client/src/components/pages/power/Power.js | 4 +- client/src/lib/docs/release-notes.json | 8 +++ .../power-instances-schema.js | 46 ++++++++++++++--- client/src/lib/state/utils.js | 13 +++-- unit-tests/forms/wizard.test.js | 24 ++++----- unit-tests/json-to-iac/outputs.test.js | 2 +- unit-tests/json-to-iac/variables.test.js | 2 +- unit-tests/state/vtl.test.js | 49 +++++++++++++++++++ 12 files changed, 187 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a47d8bc7..5d32c7b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. +## 1.13.0 + +### Upgrade Notes + + +### Features + +- Users will now get feedback when a Power VS Workspace has no VTL images when trying to create or update a VTL instance. Images can be added from the Power VS Workspace form + +### Fixes + + ## 1.12.2 ### Upgrade Notes diff --git a/client/src/components/forms/DynamicForm.js b/client/src/components/forms/DynamicForm.js index fb2c379e..73906d78 100644 --- a/client/src/components/forms/DynamicForm.js +++ b/client/src/components/forms/DynamicForm.js @@ -266,9 +266,11 @@ class DynamicForm extends React.Component {
{this.props.form.groups.map((group, index) => - group.hideWhen && group.hideWhen(this.state) ? ( + group.hideWhen && group.hideWhen(this.state, this.props) ? ( "" - ) : group.heading && group.hideWhen && group.hideWhen(this.state) ? ( + ) : group.heading && + group.hideWhen && + group.hideWhen(this.state, this.props) ? ( "" ) : group.heading ? ( diff --git a/client/src/components/forms/dynamic-form/PowerInterfaces.js b/client/src/components/forms/dynamic-form/PowerInterfaces.js index 26d53f85..b025b9b1 100644 --- a/client/src/components/forms/dynamic-form/PowerInterfaces.js +++ b/client/src/components/forms/dynamic-form/PowerInterfaces.js @@ -2,11 +2,35 @@ import React from "react"; import PropTypes from "prop-types"; import { Network_3 } from "@carbon/icons-react"; import { DynamicFormTextInput } from "./components"; -import { contains } from "lazy-z"; +import { contains, isEmpty, isNullOrEmptyString } from "lazy-z"; import { CraigFormGroup } from "../utils"; +import { CraigEmptyResourceTile } from "./tiles"; export const PowerInterfaces = (props) => { - return contains(["Power Instances", "VTL"], props.componentProps.formName) ? ( + let isVtl = contains(["VTL"], props.componentProps.formName); + return isVtl && + (!props.stateData.workspace || + isEmpty( + props.componentProps.craig.vtl.image.groups( + props.stateData, + props.componentProps + ) + )) ? ( + + VTL images selected in Power VS workspace{" "} + {props.stateData.workspace}. + Add images from the Power VS form + + ) + } + /> + ) : contains(["Power Instances", "VTL"], props.componentProps.formName) ? (
{props.stateData.network.map((nw, index) => { return ( diff --git a/client/src/components/pages/CraigForms.js b/client/src/components/pages/CraigForms.js index f02b8b70..c7696a11 100644 --- a/client/src/components/pages/CraigForms.js +++ b/client/src/components/pages/CraigForms.js @@ -1,4 +1,4 @@ -const { contains } = require("lazy-z"); +const { contains, isNullOrEmptyString, isEmpty } = require("lazy-z"); const { edgeRouterEnabledZones } = require("../../lib/constants"); const { disableSave } = require("../../lib"); @@ -1017,6 +1017,7 @@ function craigForms(craig) { }, { name: craig.power_instances.name, + ssh_key: craig.power_instances.ssh_key, workspace: craig.power_instances.workspace, }, { @@ -1024,19 +1025,16 @@ function craigForms(craig) { primary_subnet: craig.power_instances.primary_subnet, }, { - ssh_key: craig.power_instances.ssh_key, image: craig.power_instances.image, pi_sys_type: craig.power_instances.pi_sys_type, + pi_storage_pool_affinity: + craig.power_instances.pi_storage_pool_affinity, }, { pi_proc_type: craig.power_instances.pi_proc_type, pi_processors: craig.power_instances.pi_processors, pi_memory: craig.power_instances.pi_memory, }, - { - pi_storage_pool_affinity: - craig.power_instances.pi_storage_pool_affinity, - }, { pi_ibmi_css: craig.power_instances.pi_ibmi_css, pi_ibmi_pha: craig.power_instances.pi_ibmi_pha, @@ -1047,6 +1045,9 @@ function craigForms(craig) { name: "Boot Volume", type: "subHeading", }, + hideWhen: function (stateData) { + return isNullOrEmptyString(stateData.workspace, true); + }, }, { pi_storage_type: craig.power_instances.pi_storage_type, @@ -1070,6 +1071,9 @@ function craigForms(craig) { name: "IP Interface Options", type: "subHeading", }, + hideWhen: function (stateData) { + return isNullOrEmptyString(stateData.workspace, true); + }, }, ], }, @@ -1572,6 +1576,12 @@ function craigForms(craig) { name: "Boot Volume", type: "subHeading", }, + hideWhen: function (stateData, componentProps) { + return ( + isNullOrEmptyString(stateData.workspace, true) || + isEmpty(craig.vtl.image.groups(stateData, componentProps)) + ); + }, }, { storage_option: craig.vtl.storage_option, @@ -1588,6 +1598,12 @@ function craigForms(craig) { name: "IP Interface Options", type: "subHeading", }, + hideWhen: function (stateData, componentProps) { + return ( + isNullOrEmptyString(stateData.workspace, true) || + isEmpty(craig.vtl.image.groups(stateData, componentProps)) + ); + }, }, ], }, diff --git a/client/src/components/pages/power/Power.js b/client/src/components/pages/power/Power.js index 4a9013a8..cf0b6278 100644 --- a/client/src/components/pages/power/Power.js +++ b/client/src/components/pages/power/Power.js @@ -306,7 +306,9 @@ class PowerDiagram extends React.Component { craig={craig} modalService={this.state.modalService} formName={ - this.state.modalService === "power_volumes" + this.state.modalService === "vtl" + ? "VTL" + : this.state.modalService === "power_volumes" ? undefined : "Power Instances" } diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index 10e70fa2..e5beb99a 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -1,4 +1,12 @@ [ + { + "version": "1.13.0", + "features": [ + "Users will now get feedback when a Power VS Workspace has no VTL images when trying to create or update a VTL instance. Images can be added from the Power VS Workspace form" + ], + "fixes": [], + "upgrade_notes": [] + }, { "version": "1.12.2", "features": [ diff --git a/client/src/lib/state/power-vs-instances/power-instances-schema.js b/client/src/lib/state/power-vs-instances/power-instances-schema.js index 0fa8d2fd..d34c64a4 100644 --- a/client/src/lib/state/power-vs-instances/power-instances-schema.js +++ b/client/src/lib/state/power-vs-instances/power-instances-schema.js @@ -29,6 +29,23 @@ const { const { sapProfiles, systemTypes } = require("../../constants"); const { nameField } = require("../reusable-fields"); +/** + * hide field for vtl forms when no workspace is selected + * @param {*} vtl + * @returns {Function} hideWhen function + */ +function hideWhenNoWorkspaceAndVtl(vtl) { + return function (stateData, componentProps) { + return ( + vtl && + (isNullOrEmptyString(stateData.workspace, true) || + isEmpty( + componentProps.craig.vtl.image.groups(stateData, componentProps) + )) + ); + }; +} + /** * Network invalidation for powerVs instance * @returns {boolean} function will evaluate to true if should be disabled @@ -258,6 +275,7 @@ function powerVsInstanceSchema(vtl) { forceUpdateKey: function (stateData) { return stateData.workspace; }, + hideWhen: hideWhenNoWorkspaceAndVtl(vtl), }, primary_subnet: { labelText: "Primary Subnet", @@ -289,6 +307,7 @@ function powerVsInstanceSchema(vtl) { } else stateData.primary_subnet = ""; }, size: "small", + hideWhen: hideWhenNoWorkspaceAndVtl(vtl), }, ssh_key: { labelText: "SSH Key", @@ -310,6 +329,7 @@ function powerVsInstanceSchema(vtl) { } }, size: "small", + hideWhen: hideWhenNoWorkspaceAndVtl(vtl), }, image: { size: "small", @@ -330,6 +350,7 @@ function powerVsInstanceSchema(vtl) { }); } }, + hideWhen: hideWhenNoWorkspaceAndVtl(vtl), }, pi_sys_type: { size: "small", @@ -339,6 +360,7 @@ function powerVsInstanceSchema(vtl) { invalidText: selectInvalidText("systen type"), groups: vtl ? ["s922", "e980"] : systemTypes, type: "select", + hideWhen: hideWhenNoWorkspaceAndVtl(vtl), }, pi_proc_type: { default: "", @@ -349,12 +371,16 @@ function powerVsInstanceSchema(vtl) { type: "select", onRender: titleCaseRender("pi_proc_type"), onInputChange: kebabCaseInput("pi_proc_type"), + hideWhen: hideWhenNoWorkspaceAndVtl(vtl), }, pi_processors: { labelText: "Processors", placeholder: vtl ? 1 : "0.25", - hideWhen: function (stateData) { - return stateData.sap === true; + hideWhen: function (stateData, componentProps) { + return ( + stateData.sap === true || + hideWhenNoWorkspaceAndVtl(vtl)(stateData, componentProps) + ); }, size: "small", default: "", @@ -362,8 +388,11 @@ function powerVsInstanceSchema(vtl) { invalidText: invalidPowerVsProcessorTextCallback(true), }, pi_memory: { - hideWhen: function (stateData) { - return stateData.sap === true; + hideWhen: function (stateData, componentProps) { + return ( + stateData.sap === true || + hideWhenNoWorkspaceAndVtl(vtl)(stateData, componentProps) + ); }, labelText: "Memory (GB)", placeholder: "4", @@ -383,10 +412,14 @@ function powerVsInstanceSchema(vtl) { content: "To attach data volumes from different storage pools, set to false. When this is set to false it cannot be set to true without re-creation of instance.", }, + hideWhen: hideWhenNoWorkspaceAndVtl(vtl), }, pi_storage_pool: powerStoragePoolSelect(), - storage_option: powerVsStorageOptions(), - pi_storage_type: powerVsStorageType(), + storage_option: powerVsStorageOptions( + false, + hideWhenNoWorkspaceAndVtl(vtl) + ), + pi_storage_type: powerVsStorageType(false, hideWhenNoWorkspaceAndVtl(vtl)), affinity_type: powerVsAffinityType(), pi_affinity_policy: { default: null, @@ -406,6 +439,7 @@ function powerVsInstanceSchema(vtl) { : !isWholeNumber(parseInt(stateData.pi_license_repository_capacity)); }, invalidText: unconditionalInvalidText("Enter a whole number"), + hideWhen: hideWhenNoWorkspaceAndVtl(vtl), }, pi_ibmi_css: { size: "small", diff --git a/client/src/lib/state/utils.js b/client/src/lib/state/utils.js index c97e0f45..480cc3fd 100644 --- a/client/src/lib/state/utils.js +++ b/client/src/lib/state/utils.js @@ -878,9 +878,10 @@ function powerVsWorkspaceGroups(stateData, componentProps) { /** * get power vs storage options field * @param {boolean=} isVolume true if is volume + * @param {Function=} hideWhen override hidewhen function * @returns {object} schema object */ -function powerVsStorageOptions(isVolume) { +function powerVsStorageOptions(isVolume, hideWhen) { return { size: "small", default: "", @@ -947,6 +948,7 @@ function powerVsStorageOptions(isVolume) { } stateData.affinity_type = null; }, + hideWhen: hideWhen, }; } @@ -955,7 +957,7 @@ function powerVsStorageOptions(isVolume) { * @param {boolean=} isVolume true if is volume * @returns {object} schema object */ -function powerVsStorageType(isVolume) { +function powerVsStorageType(isVolume, hideWhen) { let storageField = isVolume ? "pi_volume_type" : "pi_storage_type"; return { size: "small", @@ -982,8 +984,11 @@ function powerVsStorageType(isVolume) { apiEndpoint: function (stateData) { return `/api/power/${stateData.zone}/storage-tiers`; }, - hideWhen: function (stateData) { - return isNullOrEmptyString(stateData.zone, true); + hideWhen: function (stateData, componentProps) { + return ( + (hideWhen && hideWhen(stateData, componentProps)) || + isNullOrEmptyString(stateData.zone, true) + ); }, }; } diff --git a/unit-tests/forms/wizard.test.js b/unit-tests/forms/wizard.test.js index b622555e..dc56b247 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.2", + craig_version: "1.13.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -973,7 +973,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.2", + craig_version: "1.13.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -1888,7 +1888,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.2", + craig_version: "1.13.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -2397,7 +2397,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.2", + craig_version: "1.13.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -2897,7 +2897,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.2", + craig_version: "1.13.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -3474,7 +3474,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.2", + craig_version: "1.13.0", no_vpn_secrets_manager_auth: false, }, resource_groups: [ @@ -3642,7 +3642,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.2", + craig_version: "1.13.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -4211,7 +4211,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.2", + craig_version: "1.13.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -4754,7 +4754,7 @@ describe("setup wizard", () => { enable_power_vs: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.2", + craig_version: "1.13.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -5302,7 +5302,7 @@ describe("setup wizard", () => { enable_classic: false, enable_classic: false, power_vs_zones: [], - craig_version: "1.12.2", + craig_version: "1.13.0", no_vpn_secrets_manager_auth: false, }, resource_groups: [ @@ -5792,7 +5792,7 @@ describe("setup wizard", () => { enable_power_vs: true, enable_classic: false, power_vs_zones: ["dal10"], - craig_version: "1.12.2", + craig_version: "1.13.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, @@ -6292,7 +6292,7 @@ describe("setup wizard", () => { enable_power_vs: true, enable_classic: false, power_vs_zones: ["dal10"], - craig_version: "1.12.2", + craig_version: "1.13.0", power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, }, diff --git a/unit-tests/json-to-iac/outputs.test.js b/unit-tests/json-to-iac/outputs.test.js index f04756e8..69505224 100644 --- a/unit-tests/json-to-iac/outputs.test.js +++ b/unit-tests/json-to-iac/outputs.test.js @@ -1237,7 +1237,7 @@ output "management_vpc_jv_dev_server2_vsi_1_1_floating_ip_address" { enable_classic: false, dynamic_subnets: true, enable_power_vs: true, - craig_version: "1.12.2", + craig_version: "1.13.0", power_vs_zones: ["us-south", "dal10", "dal12"], power_vs_high_availability: false, no_vpn_secrets_manager_auth: false, diff --git a/unit-tests/json-to-iac/variables.test.js b/unit-tests/json-to-iac/variables.test.js index 6b2b6a70..303e806b 100644 --- a/unit-tests/json-to-iac/variables.test.js +++ b/unit-tests/json-to-iac/variables.test.js @@ -928,7 +928,7 @@ variable "secrets_manager_example_secret_password" { enable_power_vs: true, enable_classic: false, power_vs_zones: ["dal10"], - craig_version: "1.12.2", + craig_version: "1.13.0", power_vs_high_availability: false, template: "Empty Project", fs_cloud: false, diff --git a/unit-tests/state/vtl.test.js b/unit-tests/state/vtl.test.js index 90ad0fb9..4766f4bb 100644 --- a/unit-tests/state/vtl.test.js +++ b/unit-tests/state/vtl.test.js @@ -1285,6 +1285,55 @@ describe("vtl", () => { beforeEach(() => { craig = newState(); }); + describe("vtl.pi_proc_type.hideWhen", () => { + it("should hide when no workspace is selected or no vtl images", () => { + assert.isTrue( + craig.vtl.pi_proc_type.hideWhen({}, {}), + "it should be hidden" + ); + craig.power.create({ name: "bad" }); + assert.isTrue( + craig.vtl.pi_proc_type.hideWhen( + { workspace: "bad" }, + { craig: craig } + ), + "it should be hidden" + ); + assert.isTrue( + craig.vtl.pi_processors.hideWhen( + { workspace: "bad" }, + { craig: craig } + ), + "it should be hidden" + ); + assert.isTrue( + craig.vtl.pi_memory.hideWhen({ workspace: "bad" }, { craig: craig }), + "it should be hidden" + ); + assert.isTrue( + craig.vtl.storage_option.hideWhen( + { workspace: "bad" }, + { craig: craig } + ), + "it should be hidden" + ); + assert.isTrue( + craig.vtl.storage_option.hideWhen( + { workspace: "bad", zone: "" }, + { craig: craig } + ), + "it should be hidden" + ); + craig.store.json.power[0].imageNames = ["VTL"]; + assert.isFalse( + craig.vtl.storage_option.hideWhen( + { workspace: "bad", zone: "yes" }, + { craig: craig } + ), + "it should not be hidden" + ); + }); + }); describe("vtl.pi_license_repository_capacity.invalid", () => { it("should return true when empty string", () => { assert.isTrue( From a2c37569ae28e9fab6bf13556a58778c43baf7a2 Mon Sep 17 00:00:00 2001 From: Pseusco Date: Mon, 11 Mar 2024 14:20:06 -0400 Subject: [PATCH 02/29] Issue 1613: Ansible role for download tar (#1646) * added power workspace outputs guid and name Signed-off-by: Lucas-Franke * updated for data Signed-off-by: Lucas-Franke * add newline after tf blocks, update tests Signed-off-by: Lucas-Franke * power below vpc Signed-off-by: Lucas-Franke * download tar role Signed-off-by: Lucas-Franke --------- Signed-off-by: Lucas-Franke --- ansible/template-test/main.yml | 9 +--- .../roles/download_tar/README.md | 38 ++++++++++++++ .../roles/download_tar/defaults/main.yml | 2 + .../roles/download_tar/handlers/main.yml | 2 + .../roles/download_tar/meta/main.yml | 52 +++++++++++++++++++ .../roles/download_tar/tasks/main.yml | 8 +++ .../roles/download_tar/tests/inventory | 2 + .../roles/download_tar/tests/test.yml | 5 ++ .../roles/download_tar/vars/main.yml | 2 + 9 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 ansible/template-test/roles/download_tar/README.md create mode 100644 ansible/template-test/roles/download_tar/defaults/main.yml create mode 100644 ansible/template-test/roles/download_tar/handlers/main.yml create mode 100644 ansible/template-test/roles/download_tar/meta/main.yml create mode 100644 ansible/template-test/roles/download_tar/tasks/main.yml create mode 100644 ansible/template-test/roles/download_tar/tests/inventory create mode 100644 ansible/template-test/roles/download_tar/tests/test.yml create mode 100644 ansible/template-test/roles/download_tar/vars/main.yml diff --git a/ansible/template-test/main.yml b/ansible/template-test/main.yml index 18405485..a4a542b3 100644 --- a/ansible/template-test/main.yml +++ b/ansible/template-test/main.yml @@ -18,13 +18,8 @@ - name: "Download Template Tarball" hosts: localhost vars_files: ./vars/vars.yml - tasks: - - name: Download {{template}}.tar to current directory - get_url: - url: "{{craig_url}}/{{template}}" - dest: "{{playbook_dir}}/{{template}}.tar" - async: 120 - retries: 10 + roles: + - role: download_tar - name: Create Schematics Workspace hosts: localhost vars_files: ./vars/vars.yml # variables declared in variables file are added to role automatically diff --git a/ansible/template-test/roles/download_tar/README.md b/ansible/template-test/roles/download_tar/README.md new file mode 100644 index 00000000..225dd44b --- /dev/null +++ b/ansible/template-test/roles/download_tar/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/download_tar/defaults/main.yml b/ansible/template-test/roles/download_tar/defaults/main.yml new file mode 100644 index 00000000..e2612f9b --- /dev/null +++ b/ansible/template-test/roles/download_tar/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for download_tar diff --git a/ansible/template-test/roles/download_tar/handlers/main.yml b/ansible/template-test/roles/download_tar/handlers/main.yml new file mode 100644 index 00000000..8954f993 --- /dev/null +++ b/ansible/template-test/roles/download_tar/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for download_tar diff --git a/ansible/template-test/roles/download_tar/meta/main.yml b/ansible/template-test/roles/download_tar/meta/main.yml new file mode 100644 index 00000000..c572acc9 --- /dev/null +++ b/ansible/template-test/roles/download_tar/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/download_tar/tasks/main.yml b/ansible/template-test/roles/download_tar/tasks/main.yml new file mode 100644 index 00000000..35686117 --- /dev/null +++ b/ansible/template-test/roles/download_tar/tasks/main.yml @@ -0,0 +1,8 @@ +--- +# tasks file for download_tar +- name: Download {{template}}.tar to current directory + get_url: + url: "{{craig_url}}/{{template}}" + dest: "{{playbook_dir}}/{{template}}.tar" + async: 120 + retries: 10 \ No newline at end of file diff --git a/ansible/template-test/roles/download_tar/tests/inventory b/ansible/template-test/roles/download_tar/tests/inventory new file mode 100644 index 00000000..878877b0 --- /dev/null +++ b/ansible/template-test/roles/download_tar/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/ansible/template-test/roles/download_tar/tests/test.yml b/ansible/template-test/roles/download_tar/tests/test.yml new file mode 100644 index 00000000..a45b7871 --- /dev/null +++ b/ansible/template-test/roles/download_tar/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - download_tar diff --git a/ansible/template-test/roles/download_tar/vars/main.yml b/ansible/template-test/roles/download_tar/vars/main.yml new file mode 100644 index 00000000..c9373e95 --- /dev/null +++ b/ansible/template-test/roles/download_tar/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for download_tar From d1fff44802cfd240884a3eaac306b714be281cf1 Mon Sep 17 00:00:00 2001 From: Samuel Matzek Date: Mon, 11 Mar 2024 15:58:17 -0500 Subject: [PATCH 03/29] Update README for docker ports (#1650) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a51f2ef..a21dc1c3 100644 --- a/README.md +++ b/README.md @@ -161,9 +161,11 @@ Make sure to set the `API_KEY` variable in a `.env` file to be used for IBM Clou See `.env.example` found [here](./.env.example) ```shell -docker run -it --env-file .env -- craig +docker run -t -d -p 8080:8080 --env-file .env -- craig ``` +CRAIG is now available at http://localhost:8080. + --- ### Setting Up CRAIG Development Environment From b5e70b4a4fc022b8f0ed1a825df47e9c850aaa69 Mon Sep 17 00:00:00 2001 From: Ay1man2 Date: Mon, 11 Mar 2024 13:58:36 -0700 Subject: [PATCH 04/29] Issue 1461 - Popover overflow (#1643) * updated version # to 1.12.2, added CSS styling and alignment to delete button to shift text to the left * updated to v1.13.0, fixed hoverText alignment for primary and secondary buttons + footer nav bar in classic craig, updated delete button hoverText to say Delete Resource * prettier formatting * added check for v2 in pathname for delete button hovertext * prettier formatting * updated param doc for dynamicSecondaryButtonProps to include isV2Page --- CHANGELOG.md | 1 + client/package-lock.json | 4 +- client/package.json | 2 +- client/src/app.scss | 4 ++ .../components/forms/utils/PrimaryButton.js | 2 +- .../components/forms/utils/SecondaryButton.js | 8 ++- .../forms/utils/ToggleFormComponents.js | 11 ++-- .../lib/components/toggle-form-components.js | 5 +- client/src/lib/docs/release-notes.json | 4 +- .../docs/templates/power-poc-quick-start.json | 2 +- package-lock.json | 4 +- package.json | 2 +- .../components/toggle-form-components.test.js | 53 ++++++++++++++----- unit-tests/json-to-iac/outputs.test.js | 6 +-- 14 files changed, 77 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d32c7b8..66abc166 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 with button hoverText alignment causing overflow in forms ## 1.12.2 diff --git a/client/package-lock.json b/client/package-lock.json index 1c1f469f..c78ac788 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "craig", - "version": "1.12.1", + "version": "1.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "craig", - "version": "1.12.1", + "version": "1.13.0", "license": "Apache-2.0", "dependencies": { "@apollo/client": "^3.4.10", diff --git a/client/package.json b/client/package.json index 6c879880..7877c7f4 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "craig", - "version": "1.12.1", + "version": "1.13.0", "private": true, "license": "Apache-2.0", "scripts": { diff --git a/client/src/app.scss b/client/src/app.scss index 7c8ad67d..5464bd43 100644 --- a/client/src/app.scss +++ b/client/src/app.scss @@ -397,6 +397,7 @@ input:read-only { position: relative; font-size: 80%; top: 20px; + left: -62px; } .cds--popover--open .cds--popover-content { @@ -1037,10 +1038,12 @@ div.banner-text { } .footerPopoverDismiss { + left: -143px !important; // important to override auto align right: 450% !important; // important to override auto align } .footerPopoverShow { + left: -130px !important; // important to override auto align right: 412.5% !important; // important to override auto align } @@ -2095,6 +2098,7 @@ input:read-only { position: relative; font-size: 80%; top: 20px; + left: -62px; } .cds--popover--open .cds--popover-content { diff --git a/client/src/components/forms/utils/PrimaryButton.js b/client/src/components/forms/utils/PrimaryButton.js index 743e5d15..ee122a36 100644 --- a/client/src/components/forms/utils/PrimaryButton.js +++ b/client/src/components/forms/utils/PrimaryButton.js @@ -57,7 +57,7 @@ PrimaryButton.defaultProps = { hoverText: "Save Changes", inline: false, disabled: false, - hoverTextAlign: "bottom", + hoverTextAlign: "bottom-right", }; PrimaryButton.propTypes = { diff --git a/client/src/components/forms/utils/SecondaryButton.js b/client/src/components/forms/utils/SecondaryButton.js index de70618d..0cad3ca7 100644 --- a/client/src/components/forms/utils/SecondaryButton.js +++ b/client/src/components/forms/utils/SecondaryButton.js @@ -1,12 +1,16 @@ import React from "react"; import PropTypes from "prop-types"; +import { contains } from "lazy-z"; import { default as PopoverWrapper } from "./PopoverWrapper"; import { TrashCan } from "@carbon/icons-react"; import { Button } from "@carbon/react"; import { dynamicSecondaryButtonProps } from "../../../lib/components/toggle-form-components"; export const SecondaryButton = (props) => { - let buttonProps = dynamicSecondaryButtonProps(props); + let isV2Page = + contains(window.location.pathname, "/v2") || + contains(window.location.search, "v2"); + let buttonProps = dynamicSecondaryButtonProps(props, isV2Page); return (
{ SecondaryButton.defaultProps = { disabled: false, - hoverTextAlign: "bottom", + hoverTextAlign: "bottom-right", }; SecondaryButton.propTypes = { diff --git a/client/src/components/forms/utils/ToggleFormComponents.js b/client/src/components/forms/utils/ToggleFormComponents.js index 647cb798..a8d3be7c 100644 --- a/client/src/components/forms/utils/ToggleFormComponents.js +++ b/client/src/components/forms/utils/ToggleFormComponents.js @@ -13,7 +13,7 @@ import { dynamicSecondaryButtonProps, statelessWrapperProps, } from "../../../lib/components/toggle-form-components"; -import { kebabCase } from "lazy-z"; +import { contains, kebabCase } from "lazy-z"; import { DynamicToolTipWrapper } from "../dynamic-form/components"; import PopoverWrapper from "./PopoverWrapper"; @@ -100,7 +100,7 @@ PrimaryButton.defaultProps = { hoverText: "Save Changes", inline: false, disabled: false, - hoverTextAlign: "bottom", + hoverTextAlign: "bottom-right", }; PrimaryButton.propTypes = { @@ -114,7 +114,10 @@ PrimaryButton.propTypes = { }; const SecondaryButton = (props) => { - let buttonProps = dynamicSecondaryButtonProps(props); + let isV2Page = + contains(window.location.pathname, "/v2") || + contains(window.location.search, "v2"); + let buttonProps = dynamicSecondaryButtonProps(props, isV2Page); return (
{ SecondaryButton.defaultProps = { disabled: false, - hoverTextAlign: "bottom", + hoverTextAlign: "bottom-right", }; SecondaryButton.propTypes = { diff --git a/client/src/lib/components/toggle-form-components.js b/client/src/lib/components/toggle-form-components.js index fc35367a..deef30c8 100644 --- a/client/src/lib/components/toggle-form-components.js +++ b/client/src/lib/components/toggle-form-components.js @@ -47,14 +47,17 @@ function dynamicPrimaryButtonProps(props) { /** * get props for delete button * @param {*} props + * @param {*} isV2Page * @returns {object} props object */ -function dynamicSecondaryButtonProps(props) { +function dynamicSecondaryButtonProps(props, isV2Page) { return { popoverProps: { hoverText: props.disabled && props.disableDeleteMessage ? props.disableDeleteMessage + : isV2Page + ? "Delete Resource" : "Delete " + props.name, className: props.disabled ? "inlineBlock cursorNotAllowed" : "", }, diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index e5beb99a..b48e1a6c 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -4,7 +4,9 @@ "features": [ "Users will now get feedback when a Power VS Workspace has no VTL images when trying to create or update a VTL instance. Images can be added from the Power VS Workspace form" ], - "fixes": [], + "fixes": [ + "Fixed an issue with button hoverText alignment causing overflow in forms" + ], "upgrade_notes": [] }, { 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 02d39fa2..d20d6ea1 100644 --- a/client/src/lib/docs/templates/power-poc-quick-start.json +++ b/client/src/lib/docs/templates/power-poc-quick-start.json @@ -10,7 +10,7 @@ "enable_power_vs": true, "enable_classic": false, "power_vs_zones": ["dal10"], - "craig_version": "1.12.2", + "craig_version": "1.13.0", "power_vs_high_availability": false, "template": "Power VS POC", "fs_cloud": false diff --git a/package-lock.json b/package-lock.json index 00b9f391..8aa7d8ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "craig", - "version": "1.12.1", + "version": "1.13.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "craig", - "version": "1.12.1", + "version": "1.13.0", "license": "ISC", "dependencies": { "axios": "^1.6.3", diff --git a/package.json b/package.json index 0c4e5d2b..c8275df2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "craig", - "version": "1.12.1", + "version": "1.13.0", "description": "gui for generating ibm cloud infrastructure resources", "main": "index.js", "scripts": { diff --git a/unit-tests/forms/components/toggle-form-components.test.js b/unit-tests/forms/components/toggle-form-components.test.js index 86516361..0c49e6d8 100644 --- a/unit-tests/forms/components/toggle-form-components.test.js +++ b/unit-tests/forms/components/toggle-form-components.test.js @@ -107,11 +107,14 @@ describe("toggle form component functions", () => { }); }); describe("dynamicSecondaryButtonProps", () => { - it("should return correct data with no props", () => { + it("should return correct data with no props in CRAIG v2", () => { assert.deepEqual( - dynamicSecondaryButtonProps({ - name: "Resource", - }), + dynamicSecondaryButtonProps( + { + name: "frog", + }, + true + ), { buttonClassName: "cds--btn--danger--tertiary forceTertiaryButtonStyles", @@ -124,12 +127,35 @@ describe("toggle form component functions", () => { "it should return correct data" ); }); + it("should return correct data with no props in Classic CRAIG", () => { + assert.deepEqual( + dynamicSecondaryButtonProps( + { + name: "frog", + }, + false + ), + { + buttonClassName: + "cds--btn--danger--tertiary forceTertiaryButtonStyles", + iconClassName: "redFill", + popoverProps: { + className: "", + hoverText: "Delete frog", + }, + }, + "it should return correct data" + ); + }); it("should return correct data when disabled", () => { assert.deepEqual( - dynamicSecondaryButtonProps({ - disabled: true, - name: "Resource", - }), + dynamicSecondaryButtonProps( + { + disabled: true, + name: "Resource", + }, + false + ), { buttonClassName: "cds--btn--danger--tertiary forceTertiaryButtonStyles pointerEventsNone", @@ -144,10 +170,13 @@ describe("toggle form component functions", () => { }); it("should return correct data when disabled and disabled delete message", () => { assert.deepEqual( - dynamicSecondaryButtonProps({ - disabled: true, - disableDeleteMessage: "no", - }), + dynamicSecondaryButtonProps( + { + disabled: true, + disableDeleteMessage: "no", + }, + false + ), { buttonClassName: "cds--btn--danger--tertiary forceTertiaryButtonStyles pointerEventsNone", diff --git a/unit-tests/json-to-iac/outputs.test.js b/unit-tests/json-to-iac/outputs.test.js index 69505224..8ad1aca1 100644 --- a/unit-tests/json-to-iac/outputs.test.js +++ b/unit-tests/json-to-iac/outputs.test.js @@ -6,7 +6,7 @@ describe("outputs", () => { it("should return correct outputs file", () => { let config = { _options: { - craig_version: "1.12.1", + craig_version: "1.13.0", prefix: "jv-dev", region: "eu-de", tags: ["hello", "world"], @@ -599,7 +599,7 @@ output "management_vpc_jv_dev_server_vsi_1_1_floating_ip_address" { it("should return correct outputs file with multiple deployments", () => { let config = { _options: { - craig_version: "1.12.1", + craig_version: "1.13.0", prefix: "jv-dev", region: "eu-de", tags: ["hello", "world"], @@ -1463,7 +1463,7 @@ output "power_vs_workspace_iac_power_workspace_test_output_crn" { it("should return correct outputs for power vs workspaces and vpc", () => { let config = { _options: { - craig_version: "1.12.1", + craig_version: "1.13.0", prefix: "jv-dev", region: "eu-de", tags: ["hello", "world"], From e15fe461ede97e9ff5a704425e9c97e14430a2ec Mon Sep 17 00:00:00 2001 From: jvallexm Date: Tue, 12 Mar 2024 09:29:11 -0400 Subject: [PATCH 05/29] Issue 1642: imported subnet items on v2 (#1648) * feat: power vs v2 enhancements * fix: changelog * feat: display deployments v2 * feat: display --- CHANGELOG.md | 1 + .../src/components/pages/diagrams/VpcMap.js | 28 +++++++++++++++++-- .../components/pages/vpc/VpcDeployments.js | 10 +++++++ client/src/lib/docs/release-notes.json | 3 +- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66abc166..13414634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. ### Features - Users will now get feedback when a Power VS Workspace has no VTL images when trying to create or update a VTL instance. Images can be added from the Power VS Workspace form +- Icons for deployments within imported subnets are now rendered within that subnet on the `/v2/vpcDeployments` page ### Fixes diff --git a/client/src/components/pages/diagrams/VpcMap.js b/client/src/components/pages/diagrams/VpcMap.js index 48a3ae37..ac6de51f 100644 --- a/client/src/components/pages/diagrams/VpcMap.js +++ b/client/src/components/pages/diagrams/VpcMap.js @@ -18,6 +18,7 @@ import { DeploymentIcon } from "./DeploymentIcon"; import { CraigEmptyResourceTile } from "../../forms/dynamic-form"; import HoverClassNameWrapper from "./HoverClassNameWrapper"; import { Subnet } from "./Subnet"; +import { SubnetServiceMap } from "./SubnetServiceMap"; export const VpcMap = (props) => { let craig = props.craig; @@ -203,7 +204,9 @@ export const VpcMap = (props) => { : {} } hoverClassName={ - props.small ? "" : "diagramBoxSelected" + props.small || props.static + ? "" + : "diagramBoxSelected" } > { vpc={vpc} small={props.small} imported - /> + craig={craig} + vpcIndex={vpcIndex} + > + { + props.onImportedSubnetItemClick( + vpcIndex, + field, + itemIndex + ); + } + : undefined + } + parentState={props.parentState} + small={props.small} + static={props.static} + tabSelected={props.tabSelected} + onTabClick={props.onTabClick} + /> + ); })} diff --git a/client/src/components/pages/vpc/VpcDeployments.js b/client/src/components/pages/vpc/VpcDeployments.js index 8ae320dc..d8b2e724 100644 --- a/client/src/components/pages/vpc/VpcDeployments.js +++ b/client/src/components/pages/vpc/VpcDeployments.js @@ -532,6 +532,16 @@ class VpcDeploymentsDiagramPage extends React.Component { isSelected={(vpcIndex) => { return vpcIndex === this.state.vpcIndex; }} + onImportedSubnetItemClick={( + vpcIndex, + field, + itemIndex + ) => { + this.setSelection(vpcIndex, field, itemIndex); + }} + parentState={this.state} + tabSelected={this.tabSelected} + onTabClick={this.onSgTabClick} > Date: Tue, 12 Mar 2024 11:33:38 -0400 Subject: [PATCH 06/29] Issue 1580: classic vsi (#1651) * feat: power vs v2 enhancements * feat: json to iac for classic vsi * feat: classic vsi state * feat: classic vsi * feat: changelog * feat: power ha * fix: output * feat: addtl on v2 * feat: addtl on v2 --- CHANGELOG.md | 6 +- .../components/page-template/PageTemplate.js | 2 + client/src/components/pages/CraigForms.js | 29 +++ client/src/components/pages/FormPages.js | 11 + client/src/components/pages/Home.js | 11 + client/src/components/pages/power/Power.js | 4 + client/src/lib/docs/docs.json | 8 + client/src/lib/docs/release-notes.json | 9 +- client/src/lib/forms/disable-save.js | 1 + client/src/lib/index.js | 2 + client/src/lib/json-to-iac/classic-vsi.js | 75 +++++++ .../lib/json-to-iac/config-to-files-json.js | 2 + client/src/lib/json-to-iac/index.js | 3 +- client/src/lib/json-to-iac/outputs.js | 2 +- client/src/lib/nav-catagories.js | 9 + client/src/lib/state/classic-gateways.js | 103 ++------- client/src/lib/state/classic-vsi.js | 203 ++++++++++++++++++ client/src/lib/state/options.js | 68 +++++- client/src/lib/state/reusable-fields.js | 130 ++++++++++- client/src/lib/state/state.js | 2 + client/src/lib/state/vsi.js | 1 - unit-tests/api/craig-api.test.js | 2 +- unit-tests/data-files/craig-json.json | 3 +- unit-tests/data-files/expected-hard-set.json | 3 +- unit-tests/data-files/slz.md | 9 + .../disable-save/classic-gateways.test.js | 1 + unit-tests/forms/wizard.test.js | 12 ++ unit-tests/json-to-iac/classic-vsi.test.js | 172 +++++++++++++++ unit-tests/json-to-iac/outputs.test.js | 8 +- unit-tests/state/classic-vsi.test.js | 202 +++++++++++++++++ unit-tests/state/options.test.js | 80 ++++++- 31 files changed, 1063 insertions(+), 110 deletions(-) create mode 100644 client/src/lib/json-to-iac/classic-vsi.js create mode 100644 client/src/lib/state/classic-vsi.js create mode 100644 unit-tests/json-to-iac/classic-vsi.test.js create mode 100644 unit-tests/state/classic-vsi.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 13414634..c3e1b14d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,19 @@ All notable changes to this project will be documented in this file. ### Upgrade Notes +- Deprecated field for VSI primary IPs has been change to a version that no longer throws Terraform warnings ### Features - Users will now get feedback when a Power VS Workspace has no VTL images when trying to create or update a VTL instance. Images can be added from the Power VS Workspace form - Icons for deployments within imported subnets are now rendered within that subnet on the `/v2/vpcDeployments` page +- Users can now create Classic VSI from the form page `/form/classicVsi` +- Power VS Workspace names, ids, and CRNs are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template +- Power VS High Availability is now supported for `mad02`, `mad04`, `us-east`, `wdc06`, `us-south`, `eu-de-1`, and `eu-de-2` ### Fixes -Fixed an issue with button hoverText alignment causing overflow in forms +- Fixed an issue with button hoverText alignment causing overflow in forms ## 1.12.2 diff --git a/client/src/components/page-template/PageTemplate.js b/client/src/components/page-template/PageTemplate.js index 18aa8669..acb8113b 100644 --- a/client/src/components/page-template/PageTemplate.js +++ b/client/src/components/page-template/PageTemplate.js @@ -45,6 +45,7 @@ import { AppConnectivity, ChartLine, SecurityServices, + InstanceClassic, } from "@carbon/icons-react"; import f5 from "../../images/f5.png"; import { @@ -112,6 +113,7 @@ const navIcons = { LoadBalancerPool: LoadBalancerPool, AppConnectivity: AppConnectivity, SecurityServices: SecurityServices, + InstanceClassic: InstanceClassic, }; let pageOrder = [ diff --git a/client/src/components/pages/CraigForms.js b/client/src/components/pages/CraigForms.js index c7696a11..6cabd476 100644 --- a/client/src/components/pages/CraigForms.js +++ b/client/src/components/pages/CraigForms.js @@ -398,6 +398,35 @@ function craigForms(craig) { }, ], }, + classic_vsi: { + jsonField: "classic_vsi", + groups: [ + { + name: craig.classic_vsi.name, + domain: craig.classic_vsi.domain, + datacenter: craig.classic_vsi.datacenter, + }, + { + cores: craig.classic_vsi.cores, + memory: craig.classic_vsi.memory, + image_id: craig.classic_vsi.image_id, + }, + { + network_speed: craig.classic_vsi.network_speed, + local_disk: craig.classic_vsi.local_disk, + ssh_keys: craig.classic_vsi.ssh_keys, + }, + { + private_vlan: craig.classic_vsi.private_vlan, + private_security_groups: craig.classic_vsi.private_security_groups, + private_network_only: craig.classic_vsi.private_network_only, + }, + { + public_vlan: craig.classic_vsi.public_vlan, + public_security_groups: craig.classic_vsi.public_security_groups, + }, + ], + }, classic_gateways: { jsonField: "classic_gateways", groups: [ diff --git a/client/src/components/pages/FormPages.js b/client/src/components/pages/FormPages.js index 0d6bb8ee..cbdf5fc7 100644 --- a/client/src/components/pages/FormPages.js +++ b/client/src/components/pages/FormPages.js @@ -271,6 +271,15 @@ const ClassicSecurityGroups = (craig) => { }); }; +const ClassicVsi = (craig) => { + return formPageTemplate(craig, { + name: "Classic Virtual Servers", + addText: "Create a Virtual Server", + formName: "classic-vsi", + jsonField: "classic_vsi", + }); +}; + const ClassicGateways = (craig) => { return formPageTemplate(craig, { name: "Classic Gateways", @@ -1341,6 +1350,8 @@ export const NewFormPage = (props) => { return CisGlbs(craig); } else if (form === "classicSecurityGroups") { return ClassicSecurityGroups(craig); + } else if (form === "classicVsi") { + return ClassicVsi(craig); } else if (form === "classicGateways") { return ClassicGateways(craig); } else if (form === "classicSshKeys") { diff --git a/client/src/components/pages/Home.js b/client/src/components/pages/Home.js index e7320e5c..dc643e28 100644 --- a/client/src/components/pages/Home.js +++ b/client/src/components/pages/Home.js @@ -87,10 +87,21 @@ function Home(props) { endpoints: craig.options.endpoints, account_id: craig.options.account_id, }, + { + heading: { + name: "Power VS", + type: "subHeading", + className: "marginBottomSmall", + }, + }, { enable_power_vs: craig.options.enable_power_vs, power_vs_high_availability: craig.options.power_vs_high_availability, + }, + { + power_vs_ha_zone_1: craig.options.power_vs_ha_zone_1, + power_vs_ha_zone_2: craig.options.power_vs_ha_zone_2, power_vs_zones: craig.options.power_vs_zones, }, { diff --git a/client/src/components/pages/power/Power.js b/client/src/components/pages/power/Power.js index cf0b6278..b38569f4 100644 --- a/client/src/components/pages/power/Power.js +++ b/client/src/components/pages/power/Power.js @@ -443,6 +443,10 @@ class PowerDiagram extends React.Component { craig.options.power_vs_high_availability, }, { + power_vs_ha_zone_1: + craig.options.power_vs_ha_zone_1, + power_vs_ha_zone_2: + craig.options.power_vs_ha_zone_2, power_vs_zones: craig.options.power_vs_zones, }, ], diff --git a/client/src/lib/docs/docs.json b/client/src/lib/docs/docs.json index 4b7442ec..b7727979 100644 --- a/client/src/lib/docs/docs.json +++ b/client/src/lib/docs/docs.json @@ -2314,5 +2314,13 @@ } ], "relatedLinks": [] + }, + "classic_vsi": { + "content": [ + { + "text": "NYI" + } + ], + "relatedLinks": [] } } diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index 869f2db8..34b4fd94 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -3,12 +3,17 @@ "version": "1.13.0", "features": [ "Users will now get feedback when a Power VS Workspace has no VTL images when trying to create or update a VTL instance. Images can be added from the Power VS Workspace form", - "Icons for deployments within imported subnets are now rendered within that subnet on the `/v2/vpcDeployments` page" + "Icons for deployments within imported subnets are now rendered within that subnet on the `/v2/vpcDeployments` page", + "Users can now create Classic VSI from the form page `/form/classicVsi`", + "Power VS Workspace names, ids, and CRNs are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template", + "Power VS High Availability is now supported for `mad02`, `mad04`, `us-east`, `wdc06`, `us-south`, `eu-de-1`, and `eu-de-2`" ], "fixes": [ "Fixed an issue with button hoverText alignment causing overflow in forms" ], - "upgrade_notes": [] + "upgrade_notes": [ + "Deprecated field for VSI primary IPs has been change to a version that no longer throws Terraform warnings" + ] }, { "version": "1.12.2", diff --git a/client/src/lib/forms/disable-save.js b/client/src/lib/forms/disable-save.js index 96a4ea2d..d1c2cf7a 100644 --- a/client/src/lib/forms/disable-save.js +++ b/client/src/lib/forms/disable-save.js @@ -96,6 +96,7 @@ function disableSave(field, stateData, componentProps, craig) { "fortigate_vnf", "classic_security_groups", "classic_sg_rules", + "classic_vsi", ]; let isPowerSshKey = field === "ssh_keys" && componentProps.arrayParentName; if (contains(stateDisableSaveComponents, field) || isPowerSshKey) { diff --git a/client/src/lib/index.js b/client/src/lib/index.js index 922443fa..6b4fa6a0 100644 --- a/client/src/lib/index.js +++ b/client/src/lib/index.js @@ -110,6 +110,7 @@ const { formatClassicSgRule, formatClassicSg, classicSecurityGroupTf, + classicVsiTf, } = require("./json-to-iac"); const releaseNotes = require("./docs/release-notes.json"); const docs = require("./docs/docs.json"); @@ -118,6 +119,7 @@ const { invalidForms } = require("./invalid-forms"); const { allDocText, filterDocs } = require("./docs"); module.exports = { + classicVsiTf, classicSecurityGroupTf, formatClassicSgRule, formatClassicSg, diff --git a/client/src/lib/json-to-iac/classic-vsi.js b/client/src/lib/json-to-iac/classic-vsi.js new file mode 100644 index 00000000..6a2076e9 --- /dev/null +++ b/client/src/lib/json-to-iac/classic-vsi.js @@ -0,0 +1,75 @@ +const { snakeCase } = require("lazy-z"); +const { jsonToTfPrint, kebabName, tfBlock } = require("./utils"); + +/** + * format classic vsi + * @param {*} vsi + * @param {*} config + * @returns {string} terraform formatted string + */ +function formatClassicVsi(vsi, config) { + let vsiData = { + hostname: kebabName([vsi.name]), + datacenter: vsi.datacenter, + domain: vsi.domain, + cores: Number(vsi.cores), + memory: Number(vsi.memory), + image_id: vsi.image_id, + local_disk: vsi.local_disk, + network_speed: Number(vsi.network_speed), + private_network_only: vsi.private_network_only, + private_vlan_id: `\${ibm_network_vlan.classic_vlan_${snakeCase( + vsi.private_vlan + )}.id}`, + public_vlan_id: vsi.private_network_only + ? undefined + : `\${ibm_network_vlan.classic_vlan_${snakeCase(vsi.public_vlan)}.id}`, + public_security_group_ids: vsi.private_network_only ? undefined : [], + private_security_group_ids: [], + ssh_key_ids: [], + }; + vsi.private_security_groups.forEach((sg) => { + vsiData.private_security_group_ids.push( + `\${ibm_security_group.classic_securtiy_group_${snakeCase(sg)}.id}` + ); + }); + if (!vsi.private_network_only) { + vsi.public_security_groups.forEach((sg) => { + vsiData.public_security_group_ids.push( + `\${ibm_security_group.classic_securtiy_group_${snakeCase(sg)}.id}` + ); + }); + } + vsi.ssh_keys.forEach((key) => { + vsiData.ssh_key_ids.push( + `\${ibm_compute_ssh_key.classic_ssh_key_${snakeCase(key)}.id}` + ); + }); + vsiData.tags = config._options.tags; + return jsonToTfPrint( + "resource", + "ibm_compute_vm_instance", + `classic_vsi_${snakeCase(vsi.name)}`, + vsiData + ); +} + +/** + * create classic vsi tf + * @param {*} config + * @returns {string} terraform formatted string + */ +function classicVsiTf(config) { + let tf = ""; + if (config.classic_vsi) { + config.classic_vsi.forEach((vsi) => { + tf += formatClassicVsi(vsi, config); + }); + } + return tf.length === 0 ? null : tfBlock(`Classic VSI`, tf); +} + +module.exports = { + formatClassicVsi, + classicVsiTf, +}; diff --git a/client/src/lib/json-to-iac/config-to-files-json.js b/client/src/lib/json-to-iac/config-to-files-json.js index fa0d6f7e..249706ec 100644 --- a/client/src/lib/json-to-iac/config-to-files-json.js +++ b/client/src/lib/json-to-iac/config-to-files-json.js @@ -36,6 +36,7 @@ const { scc2Tf } = require("./scc-v2"); const { fortigateTf } = require("./fortigate"); const { outputsTf } = require("./outputs"); const { classicSecurityGroupTf } = require("./classic-security-group"); +const { classicVsiTf } = require("./classic-vsi"); const apacheLicense = ` Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -319,6 +320,7 @@ function configToFilesJson(config, apiMode, templateTarMode) { "fortigate_vnf.tf": fortigateTf(config), "outputs.tf": outputsTf(config), "classic_security_groups.tf": classicSecurityGroupTf(config), + "classic_vsi.tf": classicVsiTf(config), }; vpcModuleTf(files, config); return files; diff --git a/client/src/lib/json-to-iac/index.js b/client/src/lib/json-to-iac/index.js index e8c792c0..3f02b9fc 100644 --- a/client/src/lib/json-to-iac/index.js +++ b/client/src/lib/json-to-iac/index.js @@ -192,8 +192,9 @@ const { formatClassicSgRule, classicSecurityGroupTf, } = require("./classic-security-group"); - +const { classicVsiTf } = require("./classic-vsi"); module.exports = { + classicVsiTf, classicSecurityGroupTf, formatClassicSgRule, formatClassicSg, diff --git a/client/src/lib/json-to-iac/outputs.js b/client/src/lib/json-to-iac/outputs.js index 4c2c2cda..71c7981d 100644 --- a/client/src/lib/json-to-iac/outputs.js +++ b/client/src/lib/json-to-iac/outputs.js @@ -74,7 +74,7 @@ function outputsTf(config) { `${deployment.vpc} vpc ${deployment.name} vsi ${subnetIndex + 1} ${ i + 1 }` - )}.primary_network_interface[0].primary_ipv4_address}`, + )}.primary_network_interface[0].primary_ip[0].address}`, }; if (deployment.enable_floating_ip) { deploymentOutputs[ diff --git a/client/src/lib/nav-catagories.js b/client/src/lib/nav-catagories.js index 20d14086..6c268e40 100644 --- a/client/src/lib/nav-catagories.js +++ b/client/src/lib/nav-catagories.js @@ -30,6 +30,7 @@ const { powerVsVolumeTf, classicInfraTf, classicSecurityGroupTf, + classicVsiTf, } = require("./json-to-iac"); const { cisTf } = require("./json-to-iac/cis"); const { classicGatewayTf } = require("./json-to-iac/classic-gateway"); @@ -300,6 +301,14 @@ const navCatagories = [ }, jsonField: "classic_security_groups", }, + { + title: "Classic Virtual Severs", + path: "/form/classicVsi", + react_icon: "InstanceClassic", + toTf: (config) => { + return classicVsiTf(config) || ""; + }, + }, ], }, { diff --git a/client/src/lib/state/classic-gateways.js b/client/src/lib/state/classic-gateways.js index 47d0cb0a..51d44dc9 100644 --- a/client/src/lib/state/classic-gateways.js +++ b/client/src/lib/state/classic-gateways.js @@ -5,15 +5,20 @@ const { isInRange, splat, } = require("lazy-z"); -const { RegexButWithWords } = require("regex-but-with-words"); const { fieldIsNullOrEmptyString, shouldDisableComponentSave, unconditionalInvalidText, selectInvalidText, } = require("./utils"); -const { datacenters } = require("../constants"); -const { nameField } = require("./reusable-fields"); +const { + nameField, + domainField, + classicDatacenterField, + classicPublicVlan, + classicPrivateVlan, + classicPrivateNetworkOnly, +} = require("./reusable-fields"); /** * init store @@ -94,23 +99,6 @@ function classicGatewayDelete(config, stateData, componentProps) { config.carve(["json", "classic_gateways"], componentProps.data.name); } -/** - * classic vlan filter function - * @param {string} type - * @returns {Function} groups function - */ -function classicVlanFilter(type) { - return function (stateData, componentProps) { - return splat( - componentProps.craig.store.json.classic_vlans.filter((vlan) => { - if (vlan.datacenter === stateData.datacenter && vlan.type === type) - return vlan; - }), - "name" - ); - }; -} - /** * init classic gateway store * @param {*} store @@ -147,39 +135,8 @@ function initClassicGateways(store) { }, size: "small", }), - domain: { - default: "", - invalid: function (stateData) { - return ( - // prevent returning error - (stateData.domain || "").match( - new RegexButWithWords() - .stringBegin() - .set("a-z") - .oneOrMore() - .literal(".") - .set("a-z") - .oneOrMore() - .stringEnd() - .done("g") - ) === null - ); - }, - invalidText: unconditionalInvalidText("Enter a valid domain"), - size: "small", - }, - datacenter: { - default: "", - type: "select", - invalid: fieldIsNullOrEmptyString("datacenter"), - invalidText: selectInvalidText("datacenter"), - onStateChange: function (stateData) { - stateData.private_vlan = ""; - stateData.public_vlan = ""; - }, - groups: datacenters, - size: "small", - }, + domain: domainField(), + datacenter: classicDatacenterField(), network_speed: { default: "", type: "select", @@ -232,15 +189,7 @@ function initClassicGateways(store) { groups: ["INTEL_XEON_4210_2_20"], size: "small", }, - private_vlan: { - labelText: "Private VLAN", - default: "", - type: "select", - invalid: fieldIsNullOrEmptyString("private_vlan"), - invalidText: selectInvalidText("private VLAN"), - groups: classicVlanFilter("PRIVATE"), - size: "small", - }, + private_vlan: classicPrivateVlan(), ssh_key: { labelText: "SSH Key", default: "", @@ -255,22 +204,7 @@ function initClassicGateways(store) { }, size: "small", }, - public_vlan: { - labelText: "Public VLAN", - default: "", - type: "select", - invalid: function (stateData) { - return stateData.private_network_only - ? false - : fieldIsNullOrEmptyString("public_vlan")(stateData); - }, - invalidText: selectInvalidText("public VLAN"), - groups: classicVlanFilter("PUBLIC"), - size: "small", - hideWhen: function (stateData) { - return stateData.private_network_only; - }, - }, + public_vlan: classicPublicVlan(), disk_key_names: { default: [], type: "multiselect", @@ -281,18 +215,7 @@ function initClassicGateways(store) { groups: ["HARD_DRIVE_2_00_TB_SATA_2"], size: "small", }, - private_network_only: { - type: "toggle", - labelText: "Private Network Only", - default: false, - onStateChange: function (stateData) { - if (stateData.private_network_only !== true) { - stateData.private_network_only = true; - stateData.public_vlan = ""; - } else stateData.private_network_only = false; - }, - size: "small", - }, + private_network_only: classicPrivateNetworkOnly(), tcp_monitoring: { size: "small", default: false, diff --git a/client/src/lib/state/classic-vsi.js b/client/src/lib/state/classic-vsi.js new file mode 100644 index 00000000..ee475758 --- /dev/null +++ b/client/src/lib/state/classic-vsi.js @@ -0,0 +1,203 @@ +const { splatContains, isEmpty, splat } = require("lazy-z"); +const { + shouldDisableComponentSave, + fieldIsNotWholeNumber, +} = require("./utils"); +const { + nameField, + domainField, + classicDatacenterField, + classicPrivateVlan, + classicPublicVlan, + fieldIsNullOrEmptyString, + unconditionalInvalidText, + classicPrivateNetworkOnly, +} = require("./reusable-fields"); + +/** + * init classic vsi + * @param {*} store + */ +function initClassicVsi(store) { + store.newField("classic_vsi", { + init: function (config) { + config.store.json.classic_vsi = []; + }, + create: function (config, stateData, componentProps) { + config.push(["json", "classic_vsi"], stateData); + }, + save: function (config, stateData, componentProps) { + config.updateChild( + ["json", "classic_vsi"], + componentProps.data.name, + stateData + ); + }, + delete: function (config, stateData, componentProps) { + config.carve(["json", "classic_vsi"], componentProps.data.name); + }, + onStoreUpdate: function (config) { + if (config.store.json.classic_vsi) { + config.store.json.classic_vsi.forEach((vsi) => { + ["public_vlan", "private_vlan"].forEach((field) => { + if ( + !splatContains( + config.store.json.classic_vlans, + "name", + vsi[field] + ) + ) + vsi[field] = null; + }); + let nextSgs = { + public_security_groups: [], + private_security_groups: [], + }; + ["public_security_groups", "private_security_groups"].forEach( + (field) => { + vsi[field].forEach((item) => { + if ( + splatContains( + config.store.json.classic_security_groups, + "name", + item + ) + ) { + nextSgs[field].push(item); + } + }); + vsi[field] = nextSgs[field]; + } + ); + let nextSshKeys = []; + vsi.ssh_keys.forEach((key) => { + if ( + splatContains(config.store.json.classic_ssh_keys, "name", key) + ) { + nextSshKeys.push(key); + } + }); + vsi.ssh_keys = nextSshKeys; + }); + } else config.store.json.classic_vsi = []; + }, + shouldDisableSave: shouldDisableComponentSave( + [ + "name", + "datacenter", + "domain", + "cores", + "memory", + "image_id", + "network_speed", + "private_vlan", + "public_vlan", + "private_security_groups", + "public_security_groups", + "ssh_keys", + ], + "classic_vsi" + ), + schema: { + name: nameField("classic_vsi", { + size: "small", + }), + domain: domainField(), + datacenter: classicDatacenterField(), + private_vlan: classicPrivateVlan(), + public_vlan: classicPublicVlan(), + cores: { + default: "", + invalid: fieldIsNullOrEmptyString("cores"), + size: "small", + }, + memory: { + default: "", + invalid: fieldIsNullOrEmptyString("memory"), + size: "small", + }, + image_id: { + default: "", + labelText: "Image ID", + invalid: fieldIsNullOrEmptyString("image_id"), + size: "small", + }, + network_speed: { + default: "100", + invalid: fieldIsNotWholeNumber("network_speed", 0, 10000), + invalidText: unconditionalInvalidText("Must be a whole number"), + size: "small", + }, + local_disk: { + type: "toggle", + size: "small", + labelText: "Local Disk", + }, + private_network_only: classicPrivateNetworkOnly(), + ssh_keys: { + labelText: "SSH Keys", + type: "multiselect", + size: "small", + default: [], + invalid: function (stateData) { + return !stateData.ssh_keys || isEmpty(stateData.ssh_keys); + }, + invalidText: unconditionalInvalidText("Select at least one SSH key"), + groups: function (stateData, componentProps) { + return splat( + componentProps.craig.store.json.classic_ssh_keys, + "name" + ); + }, + }, + private_security_groups: { + type: "multiselect", + size: "small", + default: [], + invalid: function (stateData) { + return ( + !stateData.private_security_groups || + isEmpty(stateData.private_security_groups) + ); + }, + invalidText: unconditionalInvalidText( + "Select at least one security group" + ), + groups: function (stateData, componentProps) { + return splat( + componentProps.craig.store.json.classic_security_groups, + "name" + ); + }, + }, + public_security_groups: { + type: "multiselect", + size: "small", + default: [], + invalid: function (stateData) { + return ( + !stateData.private_network_only && + (!stateData.public_security_groups || + isEmpty(stateData.public_security_groups)) + ); + }, + invalidText: unconditionalInvalidText( + "Select at least one security group" + ), + groups: function (stateData, componentProps) { + return splat( + componentProps.craig.store.json.classic_security_groups, + "name" + ); + }, + hideWhen: function (stateData) { + return stateData.private_network_only; + }, + }, + }, + }); +} + +module.exports = { + initClassicVsi, +}; diff --git a/client/src/lib/state/options.js b/client/src/lib/state/options.js index 8cac6d18..62c83d25 100644 --- a/client/src/lib/state/options.js +++ b/client/src/lib/state/options.js @@ -32,6 +32,14 @@ const powerVsZones = [ "ca-tor", ]; +const powerHaMap = { + mad02: "eu-de-1", + mad04: "eu-de-2", + "us-east": "us-south", + wdc06: "dal12", + wdc07: "dal10", +}; + /** * initialize options * @param {lazyZstate} config @@ -134,6 +142,19 @@ function hideWhenNotPowerVs(stateData) { return stateData.enable_power_vs !== true; } +/** + * hide a field when power vs ha not enabled + * @param {*} stateData + * @param {*} componentProps + * @returns {boolean} true when not enabled + */ +function hideWhenNotPowerHa(stateData) { + return ( + hideWhenNotPowerVs(stateData) || + stateData.power_vs_high_availability !== true + ); +} + /** * init options store * @param {*} store @@ -143,7 +164,7 @@ function initOptions(store) { init: optionsInit, save: optionsSave, shouldDisableSave: shouldDisableComponentSave( - ["prefix", "tags", "power_vs_zones", "region"], + ["prefix", "tags", "power_vs_zones", "region", "power_vs_ha_zone_1"], "options" ), schema: { @@ -265,24 +286,62 @@ function initOptions(store) { labelText: "High Availability", tooltip: { content: - "Enable High Availability and Disaster Recovery for Power VS by using enabled zones Dallas 12 and Washington DC 6", + "Enable High Availability and Disaster Recovery for Power VS by using enabled zones", }, hideWhen: hideWhenNotPowerVs, onStateChange: function (stateData) { if (!stateData.power_vs_high_availability) { stateData.power_vs_high_availability = true; - stateData.power_vs_zones = ["dal12", "wdc06"]; + stateData.power_vs_zones = []; } else { stateData.power_vs_high_availability = false; stateData.power_vs_zones = []; } }, }, + power_vs_ha_zone_1: { + size: "small", + type: "select", + labelText: "Power VS Site 1", + hideWhen: hideWhenNotPowerHa, + groups: ["mad02", "mad04", "us-east", "wdc06", "wdc07"], + onRender: function (stateData) { + return stateData.power_vs_zones[0] || ""; + }, + onStateChange: function (stateData) { + stateData.power_vs_zones = [stateData.power_vs_ha_zone_1]; + stateData.power_vs_zones.push( + powerHaMap[stateData.power_vs_ha_zone_1] + ); + }, + invalid: function (stateData) { + return ( + !stateData?.enable_power_vs || + !stateData?.power_vs_high_availability || + stateData.power_vs_zones.length === 0 + ); + }, + }, + power_vs_ha_zone_2: { + size: "small", + type: "select", + labelText: "Power VS Site 2", + hideWhen: hideWhenNotPowerHa, + groups: ["eu-de-1", "eu-de-2", "us-south", "dal10", "dal12"], + onRender: function (stateData) { + return stateData.power_vs_zones[1] || ""; + }, + readOnly: true, + }, power_vs_zones: { size: "small", type: "multiselect", labelText: "Power VS Zones", - hideWhen: hideWhenNotPowerVs, + hideWhen: function (stateData) { + return ( + !stateData.enable_power_vs || stateData.power_vs_high_availability + ); + }, default: [], forceUpdateKey: function (stateData) { return ( @@ -292,6 +351,7 @@ function initOptions(store) { invalid: function (stateData) { return ( stateData.enable_power_vs && + !stateData.power_vs_high_availability && (!stateData.power_vs_zones || isEmpty(stateData.power_vs_zones) || !contains(powerVsZones, stateData.region)) diff --git a/client/src/lib/state/reusable-fields.js b/client/src/lib/state/reusable-fields.js index 3bae32d9..ce929358 100644 --- a/client/src/lib/state/reusable-fields.js +++ b/client/src/lib/state/reusable-fields.js @@ -2,15 +2,15 @@ const { contains, allFieldsNull, validPortRange, - containsKeys, splat, isNullOrEmptyString, nestedSplat, revision, transpose, } = require("lazy-z"); -const { newResourceNameExp } = require("../constants"); +const { newResourceNameExp, datacenters } = require("../constants"); const { getAllSecrets } = require("../forms/utils"); +const { RegexButWithWords } = require("regex-but-with-words"); /** * callback function for unconditional invalid text @@ -769,6 +769,127 @@ function networkingRuleCodeField() { }; } +/** + * shortcut for domain field + */ +function domainField() { + return { + default: "", + invalid: function (stateData) { + return ( + // prevent returning error + (stateData.domain || "").match( + new RegexButWithWords() + .stringBegin() + .set("a-z") + .oneOrMore() + .literal(".") + .set("a-z") + .oneOrMore() + .stringEnd() + .done("g") + ) === null + ); + }, + invalidText: unconditionalInvalidText("Enter a valid domain"), + size: "small", + }; +} + +/** + * create field for classic datacenters + * @returns {Object} field object for classic datacenters + */ +function classicDatacenterField() { + return { + default: "", + type: "select", + invalid: fieldIsNullOrEmptyString("datacenter"), + invalidText: selectInvalidText("datacenter"), + onStateChange: function (stateData) { + stateData.private_vlan = ""; + stateData.public_vlan = ""; + }, + groups: datacenters, + size: "small", + }; +} + +/** + * classic vlan filter function + * @param {string} type + * @returns {Function} groups function + */ +function classicVlanFilter(type) { + return function (stateData, componentProps) { + return splat( + componentProps.craig.store.json.classic_vlans.filter((vlan) => { + if (vlan.datacenter === stateData.datacenter && vlan.type === type) + return vlan; + }), + "name" + ); + }; +} + +/** + * create field for classic private vlan + * @returns {Object} field object for classic private vlan + */ +function classicPrivateVlan() { + return { + labelText: "Private VLAN", + default: "", + type: "select", + invalid: fieldIsNullOrEmptyString("private_vlan"), + invalidText: selectInvalidText("private VLAN"), + groups: classicVlanFilter("PRIVATE"), + size: "small", + }; +} + +/** + * create field for classic public vlan + * @returns {Object} field object for classic public vlan + */ +function classicPublicVlan() { + return { + labelText: "Public VLAN", + default: "", + type: "select", + invalid: function (stateData) { + return stateData.private_network_only + ? false + : fieldIsNullOrEmptyString("public_vlan")(stateData); + }, + invalidText: selectInvalidText("public VLAN"), + groups: classicVlanFilter("PUBLIC"), + size: "small", + hideWhen: function (stateData) { + return stateData.private_network_only; + }, + }; +} + +/** + * classic private network toggle + * @returns {Object} field object + */ +function classicPrivateNetworkOnly() { + return { + type: "toggle", + labelText: "Private Network Only", + default: false, + onStateChange: function (stateData) { + if (stateData.private_network_only !== true) { + stateData.private_network_only = true; + stateData.public_vlan = ""; + } else stateData.private_network_only = false; + }, + size: "small", + }; +} + module.exports = { networkingRuleProtocolField, networkingRulePortField, @@ -790,4 +911,9 @@ module.exports = { genericNameCallback, duplicateNameCallback, nameHelperText, + domainField, + classicDatacenterField, + classicPrivateVlan, + classicPublicVlan, + classicPrivateNetworkOnly, }; diff --git a/client/src/lib/state/state.js b/client/src/lib/state/state.js index ef30d4f5..83e8bede 100644 --- a/client/src/lib/state/state.js +++ b/client/src/lib/state/state.js @@ -51,6 +51,7 @@ const { initSccV2 } = require("./scc-v2.js"); const { initCisGlbStore } = require("./cis-glb.js"); const { initFortigateStore } = require("./fortigate.js"); const { initClassicSecurityGroups } = require("./classic-security-groups.js"); +const { initClassicVsi } = require("./classic-vsi.js"); /** * get state for craig @@ -168,6 +169,7 @@ const state = function (legacy) { initCisGlbStore(store); initFortigateStore(store); initClassicSecurityGroups(store); + initClassicVsi(store); /** * hard set config dot json in state store diff --git a/client/src/lib/state/vsi.js b/client/src/lib/state/vsi.js index a35c30d7..22de48fa 100644 --- a/client/src/lib/state/vsi.js +++ b/client/src/lib/state/vsi.js @@ -29,7 +29,6 @@ const { encryptionKeyGroups, vpcSshKeyMultiselect, } = require("./utils"); -const { invalidNameText, invalidName } = require("../forms"); const { nameField } = require("./reusable-fields"); /** diff --git a/unit-tests/api/craig-api.test.js b/unit-tests/api/craig-api.test.js index c3ad4d6f..07d1322f 100644 --- a/unit-tests/api/craig-api.test.js +++ b/unit-tests/api/craig-api.test.js @@ -103,7 +103,7 @@ describe("craig api", () => { }, { name: "craig/outputs.tf", - data: '##############################################################################\n# Management VPC Outputs\n##############################################################################\n\noutput "management_vpc_name" {\n value = module.management_vpc.name\n}\n\noutput "management_vpc_id" {\n value = module.management_vpc.id\n}\n\noutput "management_vpc_crn" {\n value = module.management_vpc.crn\n}\n\noutput "management_vpc_subnet_vsi_zone_1_name" {\n value = module.management_vpc.vsi_zone_1_name\n}\n\noutput "management_vpc_subnet_vsi_zone_1_id" {\n value = module.management_vpc.vsi_zone_1_id\n}\n\noutput "management_vpc_subnet_vsi_zone_1_crn" {\n value = module.management_vpc.vsi_zone_1_crn\n}\n\noutput "management_vpc_subnet_vpn_zone_1_name" {\n value = module.management_vpc.vpn_zone_1_name\n}\n\noutput "management_vpc_subnet_vpn_zone_1_id" {\n value = module.management_vpc.vpn_zone_1_id\n}\n\noutput "management_vpc_subnet_vpn_zone_1_crn" {\n value = module.management_vpc.vpn_zone_1_crn\n}\n\noutput "management_vpc_subnet_vsi_zone_2_name" {\n value = module.management_vpc.vsi_zone_2_name\n}\n\noutput "management_vpc_subnet_vsi_zone_2_id" {\n value = module.management_vpc.vsi_zone_2_id\n}\n\noutput "management_vpc_subnet_vsi_zone_2_crn" {\n value = module.management_vpc.vsi_zone_2_crn\n}\n\noutput "management_vpc_subnet_vsi_zone_3_name" {\n value = module.management_vpc.vsi_zone_3_name\n}\n\noutput "management_vpc_subnet_vsi_zone_3_id" {\n value = module.management_vpc.vsi_zone_3_id\n}\n\noutput "management_vpc_subnet_vsi_zone_3_crn" {\n value = module.management_vpc.vsi_zone_3_crn\n}\n\noutput "management_vpc_subnet_vpe_zone_1_name" {\n value = module.management_vpc.vpe_zone_1_name\n}\n\noutput "management_vpc_subnet_vpe_zone_1_id" {\n value = module.management_vpc.vpe_zone_1_id\n}\n\noutput "management_vpc_subnet_vpe_zone_1_crn" {\n value = module.management_vpc.vpe_zone_1_crn\n}\n\noutput "management_vpc_subnet_vpe_zone_2_name" {\n value = module.management_vpc.vpe_zone_2_name\n}\n\noutput "management_vpc_subnet_vpe_zone_2_id" {\n value = module.management_vpc.vpe_zone_2_id\n}\n\noutput "management_vpc_subnet_vpe_zone_2_crn" {\n value = module.management_vpc.vpe_zone_2_crn\n}\n\noutput "management_vpc_subnet_vpe_zone_3_name" {\n value = module.management_vpc.vpe_zone_3_name\n}\n\noutput "management_vpc_subnet_vpe_zone_3_id" {\n value = module.management_vpc.vpe_zone_3_id\n}\n\noutput "management_vpc_subnet_vpe_zone_3_crn" {\n value = module.management_vpc.vpe_zone_3_crn\n}\n\noutput "management_vpc_security_group_management_vpe_name" {\n value = module.management_vpc.management_vpe_name\n}\n\noutput "management_vpc_security_group_management_vpe_id" {\n value = module.management_vpc.management_vpe_id\n}\n\noutput "management_vpc_security_group_management_vsi_name" {\n value = module.management_vpc.management_vsi_name\n}\n\noutput "management_vpc_security_group_management_vsi_id" {\n value = module.management_vpc.management_vsi_id\n}\n\n##############################################################################\n\n##############################################################################\n# Workload VPC Outputs\n##############################################################################\n\noutput "workload_vpc_name" {\n value = module.workload_vpc.name\n}\n\noutput "workload_vpc_id" {\n value = module.workload_vpc.id\n}\n\noutput "workload_vpc_crn" {\n value = module.workload_vpc.crn\n}\n\noutput "workload_vpc_subnet_vsi_zone_1_name" {\n value = module.workload_vpc.vsi_zone_1_name\n}\n\noutput "workload_vpc_subnet_vsi_zone_1_id" {\n value = module.workload_vpc.vsi_zone_1_id\n}\n\noutput "workload_vpc_subnet_vsi_zone_1_crn" {\n value = module.workload_vpc.vsi_zone_1_crn\n}\n\noutput "workload_vpc_subnet_vsi_zone_2_name" {\n value = module.workload_vpc.vsi_zone_2_name\n}\n\noutput "workload_vpc_subnet_vsi_zone_2_id" {\n value = module.workload_vpc.vsi_zone_2_id\n}\n\noutput "workload_vpc_subnet_vsi_zone_2_crn" {\n value = module.workload_vpc.vsi_zone_2_crn\n}\n\noutput "workload_vpc_subnet_vsi_zone_3_name" {\n value = module.workload_vpc.vsi_zone_3_name\n}\n\noutput "workload_vpc_subnet_vsi_zone_3_id" {\n value = module.workload_vpc.vsi_zone_3_id\n}\n\noutput "workload_vpc_subnet_vsi_zone_3_crn" {\n value = module.workload_vpc.vsi_zone_3_crn\n}\n\noutput "workload_vpc_subnet_vpe_zone_1_name" {\n value = module.workload_vpc.vpe_zone_1_name\n}\n\noutput "workload_vpc_subnet_vpe_zone_1_id" {\n value = module.workload_vpc.vpe_zone_1_id\n}\n\noutput "workload_vpc_subnet_vpe_zone_1_crn" {\n value = module.workload_vpc.vpe_zone_1_crn\n}\n\noutput "workload_vpc_subnet_vpe_zone_2_name" {\n value = module.workload_vpc.vpe_zone_2_name\n}\n\noutput "workload_vpc_subnet_vpe_zone_2_id" {\n value = module.workload_vpc.vpe_zone_2_id\n}\n\noutput "workload_vpc_subnet_vpe_zone_2_crn" {\n value = module.workload_vpc.vpe_zone_2_crn\n}\n\noutput "workload_vpc_subnet_vpe_zone_3_name" {\n value = module.workload_vpc.vpe_zone_3_name\n}\n\noutput "workload_vpc_subnet_vpe_zone_3_id" {\n value = module.workload_vpc.vpe_zone_3_id\n}\n\noutput "workload_vpc_subnet_vpe_zone_3_crn" {\n value = module.workload_vpc.vpe_zone_3_crn\n}\n\noutput "workload_vpc_security_group_workload_vpe_name" {\n value = module.workload_vpc.workload_vpe_name\n}\n\noutput "workload_vpc_security_group_workload_vpe_id" {\n value = module.workload_vpc.workload_vpe_id\n}\n\n##############################################################################\n\n##############################################################################\n# Management Vpc Management Server Deployment Outputs\n##############################################################################\n\noutput "management_vpc_management_server_vsi_1_1_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_1_1.primary_network_interface[0].primary_ipv4_address\n}\n\noutput "management_vpc_management_server_vsi_1_2_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_1_2.primary_network_interface[0].primary_ipv4_address\n}\n\noutput "management_vpc_management_server_vsi_2_1_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_2_1.primary_network_interface[0].primary_ipv4_address\n}\n\noutput "management_vpc_management_server_vsi_2_2_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_2_2.primary_network_interface[0].primary_ipv4_address\n}\n\noutput "management_vpc_management_server_vsi_3_1_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_3_1.primary_network_interface[0].primary_ipv4_address\n}\n\noutput "management_vpc_management_server_vsi_3_2_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_3_2.primary_network_interface[0].primary_ipv4_address\n}\n\n##############################################################################\n', + data: '##############################################################################\n# Management VPC Outputs\n##############################################################################\n\noutput "management_vpc_name" {\n value = module.management_vpc.name\n}\n\noutput "management_vpc_id" {\n value = module.management_vpc.id\n}\n\noutput "management_vpc_crn" {\n value = module.management_vpc.crn\n}\n\noutput "management_vpc_subnet_vsi_zone_1_name" {\n value = module.management_vpc.vsi_zone_1_name\n}\n\noutput "management_vpc_subnet_vsi_zone_1_id" {\n value = module.management_vpc.vsi_zone_1_id\n}\n\noutput "management_vpc_subnet_vsi_zone_1_crn" {\n value = module.management_vpc.vsi_zone_1_crn\n}\n\noutput "management_vpc_subnet_vpn_zone_1_name" {\n value = module.management_vpc.vpn_zone_1_name\n}\n\noutput "management_vpc_subnet_vpn_zone_1_id" {\n value = module.management_vpc.vpn_zone_1_id\n}\n\noutput "management_vpc_subnet_vpn_zone_1_crn" {\n value = module.management_vpc.vpn_zone_1_crn\n}\n\noutput "management_vpc_subnet_vsi_zone_2_name" {\n value = module.management_vpc.vsi_zone_2_name\n}\n\noutput "management_vpc_subnet_vsi_zone_2_id" {\n value = module.management_vpc.vsi_zone_2_id\n}\n\noutput "management_vpc_subnet_vsi_zone_2_crn" {\n value = module.management_vpc.vsi_zone_2_crn\n}\n\noutput "management_vpc_subnet_vsi_zone_3_name" {\n value = module.management_vpc.vsi_zone_3_name\n}\n\noutput "management_vpc_subnet_vsi_zone_3_id" {\n value = module.management_vpc.vsi_zone_3_id\n}\n\noutput "management_vpc_subnet_vsi_zone_3_crn" {\n value = module.management_vpc.vsi_zone_3_crn\n}\n\noutput "management_vpc_subnet_vpe_zone_1_name" {\n value = module.management_vpc.vpe_zone_1_name\n}\n\noutput "management_vpc_subnet_vpe_zone_1_id" {\n value = module.management_vpc.vpe_zone_1_id\n}\n\noutput "management_vpc_subnet_vpe_zone_1_crn" {\n value = module.management_vpc.vpe_zone_1_crn\n}\n\noutput "management_vpc_subnet_vpe_zone_2_name" {\n value = module.management_vpc.vpe_zone_2_name\n}\n\noutput "management_vpc_subnet_vpe_zone_2_id" {\n value = module.management_vpc.vpe_zone_2_id\n}\n\noutput "management_vpc_subnet_vpe_zone_2_crn" {\n value = module.management_vpc.vpe_zone_2_crn\n}\n\noutput "management_vpc_subnet_vpe_zone_3_name" {\n value = module.management_vpc.vpe_zone_3_name\n}\n\noutput "management_vpc_subnet_vpe_zone_3_id" {\n value = module.management_vpc.vpe_zone_3_id\n}\n\noutput "management_vpc_subnet_vpe_zone_3_crn" {\n value = module.management_vpc.vpe_zone_3_crn\n}\n\noutput "management_vpc_security_group_management_vpe_name" {\n value = module.management_vpc.management_vpe_name\n}\n\noutput "management_vpc_security_group_management_vpe_id" {\n value = module.management_vpc.management_vpe_id\n}\n\noutput "management_vpc_security_group_management_vsi_name" {\n value = module.management_vpc.management_vsi_name\n}\n\noutput "management_vpc_security_group_management_vsi_id" {\n value = module.management_vpc.management_vsi_id\n}\n\n##############################################################################\n\n##############################################################################\n# Workload VPC Outputs\n##############################################################################\n\noutput "workload_vpc_name" {\n value = module.workload_vpc.name\n}\n\noutput "workload_vpc_id" {\n value = module.workload_vpc.id\n}\n\noutput "workload_vpc_crn" {\n value = module.workload_vpc.crn\n}\n\noutput "workload_vpc_subnet_vsi_zone_1_name" {\n value = module.workload_vpc.vsi_zone_1_name\n}\n\noutput "workload_vpc_subnet_vsi_zone_1_id" {\n value = module.workload_vpc.vsi_zone_1_id\n}\n\noutput "workload_vpc_subnet_vsi_zone_1_crn" {\n value = module.workload_vpc.vsi_zone_1_crn\n}\n\noutput "workload_vpc_subnet_vsi_zone_2_name" {\n value = module.workload_vpc.vsi_zone_2_name\n}\n\noutput "workload_vpc_subnet_vsi_zone_2_id" {\n value = module.workload_vpc.vsi_zone_2_id\n}\n\noutput "workload_vpc_subnet_vsi_zone_2_crn" {\n value = module.workload_vpc.vsi_zone_2_crn\n}\n\noutput "workload_vpc_subnet_vsi_zone_3_name" {\n value = module.workload_vpc.vsi_zone_3_name\n}\n\noutput "workload_vpc_subnet_vsi_zone_3_id" {\n value = module.workload_vpc.vsi_zone_3_id\n}\n\noutput "workload_vpc_subnet_vsi_zone_3_crn" {\n value = module.workload_vpc.vsi_zone_3_crn\n}\n\noutput "workload_vpc_subnet_vpe_zone_1_name" {\n value = module.workload_vpc.vpe_zone_1_name\n}\n\noutput "workload_vpc_subnet_vpe_zone_1_id" {\n value = module.workload_vpc.vpe_zone_1_id\n}\n\noutput "workload_vpc_subnet_vpe_zone_1_crn" {\n value = module.workload_vpc.vpe_zone_1_crn\n}\n\noutput "workload_vpc_subnet_vpe_zone_2_name" {\n value = module.workload_vpc.vpe_zone_2_name\n}\n\noutput "workload_vpc_subnet_vpe_zone_2_id" {\n value = module.workload_vpc.vpe_zone_2_id\n}\n\noutput "workload_vpc_subnet_vpe_zone_2_crn" {\n value = module.workload_vpc.vpe_zone_2_crn\n}\n\noutput "workload_vpc_subnet_vpe_zone_3_name" {\n value = module.workload_vpc.vpe_zone_3_name\n}\n\noutput "workload_vpc_subnet_vpe_zone_3_id" {\n value = module.workload_vpc.vpe_zone_3_id\n}\n\noutput "workload_vpc_subnet_vpe_zone_3_crn" {\n value = module.workload_vpc.vpe_zone_3_crn\n}\n\noutput "workload_vpc_security_group_workload_vpe_name" {\n value = module.workload_vpc.workload_vpe_name\n}\n\noutput "workload_vpc_security_group_workload_vpe_id" {\n value = module.workload_vpc.workload_vpe_id\n}\n\n##############################################################################\n\n##############################################################################\n# Management Vpc Management Server Deployment Outputs\n##############################################################################\n\noutput "management_vpc_management_server_vsi_1_1_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_1_1.primary_network_interface[0].primary_ip[0].address\n}\n\noutput "management_vpc_management_server_vsi_1_2_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_1_2.primary_network_interface[0].primary_ip[0].address\n}\n\noutput "management_vpc_management_server_vsi_2_1_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_2_1.primary_network_interface[0].primary_ip[0].address\n}\n\noutput "management_vpc_management_server_vsi_2_2_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_2_2.primary_network_interface[0].primary_ip[0].address\n}\n\noutput "management_vpc_management_server_vsi_3_1_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_3_1.primary_network_interface[0].primary_ip[0].address\n}\n\noutput "management_vpc_management_server_vsi_3_2_primary_ip_address" {\n value = ibm_is_instance.management_vpc_management_server_vsi_3_2.primary_network_interface[0].primary_ip[0].address\n}\n\n##############################################################################\n', }, { name: "craig/management_vpc", data: "" }, { name: "craig/management_vpc", data: "" }, diff --git a/unit-tests/data-files/craig-json.json b/unit-tests/data-files/craig-json.json index 46a0aa2d..f9ae2d68 100644 --- a/unit-tests/data-files/craig-json.json +++ b/unit-tests/data-files/craig-json.json @@ -955,5 +955,6 @@ }, "cis_glbs": [], "fortigate_vnf": [], - "classic_security_groups": [] + "classic_security_groups": [], + "classic_vsi": [] } \ No newline at end of file diff --git a/unit-tests/data-files/expected-hard-set.json b/unit-tests/data-files/expected-hard-set.json index 8877efa3..679c6b19 100644 --- a/unit-tests/data-files/expected-hard-set.json +++ b/unit-tests/data-files/expected-hard-set.json @@ -1103,5 +1103,6 @@ "resource_group": null },"cis_glbs": [], "fortigate_vnf": [], - "classic_security_groups": [] + "classic_security_groups": [], + "classic_vsi": [] } \ No newline at end of file diff --git a/unit-tests/data-files/slz.md b/unit-tests/data-files/slz.md index 96fb5322..951e24da 100644 --- a/unit-tests/data-files/slz.md +++ b/unit-tests/data-files/slz.md @@ -948,4 +948,13 @@ NYI ### Related Links +----- + +## Classic Vsi + +NYI + +### Related Links + + ----- diff --git a/unit-tests/forms/disable-save/classic-gateways.test.js b/unit-tests/forms/disable-save/classic-gateways.test.js index df65254f..ce26d435 100644 --- a/unit-tests/forms/disable-save/classic-gateways.test.js +++ b/unit-tests/forms/disable-save/classic-gateways.test.js @@ -168,6 +168,7 @@ describe("classic_gateways", () => { ); }); it("should return true if a gw has no private_vlan", () => { + craig.store.json.classic_vlans = []; assert.isTrue( disableSave( "classic_gateways", diff --git a/unit-tests/forms/wizard.test.js b/unit-tests/forms/wizard.test.js index dc56b247..fab9bb08 100644 --- a/unit-tests/forms/wizard.test.js +++ b/unit-tests/forms/wizard.test.js @@ -907,6 +907,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], @@ -1823,6 +1824,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], @@ -2332,6 +2334,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], @@ -2832,6 +2835,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], @@ -3408,6 +3412,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], @@ -3577,6 +3582,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], @@ -4146,6 +4152,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], @@ -4690,6 +4697,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], @@ -5237,6 +5245,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], @@ -5728,6 +5737,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], @@ -6219,6 +6229,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], @@ -7533,6 +7544,7 @@ describe("setup wizard", () => { power_volumes: [], classic_ssh_keys: [], classic_vlans: [], + classic_vsi: [], classic_security_groups: [], classic_gateways: [], cis: [], diff --git a/unit-tests/json-to-iac/classic-vsi.test.js b/unit-tests/json-to-iac/classic-vsi.test.js new file mode 100644 index 00000000..9ec127d5 --- /dev/null +++ b/unit-tests/json-to-iac/classic-vsi.test.js @@ -0,0 +1,172 @@ +const { assert } = require("chai"); +const { + formatClassicVsi, + classicVsiTf, +} = require("../../client/src/lib/json-to-iac/classic-vsi"); + +describe("classic vsi", () => { + describe("formatClassicVsi", () => { + it("should format a classic vsi", () => { + let actualData = formatClassicVsi( + { + name: "name", + datacenter: "dal10", + domain: "example.com", + cores: "12", + memory: "256", + image_id: "xyz1234", + local_disk: true, + network_speed: "100", + private_network_only: false, + private_vlan: "example-classic-private", + public_vlan: "example-classic-public", + private_security_groups: ["priv-sg"], + public_security_groups: ["pub-sg"], + ssh_keys: ["example-classic"], + }, + { + _options: { + tags: ["hello", "world"], + }, + } + ); + let expectedData = ` +resource "ibm_compute_vm_instance" "classic_vsi_name" { + hostname = "\${var.prefix}-name" + datacenter = "dal10" + domain = "example.com" + cores = 12 + memory = 256 + image_id = "xyz1234" + local_disk = true + network_speed = 100 + private_network_only = false + private_vlan_id = ibm_network_vlan.classic_vlan_example_classic_private.id + public_vlan_id = ibm_network_vlan.classic_vlan_example_classic_public.id + public_security_group_ids = [ + ibm_security_group.classic_securtiy_group_pub_sg.id + ] + private_security_group_ids = [ + ibm_security_group.classic_securtiy_group_priv_sg.id + ] + ssh_key_ids = [ + ibm_compute_ssh_key.classic_ssh_key_example_classic.id + ] + tags = [ + "hello", + "world" + ] +} +`; + assert.deepEqual( + actualData, + expectedData, + "it should return correct vsi" + ); + }); + }); + describe("classicVsiTf", () => { + it("should format a classic vsi", () => { + let actualData = classicVsiTf({ + _options: { + tags: ["hello", "world"], + }, + classic_vsi: [ + { + name: "name", + datacenter: "dal10", + domain: "example.com", + cores: "12", + memory: "256", + image_id: "xyz1234", + local_disk: true, + network_speed: "100", + private_network_only: false, + private_vlan: "example-classic-private", + public_vlan: "example-classic-public", + private_security_groups: ["priv-sg"], + public_security_groups: ["pub-sg"], + ssh_keys: ["example-classic"], + }, + { + name: "name2", + datacenter: "dal10", + domain: "example.com", + cores: "12", + memory: "256", + image_id: "xyz1234", + local_disk: true, + network_speed: "100", + private_network_only: true, + private_vlan: "example-classic-private", + public_vlan: "example-classic-public", + private_security_groups: ["priv-sg"], + public_security_groups: ["pub-sg"], + ssh_keys: ["example-classic"], + }, + ], + }); + let expectedData = `############################################################################## +# Classic VSI +############################################################################## + +resource "ibm_compute_vm_instance" "classic_vsi_name" { + hostname = "\${var.prefix}-name" + datacenter = "dal10" + domain = "example.com" + cores = 12 + memory = 256 + image_id = "xyz1234" + local_disk = true + network_speed = 100 + private_network_only = false + private_vlan_id = ibm_network_vlan.classic_vlan_example_classic_private.id + public_vlan_id = ibm_network_vlan.classic_vlan_example_classic_public.id + public_security_group_ids = [ + ibm_security_group.classic_securtiy_group_pub_sg.id + ] + private_security_group_ids = [ + ibm_security_group.classic_securtiy_group_priv_sg.id + ] + ssh_key_ids = [ + ibm_compute_ssh_key.classic_ssh_key_example_classic.id + ] + tags = [ + "hello", + "world" + ] +} + +resource "ibm_compute_vm_instance" "classic_vsi_name2" { + hostname = "\${var.prefix}-name2" + datacenter = "dal10" + domain = "example.com" + cores = 12 + memory = 256 + image_id = "xyz1234" + local_disk = true + network_speed = 100 + private_network_only = true + private_vlan_id = ibm_network_vlan.classic_vlan_example_classic_private.id + private_security_group_ids = [ + ibm_security_group.classic_securtiy_group_priv_sg.id + ] + ssh_key_ids = [ + ibm_compute_ssh_key.classic_ssh_key_example_classic.id + ] + tags = [ + "hello", + "world" + ] +} + +############################################################################## +`; + assert.deepEqual( + actualData, + expectedData, + "it should return correct vsi" + ); + }); + }); +}); diff --git a/unit-tests/json-to-iac/outputs.test.js b/unit-tests/json-to-iac/outputs.test.js index 8ad1aca1..4f9b7300 100644 --- a/unit-tests/json-to-iac/outputs.test.js +++ b/unit-tests/json-to-iac/outputs.test.js @@ -581,7 +581,7 @@ output "management_vpc_security_group_management_vsi_id" { ############################################################################## output "management_vpc_jv_dev_server_vsi_1_1_primary_ip_address" { - value = ibm_is_instance.management_vpc_jv_dev_server_vsi_1_1.primary_network_interface[0].primary_ipv4_address + value = ibm_is_instance.management_vpc_jv_dev_server_vsi_1_1.primary_network_interface[0].primary_ip[0].address } output "management_vpc_jv_dev_server_vsi_1_1_floating_ip_address" { @@ -1195,7 +1195,7 @@ output "management_vpc_security_group_management_vsi_id" { ############################################################################## output "management_vpc_jv_dev_server_vsi_1_1_primary_ip_address" { - value = ibm_is_instance.management_vpc_jv_dev_server_vsi_1_1.primary_network_interface[0].primary_ipv4_address + value = ibm_is_instance.management_vpc_jv_dev_server_vsi_1_1.primary_network_interface[0].primary_ip[0].address } output "management_vpc_jv_dev_server_vsi_1_1_floating_ip_address" { @@ -1209,7 +1209,7 @@ output "management_vpc_jv_dev_server_vsi_1_1_floating_ip_address" { ############################################################################## output "management_vpc_jv_dev_server2_vsi_1_1_primary_ip_address" { - value = ibm_is_instance.management_vpc_jv_dev_server2_vsi_1_1.primary_network_interface[0].primary_ipv4_address + value = ibm_is_instance.management_vpc_jv_dev_server2_vsi_1_1.primary_network_interface[0].primary_ip[0].address } output "management_vpc_jv_dev_server2_vsi_1_1_floating_ip_address" { @@ -2317,7 +2317,7 @@ output "management2_vpc_subnet_vsi_zone_1_crn" { ############################################################################## output "management2_vpc_jv_dev_server2_vsi_1_1_primary_ip_address" { - value = ibm_is_instance.management2_vpc_jv_dev_server2_vsi_1_1.primary_network_interface[0].primary_ipv4_address + value = ibm_is_instance.management2_vpc_jv_dev_server2_vsi_1_1.primary_network_interface[0].primary_ip[0].address } output "management2_vpc_jv_dev_server2_vsi_1_1_floating_ip_address" { diff --git a/unit-tests/state/classic-vsi.test.js b/unit-tests/state/classic-vsi.test.js new file mode 100644 index 00000000..d43ca3b3 --- /dev/null +++ b/unit-tests/state/classic-vsi.test.js @@ -0,0 +1,202 @@ +const { assert } = require("chai"); +const { state } = require("../../client/src/lib/state"); +const { disableSave } = require("../../client/src/lib"); + +/** + * initialize store + * @returns {lazyZState} state store + */ +function newState() { + let store = new state(); + store.setUpdateCallback(() => {}); + return store; +} + +describe("classic vsi state", () => { + let craig; + beforeEach(() => { + craig = newState(); + }); + describe("crud ops", () => { + beforeEach(() => { + craig.classic_ssh_keys.create({ + name: "key", + public_key: "1234", + datacenter: "dal10", + }); + craig.classic_security_groups.create({ name: "pub", description: "" }); + craig.classic_security_groups.create({ name: "priv", description: "" }); + craig.classic_vlans.create({ + name: "pub", + datacenter: "dal10", + type: "PUBLIC", + }); + craig.classic_vlans.create({ + name: "priv", + datacenter: "dal10", + type: "PRIVATE", + }); + }); + it("should create a vsi and set unfound values to null", () => { + craig.classic_vsi.create({ + name: "test", + ssh_keys: ["aaa"], + public_vlan: "aa", + private_vlan: "aa", + private_security_groups: ["aaa"], + public_security_groups: ["aaa"], + }); + assert.deepEqual( + craig.store.json.classic_vsi, + [ + { + name: "test", + public_vlan: null, + private_vlan: null, + private_security_groups: [], + public_security_groups: [], + ssh_keys: [], + }, + ], + "it should create vsi" + ); + }); + it("should create a vsi with found values", () => { + craig.classic_vsi.create({ + name: "test", + ssh_keys: ["key"], + public_vlan: "pub", + private_vlan: "priv", + private_security_groups: ["pub"], + public_security_groups: ["priv"], + }); + assert.deepEqual( + craig.store.json.classic_vsi, + [ + { + name: "test", + public_vlan: "pub", + private_vlan: "priv", + private_security_groups: ["pub"], + public_security_groups: ["priv"], + ssh_keys: ["key"], + }, + ], + "it should create vsi" + ); + craig.classic_vsi.save( + { + name: "honk", + ssh_keys: ["key"], + public_vlan: "pub", + private_vlan: "priv", + private_security_groups: ["pub"], + public_security_groups: ["priv"], + }, + { + data: { + name: "test", + }, + } + ); + assert.deepEqual( + craig.store.json.classic_vsi, + [ + { + name: "honk", + public_vlan: "pub", + private_vlan: "priv", + private_security_groups: ["pub"], + public_security_groups: ["priv"], + ssh_keys: ["key"], + }, + ], + "it should update vsi" + ); + craig.classic_vsi.delete( + { + name: "honk", + ssh_keys: ["key"], + public_vlan: "pub", + private_vlan: "priv", + private_security_groups: ["pub"], + public_security_groups: ["priv"], + }, + { + data: { + name: "honk", + }, + } + ); + assert.deepEqual( + craig.store.json.classic_vsi, + [], + "it should delete vsi" + ); + }); + }); + describe("schema", () => { + it("should be invalid when no values", () => { + assert.isTrue( + disableSave("classic_vsi", {}, { craig: craig }), + "it should be disabled" + ); + }); + it("should return invalid when no ssh keys selected", () => { + assert.isTrue( + craig.classic_vsi.ssh_keys.invalid({ ssh_keys: [] }), + "it should be invalid" + ); + }); + it("should return groups for classic ssh keys", () => { + assert.deepEqual( + craig.classic_vsi.ssh_keys.groups({}, { craig: craig }), + [], + "it should return data" + ); + }); + it("should return invalid and groups for private security groups", () => { + assert.isTrue( + craig.classic_vsi.private_security_groups.invalid({ + private_security_groups: [], + }), + "it should be invalid" + ); + assert.deepEqual( + craig.classic_vsi.private_security_groups.groups({}, { craig: craig }), + [], + "it should return data" + ); + }); + it("should return invalid and groups for public security groups", () => { + assert.isTrue( + craig.classic_vsi.public_security_groups.invalid({ + private_security_groups: [], + public_security_groups: [], + }), + "it should be invalid" + ); + assert.isFalse( + craig.classic_vsi.public_security_groups.invalid({ + private_security_groups: [], + private_network_only: true, + public_security_groups: [], + }), + "it should be valid when none and private only" + ); + assert.isTrue( + craig.classic_vsi.public_security_groups.hideWhen({ + private_security_groups: [], + private_network_only: true, + public_security_groups: [], + }), + "it should be hidden" + ); + assert.deepEqual( + craig.classic_vsi.public_security_groups.groups({}, { craig: craig }), + [], + "it should return data" + ); + }); + }); +}); diff --git a/unit-tests/state/options.test.js b/unit-tests/state/options.test.js index 56c9025e..8f1bcf84 100644 --- a/unit-tests/state/options.test.js +++ b/unit-tests/state/options.test.js @@ -295,7 +295,7 @@ describe("options", () => { data, { power_vs_high_availability: true, - power_vs_zones: ["dal12", "wdc06"], + power_vs_zones: [], }, "it should change value" ); @@ -373,6 +373,19 @@ describe("options", () => { "it should return value" ); }); + it("should hide power vs zones", () => { + assert.isTrue( + craig.options.power_vs_zones.hideWhen({}), + "it should be hidden" + ); + assert.isTrue( + craig.options.power_vs_zones.hideWhen({ + enable_power_vs: true, + power_vs_high_availability: true, + }), + "it should be hidden" + ); + }); it("should return correct power_vs_zones groups when power_vs_high_availability true", () => { assert.deepEqual( craig.options.power_vs_zones.groups({ @@ -408,6 +421,71 @@ describe("options", () => { "it should return empty array" ); }); + it("should hide power_vs_ha_zone_1 when not ha", () => { + assert.isTrue( + craig.options.power_vs_ha_zone_1.hideWhen({ + enable_power_vs: true, + power_vs_high_availability: false, + }), + "it should be hidden" + ); + }); + it("should return correct value for power_vs_ha_zone_1 on render", () => { + assert.deepEqual( + craig.options.power_vs_ha_zone_1.onRender({ power_vs_zones: [] }), + "", + "it should return correct data" + ); + assert.deepEqual( + craig.options.power_vs_ha_zone_2.onRender({ power_vs_zones: [] }), + "", + "it should return correct data" + ); + assert.deepEqual( + craig.options.power_vs_ha_zone_1.onRender({ + power_vs_zones: ["mad02", "eu-de-1"], + }), + "mad02", + "it should return correct data" + ); + assert.deepEqual( + craig.options.power_vs_ha_zone_2.onRender({ + power_vs_zones: ["mad02", "eu-de-1"], + }), + "eu-de-1", + "it should return correct data" + ); + }); + it("should change power vs zones when changing power_vs_ha_zone_1", () => { + let data = { + power_vs_ha_zone_1: "mad02", + power_vs_zones: [], + }; + craig.options.power_vs_ha_zone_1.onStateChange(data); + assert.deepEqual( + data.power_vs_zones, + ["mad02", "eu-de-1"], + "it should set zones" + ); + }); + it("should have correct invalid for ha zone 1", () => { + assert.isTrue( + craig.options.power_vs_ha_zone_1.invalid({}), + "it should be true when no power vs" + ); + assert.isTrue( + craig.options.power_vs_ha_zone_1.invalid({ enable_power_vs: true }), + "it should be true when no power vs ha" + ); + assert.isTrue( + craig.options.power_vs_ha_zone_1.invalid({ + enable_power_vs: true, + power_vs_high_availability: true, + power_vs_zones: [], + }), + "it should be true when no power vs zones" + ); + }); }); }); }); From 210516961610f9c0ff767236e9636c81933e5d79 Mon Sep 17 00:00:00 2001 From: Ay1man2 Date: Tue, 12 Mar 2024 08:43:30 -0700 Subject: [PATCH 07/29] Issue 1465: Updated CRAIG v2 Tutorial (#1652) * updated README + changelog with published tutorial video and last udpated section * updated changelog * fixed changelog notes * remove their --------- Co-authored-by: Jennifer Valle --- CHANGELOG.md | 1 + README.md | 2 ++ client/src/lib/docs/release-notes.json | 1 + 3 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e1b14d..e167a333 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ### Upgrade Notes +- A new, improved version of the CRAIG tutorial is available in the README. This tutorial shows users how to install CRAIG, use the new V2 GUI, and integrate deployments with schematics - Deprecated field for VSI primary IPs has been change to a version that no longer throws Terraform warnings ### Features diff --git a/README.md b/README.md index a21dc1c3..af17921a 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ CRAIG configures infrastructure using JSON to create full VPC networks, manage s ***Ensure `Quality: 1080p` is selected within Box video player settings for the best viewing experience.*** +***Last Updated: March 11th, 2024*** + --- ### Running CRAIG Application Locally diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index 34b4fd94..f3896042 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -12,6 +12,7 @@ "Fixed an issue with button hoverText alignment causing overflow in forms" ], "upgrade_notes": [ + "A new, improved version of the CRAIG tutorial is available in the README. This tutorial shows users how to install CRAIG, use the new V2 GUI, and integrate deployments with schematics", "Deprecated field for VSI primary IPs has been change to a version that no longer throws Terraform warnings" ] }, From 26af405fcfe2f415467b0024398905dcc58cbd8c Mon Sep 17 00:00:00 2001 From: Ay1man2 Date: Tue, 12 Mar 2024 08:54:51 -0700 Subject: [PATCH 08/29] Shift Tutorial Section After Prereqs (#1656) --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index af17921a..83627827 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,6 @@ CRAIG configures infrastructure using JSON to create full VPC networks, manage s --- -## Installation - -1. [Running CRAIG Application Locally](#running-craig-application-locally) -2. [Deploying To IBM Code Engine](#deploying-to-ibm-code-engine) -3. [Building Local CRAIG Container Image](#building-local-container-image) -4. [Setting Up CRAIG Development Environment](.docs/dev-env-setup.md) -5. [Power VS Workspace Deployment](.docs/power-vs-workspace-deployment.md) - ---- - ### Tutorial Video [Follow this tutorial](https://ibm.box.com/v/craigTutorialVideo) for step-by-step instructions on how to get started with CRAIG. @@ -37,6 +27,16 @@ CRAIG configures infrastructure using JSON to create full VPC networks, manage s --- +## Installation + +1. [Running CRAIG Application Locally](#running-craig-application-locally) +2. [Deploying To IBM Code Engine](#deploying-to-ibm-code-engine) +3. [Building Local CRAIG Container Image](#building-local-container-image) +4. [Setting Up CRAIG Development Environment](.docs/dev-env-setup.md) +5. [Power VS Workspace Deployment](.docs/power-vs-workspace-deployment.md) + +--- + ### Running CRAIG Application Locally To get started using CRAIG locally, follow these steps: From 7e77ffe85975832887786c0c8b716c86bd9c0afb Mon Sep 17 00:00:00 2001 From: jvallexm Date: Tue, 12 Mar 2024 12:24:31 -0400 Subject: [PATCH 09/29] Issue 1654: update power vs instance ref (#1655) * feat: power vs v2 enhancements * fix: changelog * feat: update on rename * changelog * fox * fix: ha bugs * sp --- CHANGELOG.md | 2 + .../pages/cloud-services/CloudServices.js | 4 +- client/src/lib/docs/release-notes.json | 6 +- client/src/lib/nav-catagories.js | 2 +- client/src/lib/state/options.js | 10 +-- .../power-vs-instances/power-vs-instances.js | 35 +++++++- unit-tests/state/options.test.js | 9 ++- unit-tests/state/power-vs-instances.test.js | 81 +++++++++++++++++++ 8 files changed, 135 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e167a333..de64c76e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,10 +16,12 @@ All notable changes to this project will be documented in this file. - Users can now create Classic VSI from the form page `/form/classicVsi` - Power VS Workspace names, ids, and CRNs are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template - Power VS High Availability is now supported for `mad02`, `mad04`, `us-east`, `wdc06`, `us-south`, `eu-de-1`, and `eu-de-2` +- When updating a Power VS Instance name, references to that instance are now updated to match the new name ### Fixes - Fixed an issue with button hoverText alignment causing overflow in forms +- Fixed an issue causing an incorrect name for DNS Services in the `/v2/services` page ## 1.12.2 diff --git a/client/src/components/pages/cloud-services/CloudServices.js b/client/src/components/pages/cloud-services/CloudServices.js index 916371ad..81596fdf 100644 --- a/client/src/components/pages/cloud-services/CloudServices.js +++ b/client/src/components/pages/cloud-services/CloudServices.js @@ -424,10 +424,12 @@ class CloudServicesPage extends React.Component { ? "Activity Tracker" : this.state.modalService === "scc_v2" ? "Security & Compliance Center" + : this.state.modalService === "dns" + ? "DNS" : titleCase(this.state.modalService) ) .replace( - contains(["dns", "event_streams"]) ? "" : /s(?=$)/g, + contains(["DNS", "event_streams"]) ? "" : /s(?=$)/g, "" ) .replace("Dns", "DNS")}${ diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index f3896042..bdcdfc7c 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -6,10 +6,12 @@ "Icons for deployments within imported subnets are now rendered within that subnet on the `/v2/vpcDeployments` page", "Users can now create Classic VSI from the form page `/form/classicVsi`", "Power VS Workspace names, ids, and CRNs are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template", - "Power VS High Availability is now supported for `mad02`, `mad04`, `us-east`, `wdc06`, `us-south`, `eu-de-1`, and `eu-de-2`" + "Power VS High Availability is now supported for `mad02`, `mad04`, `us-east`, `wdc06`, `us-south`, `eu-de-1`, and `eu-de-2`", + "When updating a Power VS Instance name, references to that instance are now updated to match the new name" ], "fixes": [ - "Fixed an issue with button hoverText alignment causing overflow in forms" + "Fixed an issue with button hoverText alignment causing overflow in forms", + "Fixed an issue causing an incorrect name for DNS Services in the `/v2/services` page" ], "upgrade_notes": [ "A new, improved version of the CRAIG tutorial is available in the README. This tutorial shows users how to install CRAIG, use the new V2 GUI, and integrate deployments with schematics", diff --git a/client/src/lib/nav-catagories.js b/client/src/lib/nav-catagories.js index 6c268e40..366caeac 100644 --- a/client/src/lib/nav-catagories.js +++ b/client/src/lib/nav-catagories.js @@ -302,7 +302,7 @@ const navCatagories = [ jsonField: "classic_security_groups", }, { - title: "Classic Virtual Severs", + title: "Classic Virtual Servers", path: "/form/classicVsi", react_icon: "InstanceClassic", toTf: (config) => { diff --git a/client/src/lib/state/options.js b/client/src/lib/state/options.js index 62c83d25..1f5b96b7 100644 --- a/client/src/lib/state/options.js +++ b/client/src/lib/state/options.js @@ -275,6 +275,7 @@ function initOptions(store) { stateData.power_vs_zones = []; stateData.enable_power_vs = false; } else { + stateData.power_vs_zones = []; stateData.enable_power_vs = true; } }, @@ -315,11 +316,10 @@ function initOptions(store) { ); }, invalid: function (stateData) { - return ( - !stateData?.enable_power_vs || - !stateData?.power_vs_high_availability || - stateData.power_vs_zones.length === 0 - ); + return !stateData.enable_power_vs || + !stateData.power_vs_high_availability + ? false + : stateData.power_vs_zones.length === 0; }, }, power_vs_ha_zone_2: { diff --git a/client/src/lib/state/power-vs-instances/power-vs-instances.js b/client/src/lib/state/power-vs-instances/power-vs-instances.js index 595761be..19c5410c 100644 --- a/client/src/lib/state/power-vs-instances/power-vs-instances.js +++ b/client/src/lib/state/power-vs-instances/power-vs-instances.js @@ -1,4 +1,9 @@ -const { contains, splatContains, getObjectFromArray } = require("lazy-z"); +const { + contains, + splatContains, + getObjectFromArray, + carve, +} = require("lazy-z"); const { shouldDisableComponentSave } = require("../utils"); const { getSapVolumeList } = require("../../forms/sap"); const { RegexButWithWords } = require("regex-but-with-words"); @@ -263,6 +268,34 @@ function powerVsInstanceSave(vtl) { } }); } + config.store.json.power_volumes.forEach((vol) => { + let nextAttachments = []; + vol.attachments.forEach((attachment) => { + if (attachment === componentProps.data.name) { + nextAttachments.push(stateData.name); + } else nextAttachments.push(attachment); + }); + ["pi_affinity_instance", "pi_anti_affinity_instance"].forEach((field) => { + if (vol[field] === componentProps.data.name) { + vol[field] = stateData.name; + } + }); + vol.attachments = nextAttachments; + }); + ["vtl", "power_instances"].forEach((field) => { + config.store.json[field].forEach((instance) => { + if (instance.name !== componentProps.data.name) { + ["pi_affinity_instance", "pi_anti_affinity_instance"].forEach( + (subField) => { + if (instance[subField] === componentProps.data.name) { + instance[subField] = stateData.name; + } + } + ); + } + }); + }); + config.updateChild( ["json", vtl ? "vtl" : "power_instances"], componentProps.data.name, diff --git a/unit-tests/state/options.test.js b/unit-tests/state/options.test.js index 8f1bcf84..f5ebc5bb 100644 --- a/unit-tests/state/options.test.js +++ b/unit-tests/state/options.test.js @@ -272,6 +272,7 @@ describe("options", () => { data, { enable_power_vs: true, + power_vs_zones: [], }, "it should change value" ); @@ -469,13 +470,13 @@ describe("options", () => { ); }); it("should have correct invalid for ha zone 1", () => { - assert.isTrue( + assert.isFalse( craig.options.power_vs_ha_zone_1.invalid({}), - "it should be true when no power vs" + "it should be false when no power vs" ); - assert.isTrue( + assert.isFalse( craig.options.power_vs_ha_zone_1.invalid({ enable_power_vs: true }), - "it should be true when no power vs ha" + "it should be false when no power vs ha" ); assert.isTrue( craig.options.power_vs_ha_zone_1.invalid({ diff --git a/unit-tests/state/power-vs-instances.test.js b/unit-tests/state/power-vs-instances.test.js index 8983c1eb..86e0350d 100644 --- a/unit-tests/state/power-vs-instances.test.js +++ b/unit-tests/state/power-vs-instances.test.js @@ -1214,6 +1214,87 @@ describe("power_instances", () => { "it should create correct volumes" ); }); + it("should update volume reference when changing a power vs instance name", () => { + let state = newState(); + state.store.json._options.power_vs_zones = ["dal12", "dal10"]; + state.power.create({ + name: "toad", + images: [{ name: "7100-05-09", workspace: "toad" }], + zone: "dal12", + }); + state.power_instances.create({ + name: "frog", + sap: false, + zone: "dal12", + workspace: "toad", + network: [], + storage_option: "Affinity", + pi_affinity_volume: "ignore-me", + }); + state.power_instances.create({ + name: "honk", + sap: false, + zone: "dal12", + workspace: "toad", + network: [], + storage_option: "Anti-Affinity", + pi_affinity_instance: "frog", + }); + state.vtl.create({ + name: "boop", + sap: false, + zone: "dal12", + workspace: "toad", + network: [], + storage_option: "Anti-Affinity", + pi_anti_affinity_instance: "frog", + }); + state.power_volumes.create({ + attachments: ["frog"], + workspace: "toad", + name: "ignore-me", + pi_anti_affinity_instance: "frog", + }); + state.power_volumes.create({ + attachments: ["honk", "beep"], + workspace: "toad", + name: "ignore-me2", + pi_affinity_instance: "frog", + }); + state.power_instances.save( + { name: "toad" }, + { + data: { + name: "frog", + }, + } + ); + assert.deepEqual( + state.store.json.power_volumes[0].attachments, + ["toad"], + "it should update name" + ); + assert.deepEqual( + state.store.json.power_volumes[0].pi_anti_affinity_instance, + "toad", + "it should update name" + ); + assert.deepEqual( + state.store.json.power_volumes[1].pi_affinity_instance, + "toad", + "it should update name" + ); + assert.deepEqual( + state.store.json.power_instances[1].pi_affinity_instance, + "toad", + "it should update name" + ); + assert.deepEqual( + state.store.json.vtl[0].pi_anti_affinity_instance, + "toad", + "it should update name" + ); + }); }); describe("power_instances.delete", () => { it("should delete a power vs instance", () => { From f5650a6ce87a79390e0b992eec7eb7fb5cf7236d Mon Sep 17 00:00:00 2001 From: Anupama Vijayan Date: Tue, 12 Mar 2024 13:38:14 -0500 Subject: [PATCH 10/29] Powervs prereq (#1658) * Added pre requisite template for the POC template * fixed the table * Test the links in doc * fixed hyphen * Added links to the table * fixed the link * fixed the spacing * fixed spacing * title change * Added a comment * Separated the tables * added mandatory and optional * format the document * fixed typo --- .docs/poc-template-worksheet.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .docs/poc-template-worksheet.md diff --git a/.docs/poc-template-worksheet.md b/.docs/poc-template-worksheet.md new file mode 100644 index 00000000..a618074e --- /dev/null +++ b/.docs/poc-template-worksheet.md @@ -0,0 +1,33 @@ +# Worksheet for Power VS POC template + +Set up access policies in your IBM Cloud account following the instructions [here](access-policies.md)
+ +Create in your IBM cloud account:
+- API key + +## Record the values for the keys mentioned below: + +### On-Premises information + +| *Keys* | *Values* | +| ------------------------------------------------------------------------------------------- | -------- | +| [*On-Prem network CIDRs](powervs-poc.md#on-premises-network-cidrs-and-peer-address) | | +| [*On-Prem Peer Address](powervs-poc.md#on-premises-network-cidrs-and-peer-address) | | +| [*On-Prem connection preshared key](powervs-poc.md#configuring-the-on-premises-vpn-gateway) | | + +### IBM Cloud information + +| *Keys* | *Values* | +| -------------------------------------------------------------------------------------------------------------- | -------- | +| [Region](powervs-poc.md#region-and-power-vs-zone) | | +| [Power VS Zone](powervs-poc.md#region-and-power-vs-zone) | | +| [resource name prefix](powervs-poc.md#resource-prefix) (optional) | | +| [*Public SSH key](powervs-poc.md#set-public-ssh-keys) | | +| [*IBM Cloud API Key](https://cloud.ibm.com/docs/account?topic=account-userapikey&interface=ui#create_user_key) | | +| [VPC VPN network CIDR](powervs-poc.md#vpc-network-cidr) | | +| [VPC VSI network CIDR](powervs-poc.md#vpc-network-cidr) | | +| [VPC VPE network CIDR](powervs-poc.md#vpc-network-cidr) | | +| [Power VS network CIDR](powervs-poc.md#power-virtual-server-network-cidr) | | + + +*Note*: The asterisk (*) denotes mandatory fields; optional values can be provided, otherwise CRAIG will default to predefined values. \ No newline at end of file From d1ec40e546ec813e83f976bf4579108ebcbb1c5c Mon Sep 17 00:00:00 2001 From: Ay1man2 Date: Wed, 13 Mar 2024 07:13:21 -0700 Subject: [PATCH 11/29] Issue 1605: Allow FalconStor VTL decimal values (#1644) * updated version # to 1.12.2, added CSS styling and alignment to delete button to shift text to the left * updated power instance schema to remove cpu float and cpumin restrictions for vtl instances, not needed * remove changes related to issue 1461, unintentionally added * added changelog --- CHANGELOG.md | 1 + client/src/lib/docs/release-notes.json | 3 +- .../power-instances-schema.js | 36 +++++++++---------- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de64c76e..88d45b1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ All notable changes to this project will be documented in this file. - Fixed an issue with button hoverText alignment causing overflow in forms - Fixed an issue causing an incorrect name for DNS Services in the `/v2/services` page +- Fixed an issue preventing users from inputing decimal values for CPU when creating a FalconStor VTL instance with a shared processor type ## 1.12.2 diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index bdcdfc7c..d7d38580 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -11,7 +11,8 @@ ], "fixes": [ "Fixed an issue with button hoverText alignment causing overflow in forms", - "Fixed an issue causing an incorrect name for DNS Services in the `/v2/services` page" + "Fixed an issue causing an incorrect name for DNS Services in the `/v2/services` page", + "Fixed an issue preventing users from inputing decimal values for CPU when creating a FalconStor VTL instance with a shared processor type" ], "upgrade_notes": [ "A new, improved version of the CRAIG tutorial is available in the README. This tutorial shows users how to install CRAIG, use the new V2 GUI, and integrate deployments with schematics", diff --git a/client/src/lib/state/power-vs-instances/power-instances-schema.js b/client/src/lib/state/power-vs-instances/power-instances-schema.js index d34c64a4..10d1c647 100644 --- a/client/src/lib/state/power-vs-instances/power-instances-schema.js +++ b/client/src/lib/state/power-vs-instances/power-instances-schema.js @@ -69,21 +69,18 @@ function powerVsNetworkInvalid(stateData) { * Processor invalidation for powerVs instance * @returns {boolean} function will evaluate to true if should be disabled */ -function powerVsCoresInvalid(vtl) { - return function (stateData) { - if (stateData.sap) return false; - let isDedicated = stateData.pi_proc_type === "dedicated"; - let coreMax = - stateData.pi_sys_type === "e980" ? 17 : isDedicated ? 13 : 13.75; - let coreMin = isDedicated || vtl ? 1 : 0.25; - let processorsFloat = parseFloat(stateData.pi_processors); - return ( - stateData.pi_processors === "" || - (coreMin === 1 && !isWholeNumber(processorsFloat)) || - (!stateData.sap && - (processorsFloat < coreMin || processorsFloat > coreMax)) - ); - }; +function powerVsCoresInvalid(stateData) { + if (stateData.sap) return false; + let isDedicated = stateData.pi_proc_type === "dedicated"; + let coreMax = + stateData.pi_sys_type === "e980" ? 17 : isDedicated ? 13 : 13.75; + let coreMin = isDedicated ? 1 : 0.25; + let processorsFloat = parseFloat(stateData.pi_processors); + return ( + stateData.pi_processors === "" || + (isDedicated && !isWholeNumber(processorsFloat)) || + (!stateData.sap && (processorsFloat < coreMin || processorsFloat > coreMax)) + ); } /** @@ -109,12 +106,11 @@ function powerVsMemoryInvalid(stateData) { /** * return power_instances processor input invalid text * @param {Object} stateData - * @param {boolean=} vtl * @returns {string} invalid text */ -function invalidPowerVsProcessorTextCallback(stateData, vtl) { +function invalidPowerVsProcessorTextCallback(stateData) { let isDedicated = stateData.pi_proc_type === "dedicated"; - let coreMin = isDedicated || vtl ? 1 : 0.25; + let coreMin = isDedicated ? 1 : 0.25; let coreMax = stateData.pi_sys_type === "e980" ? 17 : isDedicated ? 13 : 13.75; return `Must be a ${ @@ -384,8 +380,8 @@ function powerVsInstanceSchema(vtl) { }, size: "small", default: "", - invalid: powerVsCoresInvalid(vtl), - invalidText: invalidPowerVsProcessorTextCallback(true), + invalid: powerVsCoresInvalid, + invalidText: invalidPowerVsProcessorTextCallback, }, pi_memory: { hideWhen: function (stateData, componentProps) { From dc79ab817f4d48e04d472e9338074f7ad1123dc1 Mon Sep 17 00:00:00 2001 From: jvallexm Date: Wed, 13 Mar 2024 10:42:35 -0400 Subject: [PATCH 12/29] Issue 1572: upload from file (#1661) * feat: power vs v2 enhancements * fix: changelog * feat: upload json file --- CHANGELOG.md | 1 + .../components/pages/projects/JSONModal.js | 7 +- .../src/components/pages/projects/Projects.js | 89 +++++++++++++++++-- client/src/components/utils/JSONTextArea.js | 1 - client/src/lib/docs/release-notes.json | 3 +- 5 files changed, 90 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88d45b1d..a24771d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file. - Power VS Workspace names, ids, and CRNs are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template - Power VS High Availability is now supported for `mad02`, `mad04`, `us-east`, `wdc06`, `us-south`, `eu-de-1`, and `eu-de-2` - When updating a Power VS Instance name, references to that instance are now updated to match the new name +- Users can now upload JSON directly to CRAIG from the local file explorer from the Projects page by clicking the new `Upload JSON` button ### Fixes diff --git a/client/src/components/pages/projects/JSONModal.js b/client/src/components/pages/projects/JSONModal.js index 23bed712..02ae36c1 100644 --- a/client/src/components/pages/projects/JSONModal.js +++ b/client/src/components/pages/projects/JSONModal.js @@ -20,9 +20,10 @@ export class JSONModal extends React.Component { error: "", }; - if (this.props.import) { + if (this.props.import && !this.props.uploadedJsonData) { this.state.name = ""; } else if (this.state.json) { + if (this.props.uploadedJsonData) this.state.name = ""; try { validate(this.state.json); this.state.isValid = true; @@ -82,7 +83,6 @@ export class JSONModal extends React.Component { this.state.json === undefined ? true : this.props.data && !deepEqual(this.props.data.json, this.state.json); - return ( { + // clear uploaded data only when closing the modal + if (this.state.importJSONModalOpen === false) { + this.setState({ + uploadedJsonData: undefined, + }); + } + } + ); } newProject() { @@ -401,7 +434,7 @@ class Projects extends React.Component {

Projects

-
+
Create a New Project @@ -425,6 +458,8 @@ class Projects extends React.Component { > Import from JSON +
+
+ + (this.fileClick = input)} + onChange={(event) => { + // prevent default + event.preventDefault(event); + // show validation modal + this.setState({ showValidationModal: true }, () => { + // get file and create file reader + let jsonFile = event.target.files[0]; + let reader = new FileReader(); + reader.onload = (event) => { + // callback function on event trigger + this.onFileUpload(event); + }; + // read file + reader.readAsText(jsonFile, "application/json"); + }); + }} + />
{/* hide projects section if there are none */} {projectKeys.length > 0 && ( @@ -545,6 +615,12 @@ class Projects extends React.Component { {this.state.importJSONModalOpen && ( { @@ -552,6 +628,7 @@ class Projects extends React.Component { this.setState( { showValidationModal: true, + uploadedJsonData: undefined, }, () => { this.props.onProjectSave( diff --git a/client/src/components/utils/JSONTextArea.js b/client/src/components/utils/JSONTextArea.js index 0383cd96..df3ed4c7 100644 --- a/client/src/components/utils/JSONTextArea.js +++ b/client/src/components/utils/JSONTextArea.js @@ -1,7 +1,6 @@ import React from "react"; import { Button, TextArea } from "@carbon/react"; import { DownloadCopyButtonSet } from "./downloadCopyButtons/DownloadCopyButtonSet"; -import { Locked, TextWrap, Unlocked } from "@carbon/icons-react"; export const JSONTextArea = (props) => { let label = props.big diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index d7d38580..185efccf 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -7,7 +7,8 @@ "Users can now create Classic VSI from the form page `/form/classicVsi`", "Power VS Workspace names, ids, and CRNs are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template", "Power VS High Availability is now supported for `mad02`, `mad04`, `us-east`, `wdc06`, `us-south`, `eu-de-1`, and `eu-de-2`", - "When updating a Power VS Instance name, references to that instance are now updated to match the new name" + "When updating a Power VS Instance name, references to that instance are now updated to match the new name", + "Users can now upload JSON directly to CRAIG from the local file explorer from the Projects page by clicking the new `Upload JSON` button" ], "fixes": [ "Fixed an issue with button hoverText alignment causing overflow in forms", From 8c1cbe3ad728bbac11c5306c57b2f21721168b36 Mon Sep 17 00:00:00 2001 From: Pseusco Date: Wed, 13 Mar 2024 11:49:46 -0400 Subject: [PATCH 13/29] Issue 1615: Ansible role for start tf action (#1662) * start plan apply and destroy roles Signed-off-by: Lucas-Franke * AIO start action role Signed-off-by: Lucas-Franke * rename role Signed-off-by: Lucas-Franke * bye tf_action Signed-off-by: Lucas-Franke --------- Signed-off-by: Lucas-Franke --- ansible/template-test/main.yml | 71 +++++++++++-------- ansible/template-test/roles/action/README.md | 38 ++++++++++ .../roles/action/defaults/main.yml | 2 + .../roles/action/handlers/main.yml | 2 + .../template-test/roles/action/meta/main.yml | 52 ++++++++++++++ .../template-test/roles/action/tasks/main.yml | 11 +++ .../roles/action/tests/inventory | 2 + .../template-test/roles/action/tests/test.yml | 5 ++ .../template-test/roles/action/vars/main.yml | 2 + 9 files changed, 154 insertions(+), 31 deletions(-) create mode 100644 ansible/template-test/roles/action/README.md create mode 100644 ansible/template-test/roles/action/defaults/main.yml create mode 100644 ansible/template-test/roles/action/handlers/main.yml create mode 100644 ansible/template-test/roles/action/meta/main.yml create mode 100644 ansible/template-test/roles/action/tasks/main.yml create mode 100644 ansible/template-test/roles/action/tests/inventory create mode 100644 ansible/template-test/roles/action/tests/test.yml create mode 100644 ansible/template-test/roles/action/vars/main.yml diff --git a/ansible/template-test/main.yml b/ansible/template-test/main.yml index a4a542b3..742d2f4e 100644 --- a/ansible/template-test/main.yml +++ b/ansible/template-test/main.yml @@ -64,18 +64,21 @@ body_format: json body: variablestore: "{{ variablestore }}" - - name: Start generate plan action - uri: - url: https://schematics.cloud.ibm.com/v1/workspaces/{{ workspace.json.id }}/plan - method: POST - body_format: json - headers: - Authorization: Bearer {{token.json.access_token}} - status_code: 202 - register: job +- name: Start generate plan action + hosts: localhost + vars_files: ./vars/vars.yml + roles: + - role: get_iam_token + - role: action + vars: + action: plan +- name: Await task finish + hosts: localhost + vars_files: ./vars/vars.yml + tasks: - name: Ensure generate plan finishes uri: - url: https://schematics.cloud.ibm.com/v2/jobs/{{job.json.activityid}} + url: https://schematics.cloud.ibm.com/v2/jobs/{{action_result.json.activityid}} method: GET body_format: json headers: @@ -85,18 +88,21 @@ failed_when: plan.json.status.workspace_job_status.status_code == "job_failed" delay: 90 retries: 50 - - name: Start apply plan action - uri: - url: https://schematics.cloud.ibm.com/v1/workspaces/{{workspace.json.id}}/apply - method: PUT - body_format: json - headers: - Authorization: Bearer {{token.json.access_token}} - status_code: 202 - register: apply +- name: Start apply plan action + hosts: localhost + vars_files: ./vars/vars.yml + roles: + - role: get_iam_token + - role: action + vars: + action: apply +- name: Await task finish + hosts: localhost + vars_files: ./vars/vars.yml + tasks: - name: Ensure apply plan finishes uri: - url: https://schematics.cloud.ibm.com/v2/jobs/{{apply.json.activityid}} + url: https://schematics.cloud.ibm.com/v2/jobs/{{action_result.json.activityid}} method: GET body_format: json headers: @@ -106,18 +112,21 @@ failed_when: apply_plan.json.status.workspace_job_status.status_code == "job_failed" delay: 120 retries: 50 - - name: Start destroy action - uri: - url: https://schematics.cloud.ibm.com/v1/workspaces/{{workspace.json.id}}/destroy - method: PUT - body_format: json - headers: - Authorization: Bearer {{token.json.access_token}} - status_code: 202 - register: destroy - - name: Ensure destory finishes +- name: Start destroy action + hosts: localhost + vars_files: ./vars/vars.yml + roles: + - role: get_iam_token + - role: action + vars: + action: destroy +- name: Await task finish + hosts: localhost + vars_files: ./vars/vars.yml + tasks: + - name: Ensure destroy finishes uri: - url: https://schematics.cloud.ibm.com/v2/jobs/{{destroy.json.activityid}} + url: https://schematics.cloud.ibm.com/v2/jobs/{{action_result.json.activityid}} method: GET body_format: json headers: diff --git a/ansible/template-test/roles/action/README.md b/ansible/template-test/roles/action/README.md new file mode 100644 index 00000000..225dd44b --- /dev/null +++ b/ansible/template-test/roles/action/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/action/defaults/main.yml b/ansible/template-test/roles/action/defaults/main.yml new file mode 100644 index 00000000..0686718f --- /dev/null +++ b/ansible/template-test/roles/action/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for action diff --git a/ansible/template-test/roles/action/handlers/main.yml b/ansible/template-test/roles/action/handlers/main.yml new file mode 100644 index 00000000..830712d2 --- /dev/null +++ b/ansible/template-test/roles/action/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for action diff --git a/ansible/template-test/roles/action/meta/main.yml b/ansible/template-test/roles/action/meta/main.yml new file mode 100644 index 00000000..c572acc9 --- /dev/null +++ b/ansible/template-test/roles/action/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/action/tasks/main.yml b/ansible/template-test/roles/action/tasks/main.yml new file mode 100644 index 00000000..9ad4bec3 --- /dev/null +++ b/ansible/template-test/roles/action/tasks/main.yml @@ -0,0 +1,11 @@ +--- +# tasks file for action +- name: Start {{ action }} action + uri: + url: https://schematics.cloud.ibm.com/v1/workspaces/{{ workspace.json.id }}/{{ action }} + method: "{{ 'POST' if action == 'plan' else 'PUT' }}" + body_format: json + headers: + Authorization: Bearer {{token.json.access_token}} + status_code: 202 + register: action_result \ No newline at end of file diff --git a/ansible/template-test/roles/action/tests/inventory b/ansible/template-test/roles/action/tests/inventory new file mode 100644 index 00000000..878877b0 --- /dev/null +++ b/ansible/template-test/roles/action/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/ansible/template-test/roles/action/tests/test.yml b/ansible/template-test/roles/action/tests/test.yml new file mode 100644 index 00000000..a6919dbf --- /dev/null +++ b/ansible/template-test/roles/action/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - action diff --git a/ansible/template-test/roles/action/vars/main.yml b/ansible/template-test/roles/action/vars/main.yml new file mode 100644 index 00000000..5f086714 --- /dev/null +++ b/ansible/template-test/roles/action/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for action From af5f3e52f87c2c154f3b3f15032743e47c4bb2d5 Mon Sep 17 00:00:00 2001 From: terechc Date: Wed, 13 Mar 2024 12:38:48 -0400 Subject: [PATCH 14/29] Issue 1581: Classic Bare Metal (#1664) * classic bare metal * changelog * proxy * rm extra disable save --- CHANGELOG.md | 1 + .../components/page-template/PageTemplate.js | 2 + client/src/components/pages/CraigForms.js | 28 ++++ client/src/components/pages/FormPages.js | 11 ++ client/src/lib/docs/docs.json | 8 + client/src/lib/docs/release-notes.json | 3 +- client/src/lib/forms/disable-save.js | 1 + client/src/lib/index.js | 2 + .../src/lib/json-to-iac/classic-bare-metal.js | 60 +++++++ .../lib/json-to-iac/config-to-files-json.js | 2 + client/src/lib/json-to-iac/index.js | 2 + client/src/lib/nav-catagories.js | 10 ++ client/src/lib/state/classic-bare-metal.js | 152 +++++++++++++++++ client/src/lib/state/state.js | 2 + unit-tests/data-files/craig-json.json | 3 +- unit-tests/data-files/expected-hard-set.json | 3 +- unit-tests/data-files/slz.md | 9 + unit-tests/forms/wizard.test.js | 12 ++ .../json-to-iac/classic-bare-metal.test.js | 157 ++++++++++++++++++ unit-tests/state/classic-bare-metal.test.js | 146 ++++++++++++++++ 20 files changed, 611 insertions(+), 3 deletions(-) create mode 100644 client/src/lib/json-to-iac/classic-bare-metal.js create mode 100644 client/src/lib/state/classic-bare-metal.js create mode 100644 unit-tests/json-to-iac/classic-bare-metal.test.js create mode 100644 unit-tests/state/classic-bare-metal.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index a24771d9..de603b62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file. - Power VS High Availability is now supported for `mad02`, `mad04`, `us-east`, `wdc06`, `us-south`, `eu-de-1`, and `eu-de-2` - When updating a Power VS Instance name, references to that instance are now updated to match the new name - Users can now upload JSON directly to CRAIG from the local file explorer from the Projects page by clicking the new `Upload JSON` button +- Users can now create Classic Bare Metal Servers from the form page `/form/classicBareMetal` ### Fixes diff --git a/client/src/components/page-template/PageTemplate.js b/client/src/components/page-template/PageTemplate.js index acb8113b..00c4dfce 100644 --- a/client/src/components/page-template/PageTemplate.js +++ b/client/src/components/page-template/PageTemplate.js @@ -46,6 +46,7 @@ import { ChartLine, SecurityServices, InstanceClassic, + IbmCloudBareMetalServer, } from "@carbon/icons-react"; import f5 from "../../images/f5.png"; import { @@ -114,6 +115,7 @@ const navIcons = { AppConnectivity: AppConnectivity, SecurityServices: SecurityServices, InstanceClassic: InstanceClassic, + IbmCloudBareMetalServer: IbmCloudBareMetalServer, }; let pageOrder = [ diff --git a/client/src/components/pages/CraigForms.js b/client/src/components/pages/CraigForms.js index 6cabd476..71fe8c43 100644 --- a/client/src/components/pages/CraigForms.js +++ b/client/src/components/pages/CraigForms.js @@ -387,6 +387,34 @@ function craigForms(craig) { }, ], }, + classic_bare_metal: { + jsonField: "classic_bare_metal", + groups: [ + { + name: craig.classic_bare_metal.name, + domain: craig.classic_bare_metal.domain, + datacenter: craig.classic_bare_metal.datacenter, + }, + { + os_key_name: craig.classic_bare_metal.os_key_name, + package_key_name: craig.classic_bare_metal.package_key_name, + process_key_name: craig.classic_bare_metal.process_key_name, + }, + { + memory: craig.classic_bare_metal.memory, + network_speed: craig.classic_bare_metal.network_speed, + private_network_only: craig.classic_bare_metal.private_network_only, + }, + { + private_vlan: craig.classic_bare_metal.private_vlan, + public_vlan: craig.classic_bare_metal.public_vlan, + public_bandwidth: craig.classic_bare_metal.public_bandwidth, + }, + { + disk_key_names: craig.classic_bare_metal.disk_key_names, + }, + ], + }, classic_security_groups: { jsonField: "classic_security_groups", groups: [ diff --git a/client/src/components/pages/FormPages.js b/client/src/components/pages/FormPages.js index cbdf5fc7..a2ba218e 100644 --- a/client/src/components/pages/FormPages.js +++ b/client/src/components/pages/FormPages.js @@ -262,6 +262,15 @@ const CisGlbs = (craig) => { }); }; +const ClassicBareMetal = (craig) => { + return formPageTemplate(craig, { + name: "Classic Bare Metal Servers", + addText: "Create a Bare Metal Server", + formName: "classic-bare-metal", + jsonField: "classic_bare_metal", + }); +}; + const ClassicSecurityGroups = (craig) => { return formPageTemplate(craig, { name: "Classic Security Groups", @@ -1348,6 +1357,8 @@ export const NewFormPage = (props) => { return Cis(craig); } else if (form === "cisGlbs") { return CisGlbs(craig); + } else if (form === "classicBareMetal") { + return ClassicBareMetal(craig); } else if (form === "classicSecurityGroups") { return ClassicSecurityGroups(craig); } else if (form === "classicVsi") { diff --git a/client/src/lib/docs/docs.json b/client/src/lib/docs/docs.json index b7727979..503ffc1d 100644 --- a/client/src/lib/docs/docs.json +++ b/client/src/lib/docs/docs.json @@ -2322,5 +2322,13 @@ } ], "relatedLinks": [] + }, + "classic_bare_metal": { + "content": [ + { + "text": "NYI" + } + ], + "relatedLinks": [] } } diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index 185efccf..48834d77 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -8,7 +8,8 @@ "Power VS Workspace names, ids, and CRNs are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template", "Power VS High Availability is now supported for `mad02`, `mad04`, `us-east`, `wdc06`, `us-south`, `eu-de-1`, and `eu-de-2`", "When updating a Power VS Instance name, references to that instance are now updated to match the new name", - "Users can now upload JSON directly to CRAIG from the local file explorer from the Projects page by clicking the new `Upload JSON` button" + "Users can now upload JSON directly to CRAIG from the local file explorer from the Projects page by clicking the new `Upload JSON` button", + "Users can now create Classic Bare Metal Servers from the form page `/form/classicBareMetal`" ], "fixes": [ "Fixed an issue with button hoverText alignment causing overflow in forms", diff --git a/client/src/lib/forms/disable-save.js b/client/src/lib/forms/disable-save.js index d1c2cf7a..ef29a478 100644 --- a/client/src/lib/forms/disable-save.js +++ b/client/src/lib/forms/disable-save.js @@ -97,6 +97,7 @@ function disableSave(field, stateData, componentProps, craig) { "classic_security_groups", "classic_sg_rules", "classic_vsi", + "classic_bare_metal", ]; let isPowerSshKey = field === "ssh_keys" && componentProps.arrayParentName; if (contains(stateDisableSaveComponents, field) || isPowerSshKey) { diff --git a/client/src/lib/index.js b/client/src/lib/index.js index 6b4fa6a0..c4ebf9fc 100644 --- a/client/src/lib/index.js +++ b/client/src/lib/index.js @@ -111,6 +111,7 @@ const { formatClassicSg, classicSecurityGroupTf, classicVsiTf, + classicBareMetalTf, } = require("./json-to-iac"); const releaseNotes = require("./docs/release-notes.json"); const docs = require("./docs/docs.json"); @@ -120,6 +121,7 @@ const { allDocText, filterDocs } = require("./docs"); module.exports = { classicVsiTf, + classicBareMetalTf, classicSecurityGroupTf, formatClassicSgRule, formatClassicSg, diff --git a/client/src/lib/json-to-iac/classic-bare-metal.js b/client/src/lib/json-to-iac/classic-bare-metal.js new file mode 100644 index 00000000..11861f06 --- /dev/null +++ b/client/src/lib/json-to-iac/classic-bare-metal.js @@ -0,0 +1,60 @@ +const { snakeCase } = require("lazy-z"); +const { jsonToTfPrint, kebabName, tfBlock } = require("./utils"); + +/** + * format classic bare metal servers + * @param {*} vsi + * @param {*} config + * @returns {string} terraform formatted string + */ +function formatClassicBareMetal(server, config) { + let bareMetalData = { + package_key_name: server.package_key_name, + process_key_name: server.process_key_name, + os_key_name: server.os_key_name, + memory: server.memory || 64, + hostname: kebabName([server.name]), + domain: server.domain, + datacenter: server.datacenter, + network_speed: server.network_speed || 100, + public_bandwidth: server.private_network_only + ? undefined + : server.public_bandwidth || 500, + disk_key_names: server.disk_key_names, + hourly_billing: false, + private_network_only: server.private_network_only, + private_vlan_id: `\${ibm_network_vlan.classic_vlan_${snakeCase( + server.private_vlan + )}.id}`, + public_vlan_id: server.private_network_only + ? undefined + : `\${ibm_network_vlan.classic_vlan_${snakeCase(server.public_vlan)}.id}`, + }; + + return jsonToTfPrint( + "resource", + "ibm_compute_bare_metal", + server.name, + bareMetalData + ); +} + +/** + * create classic bare metal tf + * @param {*} config + * @returns {string} terraform formatted string + */ +function classicBareMetalTf(config) { + let tf = ""; + if (config.classic_bare_metal) { + config.classic_bare_metal.forEach((server) => { + tf += formatClassicBareMetal(server, config); + }); + } + return tf.length === 0 ? null : tfBlock(`Classic Bare Metal Servers`, tf); +} + +module.exports = { + formatClassicBareMetal, + classicBareMetalTf, +}; diff --git a/client/src/lib/json-to-iac/config-to-files-json.js b/client/src/lib/json-to-iac/config-to-files-json.js index 249706ec..27f48676 100644 --- a/client/src/lib/json-to-iac/config-to-files-json.js +++ b/client/src/lib/json-to-iac/config-to-files-json.js @@ -37,6 +37,7 @@ const { fortigateTf } = require("./fortigate"); const { outputsTf } = require("./outputs"); const { classicSecurityGroupTf } = require("./classic-security-group"); const { classicVsiTf } = require("./classic-vsi"); +const { classicBareMetalTf } = require("./classic-bare-metal"); const apacheLicense = ` Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -321,6 +322,7 @@ function configToFilesJson(config, apiMode, templateTarMode) { "outputs.tf": outputsTf(config), "classic_security_groups.tf": classicSecurityGroupTf(config), "classic_vsi.tf": classicVsiTf(config), + "classic_bare_metal.tf": classicBareMetalTf(config), }; vpcModuleTf(files, config); return files; diff --git a/client/src/lib/json-to-iac/index.js b/client/src/lib/json-to-iac/index.js index 3f02b9fc..ee916b19 100644 --- a/client/src/lib/json-to-iac/index.js +++ b/client/src/lib/json-to-iac/index.js @@ -193,8 +193,10 @@ const { classicSecurityGroupTf, } = require("./classic-security-group"); const { classicVsiTf } = require("./classic-vsi"); +const { classicBareMetalTf } = require("./classic-bare-metal"); module.exports = { classicVsiTf, + classicBareMetalTf, classicSecurityGroupTf, formatClassicSgRule, formatClassicSg, diff --git a/client/src/lib/nav-catagories.js b/client/src/lib/nav-catagories.js index 366caeac..1c344849 100644 --- a/client/src/lib/nav-catagories.js +++ b/client/src/lib/nav-catagories.js @@ -31,6 +31,7 @@ const { classicInfraTf, classicSecurityGroupTf, classicVsiTf, + classicBareMetalTf, } = require("./json-to-iac"); const { cisTf } = require("./json-to-iac/cis"); const { classicGatewayTf } = require("./json-to-iac/classic-gateway"); @@ -309,6 +310,15 @@ const navCatagories = [ return classicVsiTf(config) || ""; }, }, + { + title: "Classic Bare Metal", + path: "/form/classicBareMetal", + react_icon: "IbmCloudBareMetalServer", + toTf: (config) => { + return classicBareMetalTf(config) || ""; + }, + jsonField: "classic_bare_metal", + }, ], }, { diff --git a/client/src/lib/state/classic-bare-metal.js b/client/src/lib/state/classic-bare-metal.js new file mode 100644 index 00000000..59d34626 --- /dev/null +++ b/client/src/lib/state/classic-bare-metal.js @@ -0,0 +1,152 @@ +const { isNullOrEmptyString } = require("lazy-z"); +const { + nameField, + unconditionalInvalidText, + fieldIsNullOrEmptyString, + classicDatacenterField, + domainField, + classicPrivateNetworkOnly, + classicPrivateVlan, + classicPublicVlan, +} = require("./reusable-fields"); +const { shouldDisableComponentSave, onArrayInputChange } = require("./utils"); + +/** + * init store + * @param {*} store + */ +function initClassicBareMetalStore(store) { + store.newField("classic_bare_metal", { + init: function (config) { + config.store.json.classic_bare_metal = []; + }, + onStoreUpdate: function (config) { + if (!config.store.json.classic_bare_metal) { + config.store.json.classic_bare_metal = []; + } + }, + create: function (config, stateData, componentProps) { + config.push(["json", "classic_bare_metal"], stateData); + }, + save: function (config, stateData, componentProps) { + config.updateChild( + ["json", "classic_bare_metal"], + componentProps.data.name, + stateData + ); + }, + delete: function (config, stateData, componentProps) { + config.carve(["json", "classic_bare_metal"], componentProps.data.name); + }, + shouldDisableSave: shouldDisableComponentSave( + [ + "name", + "domain", + "datacenter", + "os_key_name", + "package_key_name", + "process_key_name", + "public_vlan", + "private_vlan", + "disk_key_names", + ], + "classic_bare_metal" + ), + schema: { + name: nameField("classic_bare_metal", { + size: "small", + }), + domain: domainField(), + datacenter: classicDatacenterField(), + os_key_name: { + size: "small", + labelText: "OS Key Name", + invalid: (stateData) => { + return isNullOrEmptyString(stateData.os_key_name); + }, + invalidText: unconditionalInvalidText("Enter an OS Key Name"), + default: "", + tooltip: { + content: + "The operating system key name that you want to use to provision the computing instance. To get OS key names, find the package key name in the IBM Cloud Classic API.", + align: "right", + alignModal: "right", + }, + }, + package_key_name: { + default: "", + size: "small", + invalid: fieldIsNullOrEmptyString("package_key_name"), + invalidText: unconditionalInvalidText("Enter a Package Key Name"), + tooltip: { + content: + "The key name for the monthly Bare Metal server's package. You can find available package key names in the IBM Cloud Classic API'", + align: "right", + }, + }, + process_key_name: { + default: "", + size: "small", + invalid: fieldIsNullOrEmptyString("process_key_name"), + invalidText: unconditionalInvalidText("Enter a Process Key Name"), + tooltip: { + content: + "The key name for the monthly Bare Metal server's process. To get a process key name, find the package key name in the IBM Cloud Classic API", + align: "right", + }, + }, + memory: { + size: "small", + default: "", + labelText: "Memory (GB)", + placeholder: "64", + }, + network_speed: { + size: "small", + default: "", + labelText: "Network Speed (Mbs)", + placeholder: "100", + }, + disk_key_names: { + placeholder: "disk-key-1, disk-key-2", + default: [], + type: "textArea", + labelText: "Disk Key Names", + invalid: function (stateData, componentProps) { + return ( + !stateData.disk_key_names || stateData.disk_key_names.length === 0 + ); + }, + invalidText: unconditionalInvalidText( + "Enter a comma separated list of disk key names" + ), + helperText: unconditionalInvalidText( + "Enter a comma separated list of disk key names" + ), + onInputChange: onArrayInputChange("disk_key_names"), + tooltip: { + content: + "The internal key names for the monthly Bare Metal server's disk. To get disk key names, find the package key name in the IBM Cloud Classic API", + align: "right", + alignModal: "right", + }, + }, + private_network_only: classicPrivateNetworkOnly(), + private_vlan: classicPrivateVlan(), + public_vlan: classicPublicVlan(), + public_bandwidth: { + default: "", + placeholder: "500", + size: "small", + labelText: "Public Bandwidth (GB/month)", + hideWhen: function (stateData) { + return stateData.private_network_only; + }, + }, + }, + }); +} + +module.exports = { + initClassicBareMetalStore, +}; diff --git a/client/src/lib/state/state.js b/client/src/lib/state/state.js index 83e8bede..4f1bf07a 100644 --- a/client/src/lib/state/state.js +++ b/client/src/lib/state/state.js @@ -52,6 +52,7 @@ const { initCisGlbStore } = require("./cis-glb.js"); const { initFortigateStore } = require("./fortigate.js"); const { initClassicSecurityGroups } = require("./classic-security-groups.js"); const { initClassicVsi } = require("./classic-vsi.js"); +const { initClassicBareMetalStore } = require("./classic-bare-metal.js"); /** * get state for craig @@ -170,6 +171,7 @@ const state = function (legacy) { initFortigateStore(store); initClassicSecurityGroups(store); initClassicVsi(store); + initClassicBareMetalStore(store); /** * hard set config dot json in state store diff --git a/unit-tests/data-files/craig-json.json b/unit-tests/data-files/craig-json.json index f9ae2d68..f0b4b042 100644 --- a/unit-tests/data-files/craig-json.json +++ b/unit-tests/data-files/craig-json.json @@ -956,5 +956,6 @@ "cis_glbs": [], "fortigate_vnf": [], "classic_security_groups": [], - "classic_vsi": [] + "classic_vsi": [], + "classic_bare_metal": [] } \ No newline at end of file diff --git a/unit-tests/data-files/expected-hard-set.json b/unit-tests/data-files/expected-hard-set.json index 679c6b19..1ddd13ce 100644 --- a/unit-tests/data-files/expected-hard-set.json +++ b/unit-tests/data-files/expected-hard-set.json @@ -1104,5 +1104,6 @@ },"cis_glbs": [], "fortigate_vnf": [], "classic_security_groups": [], - "classic_vsi": [] + "classic_vsi": [], + "classic_bare_metal": [] } \ No newline at end of file diff --git a/unit-tests/data-files/slz.md b/unit-tests/data-files/slz.md index 951e24da..817ca17d 100644 --- a/unit-tests/data-files/slz.md +++ b/unit-tests/data-files/slz.md @@ -957,4 +957,13 @@ NYI ### Related Links +----- + +## Classic Bare Metal + +NYI + +### Related Links + + ----- diff --git a/unit-tests/forms/wizard.test.js b/unit-tests/forms/wizard.test.js index fab9bb08..ea37c62b 100644 --- a/unit-tests/forms/wizard.test.js +++ b/unit-tests/forms/wizard.test.js @@ -909,6 +909,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], @@ -1826,6 +1827,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], @@ -2336,6 +2338,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], @@ -2837,6 +2840,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], @@ -3414,6 +3418,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], @@ -3584,6 +3589,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], @@ -4154,6 +4160,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], @@ -4699,6 +4706,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], @@ -5247,6 +5255,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], @@ -5739,6 +5748,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], @@ -6231,6 +6241,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], @@ -7546,6 +7557,7 @@ describe("setup wizard", () => { classic_vlans: [], classic_vsi: [], classic_security_groups: [], + classic_bare_metal: [], classic_gateways: [], cis: [], vtl: [], diff --git a/unit-tests/json-to-iac/classic-bare-metal.test.js b/unit-tests/json-to-iac/classic-bare-metal.test.js new file mode 100644 index 00000000..e5c054c0 --- /dev/null +++ b/unit-tests/json-to-iac/classic-bare-metal.test.js @@ -0,0 +1,157 @@ +const { assert } = require("chai"); +const { + formatClassicBareMetal, + classicBareMetalTf, +} = require("../../client/src/lib/json-to-iac/classic-bare-metal"); + +describe("classic bare metal", () => { + describe("formatClassicBareMetal", () => { + it("should format a classic bare metal server", () => { + let actualData = formatClassicBareMetal( + { + package_key_name: "test", + process_key_name: "test", + os_key_name: "test", + memory: "256", + name: "name", + datacenter: "dal10", + domain: "example.com", + network_speed: "100", + public_bandwidth: "500", + private_network_only: false, + private_vlan: "priv", + public_vlan: "pub", + disk_key_names: ["key-1", "key-2"], + }, + { + _options: { + tags: ["hello", "world"], + }, + } + ); + let expectedData = ` +resource "ibm_compute_bare_metal" "name" { + package_key_name = "test" + process_key_name = "test" + os_key_name = "test" + memory = "256" + hostname = "\${var.prefix}-name" + domain = "example.com" + datacenter = "dal10" + network_speed = "100" + public_bandwidth = "500" + hourly_billing = false + private_network_only = false + private_vlan_id = ibm_network_vlan.classic_vlan_priv.id + public_vlan_id = ibm_network_vlan.classic_vlan_pub.id + disk_key_names = [ + "key-1", + "key-2" + ] +} +`; + assert.deepEqual( + actualData, + expectedData, + "it should return correct bare metal tf" + ); + }); + }); + describe("classicBareMetalTf", () => { + it("should format a classic bare metal server", () => { + let actualData = classicBareMetalTf({ + classic_bare_metal: [ + { + name: "test", + domain: "test.com", + datacenter: "dal10", + os_key_name: "frog", + package_key_name: "frog", + process_key_name: "frog", + private_network_only: false, + private_vlan: "private-vlan", + public_vlan: "public-vlan", + disk_key_names: ["disk-key-1", "disk-key-2"], + }, + ], + }); + let expectedData = `############################################################################## +# Classic Bare Metal Servers +############################################################################## + +resource "ibm_compute_bare_metal" "test" { + package_key_name = "frog" + process_key_name = "frog" + os_key_name = "frog" + memory = 64 + hostname = "\${var.prefix}-test" + domain = "test.com" + datacenter = "dal10" + network_speed = 100 + public_bandwidth = 500 + hourly_billing = false + private_network_only = false + private_vlan_id = ibm_network_vlan.classic_vlan_private_vlan.id + public_vlan_id = ibm_network_vlan.classic_vlan_public_vlan.id + disk_key_names = [ + "disk-key-1", + "disk-key-2" + ] +} + +############################################################################## +`; + assert.deepEqual( + actualData, + expectedData, + "it should return correct bare metal tf" + ); + }); + it("should format a bare metal server", () => { + let actualData = classicBareMetalTf({ + classic_bare_metal: [ + { + name: "test", + domain: "test.com", + datacenter: "dal10", + os_key_name: "frog", + package_key_name: "frog", + process_key_name: "frog", + private_network_only: true, + private_vlan: "private-vlan", + disk_key_names: ["disk-key-1", "disk-key-2"], + }, + ], + }); + let expectedData = `############################################################################## +# Classic Bare Metal Servers +############################################################################## + +resource "ibm_compute_bare_metal" "test" { + package_key_name = "frog" + process_key_name = "frog" + os_key_name = "frog" + memory = 64 + hostname = "\${var.prefix}-test" + domain = "test.com" + datacenter = "dal10" + network_speed = 100 + hourly_billing = false + private_network_only = true + private_vlan_id = ibm_network_vlan.classic_vlan_private_vlan.id + disk_key_names = [ + "disk-key-1", + "disk-key-2" + ] +} + +############################################################################## +`; + assert.deepEqual( + actualData, + expectedData, + "it should return correct bare metal tf" + ); + }); + }); +}); diff --git a/unit-tests/state/classic-bare-metal.test.js b/unit-tests/state/classic-bare-metal.test.js new file mode 100644 index 00000000..456b7b4d --- /dev/null +++ b/unit-tests/state/classic-bare-metal.test.js @@ -0,0 +1,146 @@ +const { assert } = require("chai"); +const { state } = require("../../client/src/lib/state"); +const { disableSave } = require("../../client/src/lib"); + +/** + * initialize store + * @returns {lazyZState} state store + */ +function newState() { + let store = new state(); + store.setUpdateCallback(() => {}); + return store; +} + +describe("classic bare metal state", () => { + let craig; + beforeEach(() => { + craig = newState(); + }); + describe("classic_bare_metal.init", () => { + it("should initialize classic bare metal servers", () => { + assert.deepEqual( + craig.store.json.classic_bare_metal, + [], + "it should initialize data" + ); + }); + }); + describe("clasic_bare_metal.create", () => { + it("should create a bare metal instance", () => { + craig.classic_bare_metal.create({ name: "test", domain: "test.com" }); + assert.deepEqual( + craig.store.json.classic_bare_metal, + [ + { + name: "test", + domain: "test.com", + }, + ], + "it should return correct data" + ); + }); + }); + describe("classic_bare_metal.save", () => { + it("should update a bare metal instance", () => { + craig.classic_bare_metal.create({ name: "test", domain: "test.com" }); + craig.classic_bare_metal.save( + { name: "frog", domain: "frog.com" }, + { + craig: craig, + data: { + name: "test", + }, + } + ); + assert.deepEqual( + craig.store.json.classic_bare_metal, + [ + { + name: "frog", + domain: "frog.com", + }, + ], + "it should return correct data" + ); + }); + }); + describe("classic_bare_metal.delete", () => { + it("should delete a bare metal instance", () => { + craig.classic_bare_metal.create({ name: "test", domain: "test.com" }); + craig.classic_bare_metal.delete( + {}, + { data: { name: "test", domain: "test.com" } } + ); + assert.deepEqual( + craig.store.json.classic_bare_metal, + [], + "it should return correct data" + ); + }); + }); + describe("classic_bare_metal.schema", () => { + it("save should be disabled when empty", () => { + assert.isTrue( + disableSave("classic_bare_metal", {}, { craig: craig }), + "it should be disabled" + ); + }); + it("should return return true if name is invalid", () => { + assert.isTrue( + craig.classic_bare_metal.name.invalid({ + name: "---", + }), + "it should return true" + ); + }); + it("should return return true if domain is invalid", () => { + assert.isTrue( + craig.classic_bare_metal.domain.invalid({ + domain: "frog", + }), + "it should return true" + ); + }); + it("should return return true if os_key_name is empty", () => { + assert.isTrue( + craig.classic_bare_metal.os_key_name.invalid({ + os_key_name: "", + }), + "it should return true" + ); + }); + it("should return return true if package_key_name is empty", () => { + assert.isTrue( + craig.classic_bare_metal.package_key_name.invalid({ + package_key_name: "", + }), + "it should return true" + ); + }); + it("should return return true if process_key_name is empty", () => { + assert.isTrue( + craig.classic_bare_metal.process_key_name.invalid({ + process_key_name: "", + }), + "it should return true" + ); + }); + it("should return return true if disk_key_names is empty", () => { + assert.isTrue( + craig.classic_bare_metal.disk_key_names.invalid({ + disk_key_names: [], + }), + "it should return true" + ); + }); + it("should hide public_bandwidth if private_network_only is true", () => { + assert.isTrue( + craig.classic_bare_metal.public_bandwidth.hideWhen({ + private_network_only: true, + }), + "it should return true" + ); + }); + }); +}); From a35089d0f53b8c9b089c36a78393df6f9dc79600 Mon Sep 17 00:00:00 2001 From: jvallexm Date: Wed, 13 Mar 2024 13:59:14 -0400 Subject: [PATCH 15/29] Issue 1667: github discoverability (#1669) * feat: power vs v2 enhancements * fix: changelog * feat: additional markdown for github * feat: additional markdown for github --- CODE_OF_CONDUCT.md | 128 +++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 28 ++++++++++ SECURITY.md | 12 +++++ 3 files changed, 168 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..de488d01 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +email. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..f57fd948 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# Contributing + +Found a bug or need an additional feature? File an issue in this repository with the following information and they will be responded to in a timely manner. + +## Bugs + +- A detailed title describing the issue with the current release and the tag `[BUG]`. For sprint one, filing a bug would have the title `[0.1.0][BUG] ` +- Steps to recreate said bug (including non-sensitive variables) +- (optional) Corresponding output logs **as text or as part of a code block** +- Tag bug issues with the `bug` label +- If you come across a vulnerability that needs to be addressed immediately, use the `vulnerability` label + + +## Features + +- A detailed title describing the desired feature that includes the current release. For sprint one, a feature would have the title `[0.1.0] ` +- A detailed description including the user story +- A checkbox list of needed features +- Tag the issue with the `enhancement` label + +Want to work on an issue? Be sure to assign it to yourself and branch from main. When you're done making the required changes, create a pull request. + +## Pull requests + +**Do not merge directly to main**. Pull requests should reference the corresponding issue filed in this repository. Please be sure to maintain **code coverage** before merging. + +At least **two** reviews are required to merge a pull request. When creating a pull request, please ensure that details about unexpected changes to the codebase are provided in the description. + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..d498ccee --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ---------- | ------------------- | +| > 1.10.x | :white_check_mark: | +| < 1.10.0 | :x: | + +## Reporting a Vulnerability + +- Create a GitHub issue and use the `vulnerability` label \ No newline at end of file From d9af7aaaa7b4158b112986195d71d64639359bdc Mon Sep 17 00:00:00 2001 From: jvallexm Date: Tue, 13 Feb 2024 14:13:48 -0500 Subject: [PATCH 16/29] feat: power vs v2 enhancements --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de603b62..5aa46df1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,11 +116,23 @@ All notable changes to this project will be documented in this file. - Users can now view Power VS Resources with no selected workspace on the CRAIG V2 Power VS page - Users can now view Power VS Resources with no selected subnets on the CRAIG V2 Power VS page +<<<<<<< HEAD - When updating a Power VS Workspace name, Transit Gateway connections will now replace the existing name with the new one +<<<<<<< HEAD - Graphs of CRAIG usage statistics (clones, views, references) are now available at `/stats` and `/v2/stats` +======= +<<<<<<< HEAD +>>>>>>> 46074a54 (feat: power vs v2 enhancements) - When using CRAIG V2 an empty space will now display in subnet tiers where all three possible zones are not present - When fetching VSI Images, the CRAIG API will now respond with up to 100 results instead of the default of 50 +<<<<<<< HEAD - Users can now manage the primary subnet for Power VS Instances from the Power VS Instance form +======= +======= +======= +>>>>>>> 4f89d000 (feat: power vs v2 enhancements) +>>>>>>> b85c0f61 (feat: power vs v2 enhancements) +>>>>>>> dc9379a5 (feat: power vs v2 enhancements) ### Fixes From 0eaef3ebd3007103f2d5116cf063ff6d58faa0ce Mon Sep 17 00:00:00 2001 From: jvallexm Date: Thu, 7 Mar 2024 16:26:55 -0500 Subject: [PATCH 17/29] fix: changelog --- CHANGELOG.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aa46df1..de603b62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,23 +116,11 @@ All notable changes to this project will be documented in this file. - Users can now view Power VS Resources with no selected workspace on the CRAIG V2 Power VS page - Users can now view Power VS Resources with no selected subnets on the CRAIG V2 Power VS page -<<<<<<< HEAD - When updating a Power VS Workspace name, Transit Gateway connections will now replace the existing name with the new one -<<<<<<< HEAD - Graphs of CRAIG usage statistics (clones, views, references) are now available at `/stats` and `/v2/stats` -======= -<<<<<<< HEAD ->>>>>>> 46074a54 (feat: power vs v2 enhancements) - When using CRAIG V2 an empty space will now display in subnet tiers where all three possible zones are not present - When fetching VSI Images, the CRAIG API will now respond with up to 100 results instead of the default of 50 -<<<<<<< HEAD - Users can now manage the primary subnet for Power VS Instances from the Power VS Instance form -======= -======= -======= ->>>>>>> 4f89d000 (feat: power vs v2 enhancements) ->>>>>>> b85c0f61 (feat: power vs v2 enhancements) ->>>>>>> dc9379a5 (feat: power vs v2 enhancements) ### Fixes From 6ee304dfc306b4e7e9e35fe99cc9fe6d54f641cf Mon Sep 17 00:00:00 2001 From: jvallexm Date: Wed, 13 Mar 2024 14:17:56 -0400 Subject: [PATCH 18/29] feat: more zones --- client/src/lib/constants.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/lib/constants.js b/client/src/lib/constants.js index 6f1082af..bf80f131 100644 --- a/client/src/lib/constants.js +++ b/client/src/lib/constants.js @@ -730,6 +730,8 @@ module.exports = { "mad04", "sao04", "dal12", + "sao01", + "tok04", ], cosPlans: [ "standard", From f0d2a7bd3a4cf4758f3e33de8b54c977a4b79991 Mon Sep 17 00:00:00 2001 From: Ay1man2 Date: Wed, 13 Mar 2024 11:26:45 -0700 Subject: [PATCH 19/29] Issue 1665: README Hotfix (#1666) * updated README to include schematics access policy requirements for deploy script Power VS integration, fixed typo in powervs-poc.md * access policy verbage and order fix --- .docs/powervs-poc.md | 2 +- .docs/schematics-how-to.md | 2 +- README.md | 15 +++++++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.docs/powervs-poc.md b/.docs/powervs-poc.md index c0297c9b..18dbcfda 100644 --- a/.docs/powervs-poc.md +++ b/.docs/powervs-poc.md @@ -77,7 +77,7 @@ After changing the region and zone(s) in the options, the existing Power VS work The default AIX and IBM i virtual servers in the template will also need to be updated with the newly selected images. Click on each of the red Virtual Server icons, set their image, and click the save button for the VSI. ### VPC network CIDR -To change CIDR of the VPC networks, click on `VPC Networks` on the left navigation bar. Click on the the network you would like to update, change the "Advanced Configuration" to true, de-select zones 2 and 3 from the Zones seletor and press the Save button. +To change CIDR of the VPC networks, click on `VPC Networks` on the left navigation bar. Click on the the network you would like to update, change the "Advanced Configuration" to true, de-select zones 2 and 3 from the Zones selector and press the Save button. Finally, change the subnet CIDR and click the Save button next to the subnet name. diff --git a/.docs/schematics-how-to.md b/.docs/schematics-how-to.md index 8df0217a..7b28515e 100644 --- a/.docs/schematics-how-to.md +++ b/.docs/schematics-how-to.md @@ -8,8 +8,8 @@ The `API_KEY` variable must be set in the `.env`. If CRAIG is deployed in IBM Co ### Access Policy In order to allow Schematics integration, users should make sure they have the following access policy roles for the Schematics service: ->* `Editor` or greater Platform access >* `Writer` or greater Service access +>* `Editor` or greater Platform access These roles allow the integration with Schematics including the Schematics workspace creation and the upload of the project. However, to create and manage the IBM Cloud resources in the template, you must be assigned the IAM platform or service access role for the individual IBM Cloud resources that are in the template. See the IBM Cloud documentation for the various services for specific roles required. diff --git a/README.md b/README.md index 83627827..ba11be9e 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,12 @@ Within the root directory is a script `deploy.sh` which deploys CRAIG to IBM Clo Users should make sure they have the following access policy roles for the IBM Code Engine service set within their IBM Cloud Account: -- IBM Cloud Platform Roles: Editor or Higher -- Code Engine Service Roles: Writer or Higher +>* `Writer` or greater Service access +>* `Editor` or greater Platform access Users should also make sure they have the following access policy roles for the IBM Cloud Container Registry service set within their IBM Cloud Account: -- Container Registry Service: Manager +>* `Manager` Service access These permissions are the minimum requirements needed in order to provision a Code Engine project, Container Registry namespace, application, image build, and secrets using the `deploy.sh` script. @@ -110,7 +110,14 @@ 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, 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: + If CRAIG is used for Power VS configuration, Power Virtual Server 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. In order to allow Schematics integration, users should make sure they have the following access policy roles for the Schematics service set within their IBM Cloud Account: + +>* `Writer` or greater Service access +>* `Editor` or greater Platform access + + Once access policy roles for the Schematics service are properly configured, users can specify the `-z` parameter to automatically create the Power Virtual Server workspaces alongside your CRAIG deployment: ```bash ./deploy.sh -z From ab76757d07f0962666c60c56b41516b624176ca5 Mon Sep 17 00:00:00 2001 From: Samuel Matzek Date: Thu, 14 Mar 2024 11:10:31 -0500 Subject: [PATCH 20/29] generate-env.sh fixes (#1678) - check for required input parameter - handle spaces in workspace names - check for user logged in --- generate-env.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/generate-env.sh b/generate-env.sh index b8ca157d..cb525704 100755 --- a/generate-env.sh +++ b/generate-env.sh @@ -23,6 +23,9 @@ install_ibmcloud_plugin () { fi } +[[ -z "$1" ]] && fatal "An output file name is required as the first parameter." +ibmcloud iam oauth-tokens &> /dev/null || fatal "Please log in with the ibmcloud CLI (ibmcloud login)" + check_runtime_prereqs # Install ibmcloud plugins if necessary @@ -36,10 +39,10 @@ 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)"') +workspaces=$(ibmcloud pi wss --json | jq -r '.[]? | "\(.name),\(.location.region),\(.id)"') # Cycle through all the workspaces -while read name region id; +while IFS=, read name region id; do echo "# Workspace: ${name}" >> $outputfile # the ${region^^} makes all characters in the region upper case From acd24f35450e1888fa03adce8e4dc1fb4b385f1d Mon Sep 17 00:00:00 2001 From: Samuel Matzek Date: Thu, 14 Mar 2024 11:15:04 -0500 Subject: [PATCH 21/29] generate-env.sh update for power-iaas v1.0.0 (#1679) Update generate-env.sh to use power-iaas plugin v1.0.0. Remove the bash 4 dependency --- .docs/craig-code-engine.md | 8 +++++- .docs/power-vs-workspace-deployment.md | 1 - generate-env.sh | 34 +++++++++++++++----------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/.docs/craig-code-engine.md b/.docs/craig-code-engine.md index 84b81a9d..31c76092 100644 --- a/.docs/craig-code-engine.md +++ b/.docs/craig-code-engine.md @@ -44,7 +44,13 @@ If you do not want Power VS workspaces created in every zone, you can create the #### generate-env.sh prerequisites - [jq](https://jqlang.github.io/jq/) v1.7 or higher - ibmcloud CLI -- Bash version 4 or higher + +#### Downloading generate-env.sh in IBM Cloud Shell +From within IBM Cloud Shell run the following two commands to download the script and make it executable: +```bash +wget https://raw.githubusercontent.com/IBM/CRAIG/main/generate-env.sh +chmod 755 generate-env.sh +``` To generate an env containing all of the workspaces in your account, you can run the following command: diff --git a/.docs/power-vs-workspace-deployment.md b/.docs/power-vs-workspace-deployment.md index fa6e4a80..0b35e781 100644 --- a/.docs/power-vs-workspace-deployment.md +++ b/.docs/power-vs-workspace-deployment.md @@ -31,7 +31,6 @@ If you do not want Power VS workspaces created in every zone or if you want to u #### 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 generate a `.env` file containing all of the workspaces in your account, you can run the following command: diff --git a/generate-env.sh b/generate-env.sh index cb525704..c43a5c2c 100755 --- a/generate-env.sh +++ b/generate-env.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash - fatal() { echo "FATAL: $*" exit 1 @@ -8,18 +7,24 @@ fatal() { 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" +install_ibmcloud_power_iaas_plugin () { + # Install the plugin if is not installed + if ! ibmcloud plugin show power-iaas &> /dev/null ; then + echo "Installing ibmcloud power-iaas plugin" + ibmcloud plugin install power-iaas || fatal "Failed to install the ibmcloud power-iaas plugin" + fi + + # Check the major version of the version and upgrade if it is less than 1 + majorVersion=$(ibmcloud plugin show power-iaas --output json | jq -r '.Version.Major' 2>/dev/null ) + [[ -z "$majorVersion" ]] && fatal "Unable to query the major version of the power-iaas ibmcloud plugin version" + if [[ $majorVersion -lt 1 ]] + then + echo "The power-iaas plugin version is less than 1.0.0, upgrading." + ibmcloud plugin update power-iaas fi } @@ -28,8 +33,8 @@ ibmcloud iam oauth-tokens &> /dev/null || fatal "Please log in with the ibmcloud check_runtime_prereqs -# Install ibmcloud plugins if necessary -install_ibmcloud_plugin power-iaas +# Install or upgrade the power_iaas plugin if necessary +install_ibmcloud_power_iaas_plugin outputfile=$1 echo "# Comment out or remove workspaces that you do not want to use with CRAIG." > $outputfile @@ -39,14 +44,15 @@ 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)"') +workspaces=$(ibmcloud pi ws list --json | jq -r '.Payload.workspaces[]? | "\(.name),\(.location.region),\(.id)"') # Cycle through all the workspaces while IFS=, read name region id; +while IFS=, 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 + region_upper=$(echo ${region} | tr '[:lower:]' '[:upper:]') + echo "POWER_WORKSPACE_${region_upper}=$id" >> $outputfile echo "" >> $outputfile done <<< "$workspaces" From 41ca0350fd858f8d74ce3dc0c6191a303eaa3b26 Mon Sep 17 00:00:00 2001 From: jvallexm Date: Thu, 14 Mar 2024 12:58:33 -0400 Subject: [PATCH 22/29] Issue 1623: Power VS Instances Outputs, Light Refactoring (#1675) * cleanup: some project methods * mv: dynamic form components * feat: better exports * feat: better exports * feat: better imports * feat: better imports * mv: workspace before ssh key in power form * feat: power vsi outputs --- CHANGELOG.md | 1 + client/src/components/forms/DynamicForm.js | 10 +- .../forms/dynamic-form/DynamicDatePicker.js | 41 +++ .../dynamic-form/DynamicFetchMultiSelect.js | 74 +++++ .../forms/dynamic-form/DynamicFetchSelect.js | 135 ++++++++++ .../forms/dynamic-form/components.js | 206 +------------- .../components/forms/dynamic-form/index.js | 4 +- client/src/components/pages/CraigForms.js | 2 +- .../components/pages/projects/LoadingModal.js | 32 ++- .../src/components/pages/projects/Wizard.js | 24 +- client/src/lib/docs/release-notes.json | 3 +- .../lib/forms/dynamic-form-fields/index.js | 2 + client/src/lib/forms/index.js | 34 +++ client/src/lib/index.js | 32 +++ client/src/lib/json-to-iac/outputs.js | 25 +- client/src/lib/state/appid.js | 2 +- unit-tests/json-to-iac/outputs.test.js | 252 ++++++++++++++++++ 17 files changed, 639 insertions(+), 240 deletions(-) create mode 100644 client/src/components/forms/dynamic-form/DynamicDatePicker.js create mode 100644 client/src/components/forms/dynamic-form/DynamicFetchMultiSelect.js create mode 100644 client/src/components/forms/dynamic-form/DynamicFetchSelect.js diff --git a/CHANGELOG.md b/CHANGELOG.md index de603b62..f89cec5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file. - When updating a Power VS Instance name, references to that instance are now updated to match the new name - Users can now upload JSON directly to CRAIG from the local file explorer from the Projects page by clicking the new `Upload JSON` button - Users can now create Classic Bare Metal Servers from the form page `/form/classicBareMetal` +- Power VS Instance Primary IP addresses are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template ### Fixes diff --git a/client/src/components/forms/DynamicForm.js b/client/src/components/forms/DynamicForm.js index 73906d78..ab204220 100644 --- a/client/src/components/forms/DynamicForm.js +++ b/client/src/components/forms/DynamicForm.js @@ -6,17 +6,15 @@ import { isFunction, getObjectFromArray, } from "lazy-z"; -import { forceShowForm, propsMatchState } from "../../lib"; import { + forceShowForm, + propsMatchState, dynamicCraigFormGroupsProps, dynamicHeadingProps, dynamicToolTipWrapperProps, -} from "../../lib/forms/dynamic-form-fields"; +} from "../../lib"; import { edgeRouterEnabledZones } from "../../lib/constants"; -import { - DynamicFetchMultiSelect, - DynamicFetchSelect, -} from "./dynamic-form/components"; +import { DynamicFetchMultiSelect, DynamicFetchSelect } from "./dynamic-form"; import { SubnetTileSubForm, SubnetTileTitle, diff --git a/client/src/components/forms/dynamic-form/DynamicDatePicker.js b/client/src/components/forms/dynamic-form/DynamicDatePicker.js new file mode 100644 index 00000000..eb66f3ea --- /dev/null +++ b/client/src/components/forms/dynamic-form/DynamicDatePicker.js @@ -0,0 +1,41 @@ +import React from "react"; +import { DatePicker, DatePickerInput } from "@carbon/react"; +import PropTypes from "prop-types"; + +const DynamicDatePicker = (props) => { + // only used in opaque secrets, if we use this in other places we can + // change it to be more dynamic + return ( + { + let event = { + target: { + name: "expiration_date", + value: selectEvent[0], + }, + }; + props.handleInputChange(event); + }} + > + + + ); +}; + +DynamicDatePicker.propTypes = { + parentState: PropTypes.shape({ + expiration_date: PropTypes.string, + }).isRequired, + handleInputChange: PropTypes.func.isRequired, +}; + +export { DynamicDatePicker }; diff --git a/client/src/components/forms/dynamic-form/DynamicFetchMultiSelect.js b/client/src/components/forms/dynamic-form/DynamicFetchMultiSelect.js new file mode 100644 index 00000000..78a99731 --- /dev/null +++ b/client/src/components/forms/dynamic-form/DynamicFetchMultiSelect.js @@ -0,0 +1,74 @@ +import React from "react"; +import { FilterableMultiSelect } from "@carbon/react"; +import { dynamicMultiSelectProps } from "../../../lib"; +import PropTypes from "prop-types"; +import { deepEqual } from "lazy-z"; + +class DynamicFetchMultiSelect extends React.Component { + _isMounted = false; + constructor(props) { + super(props); + this.state = { + data: ["Loading..."], + }; + } + + componentDidMount() { + this._isMounted = true; + // on mount if not items have been set + if (deepEqual(this.state.data, ["Loading..."])) { + fetch( + // generate api endpoint based on state and props + this.props.field.apiEndpoint( + this.props.parentState, + this.props.parentProps + ) + ) + .then((res) => res.json()) + .then((data) => { + // set state with data if mounted + if (this._isMounted) { + this.setState({ data: data }, () => { + this.props.onPowerImageLoad(data); + }); + } + }) + .catch((err) => { + console.error(err); + }); + } + } + + componentWillUnmount() { + this._isMounted = false; + } + + // Force re-fetch of images on zone change + componentDidUpdate(prevProps) { + if (prevProps.parentState.zone != this.props.parentState.zone) { + this._isMounted = false; + this.setState({ data: ["Loading..."] }, () => { + this.componentDidMount(); + }); + } + } + + render() { + let props = { ...this.props }; + return ( + + ); + } +} + +DynamicFetchMultiSelect.propTypes = { + onPowerImageLoad: PropTypes.func.isRequired, + field: PropTypes.shape({ + apiEndpoint: PropTypes.func.isRequired, + }).isRequired, + parentState: PropTypes.shape({}), +}; + +export default DynamicFetchMultiSelect; diff --git a/client/src/components/forms/dynamic-form/DynamicFetchSelect.js b/client/src/components/forms/dynamic-form/DynamicFetchSelect.js new file mode 100644 index 00000000..37154ffe --- /dev/null +++ b/client/src/components/forms/dynamic-form/DynamicFetchSelect.js @@ -0,0 +1,135 @@ +import React from "react"; +import PropTypes from "prop-types"; +// popover wrapper needs to be imported this way to prevent an error importing +// dynamic form before initializtion +import { default as PopoverWrapper } from "../utils/PopoverWrapper"; +import { contains, deepEqual, isFunction, isNullOrEmptyString } from "lazy-z"; +import { dynamicFieldId, dynamicSelectProps } from "../../../lib"; +import { Select, SelectItem } from "@carbon/react"; + +class DynamicFetchSelect extends React.Component { + _isMounted = false; + constructor(props) { + super(props); + this.state = { + data: ["Loading..."], + }; + this.dataToGroups = this.dataToGroups.bind(this); + } + + componentDidMount() { + this._isMounted = true; + // on mount if not items have been set + if (deepEqual(this.state.data, ["Loading..."])) { + fetch( + // generate api endpoint based on state and props + this.props.field.apiEndpoint( + this.props.parentState, + this.props.parentProps + ) + ) + .then((res) => res.json()) + .then((data) => { + // set state with data if mounted + if (this._isMounted) { + this.setState({ data: data }); + } + }) + .catch((err) => { + console.error(err); + }); + } + } + + componentWillUnmount() { + this._isMounted = false; + } + + dataToGroups() { + let apiEndpoint = this.props.field.apiEndpoint( + this.props.parentState, + this.props.parentProps + ); + if (apiEndpoint === "/api/cluster/versions") { + // add "" if kube version is reset + return ( + this.props.parentProps.isModal || + isNullOrEmptyString(this.props.parentState.kube_version) + ? [""] + : [] + ).concat( + // filter version based on kube type + this.state.data.filter((version) => { + if ( + (this.props.parentState.kube_type === "openshift" && + contains(version, "openshift")) || + (this.props.parentState.kube_type === "iks" && + !contains(version, "openshift")) || + version === "default" + ) { + return version.replace(/\s\(Default\)/g, ""); + } + }) + ); + } else { + return ( + // to prevent storage pools from being loaded incorrectly, + // prevent first item in storage groups from being loaded when not selected + ( + dynamicSelectProps(this.props).value === "" && + this._isMounted && + !deepEqual(this.state.data, ["Loading..."]) + ? [""] + : [] + ) + .concat(this.state.data) + .map((item) => { + if (isFunction(this.props.field.onRender)) { + return this.props.field.onRender({ + [this.props.name]: item, + }); + } else return item; + }) + ); + } + } + + render() { + let props = { ...this.props }; + return ( + + + + ); + } +} + +DynamicFetchSelect.propTypes = { + parentState: PropTypes.shape({ + kube_version: PropTypes.string, + }).isRequired, + parentProps: PropTypes.shape({ + isModal: PropTypes.bool, + }).isRequired, + field: PropTypes.shape({ + apiEndpoint: PropTypes.func.isRequired, + onRender: PropTypes.func, + tooltip: PropTypes.shape({}), + }).isRequired, +}; + +export default DynamicFetchSelect; diff --git a/client/src/components/forms/dynamic-form/components.js b/client/src/components/forms/dynamic-form/components.js index 293e3b4b..ec979aad 100644 --- a/client/src/components/forms/dynamic-form/components.js +++ b/client/src/components/forms/dynamic-form/components.js @@ -9,7 +9,8 @@ import { dynamicToggleProps, dynamicTextAreaProps, dynamicMultiSelectProps, -} from "../../../lib/forms/dynamic-form-fields"; + dynamicPasswordInputProps, +} from "../../../lib"; import { FilterableMultiSelect, SelectItem, @@ -18,12 +19,8 @@ import { TextInput, Select, Tag, - DatePicker, - DatePickerInput, } from "@carbon/react"; import PropTypes from "prop-types"; -import { dynamicPasswordInputProps } from "../../../lib/forms/dynamic-form-fields/password-input"; -import { contains, deepEqual, isFunction, isNullOrEmptyString } from "lazy-z"; import { ToolTipWrapper } from "../utils/ToolTip"; import { RenderForm } from "../utils"; @@ -224,204 +221,6 @@ DynamicPublicKey.propTypes = { handleInputChange: PropTypes.func.isRequired, }; -export class DynamicFetchSelect extends React.Component { - _isMounted = false; - constructor(props) { - super(props); - this.state = { - data: ["Loading..."], - }; - this.dataToGroups = this.dataToGroups.bind(this); - } - - componentDidMount() { - this._isMounted = true; - // on mount if not items have been set - if (deepEqual(this.state.data, ["Loading..."])) - fetch( - // generate api endpoint based on state and props - this.props.field.apiEndpoint( - this.props.parentState, - this.props.parentProps - ) - ) - .then((res) => res.json()) - .then((data) => { - // set state with data if mounted - if (this._isMounted) { - this.setState({ data: data }); - } - }) - .catch((err) => { - console.error(err); - }); - } - - componentWillUnmount() { - this._isMounted = false; - } - - dataToGroups() { - let apiEndpoint = this.props.field.apiEndpoint( - this.props.parentState, - this.props.parentProps - ); - if (apiEndpoint === "/api/cluster/versions") { - // add "" if kube version is reset - return ( - this.props.parentProps.isModal || - isNullOrEmptyString(this.props.parentState.kube_version) - ? [""] - : [] - ).concat( - // filter version based on kube type - this.state.data.filter((version) => { - if ( - (this.props.parentState.kube_type === "openshift" && - contains(version, "openshift")) || - (this.props.parentState.kube_type === "iks" && - !contains(version, "openshift")) || - version === "default" - ) { - return version.replace(/\s\(Default\)/g, ""); - } - }) - ); - } else { - return ( - // to prevent storage pools from being loaded incorrectly, - // prevent first item in storage groups from being loaded when not selected - ( - dynamicSelectProps(this.props).value === "" && - this._isMounted && - !deepEqual(this.state.data, ["Loading..."]) - ? [""] - : [] - ) - .concat(this.state.data) - .map((item) => { - if (isFunction(this.props.field.onRender)) { - return this.props.field.onRender({ - [this.props.name]: item, - }); - } else return item; - }) - ); - } - } - - render() { - let props = { ...this.props }; - return ( - - - - ); - } -} - -export class DynamicFetchMultiSelect extends React.Component { - _isMounted = false; - constructor(props) { - super(props); - this.state = { - data: ["Loading..."], - }; - } - - componentDidMount() { - this._isMounted = true; - // on mount if not items have been set - if (deepEqual(this.state.data, ["Loading..."])) { - fetch( - // generate api endpoint based on state and props - this.props.field.apiEndpoint( - this.props.parentState, - this.props.parentProps - ) - ) - .then((res) => res.json()) - .then((data) => { - // set state with data if mounted - if (this._isMounted) { - this.setState({ data: data }, () => { - this.props.onPowerImageLoad(data); - }); - } - }) - .catch((err) => { - console.error(err); - }); - } - } - - componentWillUnmount() { - this._isMounted = false; - } - - // Force re-fetch of images on zone change - componentDidUpdate(prevProps) { - if (prevProps.parentState.zone != this.props.parentState.zone) { - this._isMounted = false; - this.setState({ data: ["Loading..."] }, () => { - this.componentDidMount(); - }); - } - } - - render() { - let props = { ...this.props }; - return ( - - ); - } -} - -const DynamicDatePicker = (props) => { - // only used in opaque secrets, if we use this in other places we can - // change it to be more dynamic - return ( - { - let event = { - target: { - name: "expiration_date", - value: selectEvent[0], - }, - }; - props.handleInputChange(event); - }} - > - - - ); -}; - export { DynamicFormTextInput, DynamicFormSelect, @@ -431,5 +230,4 @@ export { DynamicPublicKey, DynamicToolTipWrapper, tagColors, - DynamicDatePicker, }; diff --git a/client/src/components/forms/dynamic-form/index.js b/client/src/components/forms/dynamic-form/index.js index 9b26bbb1..095d61ba 100644 --- a/client/src/components/forms/dynamic-form/index.js +++ b/client/src/components/forms/dynamic-form/index.js @@ -5,11 +5,13 @@ export { DynamicFormToggle, DynamicTextArea, DynamicPublicKey, - DynamicDatePicker, DynamicToolTipWrapper, } from "./components"; +export { DynamicDatePicker } from "./DynamicDatePicker"; export { PowerInterfaces } from "./PowerInterfaces"; export { SubFormOverrideTile } from "./SubFormOverrideTile"; +export { default as DynamicFetchMultiSelect } from "./DynamicFetchMultiSelect"; +export { default as DynamicFetchSelect } from "./DynamicFetchSelect"; export { ClassicDisabledTile, NoClassicGatewaysTile, diff --git a/client/src/components/pages/CraigForms.js b/client/src/components/pages/CraigForms.js index 71fe8c43..2121d7b1 100644 --- a/client/src/components/pages/CraigForms.js +++ b/client/src/components/pages/CraigForms.js @@ -1074,8 +1074,8 @@ function craigForms(craig) { }, { name: craig.power_instances.name, - ssh_key: craig.power_instances.ssh_key, workspace: craig.power_instances.workspace, + ssh_key: craig.power_instances.ssh_key, }, { network: craig.power_instances.network, diff --git a/client/src/components/pages/projects/LoadingModal.js b/client/src/components/pages/projects/LoadingModal.js index 5b5dfc19..c948fe7c 100644 --- a/client/src/components/pages/projects/LoadingModal.js +++ b/client/src/components/pages/projects/LoadingModal.js @@ -11,9 +11,12 @@ import { } from "@carbon/react"; import { CheckmarkOutline, CloseOutline } from "@carbon/icons-react"; import PropTypes from "prop-types"; -import { distinct, eachKey, keys, splat, titleCase } from "lazy-z"; +import { eachKey, keys, titleCase } from "lazy-z"; export const LoadingModal = (props) => { + let isUpload = props.action === "upload"; + let task = isUpload ? "Upload" : "Create"; + let isFailed = props.failed === true; return ( { open={props.open} passiveModal={!props.completed} modalHeading={ - props.action === "upload" - ? props.completed === false - ? `Upload to Schematics Workspace: ${props.workspace}` - : "Upload " + (props.failed === true ? "Failed!" : "Completed!") - : props.completed === false - ? `Create Schematics Workspace: ${props.workspace}` - : "Create " + (props.failed === true ? "Failed!" : "Completed!") + props.completed === false + ? `${task} Schematics Workspace: ${props.workspace}` + : `${task} ` + (isFailed ? "Failed!" : "Completed!") } // complete onRequestSubmit={() => { - if (props.failed === true) { + if (isFailed) { if (props.action === "create") { // for fake state for onProjectSave when action is "create" let fakeState = { @@ -50,16 +49,15 @@ export const LoadingModal = (props) => { } else props.retryCallback(); } else { // didn't fail so only need onRequestSubmit for upload - props.action === "upload" - ? window.open(props.workspace_url + "/jobs", "_blank") - : window.open(props.workspace_url, "_blank"); + window.open( + props.workspace_url + (isUpload ? "/jobs" : ""), + "_blank" + ); } }} onSecondarySubmit={props.toggleModal} - primaryButtonText={ - props.failed === true ? "Retry" : "Launch Workspace in New Tab" - } - secondaryButtonText={props.failed === true ? "Cancel" : "Close"} + primaryButtonText={isFailed ? "Retry" : "Launch Workspace in New Tab"} + secondaryButtonText={isFailed ? "Cancel" : "Close"} onRequestClose={props.toggleModal} > {props.completed === false ? ( @@ -73,7 +71,7 @@ export const LoadingModal = (props) => { ) : ( <> - {props.failed === true ? ( + {isFailed ? ( <> {/* when complete but failed show X */}
diff --git a/client/src/components/pages/projects/Wizard.js b/client/src/components/pages/projects/Wizard.js index a02aaab4..a28041be 100644 --- a/client/src/components/pages/projects/Wizard.js +++ b/client/src/components/pages/projects/Wizard.js @@ -53,6 +53,19 @@ class Wizard extends React.Component { this.handleToggle = this.handleToggle.bind(this); this.handlePowerZonesChange = this.handlePowerZonesChange.bind(this); this.onRequestSubmit = this.onRequestSubmit.bind(this); + this.modalShouldBeDisabled = this.modalShouldBeDisabled.bind(this); + } + + modalShouldBeDisabled() { + return ( + // if project name is invalid + invalidProjectName(this.state, this.props) || + // if is power vs and no zones + (this.state.enable_power_vs && isEmpty(this.state.power_vs_zones)) || + // or is fs cloud and no key management service + (this.state.fs_cloud && + isNullOrEmptyString(this.state.key_management_service)) + ); } onRequestSubmit() { @@ -80,7 +93,7 @@ class Wizard extends React.Component { if (name === "region") { this.setState({ region: value, - power_vs_zones: [], + power_vs_zones: ["dal12", "wdc06"], }); } else this.setState({ [name]: value }); } @@ -94,7 +107,7 @@ class Wizard extends React.Component { } else if (name === "power_vs_high_availability" && !this.state[name]) { this.setState({ [name]: !this.state.name, - power_vs_zones: ["dal12", "wdc06"], + power_vs_zones: [], }); } else this.setState({ [name]: !this.state[name] }); } @@ -125,12 +138,7 @@ class Wizard extends React.Component { primaryButtonText="Get Started" onRequestClose={this.props.onRequestClose} onRequestSubmit={this.onRequestSubmit} - primaryButtonDisabled={ - invalidProjectName(this.state, this.props) || - (this.state.enable_power_vs && isEmpty(this.state.power_vs_zones)) || - (this.state.fs_cloud && - isNullOrEmptyString(this.state.key_management_service)) - } + primaryButtonDisabled={this.modalShouldBeDisabled()} size="lg" className="leftTextAlign" > diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index 48834d77..8919fa9f 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -9,7 +9,8 @@ "Power VS High Availability is now supported for `mad02`, `mad04`, `us-east`, `wdc06`, `us-south`, `eu-de-1`, and `eu-de-2`", "When updating a Power VS Instance name, references to that instance are now updated to match the new name", "Users can now upload JSON directly to CRAIG from the local file explorer from the Projects page by clicking the new `Upload JSON` button", - "Users can now create Classic Bare Metal Servers from the form page `/form/classicBareMetal`" + "Users can now create Classic Bare Metal Servers from the form page `/form/classicBareMetal`", + "Power VS Instance Primary IP addresses are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template" ], "fixes": [ "Fixed an issue with button hoverText alignment causing overflow in forms", diff --git a/client/src/lib/forms/dynamic-form-fields/index.js b/client/src/lib/forms/dynamic-form-fields/index.js index ae77a9c0..1efa6def 100644 --- a/client/src/lib/forms/dynamic-form-fields/index.js +++ b/client/src/lib/forms/dynamic-form-fields/index.js @@ -15,8 +15,10 @@ const { dynamicTextAreaProps } = require("./text-area"); const { dynamicHeadingProps } = require("./heading"); const { dynamicCraigFormGroupsProps } = require("./craig-form-group"); const { dynamicToolTipWrapperProps } = require("./dynamic-tooltip-wrapper"); +const { dynamicPasswordInputProps } = require("./password-input"); module.exports = { + dynamicPasswordInputProps, dynamicToolTipWrapperProps, dynamicCraigFormGroupsProps, dynamicHeadingProps, diff --git a/client/src/lib/forms/index.js b/client/src/lib/forms/index.js index f57e747c..371e22e4 100644 --- a/client/src/lib/forms/index.js +++ b/client/src/lib/forms/index.js @@ -37,8 +37,42 @@ const { getDisplayTierSubnetList, shouldDisplayService, } = require("./diagrams"); +const { + dynamicToolTipWrapperProps, + dynamicCraigFormGroupsProps, + dynamicHeadingProps, + dynamicTextAreaProps, + dynamicSelectProps, + dynamicMultiSelectProps, + dynamicToggleProps, + fieldFunctionReturnsBooleanCheck, + disabledReturnsBooleanCheck, + invalidReturnsBooleanCheck, + fieldFunctionReturnsStringCheck, + groupsEvaluatesToArrayCheck, + dynamicFieldId, + addClassName, + dynamicTextInputProps, + dynamicPasswordInputProps, +} = require("./dynamic-form-fields"); module.exports = { + dynamicPasswordInputProps, + dynamicToolTipWrapperProps, + dynamicCraigFormGroupsProps, + dynamicHeadingProps, + dynamicTextAreaProps, + dynamicSelectProps, + dynamicMultiSelectProps, + dynamicToggleProps, + fieldFunctionReturnsBooleanCheck, + disabledReturnsBooleanCheck, + invalidReturnsBooleanCheck, + fieldFunctionReturnsStringCheck, + groupsEvaluatesToArrayCheck, + dynamicFieldId, + addClassName, + dynamicTextInputProps, shouldDisplayService, getDisplayTierSubnetList, getDisplaySubnetTiers, diff --git a/client/src/lib/index.js b/client/src/lib/index.js index c4ebf9fc..a7c7be16 100644 --- a/client/src/lib/index.js +++ b/client/src/lib/index.js @@ -26,6 +26,22 @@ const { getDisplaySubnetTiers, getDisplayTierSubnetList, shouldDisplayService, + dynamicToolTipWrapperProps, + dynamicCraigFormGroupsProps, + dynamicHeadingProps, + dynamicTextAreaProps, + dynamicSelectProps, + dynamicMultiSelectProps, + dynamicToggleProps, + fieldFunctionReturnsBooleanCheck, + disabledReturnsBooleanCheck, + invalidReturnsBooleanCheck, + fieldFunctionReturnsStringCheck, + groupsEvaluatesToArrayCheck, + dynamicFieldId, + addClassName, + dynamicTextInputProps, + dynamicPasswordInputProps, } = require("./forms"); const { slzToCraig } = require("./slz-to-craig"); const validate = require("./validate"); @@ -120,6 +136,22 @@ const { invalidForms } = require("./invalid-forms"); const { allDocText, filterDocs } = require("./docs"); module.exports = { + dynamicPasswordInputProps, + dynamicToolTipWrapperProps, + dynamicCraigFormGroupsProps, + dynamicHeadingProps, + dynamicTextAreaProps, + dynamicSelectProps, + dynamicMultiSelectProps, + dynamicToggleProps, + fieldFunctionReturnsBooleanCheck, + disabledReturnsBooleanCheck, + invalidReturnsBooleanCheck, + fieldFunctionReturnsStringCheck, + groupsEvaluatesToArrayCheck, + dynamicFieldId, + addClassName, + dynamicTextInputProps, classicVsiTf, classicBareMetalTf, classicSecurityGroupTf, diff --git a/client/src/lib/json-to-iac/outputs.js b/client/src/lib/json-to-iac/outputs.js index 71c7981d..0d253b7a 100644 --- a/client/src/lib/json-to-iac/outputs.js +++ b/client/src/lib/json-to-iac/outputs.js @@ -59,6 +59,8 @@ function outputsTf(config) { "\n" ) + (config.vpcs[index + 1] ? "\n" : ""); }); + + // vsi outputs config.vsi.forEach((deployment) => { let deploymentOutputs = {}; deployment.subnets.sort(azsort).forEach((subnet, subnetIndex) => { @@ -106,9 +108,10 @@ function outputsTf(config) { "\n" ); }); + + // power workspaces config.power.forEach((workspace, index) => { let outputs = {}; - // power workspace outputs ["name", "guid", "crn"].forEach((field) => { outputs["power_vs_workspace_" + snakeCase(workspace.name) + "_" + field] = @@ -135,6 +138,26 @@ function outputsTf(config) { ) + (config.power[index + 1] ? "\n" : ""); }); + + let powerInstanceOutputs = {}; + // power instances + if (config.power_instances) + config.power_instances.forEach((instance, index) => { + let vsiRef = `${snakeCase( + instance.workspace + )}_workspace_instance_${snakeCase(instance.name)}`; + powerInstanceOutputs[vsiRef + "_primary_ip"] = { + value: `\${ibm_pi_instance.${vsiRef}.pi_network[0].ip_address}`, + }; + }); + if (Object.keys(powerInstanceOutputs).length > 0) + tf += + "\n" + + tfBlock( + "Power VS Instance Outputs", + "\n" + jsonToTf(JSON.stringify({ output: powerInstanceOutputs })) + "\n" + ); + return tf; } diff --git a/client/src/lib/state/appid.js b/client/src/lib/state/appid.js index 1f6819b0..fb52cdeb 100644 --- a/client/src/lib/state/appid.js +++ b/client/src/lib/state/appid.js @@ -1,4 +1,4 @@ -const { transpose, splatContains, isNullOrEmptyString } = require("lazy-z"); +const { transpose, splatContains } = require("lazy-z"); const { setUnfoundResourceGroup, updateSubChild, diff --git a/unit-tests/json-to-iac/outputs.test.js b/unit-tests/json-to-iac/outputs.test.js index 4f9b7300..00a34525 100644 --- a/unit-tests/json-to-iac/outputs.test.js +++ b/unit-tests/json-to-iac/outputs.test.js @@ -2368,5 +2368,257 @@ output "power_vs_workspace_iac_power_workspace_test_output_crn" { "it should return correct outputs" ); }); + it("should return the correct outputs for power vs instances", () => { + let actualData = outputsTf({ + _options: { + prefix: "jvdev", + region: "us-south", + tags: ["hello", "world"], + zones: 3, + endpoints: "private", + account_id: null, + fs_cloud: false, + enable_classic: false, + dynamic_subnets: true, + enable_power_vs: true, + craig_version: "1.13.0", + power_vs_zones: ["dal10"], + power_vs_high_availability: false, + no_vpn_secrets_manager_auth: false, + template: "Empty Project", + power_vs_ha_zone_1: null, + power_vs_ha_zone_2: null, + }, + access_groups: [], + appid: [], + atracker: { + enabled: false, + type: "cos", + name: "atracker", + target_name: "atracker-cos", + bucket: null, + add_route: true, + cos_key: null, + locations: ["global", "us-south"], + instance: false, + plan: "lite", + resource_group: null, + }, + cbr_rules: [], + cbr_zones: [], + clusters: [], + dns: [], + event_streams: [], + f5_vsi: [], + iam_account_settings: { + enable: false, + mfa: null, + allowed_ip_addresses: null, + include_history: false, + if_match: null, + max_sessions_per_identity: null, + restrict_create_service_id: null, + restrict_create_platform_apikey: null, + session_expiration_in_seconds: null, + session_invalidation_in_seconds: null, + }, + icd: [], + key_management: [], + load_balancers: [], + logdna: { + name: "logdna", + archive: false, + enabled: false, + plan: "lite", + endpoints: "private", + platform_logs: false, + resource_group: null, + cos: null, + bucket: null, + }, + object_storage: [], + power: [ + { + use_data: false, + name: "vsi", + zone: "dal10", + resource_group: "asset-development", + imageNames: ["CentOS-Stream-8"], + images: [ + { + creationDate: "2023-09-20T22:15:08.000Z", + description: "", + href: "/pcloud/v1/cloud-instances/d839ff9f75e2465a81707aa69ee9a9b7/stock-images/ecb9553c-9b7d-4a53-bf0c-0ab2c748bbc7", + imageID: "ecb9553c-9b7d-4a53-bf0c-0ab2c748bbc7", + lastUpdateDate: "2023-09-21T09:35:17.000Z", + name: "CentOS-Stream-8", + specifications: { + architecture: "ppc64", + containerFormat: "bare", + diskFormat: "raw", + endianness: "little-endian", + hypervisorType: "phyp", + operatingSystem: "rhel", + }, + state: "active", + storagePool: "Tier3-Flash-1", + storageType: "tier3", + workspace: "vsi", + zone: "dal10", + workspace_use_data: false, + }, + ], + ssh_keys: [ + { + workspace_use_data: false, + use_data: false, + name: "vsi", + public_key: "NONE", + workspace: "vsi", + zone: "dal10", + }, + ], + network: [ + { + workspace_use_data: false, + name: "nw", + use_data: false, + pi_network_type: "vlan", + pi_cidr: "10.10.10.10/25", + pi_dns: ["127.0.0.1"], + pi_network_mtu: "1450", + workspace: "vsi", + zone: "dal10", + }, + ], + cloud_connections: [], + attachments: [], + }, + ], + power_instances: [ + { + sap: false, + sap_profile: null, + name: "output-test", + ssh_key: "vsi", + workspace: "vsi", + network: [ + { + name: "nw", + ip_address: "", + }, + ], + primary_subnet: "nw", + image: "CentOS-Stream-8", + pi_sys_type: "e880", + pi_storage_pool_affinity: false, + pi_proc_type: "shared", + pi_processors: "0.25", + pi_memory: "2", + pi_ibmi_css: false, + pi_ibmi_pha: false, + pi_ibmi_rds_users: null, + pi_storage_type: "tier1", + storage_option: "None", + pi_storage_pool: null, + affinity_type: null, + pi_affinity_volume: null, + pi_anti_affinity_volume: null, + pi_anti_affinity_instance: null, + pi_affinity_instance: null, + pi_user_data: null, + zone: "dal10", + pi_affinity_policy: null, + }, + ], + power_volumes: [], + resource_groups: [ + { + use_prefix: false, + name: "asset-development", + use_data: true, + }, + ], + routing_tables: [], + scc: { + credential_description: null, + id: null, + passphrase: null, + name: "", + location: "us", + collector_description: null, + is_public: false, + scope_description: null, + enable: false, + }, + secrets_manager: [], + security_groups: [], + ssh_keys: [], + sysdig: { + enabled: false, + plan: "graduated-tier", + resource_group: null, + name: "sysdig", + platform_logs: false, + }, + teleport_vsi: [], + transit_gateways: [], + virtual_private_endpoints: [], + vpcs: [], + vpn_gateways: [], + vpn_servers: [], + vsi: [], + classic_ssh_keys: [], + classic_vlans: [], + vtl: [], + classic_gateways: [], + cis: [], + scc_v2: { + enable: false, + resource_group: null, + region: "", + account_id: "${var.account_id}", + profile_attachments: [], + }, + cis_glbs: [], + fortigate_vnf: [], + classic_security_groups: [], + classic_vsi: [], + classic_bare_metal: [], + }); + let expectedData = `############################################################################## +# VSI Power Workspace Outputs +############################################################################## + +output "power_vs_workspace_vsi_name" { + value = ibm_resource_instance.power_vs_workspace_vsi.name +} + +output "power_vs_workspace_vsi_guid" { + value = ibm_resource_instance.power_vs_workspace_vsi.guid +} + +output "power_vs_workspace_vsi_crn" { + value = ibm_resource_instance.power_vs_workspace_vsi.crn +} + +############################################################################## + +############################################################################## +# Power VS Instance Outputs +############################################################################## + +output "vsi_workspace_instance_output_test_primary_ip" { + value = ibm_pi_instance.vsi_workspace_instance_output_test.pi_network[0].ip_address +} + +############################################################################## +`; + assert.deepEqual( + actualData, + expectedData, + "it should create correct outputs" + ); + }); }); }); From b68fa99d4d65849d0ac41c4e42a5dffb1faeb9a4 Mon Sep 17 00:00:00 2001 From: terechc Date: Thu, 14 Mar 2024 13:05:34 -0400 Subject: [PATCH 23/29] Issue 1659: Add Classic VSI and Bare Metal to V2 (#1670) * v2 classic vsi + bare metal * render icons in vlan * prop types * spacing * prettier * no margin when small * required fields --- .../src/components/pages/classic/Classic.js | 91 +++++++++++++++++++ .../pages/diagrams/ClassicBareMetal.js | 69 ++++++++++++++ .../pages/diagrams/ClassicGateways.js | 1 + .../components/pages/diagrams/ClassicVsi.js | 65 +++++++++++++ .../src/components/pages/diagrams/DocTabs.js | 2 + .../src/components/pages/diagrams/Overview.js | 8 ++ .../pages/diagrams/PowerSubnetInnerBox.js | 3 + client/src/components/pages/diagrams/index.js | 2 + client/src/lib/state/classic-bare-metal.js | 14 +++ unit-tests/state/classic-bare-metal.test.js | 9 ++ 10 files changed, 264 insertions(+) create mode 100644 client/src/components/pages/diagrams/ClassicBareMetal.js create mode 100644 client/src/components/pages/diagrams/ClassicVsi.js diff --git a/client/src/components/pages/classic/Classic.js b/client/src/components/pages/classic/Classic.js index 4b47ed88..40bddb74 100644 --- a/client/src/components/pages/classic/Classic.js +++ b/client/src/components/pages/classic/Classic.js @@ -4,6 +4,8 @@ import { ClassicMap, ClassicSecurityGroups, ClassicSubnets, + ClassicBareMetal, + ClassicVsi, SshKeys, docTabs, } from "../diagrams"; @@ -14,6 +16,8 @@ import { Password, SecurityServices, VlanIbm, + IbmCloudBareMetalServer, + InstanceClassic, } from "@carbon/icons-react"; import { StatefulTabs, @@ -26,6 +30,8 @@ import { import { classicInfraTf, classicSecurityGroupTf, + classicBareMetalTf, + classicVsiTf, disableSave, propsMatchState, } from "../../../lib"; @@ -51,6 +57,8 @@ class ClassicDiagram extends React.Component { this.resetSelection = this.resetSelection.bind(this); this.onVlanClick = this.onVlanClick.bind(this); this.onSgClick = this.onSgClick.bind(this); + this.onBareMetalClick = this.onBareMetalClick.bind(this); + this.onVsiClick = this.onVsiClick.bind(this); this.getIcon = this.getIcon.bind(this); this.onGwClick = this.onGwClick.bind(this); this.handleInputChange = this.handleInputChange.bind(this); @@ -90,6 +98,10 @@ class ClassicDiagram extends React.Component { ? Password : this.state.selectedItem === "classic_vlans" ? VlanIbm + : this.state.selectedItem === "classic_bare_metal" + ? IbmCloudBareMetalServer + : this.state.selectedItem === "classic_vsi" + ? InstanceClassic : FirewallClassic; } @@ -166,6 +178,46 @@ class ClassicDiagram extends React.Component { } } + /** + * on bare metal click + * @param {number} bareMetalIndex + */ + onBareMetalClick(bareMetalIndex) { + if ( + this.state.editing && + this.state.selectedItem === "classic_bare_metal" && + bareMetalIndex === this.state.selectedIndex + ) { + this.resetSelection(); + } else { + this.setState({ + editing: true, + selectedIndex: bareMetalIndex, + selectedItem: "classic_bare_metal", + }); + } + } + + /** + * on classic vsi click + * @param {number} vsiIndex + */ + onVsiClick(vsiIndex) { + if ( + this.state.editing && + this.state.selectedItem === "classic_vsi" && + vsiIndex === this.state.selectedIndex + ) { + this.resetSelection(); + } else { + this.setState({ + editing: true, + selectedIndex: vsiIndex, + selectedItem: "classic_vsi", + }); + } + } + /** * on ssh key click * @param {number} keyIndex @@ -228,6 +280,8 @@ class ClassicDiagram extends React.Component { "Classic VLANs", "Classic Gateways", "Classic Security Groups", + "Classic Bare Metal", + "Classic VSI", ], disabled: (stateData) => { return false; @@ -257,6 +311,7 @@ class ClassicDiagram extends React.Component { name={`New ${titleCase(this.state.modalService) .replace("Vlans", "VLANs") .replace("Ssh", "SSH") + .replace("Vsi", "VSI") .replace(/s$/g, "")}`} className="marginBottomSmall" type="subHeading" @@ -317,6 +372,8 @@ class ClassicDiagram extends React.Component { "Classic VLANs", "Classic Gateways", "Classic Security Groups", + "Classic Bare Metal", + "Classic VSIs", ], craig )} @@ -333,6 +390,14 @@ class ClassicDiagram extends React.Component { name: "Classic Security Groups", tf: classicSecurityGroupTf(craig.store.json) || "", }, + { + name: "Classic Bare Metal", + tf: classicBareMetalTf(craig.store.json) || "", + }, + { + name: "Classic VSIs", + tf: classicVsiTf(craig.store.json) || "", + }, ]} form={ <> @@ -418,6 +483,27 @@ class ClassicDiagram extends React.Component { ); }} /> + { + return ( + this.state.selectedItem === "classic_vsi" && + this.state.selectedIndex === vsiIndex + ); + }} + onClick={this.onVsiClick} + /> + { + return ( + this.state.selectedItem === + "classic_bare_metal" && + this.state.selectedIndex === bareMetalIndex + ); + }} + onClick={this.onBareMetalClick} + />
@@ -447,6 +533,11 @@ class ClassicDiagram extends React.Component { ? "Classic SSH Key" : this.state.selectedItem === "classic_vlans" ? "VLAN" + : this.state.selectedItem === + "classic_bare_metal" + ? "Classic Bare Metal" + : this.state.selectedItem === "classic_vsi" + ? "Classic VSI" : "Classic Gateway" } ${ craig.store.json[this.state.selectedItem][ diff --git a/client/src/components/pages/diagrams/ClassicBareMetal.js b/client/src/components/pages/diagrams/ClassicBareMetal.js new file mode 100644 index 00000000..e7732b94 --- /dev/null +++ b/client/src/components/pages/diagrams/ClassicBareMetal.js @@ -0,0 +1,69 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { PowerSubnetInnerBox } from "./PowerSubnetInnerBox"; +import { IbmCloudBareMetalServer } from "@carbon/icons-react"; +import { DeploymentIcon } from "./DeploymentIcon"; +import HoverClassNameWrapper from "./HoverClassNameWrapper"; + +export const ClassicBareMetal = (props) => { + let bare_metals = []; + props.craig.store.json.classic_bare_metal.forEach((server, serverIndex) => { + if ( + server.private_vlan === props.vlan || + server.public_vlan === props.vlan + ) { + let copyServer = { ...server }; + copyServer.index = serverIndex; + bare_metals.push(copyServer); + } + }); + return bare_metals.length === 0 ? ( + "" + ) : ( + + {bare_metals.map((server) => { + return ( + + { + props.onClick(server.index); + } + : undefined + } + isSelected={ + props.isSelected + ? () => props.isSelected(server.index) + : undefined + } + /> + + ); + })} + + ); +}; + +ClassicBareMetal.propTypes = { + craig: PropTypes.shape({}).isRequired, + isSelected: PropTypes.func, + onClick: PropTypes.func, + vlan: PropTypes.string, + datacenter: PropTypes.string, +}; diff --git a/client/src/components/pages/diagrams/ClassicGateways.js b/client/src/components/pages/diagrams/ClassicGateways.js index ec789e0e..af8753d4 100644 --- a/client/src/components/pages/diagrams/ClassicGateways.js +++ b/client/src/components/pages/diagrams/ClassicGateways.js @@ -30,6 +30,7 @@ export const ClassicGateways = (props) => { name="Classic Gateways" static={props.static} small={props.small} + marginBottom={props.craig.store.json.classic_vsi.length !== 0} > {gateways.map((gw) => { return ( diff --git a/client/src/components/pages/diagrams/ClassicVsi.js b/client/src/components/pages/diagrams/ClassicVsi.js new file mode 100644 index 00000000..d628c9b4 --- /dev/null +++ b/client/src/components/pages/diagrams/ClassicVsi.js @@ -0,0 +1,65 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { PowerSubnetInnerBox } from "./PowerSubnetInnerBox"; +import { InstanceClassic } from "@carbon/icons-react"; +import { DeploymentIcon } from "./DeploymentIcon"; +import HoverClassNameWrapper from "./HoverClassNameWrapper"; + +export const ClassicVsi = (props) => { + let vsis = []; + props.craig.store.json.classic_vsi.forEach((vsi, vsiIndex) => { + if (vsi.private_vlan === props.vlan || vsi.public_vlan === props.vlan) { + let copyVsi = { ...vsi }; + copyVsi.index = vsiIndex; + vsis.push(copyVsi); + } + }); + return vsis.length === 0 ? ( + "" + ) : ( + + {vsis.map((vsi) => { + return ( + + { + props.onClick(vsi.index); + } + : undefined + } + isSelected={ + props.isSelected ? () => props.isSelected(vsi.index) : undefined + } + /> + + ); + })} + + ); +}; + +ClassicVsi.propTypes = { + craig: PropTypes.shape({}).isRequired, + isSelected: PropTypes.func, + onClick: PropTypes.func, + vlan: PropTypes.string, + datacenter: PropTypes.string, +}; diff --git a/client/src/components/pages/diagrams/DocTabs.js b/client/src/components/pages/diagrams/DocTabs.js index 8243a375..7b3e4c00 100644 --- a/client/src/components/pages/diagrams/DocTabs.js +++ b/client/src/components/pages/diagrams/DocTabs.js @@ -36,6 +36,8 @@ export const docTabs = (tabs, craig) => { ? "subnets" : tab === "Virtual Servers" ? "vsi" + : tab === "Classic VSIs" + ? "classic_vsi" : tab ), craig.store.json._options.template diff --git a/client/src/components/pages/diagrams/Overview.js b/client/src/components/pages/diagrams/Overview.js index 4b27ae97..dc8a25fc 100644 --- a/client/src/components/pages/diagrams/Overview.js +++ b/client/src/components/pages/diagrams/Overview.js @@ -26,6 +26,8 @@ import { ClassicSubnets } from "./ClassicSubnets"; import { ClassicGateways } from "./ClassicGateways"; import { RoutingTables } from "./RoutingTables"; import { PassThroughWrapper } from "./PassthroughWrapper"; +import { ClassicVsi } from "./ClassicVsi"; +import { ClassicBareMetal } from "./ClassicBareMetal"; export class Overview extends React.Component { constructor(props) { @@ -194,6 +196,12 @@ export class Overview extends React.Component { static small={this.props.small} /> + +
diff --git a/client/src/components/pages/diagrams/PowerSubnetInnerBox.js b/client/src/components/pages/diagrams/PowerSubnetInnerBox.js index 992dda63..2aa813c7 100644 --- a/client/src/components/pages/diagrams/PowerSubnetInnerBox.js +++ b/client/src/components/pages/diagrams/PowerSubnetInnerBox.js @@ -9,6 +9,9 @@ export const PowerSubnetInnerBox = (props) => { if (props.marginTop) { powerInnerBoxClassName += " marginTopThreeQuartersRem"; } + if (props.marginBottom && !props.small) { + powerInnerBoxClassName += " marginBottomSmall"; + } return ( { + return ( + !stateData.private_network_only && + isNullOrEmptyString(stateData.public_bandwidth) + ); + }, + invalidText: unconditionalInvalidText("Invalid public bandwidth value"), }, }, }); diff --git a/unit-tests/state/classic-bare-metal.test.js b/unit-tests/state/classic-bare-metal.test.js index 456b7b4d..36dc5219 100644 --- a/unit-tests/state/classic-bare-metal.test.js +++ b/unit-tests/state/classic-bare-metal.test.js @@ -142,5 +142,14 @@ describe("classic bare metal state", () => { "it should return true" ); }); + it("should return true if private_network_only is false and bandwidth is empty", () => { + assert.isTrue( + craig.classic_bare_metal.public_bandwidth.invalid({ + private_network_only: false, + public_bandwidth: "", + }), + "it should return true" + ); + }); }); }); From 4b923745dc8594073c4880bb263e0333860bcc8a Mon Sep 17 00:00:00 2001 From: jvallexm Date: Thu, 14 Mar 2024 13:11:47 -0400 Subject: [PATCH 24/29] feat: await action role (#1676) --- ansible/template-test/main.yml | 86 ++++--------------- .../roles/await_action/README.md | 38 ++++++++ .../roles/await_action/defaults/main.yml | 2 + .../roles/await_action/handlers/main.yml | 2 + .../roles/await_action/meta/main.yml | 52 +++++++++++ .../roles/await_action/tasks/main.yml | 15 ++++ .../roles/await_action/tests/inventory | 2 + .../roles/await_action/tests/test.yml | 5 ++ .../roles/await_action/vars/main.yml | 2 + 9 files changed, 133 insertions(+), 71 deletions(-) create mode 100644 ansible/template-test/roles/await_action/README.md create mode 100644 ansible/template-test/roles/await_action/defaults/main.yml create mode 100644 ansible/template-test/roles/await_action/handlers/main.yml create mode 100644 ansible/template-test/roles/await_action/meta/main.yml create mode 100644 ansible/template-test/roles/await_action/tasks/main.yml create mode 100644 ansible/template-test/roles/await_action/tests/inventory create mode 100644 ansible/template-test/roles/await_action/tests/test.yml create mode 100644 ansible/template-test/roles/await_action/vars/main.yml diff --git a/ansible/template-test/main.yml b/ansible/template-test/main.yml index 742d2f4e..5056a88a 100644 --- a/ansible/template-test/main.yml +++ b/ansible/template-test/main.yml @@ -10,20 +10,9 @@ - name: Upload CRAIG template to schematics workspace hosts: localhost vars_files: ./vars/vars.yml -- name: Get IAM token - hosts: localhost - vars_files: ./vars/vars.yml # variables declared in variables file are added to role automatically roles: - role: get_iam_token -- name: "Download Template Tarball" - hosts: localhost - vars_files: ./vars/vars.yml - roles: - role: download_tar -- name: Create Schematics Workspace - hosts: localhost - vars_files: ./vars/vars.yml # variables declared in variables file are added to role automatically - roles: - role: create_schematics_workspace vars: description: Automated CRAIG Testing Workspace @@ -72,71 +61,26 @@ - role: action vars: action: plan -- name: Await task finish - hosts: localhost - vars_files: ./vars/vars.yml - tasks: - - name: Ensure generate plan finishes - uri: - url: https://schematics.cloud.ibm.com/v2/jobs/{{action_result.json.activityid}} - method: GET - body_format: json - headers: - Authorization: Bearer {{token.json.access_token}} - register: plan - until: plan.json.status.workspace_job_status.status_code == "job_finished" or plan.json.status.workspace_job_status.status_code == "job_failed" - failed_when: plan.json.status.workspace_job_status.status_code == "job_failed" - delay: 90 - retries: 50 -- name: Start apply plan action - hosts: localhost - vars_files: ./vars/vars.yml - roles: + - role: get_iam_token + - role: await_action + vars: + action: plan + register_name: await_plan - role: get_iam_token - role: action vars: action: apply -- name: Await task finish - hosts: localhost - vars_files: ./vars/vars.yml - tasks: - - name: Ensure apply plan finishes - uri: - url: https://schematics.cloud.ibm.com/v2/jobs/{{action_result.json.activityid}} - method: GET - body_format: json - headers: - Authorization: Bearer {{token.json.access_token}} - register: apply_plan - until: apply_plan.json.status.workspace_job_status.status_code == "job_finished" or apply_plan.json.status.workspace_job_status.status_code == "job_failed" - failed_when: apply_plan.json.status.workspace_job_status.status_code == "job_failed" - delay: 120 - retries: 50 -- name: Start destroy action - hosts: localhost - vars_files: ./vars/vars.yml - roles: + - role: get_iam_token + - role: await_action + vars: + action: apply + register_name: await_apply - role: get_iam_token - role: action vars: action: destroy -- name: Await task finish - hosts: localhost - vars_files: ./vars/vars.yml - tasks: - - name: Ensure destroy finishes - uri: - url: https://schematics.cloud.ibm.com/v2/jobs/{{action_result.json.activityid}} - method: GET - body_format: json - headers: - Authorization: Bearer {{token.json.access_token}} - register: destroy_plan - until: destroy_plan.json.status.workspace_job_status.status_code == "job_finished" or destroy_plan.json.status.workspace_job_status.status_code == "job_failed" - failed_when: destroy_plan.json.status.workspace_job_status.status_code == "job_failed" - delay: 120 - retries: 50 - - name: Delete local {{template}}.tar file - file: - state: absent - path: /{{playbook_dir}}/{{template}}.tar \ No newline at end of file + - role: get_iam_token + - role: await_action + vars: + action: destroy + register_name: await_destroy \ No newline at end of file diff --git a/ansible/template-test/roles/await_action/README.md b/ansible/template-test/roles/await_action/README.md new file mode 100644 index 00000000..225dd44b --- /dev/null +++ b/ansible/template-test/roles/await_action/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/await_action/defaults/main.yml b/ansible/template-test/roles/await_action/defaults/main.yml new file mode 100644 index 00000000..40b387ed --- /dev/null +++ b/ansible/template-test/roles/await_action/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for await_action diff --git a/ansible/template-test/roles/await_action/handlers/main.yml b/ansible/template-test/roles/await_action/handlers/main.yml new file mode 100644 index 00000000..a886d9d5 --- /dev/null +++ b/ansible/template-test/roles/await_action/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for await_action diff --git a/ansible/template-test/roles/await_action/meta/main.yml b/ansible/template-test/roles/await_action/meta/main.yml new file mode 100644 index 00000000..c572acc9 --- /dev/null +++ b/ansible/template-test/roles/await_action/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/await_action/tasks/main.yml b/ansible/template-test/roles/await_action/tasks/main.yml new file mode 100644 index 00000000..969284ae --- /dev/null +++ b/ansible/template-test/roles/await_action/tasks/main.yml @@ -0,0 +1,15 @@ +- name: Ensure {{ action }} finishes + uri: + url: https://schematics.cloud.ibm.com/v2/jobs/{{ action_result.json.activityid }} + method: GET + body_format: json + headers: + Authorization: Bearer {{ token.json.access_token }} + register: dynamic_value + until: dynamic_value.json.status.workspace_job_status.status_code == "job_finished" or dynamic_value.json.status.workspace_job_status.status_code == "job_failed" + failed_when: dynamic_value.json.status.workspace_job_status.status_code == "job_failed" + delay: 120 + retries: 50 +- name: Set Variable + set_fact: + "{{ register_name }}": "{{ dynamic_value }}" \ No newline at end of file diff --git a/ansible/template-test/roles/await_action/tests/inventory b/ansible/template-test/roles/await_action/tests/inventory new file mode 100644 index 00000000..878877b0 --- /dev/null +++ b/ansible/template-test/roles/await_action/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/ansible/template-test/roles/await_action/tests/test.yml b/ansible/template-test/roles/await_action/tests/test.yml new file mode 100644 index 00000000..83cf53f8 --- /dev/null +++ b/ansible/template-test/roles/await_action/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - await_action diff --git a/ansible/template-test/roles/await_action/vars/main.yml b/ansible/template-test/roles/await_action/vars/main.yml new file mode 100644 index 00000000..72bd7e8c --- /dev/null +++ b/ansible/template-test/roles/await_action/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for await_action From 6522297d87c06353720c8dcf618a6bf6a38bbdf6 Mon Sep 17 00:00:00 2001 From: jvallexm Date: Thu, 14 Mar 2024 13:46:24 -0400 Subject: [PATCH 25/29] fix: classic sg box size --- client/src/components/pages/diagrams/ClassicSecurityGroups.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/pages/diagrams/ClassicSecurityGroups.js b/client/src/components/pages/diagrams/ClassicSecurityGroups.js index 70781bb5..ab8b91e4 100644 --- a/client/src/components/pages/diagrams/ClassicSecurityGroups.js +++ b/client/src/components/pages/diagrams/ClassicSecurityGroups.js @@ -11,7 +11,7 @@ export const ClassicSecurityGroups = (props) => { return ( Date: Thu, 14 Mar 2024 16:10:18 -0400 Subject: [PATCH 26/29] Issue 1463: download image (#1684) * feat: download image in zip * feat: download image in zip * feat: download image in zip --- CHANGELOG.md | 1 + client/package-lock.json | 6 + client/package.json | 1 + client/src/Craig.js | 51 +++ .../components/page-template/Navigation.js | 25 +- .../components/page-template/PageTemplate.js | 1 + .../src/components/pages/diagrams/Overview.js | 319 +++++++++--------- .../downloadCopyButtons/DownloadConfig.js | 9 +- client/src/lib/docs/release-notes.json | 3 +- 9 files changed, 249 insertions(+), 167 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f89cec5e..a944a2e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file. - Users can now upload JSON directly to CRAIG from the local file explorer from the Projects page by clicking the new `Upload JSON` button - Users can now create Classic Bare Metal Servers from the form page `/form/classicBareMetal` - Power VS Instance Primary IP addresses are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template +- When downloading a `.zip` file of an environment from the CRAIG GUI, an image of the current environment is now included in ### Fixes diff --git a/client/package-lock.json b/client/package-lock.json index c78ac788..69abd4ee 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,6 +17,7 @@ "chart.js": "^4.4.1", "file-saver": "^2.0.5", "graphql": "15.5.2", + "html-to-image": "^1.11.11", "json-to-tf": "^0.3.1", "jszip": "^3.10.1", "lazy-z": "^1.11.17", @@ -13378,6 +13379,11 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/html-to-image": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.11.tgz", + "integrity": "sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==" + }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", diff --git a/client/package.json b/client/package.json index 7877c7f4..f0a5d410 100644 --- a/client/package.json +++ b/client/package.json @@ -23,6 +23,7 @@ "chart.js": "^4.4.1", "file-saver": "^2.0.5", "graphql": "15.5.2", + "html-to-image": "^1.11.11", "json-to-tf": "^0.3.1", "jszip": "^3.10.1", "lazy-z": "^1.11.17", diff --git a/client/src/Craig.js b/client/src/Craig.js index b77337bf..49d15869 100644 --- a/client/src/Craig.js +++ b/client/src/Craig.js @@ -45,6 +45,8 @@ import { Overview } from "./components/pages/diagrams/Overview.js"; import PowerDiagram from "./components/pages/power/Power.js"; import VpcConnectivityPage from "./components/pages/vpc/Connectivity.js"; import ClassicDiagram from "./components/pages/classic/Classic.js"; +import * as htmlToImage from "html-to-image"; +import FileSaver from "file-saver"; const withRouter = (Page) => (props) => { const params = useParams(); @@ -91,6 +93,7 @@ class Craig extends React.Component { clickedWorkspace: "", clickedWorkspaceUrl: "", current_project: craig.store.project_name, + showOverviewForDownload: false, }; } catch (err) { // if there are initialization errors, redirect user to reset state path @@ -111,6 +114,7 @@ class Craig extends React.Component { this.getProject = this.getProject.bind(this); this.projectFetch = this.projectFetch.bind(this); this.saveProject = this.saveProject.bind(this); + this.showAndSnapshot = this.showAndSnapshot.bind(this); } // when react component mounts, set update callback for store @@ -125,6 +129,19 @@ class Craig extends React.Component { }); } + showAndSnapshot(callback) { + this.setState({ showOverviewForDownload: true }, () => { + htmlToImage + .toBlob(document.getElementById("overview-big")) + .then(function (blob) { + callback(blob); + }) + .then(() => { + this.setState({ showOverviewForDownload: false }); + }); + }); + } + /** * update components * @param {string} message @@ -569,6 +586,39 @@ class Craig extends React.Component { ) : ( <> + {this.state.showOverviewForDownload ? ( + // this is only shown when getting an image for the screengrab, + // otherwise will not be rendered + // the first div is a container for the overview image + // the second div is there to mask the first while creting the image + <> +
+ +
+
+ + ) : ( + "" + )} {this.props.params.doc ? ( this.props.params.doc === "about" ? ( diff --git a/client/src/components/page-template/Navigation.js b/client/src/components/page-template/Navigation.js index 101d07de..0a20f86b 100644 --- a/client/src/components/page-template/Navigation.js +++ b/client/src/components/page-template/Navigation.js @@ -89,17 +89,22 @@ class Navigation extends React.Component { }; } if (validated) { - let error = downloadContent(this.props.json, this.props.project?.name); - if (error) { - console.error(error); - notification = { - title: "Error", - kind: "error", - text: `Unable to download configuration.\n${error.message}`, - }; - } + this.props.showAndSnapshot((imageBlob) => { + let error = downloadContent( + this.props.json, + this.props.project?.name, + imageBlob + ); + if (error) { + console.error(error); + notification = { + title: "Error", + kind: "error", + text: `Unable to download configuration.\n${error.message}`, + }; + } + }); } - this.props.notify(notification); } resetSearch() { diff --git a/client/src/components/page-template/PageTemplate.js b/client/src/components/page-template/PageTemplate.js index 00c4dfce..f53f1fa5 100644 --- a/client/src/components/page-template/PageTemplate.js +++ b/client/src/components/page-template/PageTemplate.js @@ -253,6 +253,7 @@ const PageTemplate = (props) => { isResetState={isResetState} formPathNotPresent={formPathNotPresent} invalidForms={props.invalidForms} + showAndSnapshot={props.showAndSnapshot} /> {!isResetState && ( <> diff --git a/client/src/components/pages/diagrams/Overview.js b/client/src/components/pages/diagrams/Overview.js index dc8a25fc..a044d690 100644 --- a/client/src/components/pages/diagrams/Overview.js +++ b/client/src/components/pages/diagrams/Overview.js @@ -37,7 +37,7 @@ export class Overview extends React.Component { render() { let craig = this.props.craig; return ( -
+
{this.props.small ? ( "" ) : ( @@ -53,181 +53,194 @@ export class Overview extends React.Component { /> )}
-
- } - /> -
- -
-
-
-
- } - /> - {this.props.small || craig.store.json.ssh_keys.length === 0 ? ( - "" - ) : ( -
- -
- )}
- - {this.props.small ? ( - <> - ) : ( - - )} - {this.props.small ? ( - <> - ) : ( - - )} - - - } - small={this.props.small} - /> - - -
-
- {craig.store.json.power.length === 0 ? ( - "" - ) : ( -
} + icon={} /> - - - - - - {this.props.small ? ( - <> - ) : ( - - - - )} - - +
+ +
- )} - {craig.store.json._options.enable_classic ? (
} + icon={} /> -
- - - + +
+ )} +
+ + {this.props.small ? ( + <> + ) : ( + + )} + {this.props.small ? ( + <> + ) : ( + + )} + + - - + } small={this.props.small} /> - - + +
- ) : ( - "" - )} - {craig.store.json.transit_gateways.length === 0 || this.props.small ? ( - "" - ) : ( -
-
- } - /> -
- +
+ } /> + + + + + + {this.props.small ? ( + <> + ) : ( + + + + )} + +
-
- )} + )} + {craig.store.json._options.enable_classic ? ( +
+
+ } + /> +
+ + + + + + + +
+
+ ) : ( + "" + )} + {craig.store.json.transit_gateways.length === 0 || + this.props.small ? ( + "" + ) : ( +
+
+ } + /> +
+ +
+
+ )} +
); } diff --git a/client/src/components/utils/downloadCopyButtons/DownloadConfig.js b/client/src/components/utils/downloadCopyButtons/DownloadConfig.js index f60b2ed9..0c324075 100644 --- a/client/src/components/utils/downloadCopyButtons/DownloadConfig.js +++ b/client/src/components/utils/downloadCopyButtons/DownloadConfig.js @@ -7,18 +7,21 @@ const JSZip = require("jszip"); * Download configuration object * @returns (Object|undefined) error */ -export const downloadContent = (json, projectName) => { +export const downloadContent = (json, projectName, imageBlob) => { const zip = new JSZip(); try { let files = configToFilesJson(json); // get files + if (imageBlob) { + zip.file(`${projectName}.png`, imageBlob); + } eachKey(files, (file) => { // add each file's contents to the zip if it is not null or empty string if ( isNullOrEmptyString(files[file]) === false && (contains(file, ".") || contains(file, "LICENSE")) - ) + ) { zip.file(file, files[file]); - else if (isNullOrEmptyString(files[file]) === false) { + } else if (isNullOrEmptyString(files[file]) === false) { zip.folder(file); eachKey(files[file], (subFile) => { zip.file(file + "/" + subFile, files[file][subFile]); diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index 8919fa9f..7d9b78cc 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -10,7 +10,8 @@ "When updating a Power VS Instance name, references to that instance are now updated to match the new name", "Users can now upload JSON directly to CRAIG from the local file explorer from the Projects page by clicking the new `Upload JSON` button", "Users can now create Classic Bare Metal Servers from the form page `/form/classicBareMetal`", - "Power VS Instance Primary IP addresses are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template" + "Power VS Instance Primary IP addresses are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template", + "When downloading a `.zip` file of an environment from the CRAIG GUI, an image of the current environment is now included in " ], "fixes": [ "Fixed an issue with button hoverText alignment causing overflow in forms", From a6366bcd5c8631a933c8d8e53cb0a750de05f477 Mon Sep 17 00:00:00 2001 From: terechc Date: Fri, 15 Mar 2024 10:08:26 -0400 Subject: [PATCH 27/29] Issue 1647: Delete Workspace Role (#1682) * feat: await action role * delete workspace action * retry --------- Co-authored-by: Jennifer-Valle --- ansible/template-test/main.yml | 4 +- .../delete_schematics_workspace/README.md | 38 ++++++++++++++ .../defaults/main.yml | 2 + .../handlers/main.yml | 2 + .../delete_schematics_workspace/meta/main.yml | 52 +++++++++++++++++++ .../tasks/main.yml | 13 +++++ .../tests/inventory | 2 + .../tests/test.yml | 5 ++ .../delete_schematics_workspace/vars/main.yml | 2 + 9 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 ansible/template-test/roles/delete_schematics_workspace/README.md create mode 100644 ansible/template-test/roles/delete_schematics_workspace/defaults/main.yml create mode 100644 ansible/template-test/roles/delete_schematics_workspace/handlers/main.yml create mode 100644 ansible/template-test/roles/delete_schematics_workspace/meta/main.yml create mode 100644 ansible/template-test/roles/delete_schematics_workspace/tasks/main.yml create mode 100644 ansible/template-test/roles/delete_schematics_workspace/tests/inventory create mode 100644 ansible/template-test/roles/delete_schematics_workspace/tests/test.yml create mode 100644 ansible/template-test/roles/delete_schematics_workspace/vars/main.yml diff --git a/ansible/template-test/main.yml b/ansible/template-test/main.yml index 5056a88a..912272c9 100644 --- a/ansible/template-test/main.yml +++ b/ansible/template-test/main.yml @@ -83,4 +83,6 @@ - role: await_action vars: action: destroy - register_name: await_destroy \ No newline at end of file + register_name: await_destroy + - role: get_iam_token + - role: delete_schematics_workspace \ No newline at end of file diff --git a/ansible/template-test/roles/delete_schematics_workspace/README.md b/ansible/template-test/roles/delete_schematics_workspace/README.md new file mode 100644 index 00000000..225dd44b --- /dev/null +++ b/ansible/template-test/roles/delete_schematics_workspace/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/delete_schematics_workspace/defaults/main.yml b/ansible/template-test/roles/delete_schematics_workspace/defaults/main.yml new file mode 100644 index 00000000..87646b0e --- /dev/null +++ b/ansible/template-test/roles/delete_schematics_workspace/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for delete_schematics_workspace diff --git a/ansible/template-test/roles/delete_schematics_workspace/handlers/main.yml b/ansible/template-test/roles/delete_schematics_workspace/handlers/main.yml new file mode 100644 index 00000000..3b193b92 --- /dev/null +++ b/ansible/template-test/roles/delete_schematics_workspace/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for delete_schematics_workspace diff --git a/ansible/template-test/roles/delete_schematics_workspace/meta/main.yml b/ansible/template-test/roles/delete_schematics_workspace/meta/main.yml new file mode 100644 index 00000000..c572acc9 --- /dev/null +++ b/ansible/template-test/roles/delete_schematics_workspace/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/delete_schematics_workspace/tasks/main.yml b/ansible/template-test/roles/delete_schematics_workspace/tasks/main.yml new file mode 100644 index 00000000..9a02962b --- /dev/null +++ b/ansible/template-test/roles/delete_schematics_workspace/tasks/main.yml @@ -0,0 +1,13 @@ +--- +# tasks file for delete_schematics_workspace +- name: Delete Schematics Workspace + uri: + url: https://schematics.cloud.ibm.com/v1/workspaces/{{workspace.json.id}} + method: DELETE + headers: + Authorization: Bearer {{token.json.access_token}} + status_code: 200 + register: delete_response + until: delete_response.status == 200 + retries: 2 + delay: 120 \ No newline at end of file diff --git a/ansible/template-test/roles/delete_schematics_workspace/tests/inventory b/ansible/template-test/roles/delete_schematics_workspace/tests/inventory new file mode 100644 index 00000000..878877b0 --- /dev/null +++ b/ansible/template-test/roles/delete_schematics_workspace/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/ansible/template-test/roles/delete_schematics_workspace/tests/test.yml b/ansible/template-test/roles/delete_schematics_workspace/tests/test.yml new file mode 100644 index 00000000..3c1a8e6a --- /dev/null +++ b/ansible/template-test/roles/delete_schematics_workspace/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - delete_schematics_workspace diff --git a/ansible/template-test/roles/delete_schematics_workspace/vars/main.yml b/ansible/template-test/roles/delete_schematics_workspace/vars/main.yml new file mode 100644 index 00000000..7ae746a9 --- /dev/null +++ b/ansible/template-test/roles/delete_schematics_workspace/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for delete_schematics_workspace From c2d298d31066d89ea17f03ad2d163a4622ded7ba Mon Sep 17 00:00:00 2001 From: Pseusco Date: Fri, 15 Mar 2024 12:44:05 -0400 Subject: [PATCH 28/29] Issue 1634: Add new replication pools to constants (#1687) * classic bare metal * changelog * proxy * add replication pools to constants, forceupdatekey to replication toggle Signed-off-by: Lucas-Franke * remove classic baremetal Signed-off-by: Lucas-Franke --------- Signed-off-by: Lucas-Franke Co-authored-by: Connor Terech --- client/src/lib/constants.js | 38 +++++++++++++++++-- .../lib/forms/dynamic-form-fields/toggle.js | 5 ++- client/src/lib/state/power-vs-volumes.js | 3 ++ client/src/lib/state/utils.js | 10 +++++ .../forms/dynamic-form-fields/toggle.test.js | 16 ++++++++ unit-tests/state/power-vs-volumes.test.js | 33 +++++++++++++++- 6 files changed, 99 insertions(+), 6 deletions(-) diff --git a/client/src/lib/constants.js b/client/src/lib/constants.js index bf80f131..d1038335 100644 --- a/client/src/lib/constants.js +++ b/client/src/lib/constants.js @@ -841,10 +841,40 @@ module.exports = { wdc07: ["Tier3-Flash-2", "Tier3-Flash-1", "Tier1-Flash-2", "Tier1-Flash-1"], }, replicationEnabledStoragePoolMap: { - "us-east": ["Tier1-Flash-8"], - wdc06: ["Tier1-Flash-1", "Tier3-Flash-2", "Tier3-Flash-1"], - "us-south": ["Tier1-Flash-6"], - dal12: ["Tier1-Flash-3", "Tier3-Flash-4", "Tier3-Flash-3"], + "us-east": ["Tier1-Flash-8", "General-Flash-185", "General-Flash-182"], + wdc06: [ + "Tier1-Flash-1", + "Tier3-Flash-2", + "Tier3-Flash-1", + "General-Flash-83", + "General-Flash-77", + "General-Flash-74", + ], + wdc07: [ + "General-Flash-83", + "General-Flash-80", + "General-Flash-77", + "General-Flash-74", + ], + "us-south": ["Tier1-Flash-6", "General-Flash-26", "General-Flash-23"], + dal12: [ + "Tier1-Flash-3", + "Tier3-Flash-4", + "Tier3-Flash-3", + "General-Flash-93", + "General-Flash-87", + "General-Flash-84", + ], + dal10: [ + "General-Flash-53", + "General-Flash-59", + "General-Flash-56", + "General-Flash-50", + ], + mad02: ["General-Flash-56", "General-Flash-53", "General-Flash-50"], + mad04: ["General-Flash-59", "General-Flash-56", "General-Flash-50"], + "eu-de-1": ["General-Flash-90", "General-Flash-96", "General-Flash-87"], + "eu-de-2": ["General-Flash-96", "General-Flash-93", "General-Flash-90"], }, template_dropdown_map: { Mixed: { diff --git a/client/src/lib/forms/dynamic-form-fields/toggle.js b/client/src/lib/forms/dynamic-form-fields/toggle.js index 1c3eb158..1a27b628 100644 --- a/client/src/lib/forms/dynamic-form-fields/toggle.js +++ b/client/src/lib/forms/dynamic-form-fields/toggle.js @@ -1,4 +1,4 @@ -const { kebabCase, paramTest } = require("lazy-z"); +const { kebabCase, paramTest, isFunction } = require("lazy-z"); const { disabledReturnsBooleanCheck, addClassName } = require("./utils"); /** @@ -70,6 +70,9 @@ function dynamicToggleProps(props) { ? props.field.onRender(props.parentState, props.parentProps) : props.parentState[props.name], disabled: isDisabled, + key: isFunction(props.field.forceUpdateKey) + ? props.field.forceUpdateKey(props.parentState, props.parentProps) + : undefined, }; } diff --git a/client/src/lib/state/power-vs-volumes.js b/client/src/lib/state/power-vs-volumes.js index 6cd981ad..acb5b184 100644 --- a/client/src/lib/state/power-vs-volumes.js +++ b/client/src/lib/state/power-vs-volumes.js @@ -246,6 +246,9 @@ function initPowerVsVolumeStore(store) { labelText: "Enable Volume Replication", default: false, type: "toggle", + forceUpdateKey: function (stateData) { + return JSON.stringify(stateData); + }, disabled: function (stateData, componentProps) { let pool = stateData.pi_volume_pool; if (!stateData.zone) { diff --git a/client/src/lib/state/utils.js b/client/src/lib/state/utils.js index 480cc3fd..5ddeeb01 100644 --- a/client/src/lib/state/utils.js +++ b/client/src/lib/state/utils.js @@ -27,6 +27,7 @@ const { fieldIsNullOrEmptyString, selectInvalidText, } = require("./reusable-fields"); +const { replicationEnabledStoragePoolMap } = require("../constants"); /** * set kms from encryption key on store update @@ -1213,6 +1214,15 @@ function powerStoragePoolSelect(isVolume) { ? "Select a workspace" : selectInvalidText("storage pool")(stateData, componentProps); }, + onInputChange: function (stateData) { + let replicationEnabledPools = + replicationEnabledStoragePoolMap[stateData.zone] || []; + if (!contains(replicationEnabledPools, stateData[field])) { + stateData.pi_replication_enabled = false; + } + + return stateData[field]; + }, apiEndpoint: function (stateData, componentProps) { return `/api/power/${stateData.zone}/storage-pools`; }, diff --git a/unit-tests/forms/dynamic-form-fields/toggle.test.js b/unit-tests/forms/dynamic-form-fields/toggle.test.js index 959b516c..284e327e 100644 --- a/unit-tests/forms/dynamic-form-fields/toggle.test.js +++ b/unit-tests/forms/dynamic-form-fields/toggle.test.js @@ -39,6 +39,10 @@ describe("dynamic toggle", () => { }); it("should return props form properly formatted toggle", () => { let toggleData; + let keyData; + let forceUpdateKey = function () { + return "foo"; + }; let actualData = dynamicToggleProps({ parentProps: {}, parentState: {}, @@ -49,6 +53,7 @@ describe("dynamic toggle", () => { return false; }, labelText: "Use Data", + forceUpdateKey: forceUpdateKey, }, handleInputChange: function (name) { toggleData = name; @@ -62,6 +67,7 @@ describe("dynamic toggle", () => { labelB: "True", labelText: "Use Data", disabled: false, + key: "foo", }; assert.isFunction(actualData.onToggle, "it should be a function"); actualData.onToggle(); @@ -70,6 +76,11 @@ describe("dynamic toggle", () => { "use_data", "it should return name to parent function" ); + assert.deepEqual( + actualData.key, + "foo", + "it should return name to parent function" + ); delete actualData.onToggle; assert.deepEqual( actualData, @@ -102,6 +113,7 @@ describe("dynamic toggle", () => { labelB: "CRAIG Managed Network Addresses", labelText: "VPC Network Address Management", disabled: false, + key: undefined, }; assert.isFunction(actualData.onToggle, "it should be a function"); actualData.onToggle(); @@ -145,6 +157,7 @@ describe("dynamic toggle", () => { labelB: "True", labelText: "Use Data", disabled: false, + key: undefined, }; assert.isFunction(actualData.onToggle, "it should be a function"); actualData.onToggle(); @@ -186,6 +199,7 @@ describe("dynamic toggle", () => { labelB: "True", labelText: " ", disabled: false, + key: undefined, }; assert.isFunction(actualData.onToggle, "it should be a function"); actualData.onToggle(); @@ -227,6 +241,7 @@ describe("dynamic toggle", () => { labelB: "True", labelText: " ", disabled: false, + key: undefined, }; assert.isFunction(actualData.onToggle, "it should be a function"); actualData.onToggle(); @@ -269,6 +284,7 @@ describe("dynamic toggle", () => { labelB: "On", labelText: " ", disabled: false, + key: undefined, }; assert.isFunction(actualData.onToggle, "it should be a function"); actualData.onToggle(); diff --git a/unit-tests/state/power-vs-volumes.test.js b/unit-tests/state/power-vs-volumes.test.js index cc85fe4d..2e15361b 100644 --- a/unit-tests/state/power-vs-volumes.test.js +++ b/unit-tests/state/power-vs-volumes.test.js @@ -360,6 +360,26 @@ describe("power_volumes", () => { "it should return correct invalid text" ); }); + it("should not disable replication on input change when zone is replication enabled", () => { + let data = { + zone: "dal10", + pi_volume_pool: "General-Flash-53", + pi_replication_enabled: true, + }; + let actualData = craig.power_volumes.pi_volume_pool.onInputChange(data); + assert.deepEqual(actualData, "General-Flash-53", "should return pool"); + assert.isTrue(data.pi_replication_enabled, "should be true"); + }); + it("should disable replication on input change when zone is not replication enabled", () => { + let data = { + zone: "eu-es", + pi_volume_pool: "General-Flash-53", + pi_replication_enabled: true, + }; + let actualData = craig.power_volumes.pi_volume_pool.onInputChange(data); + assert.deepEqual(actualData, "General-Flash-53", "should return pool"); + assert.isFalse(data.pi_replication_enabled, "should be false"); + }); it("should update state on workspace change", () => { let actualData = {}; craig.power_volumes.workspace.onStateChange( @@ -619,11 +639,22 @@ describe("power_volumes", () => { it("should be true for when the workspace's zone does not have replication enabled", () => { let data = { pi_volume_pool: "Tier1-Flash-8", - zone: "dal10", + zone: "eu-es", }; assert.isTrue( craig.power_volumes.pi_replication_enabled.disabled(data, {}) ); }); + it("should return correct JSON string from forceUpdateKey", () => { + let data = { + foo: "bar", + }; + let expectedData = '{"foo":"bar"}'; + assert.deepEqual( + craig.power_volumes.pi_replication_enabled.forceUpdateKey(data), + expectedData, + "should be equal" + ); + }); }); }); From c3b6c33aeda514b26e32526d50410b929e3e7419 Mon Sep 17 00:00:00 2001 From: jvallexm Date: Fri, 15 Mar 2024 14:13:28 -0400 Subject: [PATCH 29/29] feat: pin policy (#1690) --- CHANGELOG.md | 3 +- client/src/components/pages/CraigForms.js | 1 + client/src/lib/docs/release-notes.json | 3 +- .../power-instances-schema.js | 13 +++++ .../json-to-iac/power-vs-instances.test.js | 47 +++++++++++++++++++ 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a944a2e5..fd7a8f6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,8 @@ All notable changes to this project will be documented in this file. - Users can now upload JSON directly to CRAIG from the local file explorer from the Projects page by clicking the new `Upload JSON` button - Users can now create Classic Bare Metal Servers from the form page `/form/classicBareMetal` - Power VS Instance Primary IP addresses are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template -- When downloading a `.zip` file of an environment from the CRAIG GUI, an image of the current environment is now included in +- When downloading a `.zip` file of an environment from the CRAIG GUI, an image of the current environment is now included in the archive +- CRAIG now supports adding a Pin Policy to Power VS instances ### Fixes diff --git a/client/src/components/pages/CraigForms.js b/client/src/components/pages/CraigForms.js index 2121d7b1..cc97fd03 100644 --- a/client/src/components/pages/CraigForms.js +++ b/client/src/components/pages/CraigForms.js @@ -1080,6 +1080,7 @@ function craigForms(craig) { { network: craig.power_instances.network, primary_subnet: craig.power_instances.primary_subnet, + pi_pin_policy: craig.power_instances.pi_pin_policy, }, { image: craig.power_instances.image, diff --git a/client/src/lib/docs/release-notes.json b/client/src/lib/docs/release-notes.json index 7d9b78cc..c9c2d3cc 100644 --- a/client/src/lib/docs/release-notes.json +++ b/client/src/lib/docs/release-notes.json @@ -11,7 +11,8 @@ "Users can now upload JSON directly to CRAIG from the local file explorer from the Projects page by clicking the new `Upload JSON` button", "Users can now create Classic Bare Metal Servers from the form page `/form/classicBareMetal`", "Power VS Instance Primary IP addresses are now included as outputs in the `outputs.tf` file of any CRAIG Terraform template", - "When downloading a `.zip` file of an environment from the CRAIG GUI, an image of the current environment is now included in " + "When downloading a `.zip` file of an environment from the CRAIG GUI, an image of the current environment is now included in the archive", + "CRAIG now supports adding a Pin Policy to Power VS instances" ], "fixes": [ "Fixed an issue with button hoverText alignment causing overflow in forms", diff --git a/client/src/lib/state/power-vs-instances/power-instances-schema.js b/client/src/lib/state/power-vs-instances/power-instances-schema.js index 10d1c647..74bfde45 100644 --- a/client/src/lib/state/power-vs-instances/power-instances-schema.js +++ b/client/src/lib/state/power-vs-instances/power-instances-schema.js @@ -473,6 +473,19 @@ function powerVsInstanceSchema(vtl) { labelText: "User Data", placeholder: "Cloud init data", }, + pi_pin_policy: { + labelText: "Pin Policy", + type: "select", + size: "small", + groups: ["Soft", "Hard", "None"], + default: "none", + onRender: titleCaseRender("pi_pin_policy"), + onInputChange: kebabCaseInput("pi_pin_policy"), + tooltip: { + content: + "When you soft pin an instance for high availability, the instance automatically migrates back to the original host once the host is back to its operating state. If the instance has a licensing restriction with the host, the hard pin option restricts the movement of the instance during remote restart, automated remote restart, DRO, and live partition migration. The default pinning policy is none", + }, + }, }; } diff --git a/unit-tests/json-to-iac/power-vs-instances.test.js b/unit-tests/json-to-iac/power-vs-instances.test.js index edadab2d..91201de7 100644 --- a/unit-tests/json-to-iac/power-vs-instances.test.js +++ b/unit-tests/json-to-iac/power-vs-instances.test.js @@ -47,6 +47,53 @@ resource "ibm_pi_instance" "example_workspace_instance_test" { network_id = ibm_pi_network.power_network_example_dev_nw.network_id } } +`; + assert.deepEqual( + actualData, + expectedData, + "it should return correct instance data" + ); + }); + it("should correctly return power vs instance data with pin policy", () => { + let actualData = formatPowerVsInstance({ + zone: "dal12", + workspace: "example", + name: "test", + image: "SLES15-SP3-SAP", + ssh_key: "keyname", + network: [ + { + name: "dev-nw", + }, + ], + primary_subnet: "dev-nw", + pi_memory: "4", + pi_processors: "2", + pi_proc_type: "shared", + pi_sys_type: "s922", + pi_health_status: "WARNING", + pi_storage_type: "tier1", + pi_user_data: "", + pi_pin_policy: "soft", + }); + let expectedData = ` +resource "ibm_pi_instance" "example_workspace_instance_test" { + provider = ibm.power_vs_dal12 + pi_image_id = ibm_pi_image.power_image_example_sles15_sp3_sap.image_id + pi_key_pair_name = ibm_pi_key.power_vs_ssh_key_keyname.pi_key_name + pi_cloud_instance_id = ibm_resource_instance.power_vs_workspace_example.guid + pi_instance_name = "\${var.prefix}-test" + pi_memory = "4" + pi_processors = "2" + pi_proc_type = "shared" + pi_sys_type = "s922" + pi_health_status = "WARNING" + pi_storage_type = "tier1" + pi_pin_policy = "soft" + pi_network { + network_id = ibm_pi_network.power_network_example_dev_nw.network_id + } +} `; assert.deepEqual( actualData,