From 484442476bb415516b7245cc2fff8a92d47c9a6d Mon Sep 17 00:00:00 2001 From: Ganesh Revanakar Date: Wed, 18 Feb 2026 16:36:25 +0530 Subject: [PATCH 1/2] change: [UIE-9888] - Make firewall selection mandatory while creating linode and its interfaces --- .../images/create-linode-from-image.spec.ts | 15 ++- .../e2e/core/linodes/alerts-create.spec.ts | 31 ++++- .../e2e/core/linodes/clone-linode.spec.ts | 10 ++ .../linodes/create-linode-blackwell.spec.ts | 15 +++ .../create-linode-in-core-region.spec.ts | 14 +++ ...reate-linode-in-distributed-region.spec.ts | 16 ++- .../core/linodes/create-linode-mobile.spec.ts | 14 ++- .../create-linode-view-code-snippet.spec.ts | 12 +- .../create-linode-vm-host-maintenance.spec.ts | 12 +- .../create-linode-with-add-ons.spec.ts | 21 ++++ ...create-linode-with-disk-encryption.spec.ts | 16 ++- .../create-linode-with-firewall.spec.ts | 58 +++------ .../create-linode-with-ssh-key.spec.ts | 19 ++- .../create-linode-with-user-data.spec.ts | 13 ++- .../linodes/create-linode-with-vlan.spec.ts | 38 ++++++ .../linodes/create-linode-with-vpc.spec.ts | 61 ++++++++++ .../e2e/core/linodes/create-linode.spec.ts | 35 ++++-- ...reate-linode-with-placement-groups.spec.ts | 19 ++- .../support/ui/pages/linode-create-page.ts | 26 +++++ .../manager/cypress/support/util/linodes.ts | 2 +- .../src/features/Account/DefaultFirewalls.tsx | 4 + .../components/FirewallSelect.test.tsx | 75 ++++++++++++ .../Firewalls/components/FirewallSelect.tsx | 110 ++++++++++++------ .../Kubernetes/NodePoolFirewallSelect.tsx | 1 + .../Linodes/LinodeCreate/Firewall.tsx | 6 +- .../LinodeCreate/Networking/Firewall.tsx | 7 +- .../Networking/InterfaceFirewall.tsx | 6 +- .../LinodeCreate/Networking/InterfaceType.tsx | 4 +- .../LinodeCreate/Networking/utilities.test.ts | 4 + .../LinodeCreate/Networking/utilities.ts | 19 ++- .../Linodes/LinodeCreate/Summary/Summary.tsx | 2 +- .../Linodes/LinodeCreate/resolvers.ts | 16 +++ .../Linodes/LinodeCreate/utilities.test.tsx | 33 +----- .../Linodes/LinodeCreate/utilities.ts | 5 +- .../AddInterfaceDrawer/AddInterfaceForm.tsx | 14 ++- .../AddInterfaceDrawer/InterfaceFirewall.tsx | 4 +- .../AddInterfaceDrawer/InterfaceType.tsx | 4 +- .../EditInterfaceFirewall.tsx | 1 + .../manager/src/features/Linodes/constants.ts | 3 + .../VPCDetail/SubnetAssignLinodesDrawer.tsx | 4 + packages/manager/src/features/VPCs/utils.ts | 2 +- .../codesnippets/generate-cli.test.ts | 1 + packages/utilities/src/factories/linodes.ts | 1 + packages/validation/src/linodes.schema.ts | 8 +- 44 files changed, 627 insertions(+), 154 deletions(-) diff --git a/packages/manager/cypress/e2e/core/images/create-linode-from-image.spec.ts b/packages/manager/cypress/e2e/core/images/create-linode-from-image.spec.ts index 40aef7bf9ed..3206e9ed69e 100644 --- a/packages/manager/cypress/e2e/core/images/create-linode-from-image.spec.ts +++ b/packages/manager/cypress/e2e/core/images/create-linode-from-image.spec.ts @@ -1,7 +1,9 @@ import { linodeFactory } from '@linode/utilities'; -import { imageFactory } from '@src/factories'; +import { firewallFactory, imageFactory } from '@src/factories'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockGetAllImages } from 'support/intercepts/images'; import { ui } from 'support/ui'; +import { linodeCreatePage } from 'support/ui/pages'; import { apiMatcher } from 'support/util/intercepts'; import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { chooseRegion } from 'support/util/regions'; @@ -20,8 +22,14 @@ const mockImage = imageFactory.build({ label: randomLabel(), }); +const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), +}); + const createLinodeWithImageMock = (url: string, preselectedImage: boolean) => { mockGetAllImages([mockImage]).as('mockImage'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); cy.intercept('POST', apiMatcher('linode/instances'), (req) => { req.reply({ @@ -52,6 +60,11 @@ const createLinodeWithImageMock = (url: string, preselectedImage: boolean) => { cy.findByText('Shared CPU').click(); cy.get('[id="g6-nanode-1"][type="radio"]').click(); cy.get('[id="root-password"]').type(randomString(32)); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); ui.button .findByTitle('Create Linode') diff --git a/packages/manager/cypress/e2e/core/linodes/alerts-create.spec.ts b/packages/manager/cypress/e2e/core/linodes/alerts-create.spec.ts index 43325319275..329debdf69c 100644 --- a/packages/manager/cypress/e2e/core/linodes/alerts-create.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/alerts-create.spec.ts @@ -2,15 +2,21 @@ import { regionAvailabilityFactory, regionFactory } from '@linode/utilities'; import { mockGetAccountSettings } from 'support/intercepts/account'; import { mockGetAlertDefinition } from 'support/intercepts/cloudpulse'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { interceptCreateLinode } from 'support/intercepts/linodes'; import { mockGetRegionAvailability, mockGetRegions, } from 'support/intercepts/regions'; import { ui } from 'support/ui'; -import { randomLabel, randomString } from 'support/util/random'; +import { linodeCreatePage } from 'support/ui/pages'; +import { randomLabel, randomNumber, randomString } from 'support/util/random'; -import { accountSettingsFactory, alertFactory } from 'src/factories'; +import { + accountSettingsFactory, + alertFactory, + firewallFactory, +} from 'src/factories'; import { ALERTS_BETA_MODE_BANNER_TEXT, ALERTS_BETA_MODE_BUTTON_TEXT, @@ -18,6 +24,11 @@ import { ALERTS_LEGACY_MODE_BUTTON_TEXT, } from 'src/features/Linodes/constants'; +const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), +}); + describe('Create flow when beta alerts enabled by region and feature flag', function () { beforeEach(() => { const mockEnabledRegion = regionFactory.build({ @@ -54,6 +65,7 @@ describe('Create flow when beta alerts enabled by region and feature flag', func interfaces_for_new_linodes: 'legacy_config_default_but_linode_allowed', }); mockGetAccountSettings(mockInitialAccountSettings).as('getSettings'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); }); it('Alerts panel becomes visible after switching to region w/ alerts enabled', function () { @@ -88,6 +100,11 @@ describe('Create flow when beta alerts enabled by region and feature flag', func const enabledRegion = this.mockRegions[0]; mockGetRegionAvailability(enabledRegion.id, []).as('getRegionAvailability'); ui.regionSelect.find().type(`${enabledRegion.label}{enter}`); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); // legacy alerts panel appears cy.wait('@getRegionAvailability'); @@ -208,6 +225,11 @@ describe('Create flow when beta alerts enabled by region and feature flag', func const enabledRegion = this.mockRegions[0]; mockGetRegionAvailability(enabledRegion.id, []).as('getRegionAvailability'); ui.regionSelect.find().type(`${enabledRegion.label}{enter}`); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); // legacy alerts panel appears cy.wait('@getRegionAvailability'); @@ -437,6 +459,11 @@ describe('Create flow when beta alerts enabled by region and feature flag', func 'getRegionAvailability' ); ui.regionSelect.find().type(`${disabledRegion.label}{enter}`); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); cy.wait('@getRegionAvailability'); // enter plan and password form fields to enable "View Code Snippets" button diff --git a/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts index 434d839b5ef..c611bfd31d7 100644 --- a/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/clone-linode.spec.ts @@ -20,6 +20,7 @@ import { LINODE_CREATE_TIMEOUT } from 'support/constants/linodes'; import { mockGetLinodeConfigs } from 'support/intercepts/configs'; import { interceptEvents } from 'support/intercepts/events'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { interceptCloneLinode, mockCloneLinode, @@ -44,6 +45,8 @@ import { } from 'support/util/random'; import { chooseRegion, extendRegion } from 'support/util/regions'; +import { firewallFactory } from 'src/factories'; + import type { Linode } from '@linode/api-v4'; /** @@ -195,8 +198,13 @@ describe('clone linode', () => { id: mockLinode.id + 1, label: newLinodeLabel, }; + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); mockGetVLANs([mockVlan]); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); mockGetLinodeDetails(mockLinode.id, mockLinode).as('getLinode'); mockGetLinodeVolumes(clonedLinode.id, [mockVolume]).as('getLinodeVolumes'); @@ -229,6 +237,8 @@ describe('clone linode', () => { .type(mockVlan.cidr_block); }); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Assign Firewall'); // Confirm that VLAN attachment is listed in summary, then create Linode. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); cy.get('[data-qa-linode-create-summary]').within(() => { diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-blackwell.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-blackwell.spec.ts index 93e7ef5fc69..035c51d9169 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-blackwell.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-blackwell.spec.ts @@ -5,6 +5,7 @@ import { regionFactory, } from '@linode/utilities'; import { LINODE_CREATE_TIMEOUT } from 'support/constants/linodes'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode, mockGetLinodeTypes, @@ -14,7 +15,11 @@ import { mockGetRegions, } from 'support/intercepts/regions'; import { ui } from 'support/ui'; +import { linodeCreatePage } from 'support/ui/pages'; import { randomLabel, randomString } from 'support/util/random'; +import { randomNumber } from 'support/util/random'; + +import { firewallFactory } from 'src/factories'; const mockEnabledRegion = regionFactory.build({ id: 'us-east', @@ -37,10 +42,15 @@ const mockBlackwellLinodeTypes = new Array(4).fill(null).map((_, index) => const selectedBlackwell = mockBlackwellLinodeTypes[0]; describe('smoketest for Nvidia blackwell GPUs in linodes/create page', () => { + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); beforeEach(() => { mockGetRegions([mockEnabledRegion, mockDisabledRegion]).as('getRegions'); mockGetLinodeTypes(mockBlackwellLinodeTypes).as('getLinodeTypes'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); }); /* @@ -120,6 +130,11 @@ describe('smoketest for Nvidia blackwell GPUs in linodes/create page', () => { cy.findByLabelText('Linode Label').type(newLinodeLabel); cy.get('[type="password"]').should('be.visible').scrollIntoView(); cy.get('[id="root-password"]').type(randomString(12)); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); cy.scrollTo('bottom'); const mockLinode = linodeFactory.build({ label: randomLabel(), diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-in-core-region.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-in-core-region.spec.ts index df59f00ebe3..77d3236aaca 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-in-core-region.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-in-core-region.spec.ts @@ -1,5 +1,6 @@ import { linodeFactory, regionFactory } from '@linode/utilities'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode } from 'support/intercepts/linodes'; import { mockGetRegionAvailability, @@ -8,6 +9,9 @@ import { import { ui } from 'support/ui'; import { linodeCreatePage } from 'support/ui/pages'; import { randomLabel, randomString } from 'support/util/random'; +import { randomNumber } from 'support/util/random'; + +import { firewallFactory } from 'src/factories'; describe('Create Linode in a Core Region', () => { /* @@ -30,6 +34,10 @@ describe('Create Linode in a Core Region', () => { region: mockRegion1.id, }); const rootPass = randomString(32); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); mockAppendFeatureFlags({ gecko2: { @@ -39,6 +47,7 @@ describe('Create Linode in a Core Region', () => { }).as('getFeatureFlags'); mockGetRegions(mockRegions).as('getRegions'); mockGetRegionAvailability(mockRegion1.id, []).as('getRegionAvailability'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); cy.visitWithLogin('/linodes/create'); @@ -55,6 +64,11 @@ describe('Create Linode in a Core Region', () => { linodeCreatePage.selectImage('Debian 11'); linodeCreatePage.setRootPassword(rootPass); linodeCreatePage.selectPlan('Shared CPU', 'Nanode 1 GB'); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); ui.button .findByTitle('Create Linode') diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-in-distributed-region.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-in-distributed-region.spec.ts index 8f1ccfd6833..be42ffc1206 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-in-distributed-region.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-in-distributed-region.spec.ts @@ -4,6 +4,7 @@ import { regionFactory, } from '@linode/utilities'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode, mockGetLinodeTypes, @@ -14,9 +15,11 @@ import { } from 'support/intercepts/regions'; import { ui } from 'support/ui'; import { linodeCreatePage } from 'support/ui/pages'; -import { randomLabel, randomString } from 'support/util/random'; +import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { extendRegion } from 'support/util/regions'; +import { firewallFactory } from 'src/factories'; + import type { Region } from '@linode/api-v4'; describe('Create Linode in Distributed Region', () => { @@ -42,6 +45,10 @@ describe('Create Linode in Distributed Region', () => { label: randomLabel(), region: mockRegion.id, }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); const rootPass = randomString(32); mockAppendFeatureFlags({ @@ -51,6 +58,7 @@ describe('Create Linode in Distributed Region', () => { }, }).as('getFeatureFlags'); mockGetRegions([mockRegion]).as('getRegions'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockGetLinodeTypes(mockLinodeTypes).as('getLinodeTypes'); mockGetRegionAvailability(mockRegion.id, []).as('getRegionAvailability'); mockCreateLinode(mockLinode).as('createLinode'); @@ -75,6 +83,12 @@ describe('Create Linode in Distributed Region', () => { .click(); }); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); + ui.button .findByTitle('Create Linode') .should('be.visible') diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-mobile.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-mobile.spec.ts index ffc2939ce95..c12df22e5e3 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-mobile.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-mobile.spec.ts @@ -4,12 +4,15 @@ import { linodeFactory } from '@linode/utilities'; import { MOBILE_VIEWPORTS } from 'support/constants/environment'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode } from 'support/intercepts/linodes'; import { ui } from 'support/ui'; import { linodeCreatePage } from 'support/ui/pages'; import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { chooseRegion } from 'support/util/regions'; +import { firewallFactory } from 'src/factories'; + describe('Linode create mobile smoke', () => { MOBILE_VIEWPORTS.forEach((viewport) => { /* @@ -23,7 +26,11 @@ describe('Linode create mobile smoke', () => { label: randomLabel(), region: mockLinodeRegion.id, }); - + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); cy.viewport(viewport.width, viewport.height); @@ -34,6 +41,11 @@ describe('Linode create mobile smoke', () => { linodeCreatePage.selectPlanCard('Shared CPU', 'Nanode 1 GB'); linodeCreatePage.setLabel(mockLinode.label); linodeCreatePage.setRootPassword(randomString(32)); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); cy.get('[data-qa-linode-create-summary]').scrollIntoView(); cy.get('[data-qa-linode-create-summary]').within(() => { diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-view-code-snippet.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-view-code-snippet.spec.ts index b74046af2fc..358c01392a1 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-view-code-snippet.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-view-code-snippet.spec.ts @@ -3,11 +3,14 @@ */ import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { ui } from 'support/ui'; import { linodeCreatePage } from 'support/ui/pages'; -import { randomLabel, randomString } from 'support/util/random'; +import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { chooseRegion } from 'support/util/regions'; +import { firewallFactory } from 'src/factories'; + describe('Create Linode flow to validate code snippet modal', () => { beforeEach(() => { mockAppendFeatureFlags({ @@ -24,6 +27,11 @@ describe('Create Linode flow to validate code snippet modal', () => { const mockLinodeRegion = chooseRegion({ capabilities: ['Linodes'], }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); cy.visitWithLogin('/linodes/create'); // Set Linode label, distribution, plan type, password, etc. @@ -32,6 +40,8 @@ describe('Create Linode flow to validate code snippet modal', () => { linodeCreatePage.selectRegionById(mockLinodeRegion.id); linodeCreatePage.selectPlan('Shared CPU', 'Nanode 1 GB'); linodeCreatePage.setRootPassword(rootPass); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Assign Firewall'); // View Code Snippets and confirm it's provisioned as expected. ui.button diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-vm-host-maintenance.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-vm-host-maintenance.spec.ts index 7f0e8989401..256aecd29e3 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-vm-host-maintenance.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-vm-host-maintenance.spec.ts @@ -1,13 +1,14 @@ import { linodeFactory, regionFactory } from '@linode/utilities'; import { mockGetAccountSettings } from 'support/intercepts/account'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode } from 'support/intercepts/linodes'; import { mockGetRegions } from 'support/intercepts/regions'; import { ui } from 'support/ui'; import { linodeCreatePage } from 'support/ui/pages'; -import { randomLabel, randomString } from 'support/util/random'; +import { randomLabel, randomNumber, randomString } from 'support/util/random'; -import { accountSettingsFactory } from 'src/factories'; +import { accountSettingsFactory, firewallFactory } from 'src/factories'; const mockEnabledRegion = regionFactory.build({ capabilities: ['Linodes', 'Maintenance Policy'], }); @@ -35,7 +36,12 @@ describe('vmHostMaintenance feature flag', () => { label: randomLabel(), region: mockEnabledRegion.id, }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); mockCreateLinode(mockLinode).as('createLinode'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); cy.visitWithLogin('/linodes/create'); cy.wait(['@getAccountSettings', '@getFeatureFlags', '@getRegions']); @@ -82,6 +88,8 @@ describe('vmHostMaintenance feature flag', () => { planLabel: 'Nanode 1 GB', }; linodeCreatePage.selectPlan(mockPlan.planType, mockPlan.planLabel); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Firewall'); cy.scrollTo('bottom'); ui.button .findByTitle('View Code Snippets') diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-with-add-ons.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-with-add-ons.spec.ts index 3dbdf20a47f..c5ec7562dd2 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-with-add-ons.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-with-add-ons.spec.ts @@ -1,4 +1,5 @@ import { linodeFactory } from '@linode/utilities'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode, mockGetLinodeDetails, @@ -8,7 +9,13 @@ import { linodeCreatePage } from 'support/ui/pages'; import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { chooseRegion } from 'support/util/regions'; +import { firewallFactory } from 'src/factories'; + describe('Create Linode with Add-ons', () => { + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); /* * - Confirms UI flow to create a Linode with backups using mock API data. * - Confirms that backups is reflected in create summary section. @@ -25,6 +32,7 @@ describe('Create Linode with Add-ons', () => { mockCreateLinode(mockLinode).as('createLinode'); mockGetLinodeDetails(mockLinode.id, mockLinode); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); cy.visitWithLogin('/linodes/create'); @@ -33,6 +41,11 @@ describe('Create Linode with Add-ons', () => { linodeCreatePage.selectRegionById(linodeRegion.id); linodeCreatePage.selectPlan('Shared CPU', 'Nanode 1 GB'); linodeCreatePage.setRootPassword(randomString(32)); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); linodeCreatePage.checkBackups(); linodeCreatePage.checkEUAgreements(); @@ -78,6 +91,7 @@ describe('Create Linode with Add-ons', () => { mockCreateLinode(mockLinode).as('createLinode'); mockGetLinodeDetails(mockLinode.id, mockLinode); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); cy.visitWithLogin('/linodes/create'); @@ -86,8 +100,15 @@ describe('Create Linode with Add-ons', () => { linodeCreatePage.selectRegionById(linodeRegion.id); linodeCreatePage.selectPlan('Shared CPU', 'Nanode 1 GB'); linodeCreatePage.setRootPassword(randomString(32)); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); linodeCreatePage.checkEUAgreements(); linodeCreatePage.selectInterfaceGeneration('legacy_config'); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Firewall'); linodeCreatePage.checkPrivateIPs(); // Confirm Private IP assignment indicator is shown in Linode summary. diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-with-disk-encryption.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-with-disk-encryption.spec.ts index 093b89bd120..351d0e56400 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-with-disk-encryption.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-with-disk-encryption.spec.ts @@ -3,9 +3,10 @@ import { linodeTypeFactory, regionFactory, } from '@linode/utilities'; -import { accountFactory } from '@src/factories'; +import { accountFactory, firewallFactory } from '@src/factories'; import { mockGetAccount } from 'support/intercepts/account'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode, mockGetLinodeTypes, @@ -17,7 +18,7 @@ import { import { ui } from 'support/ui'; import { linodeCreatePage } from 'support/ui/pages'; import { makeFeatureFlagData } from 'support/util/feature-flags'; -import { randomLabel, randomString } from 'support/util/random'; +import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { extendRegion } from 'support/util/regions'; import { @@ -156,6 +157,10 @@ describe('Create Linode with Disk Encryption', () => { label: randomLabel(), region: distributedRegion.id, }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); mockAppendFeatureFlags({ gecko2: { @@ -167,6 +172,7 @@ describe('Create Linode with Disk Encryption', () => { mockGetLinodeTypes([mockLinodeType]); mockGetRegionAvailability(distributedRegion.id, []); mockCreateLinode(mockLinode).as('createLinode'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); cy.visitWithLogin('/linodes/create'); cy.get('[data-qa-linode-region]').within(() => { @@ -185,7 +191,11 @@ describe('Create Linode with Disk Encryption', () => { linodeCreatePage.setLabel(mockLinode.label); linodeCreatePage.setRootPassword(randomString(32)); - + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); // Select mock Nanode plan type. cy.get('[data-qa-plan-row="Nanode 1 GB"]').click(); diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-with-firewall.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-with-firewall.spec.ts index b87f8dea920..26562828dcf 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-with-firewall.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-with-firewall.spec.ts @@ -424,17 +424,8 @@ describe('Create Linode with Firewall (Linode Interfaces)', () => { // Switch to legacy Config Interfaces linodeCreatePage.selectLegacyConfigInterfacesType(); - // Confirm that mocked Firewall is shown in the Autocomplete, and then select it. - cy.findByLabelText('Firewall').should('be.visible'); - cy.get('[data-qa-autocomplete="Firewall"]').within(() => { - cy.get('[data-testid="textfield-input"]').click(); - cy.focused().type(`${mockFirewall.label}`); - }); - - ui.autocompletePopper - .findByTitle(mockFirewall.label) - .should('be.visible') - .click(); + // Select a firewall for the VPC interface + linodeCreatePage.selectFirewall(mockFirewall.label, 'Firewall'); // Confirm Firewall assignment indicator is shown in Linode summary. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); @@ -494,17 +485,10 @@ describe('Create Linode with Firewall (Linode Interfaces)', () => { // Confirm the Linode Interfaces section is shown. assertNewLinodeInterfacesIsAvailable(); - // Confirm that mocked Firewall is shown in the Autocomplete, and then select it. - cy.findByLabelText('Public Interface Firewall').should('be.visible'); - cy.get('[data-qa-autocomplete="Public Interface Firewall"]').within(() => { - cy.get('[data-testid="textfield-input"]').click(); - cy.focused().type(`${mockFirewall.label}`); - }); - - ui.autocompletePopper - .findByTitle(mockFirewall.label) - .should('be.visible') - .click(); + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); // Confirm Firewall assignment indicator is shown in Linode summary. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); @@ -593,17 +577,7 @@ describe('Create Linode with Firewall (Linode Interfaces)', () => { `Firewall ${mockFirewall.label} successfully created` ); - // Confirm that mocked Firewall is shown in the Autocomplete, and then select it. - cy.findByLabelText('Firewall').should('be.visible'); - cy.get('[data-qa-autocomplete="Firewall"]').within(() => { - cy.get('[data-testid="textfield-input"]').click(); - cy.focused().type(`${mockFirewall.label}`); - }); - - ui.autocompletePopper - .findByTitle(mockFirewall.label) - .should('be.visible') - .click(); + linodeCreatePage.selectFirewall(mockFirewall.label, 'Firewall'); // Confirm Firewall assignment indicator is shown in Linode summary. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); @@ -688,17 +662,10 @@ describe('Create Linode with Firewall (Linode Interfaces)', () => { `Firewall ${mockFirewall.label} successfully created` ); - // Confirm that mocked Firewall is shown in the Autocomplete, and then select it. - cy.findByLabelText('Public Interface Firewall').should('be.visible'); - cy.get('[data-qa-autocomplete="Public Interface Firewall"]').within(() => { - cy.get('[data-testid="textfield-input"]').click(); - cy.focused().type(`${mockFirewall.label}`); - }); - - ui.autocompletePopper - .findByTitle(mockFirewall.label) - .should('be.visible') - .click(); + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); // Confirm Firewall assignment indicator is shown in Linode summary. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); @@ -769,6 +736,9 @@ describe('Create Linode with Firewall (Linode Interfaces)', () => { // Switch to legacy Config Interfaces linodeCreatePage.selectLegacyConfigInterfacesType(); + // Select Firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Firewall'); + // Creating the linode without a firewall should display a warning. ui.button .findByTitle('Create Linode') diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-with-ssh-key.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-with-ssh-key.spec.ts index fac8dd00c69..b15ecf35878 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-with-ssh-key.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-with-ssh-key.spec.ts @@ -1,5 +1,6 @@ import { linodeFactory, sshKeyFactory } from '@linode/utilities'; import { mockGetUser, mockGetUsers } from 'support/intercepts/account'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode } from 'support/intercepts/linodes'; import { mockCreateSSHKey } from 'support/intercepts/profile'; import { ui } from 'support/ui'; @@ -7,9 +8,13 @@ import { linodeCreatePage } from 'support/ui/pages'; import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { chooseRegion } from 'support/util/regions'; -import { accountUserFactory } from 'src/factories'; +import { accountUserFactory, firewallFactory } from 'src/factories'; describe('Create Linode with SSH Key', () => { + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); /* * - Confirms UI flow when creating a Linode with an authorized SSH key. * - Confirms that existing SSH keys are listed on page and can be selected. @@ -34,6 +39,7 @@ describe('Create Linode with SSH Key', () => { mockGetUsers([mockUser]); mockGetUser(mockUser); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); cy.visitWithLogin('/linodes/create'); @@ -43,6 +49,11 @@ describe('Create Linode with SSH Key', () => { linodeCreatePage.selectRegionById(linodeRegion.id); linodeCreatePage.selectPlan('Shared CPU', 'Nanode 1 GB'); linodeCreatePage.setRootPassword(randomString(32)); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); // Confirm that SSH key is listed, then select it. cy.findByText(mockSshKey.label).scrollIntoView(); @@ -101,6 +112,7 @@ describe('Create Linode with SSH Key', () => { mockGetUsers([mockUser]); mockCreateLinode(mockLinode).as('createLinode'); mockCreateSSHKey(mockSshKey).as('createSSHKey'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); cy.visitWithLogin('/linodes/create'); @@ -109,6 +121,11 @@ describe('Create Linode with SSH Key', () => { linodeCreatePage.selectRegionById(linodeRegion.id); linodeCreatePage.selectPlan('Shared CPU', 'Nanode 1 GB'); linodeCreatePage.setRootPassword(randomString(32)); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); // Confirm that no SSH keys are listed for the mocked user. cy.findByText(mockUser.username).scrollIntoView(); diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-with-user-data.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-with-user-data.spec.ts index 9fb53cba649..8330b471b9d 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-with-user-data.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-with-user-data.spec.ts @@ -1,4 +1,5 @@ import { linodeFactory, regionFactory } from '@linode/utilities'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockGetAllImages, mockGetImage } from 'support/intercepts/images'; import { mockCreateLinode, @@ -10,7 +11,7 @@ import { linodeCreatePage } from 'support/ui/pages'; import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { chooseRegion } from 'support/util/regions'; -import { imageFactory } from 'src/factories'; +import { firewallFactory, imageFactory } from 'src/factories'; describe('Create Linode with user data', () => { /* @@ -26,10 +27,15 @@ describe('Create Linode with user data', () => { label: randomLabel(), region: linodeRegion.id, }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); const userDataFixturePath = 'user-data/user-data-config-basic.yml'; mockCreateLinode(mockLinode).as('createLinode'); mockGetLinodeDetails(mockLinode.id, mockLinode); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); cy.visitWithLogin('/linodes/create'); @@ -40,6 +46,11 @@ describe('Create Linode with user data', () => { linodeCreatePage.selectRegionById(linodeRegion.id); linodeCreatePage.selectPlan('Shared CPU', 'Nanode 1 GB'); linodeCreatePage.setRootPassword(randomString(32)); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); // Expand "Add User Data" accordion and enter user data config. ui.accordionHeading diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-with-vlan.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-with-vlan.spec.ts index 1ed9356b23b..f55e562ee2b 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-with-vlan.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-with-vlan.spec.ts @@ -4,6 +4,7 @@ import { mockGetAccountSettings, } from 'support/intercepts/account'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode } from 'support/intercepts/linodes'; import { mockGetRegion, mockGetRegions } from 'support/intercepts/regions'; import { mockGetVLANs } from 'support/intercepts/vlans'; @@ -21,6 +22,7 @@ import { chooseRegion } from 'support/util/regions'; import { accountFactory, accountSettingsFactory, + firewallFactory, VLANFactory, } from 'src/factories'; @@ -56,7 +58,13 @@ describe('Create Linode with VLANs (Legacy)', () => { region: mockLinodeRegion.id, }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + mockGetVLANs([mockVlan]); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); cy.visitWithLogin('/linodes/create'); @@ -89,6 +97,9 @@ describe('Create Linode with VLANs (Legacy)', () => { .type(mockVlan.cidr_block); }); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Assign Firewall'); + // Confirm that VLAN attachment is listed in summary, then create Linode. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); cy.get('[data-qa-linode-create-summary]').within(() => { @@ -146,7 +157,13 @@ describe('Create Linode with VLANs (Legacy)', () => { region: mockLinodeRegion.id, }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + mockGetVLANs([]); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); cy.visitWithLogin('/linodes/create'); @@ -175,6 +192,9 @@ describe('Create Linode with VLANs (Legacy)', () => { .click(); }); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Assign Firewall'); + // Confirm that VLAN attachment is listed in summary, then create Linode. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); cy.get('[data-qa-linode-create-summary]').within(() => { @@ -305,7 +325,13 @@ describe('Create Linode with VLANs (Linode Interfaces)', () => { region: mockLinodeRegion.id, }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + mockGetVLANs([mockVlan]); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); cy.visitWithLogin('/linodes/create'); @@ -338,6 +364,9 @@ describe('Create Linode with VLANs (Linode Interfaces)', () => { .should('be.enabled') .type(mockVlan.cidr_block); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Firewall'); + // Confirm that VLAN attachment is listed in summary, then create Linode. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); cy.get('[data-qa-linode-create-summary]').within(() => { @@ -471,7 +500,13 @@ describe('Create Linode with VLANs (Linode Interfaces)', () => { region: mockLinodeRegion.id, }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + mockGetVLANs([]); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); cy.visitWithLogin('/linodes/create'); @@ -505,6 +540,9 @@ describe('Create Linode with VLANs (Linode Interfaces)', () => { .should('be.enabled') .type(mockVlan.cidr_block); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Firewall'); + // Confirm that VLAN attachment is listed in summary, then create Linode. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); cy.get('[data-qa-linode-create-summary]').within(() => { diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts index 0f9fa5ab253..b1a97b5da75 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts @@ -9,6 +9,7 @@ import { } from 'support/intercepts/account'; import { mockGetLinodeConfig } from 'support/intercepts/configs'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode, mockGetLinodeDetails, @@ -36,6 +37,7 @@ import { chooseRegion } from 'support/util/regions'; import { accountFactory, accountSettingsFactory, + firewallFactory, linodeConfigFactory, subnetFactory, vpcFactory, @@ -106,8 +108,14 @@ describe('Create Linode with VPCs (Legacy)', () => { ], }; + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + mockGetVPCs([mockVPC]).as('getVPCs'); mockGetVPC(mockVPC).as('getVPC'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); mockGetLinodeDetails(mockLinode.id, mockLinode); @@ -137,6 +145,9 @@ describe('Create Linode with VPCs (Legacy)', () => { `${mockSubnet.label} (${mockSubnet.ipv4})` ); + // Select a firewall for the VPC interface + linodeCreatePage.selectFirewall(mockFirewall.label, 'Assign Firewall'); + // Confirm VPC assignment indicator is shown in Linode summary. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); cy.get('[data-qa-linode-create-summary]').within(() => { @@ -240,7 +251,13 @@ describe('Create Linode with VPCs (Legacy)', () => { ], }; + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + mockGetVPCs([]); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); cy.visitWithLogin('/linodes/create'); @@ -293,6 +310,9 @@ describe('Create Linode with VPCs (Legacy)', () => { cy.findByLabelText('Clear').click(); }); + // Select a firewall for the VPC interface + linodeCreatePage.selectFirewall(mockFirewall.label, 'Assign Firewall'); + // Try to submit the form without a subnet selected ui.button .findByTitle('Create Linode') @@ -460,6 +480,11 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { ], }; + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockGetVPCs([mockVPC]).as('getVPCs'); mockGetVPC(mockVPC).as('getVPC'); mockCreateLinode(mockLinode).as('createLinode'); @@ -500,6 +525,9 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { `${mockSubnet.label} (${mockSubnet.ipv4})` ); + // Select a firewall for the VPC interface + linodeCreatePage.selectFirewall(mockFirewall.label, 'Firewall'); + // Confirm VPC assignment indicator is shown in Linode summary. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); cy.get('[data-qa-linode-create-summary]').within(() => { @@ -572,6 +600,11 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { region: linodeRegion.id, }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + const mockInterface = linodeConfigInterfaceFactoryWithVPC.build({ active: true, primary: true, @@ -601,6 +634,7 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { mockGetVPCs([mockVPC]).as('getVPCs'); mockGetVPC(mockVPC).as('getVPC'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); mockGetLinodeDetails(mockLinode.id, mockLinode); @@ -636,6 +670,12 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { `${mockSubnet.label} (${mockSubnet.ipv4})` ); + // Select a firewall for the VPC interface + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'VPC Interface Firewall' + ); + // Confirm VPC assignment indicator is shown in Linode summary. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); cy.get('[data-qa-linode-create-summary]').within(() => { @@ -737,6 +777,13 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { ], }; + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + + mockGetFirewalls([mockFirewall]).as('getFirewalls'); + mockGetVPCs([]); mockCreateLinode(mockLinode).as('createLinode'); cy.visitWithLogin('/linodes/create'); @@ -755,6 +802,8 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { // Select VPC card linodeCreatePage.selectInterface('vpc'); + // Select a firewall for the VPC interface + linodeCreatePage.selectFirewall(mockFirewall.label, 'Firewall'); cy.findByText('Create VPC').should('be.visible').click(); @@ -896,6 +945,11 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { region: linodeRegion.id, }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + const mockInterface = linodeConfigInterfaceFactoryWithVPC.build({ active: true, primary: true, @@ -924,6 +978,7 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { }; mockGetVPCs([]); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); cy.visitWithLogin('/linodes/create'); @@ -1009,6 +1064,12 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { .should('be.visible') .click(); + // Select a firewall for the VPC interface + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'VPC Interface Firewall' + ); + // Create Linode and confirm contents of outgoing API request payload. ui.button .findByTitle('Create Linode') diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts index eb6eec87f92..ce8d8c931fb 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts @@ -13,6 +13,7 @@ import { authenticate } from 'support/api/authentication'; import { LINODE_CREATE_TIMEOUT } from 'support/constants/linodes'; import { mockGetAccount, mockGetUser } from 'support/intercepts/account'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { interceptCreateLinode, mockCreateLinode, @@ -31,10 +32,17 @@ import { cleanUp } from 'support/util/cleanup'; import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { chooseRegion } from 'support/util/regions'; -import { accountFactory, accountUserFactory } from 'src/factories'; +import { + accountFactory, + accountUserFactory, + firewallFactory, +} from 'src/factories'; let username: string; - +const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), +}); authenticate(); describe('Create Linode', () => { before(() => { @@ -60,16 +68,7 @@ describe('Create Linode', () => { planLabel: 'Nanode 1 GB', planType: 'Shared CPU', }, - { - planId: 'g6-dedicated-2', - planLabel: 'Dedicated 4 GB', - planType: 'Dedicated CPU', - }, - { - planId: 'g7-highmem-1', - planLabel: 'Linode 24 GB', - planType: 'High Memory', - }, + // TODO Include GPU plan types. // TODO Include Accelerated plan types (when they're no longer as restricted) ].forEach((planConfig) => { @@ -81,6 +80,7 @@ describe('Create Linode', () => { const linodeRegion = chooseRegion({ capabilities: ['Linodes', 'Vlans'], }); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); const linodeLabel = randomLabel(); @@ -108,6 +108,11 @@ describe('Create Linode', () => { planConfig.planLabel ); linodeCreatePage.setRootPassword(randomString(32)); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Assign Firewall' + ); // Confirm information in summary is shown as expected. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); @@ -227,6 +232,7 @@ describe('Create Linode', () => { }).as('getFeatureFlags'); mockGetRegions(mockRegions).as('getRegions'); mockGetLinodeTypes([...mockAcceleratedType]).as('getLinodeTypes'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinode(mockLinode).as('createLinode'); cy.visitWithLogin('/linodes/create'); @@ -243,6 +249,8 @@ describe('Create Linode', () => { linodeCreatePage.selectRegionById(linodeRegion.id); linodeCreatePage.selectPlan('Accelerated', mockAcceleratedType[0].label); linodeCreatePage.setRootPassword(randomString(32)); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Assign Firewall'); // Confirm information in summary is shown as expected. cy.get('[data-qa-linode-create-summary]').scrollIntoView(); @@ -297,6 +305,7 @@ describe('Create Linode', () => { const createLinodeErrorMessage = 'An error has occurred during Linode creation flow'; + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockCreateLinodeError(createLinodeErrorMessage).as('createLinodeError'); cy.visitWithLogin('/linodes/create'); @@ -306,6 +315,8 @@ describe('Create Linode', () => { linodeCreatePage.selectRegionById(linodeRegion.id); linodeCreatePage.selectPlan('Shared CPU', 'Nanode 1 GB'); linodeCreatePage.setRootPassword(randomString(32)); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Assign Firewall'); // Create Linode by clicking the button. ui.button diff --git a/packages/manager/cypress/e2e/core/placementGroups/create-linode-with-placement-groups.spec.ts b/packages/manager/cypress/e2e/core/placementGroups/create-linode-with-placement-groups.spec.ts index dfd189af374..f592606a2e1 100644 --- a/packages/manager/cypress/e2e/core/placementGroups/create-linode-with-placement-groups.spec.ts +++ b/packages/manager/cypress/e2e/core/placementGroups/create-linode-with-placement-groups.spec.ts @@ -1,5 +1,6 @@ import { linodeFactory, regionFactory } from '@linode/utilities'; import { mockGetAccount } from 'support/intercepts/account'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode, mockGetLinodeDetails, @@ -11,10 +12,14 @@ import { import { mockGetRegions } from 'support/intercepts/regions'; import { ui } from 'support/ui/'; import { linodeCreatePage } from 'support/ui/pages'; -import { randomNumber, randomString } from 'support/util/random'; +import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { extendRegion } from 'support/util/regions'; -import { accountFactory, placementGroupFactory } from 'src/factories'; +import { + accountFactory, + firewallFactory, + placementGroupFactory, +} from 'src/factories'; import { CANNOT_CHANGE_PLACEMENT_GROUP_POLICY_MESSAGE } from 'src/features/PlacementGroups/constants'; const mockAccount = accountFactory.build(); @@ -37,12 +42,18 @@ const mockDallasRegion = extendRegion( }) ); +const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), +}); + const mockRegions = [mockNewarkRegion, mockDallasRegion]; describe('Linode create flow with Placement Group', () => { beforeEach(() => { mockGetAccount(mockAccount); mockGetRegions(mockRegions).as('getRegions'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); }); /* @@ -90,6 +101,8 @@ describe('Linode create flow with Placement Group', () => { // Choose plan cy.findByText('Shared CPU').click(); cy.get('[id="g6-nanode-1"]').click(); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Assign Firewall'); // Choose Placement Group // No Placement Group available @@ -241,6 +254,8 @@ describe('Linode create flow with Placement Group', () => { linodeCreatePage.selectPlan('Shared CPU', 'Nanode 1 GB'); linodeCreatePage.setRootPassword(randomString(32)); linodeCreatePage.setLabel(mockLinode.label); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Assign Firewall'); // Confirm that mocked Placement Group is shown in the Autocomplete, and then select it. cy.findByText( diff --git a/packages/manager/cypress/support/ui/pages/linode-create-page.ts b/packages/manager/cypress/support/ui/pages/linode-create-page.ts index 47e370d7ffd..b3f9741a81f 100644 --- a/packages/manager/cypress/support/ui/pages/linode-create-page.ts +++ b/packages/manager/cypress/support/ui/pages/linode-create-page.ts @@ -157,4 +157,30 @@ export const linodeCreatePage = { selectInterface: (type: 'public' | 'vlan' | 'vpc') => { cy.get(`[data-qa-interface-type-option="${type}"]`).click(); }, + + /** + * Selects a firewall from the firewall dropdown. + * + * @param firewallLabel - Label of the firewall to select. + * @param interfaceType - Optional interface type for the firewall dropdown label (e.g., 'Public Interface Firewall', 'VPC Interface Firewall'). + */ + selectFirewall: ( + firewallLabel: string, + dropdownLabel: + | 'Assign Firewall' + | 'Firewall' + | 'Public Interface Firewall' + | 'VPC Interface Firewall' + ) => { + cy.findByLabelText(dropdownLabel).should('be.visible'); + cy.get(`[data-qa-autocomplete="${dropdownLabel}"]`).within(() => { + cy.get('[data-testid="textfield-input"]').click(); + cy.focused().type(firewallLabel); + }); + + ui.autocompletePopper + .findByTitle(firewallLabel) + .should('be.visible') + .click(); + }, }; diff --git a/packages/manager/cypress/support/util/linodes.ts b/packages/manager/cypress/support/util/linodes.ts index ba990a3c94d..95494c39820 100644 --- a/packages/manager/cypress/support/util/linodes.ts +++ b/packages/manager/cypress/support/util/linodes.ts @@ -135,7 +135,7 @@ export const createTestLinode = async ( const resolvedCreatePayload = { ...createLinodeRequestFactory.build({ interface_generation: 'legacy_config', - firewall_id: null, + firewall_id: -1, booted: false, image: 'linode/ubuntu24.04', label: randomLabel(), diff --git a/packages/manager/src/features/Account/DefaultFirewalls.tsx b/packages/manager/src/features/Account/DefaultFirewalls.tsx index 84af676e298..5197c6d3038 100644 --- a/packages/manager/src/features/Account/DefaultFirewalls.tsx +++ b/packages/manager/src/features/Account/DefaultFirewalls.tsx @@ -126,6 +126,7 @@ export const DefaultFirewalls = () => { label="Configuration Profile Interfaces Firewall" onChange={(e, firewall) => field.onChange(firewall.id)} placeholder={DEFAULT_FIREWALL_PLACEHOLDER} + showNoFirewallOption={false} value={field.value} /> )} @@ -142,6 +143,7 @@ export const DefaultFirewalls = () => { label="Linode Interfaces - Public Interface Firewall" onChange={(e, firewall) => field.onChange(firewall.id)} placeholder={DEFAULT_FIREWALL_PLACEHOLDER} + showNoFirewallOption={false} value={field.value} /> )} @@ -158,6 +160,7 @@ export const DefaultFirewalls = () => { label="Linode Interfaces - VPC Interface Firewall" onChange={(e, firewall) => field.onChange(firewall.id)} placeholder={DEFAULT_FIREWALL_PLACEHOLDER} + showNoFirewallOption={false} value={field.value} /> )} @@ -177,6 +180,7 @@ export const DefaultFirewalls = () => { label="NodeBalancers Firewall" onChange={(e, firewall) => field.onChange(firewall.id)} placeholder={DEFAULT_FIREWALL_PLACEHOLDER} + showNoFirewallOption={false} value={field.value} /> )} diff --git a/packages/manager/src/features/Firewalls/components/FirewallSelect.test.tsx b/packages/manager/src/features/Firewalls/components/FirewallSelect.test.tsx index 5489c7cff9c..a002de27d1b 100644 --- a/packages/manager/src/features/Firewalls/components/FirewallSelect.test.tsx +++ b/packages/manager/src/features/Firewalls/components/FirewallSelect.test.tsx @@ -8,6 +8,10 @@ import { renderWithTheme } from 'src/utilities/testHelpers'; import { FirewallSelect } from './FirewallSelect'; +const NO_FIREWALL_ID = -1; +const NO_FIREWALL_LABEL = + 'No firewall - traffic is unprotected (not recommended)'; + describe('FirewallSelect', () => { it('renders a default label', () => { const { getByText } = renderWithTheme(); @@ -50,4 +54,75 @@ describe('FirewallSelect', () => { expect(getByText(firewall.label)).toBeVisible(); } }); + + it('renders "No firewall" option in the dropdown by default', async () => { + const firewalls = firewallFactory.buildList(2); + + server.use( + http.get('*/v4/networking/firewalls', () => { + return HttpResponse.json(makeResourcePage(firewalls)); + }) + ); + + const { getByLabelText, getByText } = renderWithTheme( + + ); + + await userEvent.click(getByLabelText('Firewall')); + + expect(getByText(NO_FIREWALL_LABEL)).toBeVisible(); + }); + + it('does not render "No firewall" option when showNoFirewallOption is false', async () => { + const firewalls = firewallFactory.buildList(2); + + server.use( + http.get('*/v4/networking/firewalls', () => { + return HttpResponse.json(makeResourcePage(firewalls)); + }) + ); + + const { getByLabelText, queryByText } = renderWithTheme( + + ); + + await userEvent.click(getByLabelText('Firewall')); + + expect(queryByText(NO_FIREWALL_LABEL)).not.toBeInTheDocument(); + }); + + it('displays warning notice when "No firewall" is selected and warningMessageForNoFirewallOption is provided', () => { + const warningMessage = 'This Linode is not secured with a Cloud Firewall.'; + + const { getByText } = renderWithTheme( + + ); + + expect(getByText(warningMessage)).toBeVisible(); + }); + + it('does not display warning notice when "No firewall" is selected but warningMessageForNoFirewallOption is not provided', () => { + const { queryByRole } = renderWithTheme( + + ); + + expect(queryByRole('alert')).not.toBeInTheDocument(); + }); + + it('shows "No firewall" as selected when value is NO_FIREWALL_ID', async () => { + server.use( + http.get('*/v4/networking/firewalls', () => { + return HttpResponse.json(makeResourcePage([])); + }) + ); + + const { findByDisplayValue } = renderWithTheme( + + ); + + expect(await findByDisplayValue(NO_FIREWALL_LABEL)).toBeInTheDocument(); + }); }); diff --git a/packages/manager/src/features/Firewalls/components/FirewallSelect.tsx b/packages/manager/src/features/Firewalls/components/FirewallSelect.tsx index 0b17aaf6de8..3d4a819d683 100644 --- a/packages/manager/src/features/Firewalls/components/FirewallSelect.tsx +++ b/packages/manager/src/features/Firewalls/components/FirewallSelect.tsx @@ -1,5 +1,5 @@ import { useAllFirewallsQuery } from '@linode/queries'; -import { Autocomplete, InputAdornment } from '@linode/ui'; +import { Autocomplete, InputAdornment, Notice, Stack } from '@linode/ui'; import React, { useMemo } from 'react'; import { useDefaultFirewallChipInformation } from 'src/hooks/useDefaultFirewallChipInformation'; @@ -10,6 +10,13 @@ import { FirewallSelectOption } from './FirewallSelectOption'; import type { Firewall } from '@linode/api-v4'; import type { EnhancedAutocompleteProps } from '@linode/ui'; +const NO_FIREWALL_ID = -1; + +const noFirewallOption = { + label: 'No firewall - traffic is unprotected (not recommended)', + id: NO_FIREWALL_ID, +} as Firewall; + interface Props extends Omit< EnhancedAutocompleteProps, @@ -31,10 +38,18 @@ interface Props * All Firewall will show if this is omitted. */ options?: Firewall[]; + /** + * Show an additional "No firewall (not recommended)" option in the dropdown, which has a value of `-1`. + */ + showNoFirewallOption?: boolean; /** * The ID of the selected Firewall */ value: null | number | undefined; + /** + * Warning notice when no firewall is selected. + */ + warningMessageForNoFirewallOption?: string; } /** @@ -47,50 +62,79 @@ interface Props export const FirewallSelect = ( props: Props ) => { - const { errorText, hideDefaultChips, label, loading, value, ...rest } = props; + const { + errorText, + hideDefaultChips, + label, + loading, + showNoFirewallOption = true, + value, + warningMessageForNoFirewallOption, + ...rest + } = props; const { data: firewalls, error, isLoading } = useAllFirewallsQuery(); const { defaultNumEntities, isDefault, tooltipText } = useDefaultFirewallChipInformation(value, hideDefaultChips); + const options = useMemo( + () => [ + ...(firewalls ?? []), + ...(showNoFirewallOption ? [noFirewallOption] : []), + ], + [firewalls, showNoFirewallOption] + ); + const selectedFirewall = useMemo( - () => firewalls?.find((firewall) => firewall.id === value) ?? null, + () => + value === NO_FIREWALL_ID + ? noFirewallOption + : (firewalls?.find((firewall) => firewall.id === value) ?? null), [firewalls, value] ); return ( - - aria-label={label === '' ? 'Firewall' : undefined} - errorText={errorText ?? error?.[0].reason} - label={label ?? 'Firewall'} - loading={isLoading || loading} - noMarginTop - options={firewalls ?? []} - placeholder="None" - renderOption={({ key, ...props }, option, state) => ( - + + aria-label={label === '' ? 'Firewall' : undefined} + errorText={errorText ?? error?.[0].reason} + label={label ?? 'Firewall'} + loading={isLoading || loading} + noMarginTop + options={options} + placeholder="Select a Firewall" + renderOption={({ key, ...props }, option, state) => ( + + )} + textFieldProps={{ + InputProps: { + endAdornment: isDefault && !hideDefaultChips && ( + + + + ), + }, + }} + value={selectedFirewall!} + {...rest} + /> + {value === NO_FIREWALL_ID && warningMessageForNoFirewallOption && ( + )} - textFieldProps={{ - InputProps: { - endAdornment: isDefault && !hideDefaultChips && ( - - - - ), - }, - }} - value={selectedFirewall!} - {...rest} - /> + ); }; diff --git a/packages/manager/src/features/Kubernetes/NodePoolFirewallSelect.tsx b/packages/manager/src/features/Kubernetes/NodePoolFirewallSelect.tsx index a272cbebdc8..cc0008e5a50 100644 --- a/packages/manager/src/features/Kubernetes/NodePoolFirewallSelect.tsx +++ b/packages/manager/src/features/Kubernetes/NodePoolFirewallSelect.tsx @@ -137,6 +137,7 @@ export const NodePoolFirewallSelect = (props: NodePoolFirewallSelectProps) => { } }} placeholder="Select firewall" + showNoFirewallOption={false} value={field.value} /> )} diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Firewall.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Firewall.tsx index 93edc25385b..08c40cadcbc 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Firewall.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Firewall.tsx @@ -14,6 +14,7 @@ import { useFlags } from 'src/hooks/useFlags'; import { useSecureVMNoticesEnabled } from 'src/hooks/useSecureVMNoticesEnabled'; import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEventAnalytics'; +import { WARNING_MESSAGE_FOR_NO_FIREWALL_OPTION } from '../constants'; import { useGetLinodeCreateType } from './Tabs/utils/useGetLinodeCreateType'; import type { CreateLinodeRequest } from '@linode/api-v4'; @@ -108,8 +109,11 @@ export const Firewall = () => { }); } }} - placeholder="None" + placeholder="Select a Firewall" value={field.value} + warningMessageForNoFirewallOption={ + WARNING_MESSAGE_FOR_NO_FIREWALL_OPTION + } /> { @@ -61,8 +63,11 @@ export const Firewall = () => { errorText={fieldState.error?.message} onBlur={field.onBlur} onChange={(e, firewall) => field.onChange(firewall?.id ?? null)} - placeholder="None" + placeholder="Select a Firewall" value={field.value} + warningMessageForNoFirewallOption={ + WARNING_MESSAGE_FOR_NO_FIREWALL_OPTION + } /> { label={`${labelMap[interfaceType ?? 'public']} Interface Firewall`} onBlur={field.onBlur} onChange={(e, firewall) => field.onChange(firewall?.id ?? null)} - placeholder="None" + placeholder="Select a Firewall" value={field.value} + warningMessageForNoFirewallOption={ + WARNING_MESSAGE_FOR_NO_FIREWALL_OPTION + } /> { field.onChange(value); // VLAN interfaces do not support Firewalls, so set - // the Firewall ID to `null` to be safe and early return. + // the Firewall ID to `-1` to be safe and early return. if (value === 'vlan') { - setValue(`linodeInterfaces.${index}.firewall_id`, null); + setValue(`linodeInterfaces.${index}.firewall_id`, -1); return; } diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.test.ts b/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.test.ts index 096e7385eb2..6885e046234 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.test.ts +++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.test.ts @@ -50,6 +50,7 @@ describe('getLinodeInterfacesPayload', () => { vpc_id: 1, }, }), + firewall_id: null, purpose: 'public' as const, }; @@ -66,6 +67,7 @@ describe('getLinodeInterfacePayload', () => { const networkInterface = { ...linodeInterfaceFactoryPublic.build(), purpose: 'public' as const, + firewall_id: null, }; expect(getLinodeInterfacePayload(networkInterface)).toEqual({ @@ -78,6 +80,7 @@ describe('getLinodeInterfacePayload', () => { const networkInterface = { ...vpcInterface, purpose: 'vpc' as const, + firewall_id: null, }; const newInterface = { @@ -86,6 +89,7 @@ describe('getLinodeInterfacePayload', () => { }; expect(getLinodeInterfacePayload(networkInterface)).toEqual({ ...newInterface, + firewall_id: null, }); }); }); diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.ts b/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.ts index 00ac034fe60..4b075103d59 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.ts +++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/utilities.ts @@ -19,7 +19,9 @@ interface VPC extends NonNullable { /** * We extend the new `CreateLinodeInterfacePayload` to add extra state we need to track */ -export interface LinodeCreateInterface extends CreateLinodeInterfacePayload { +export interface LinodeCreateInterface + extends Omit { + firewall_id: null | number; purpose: InterfacePurpose; vpc: null | VPC; } @@ -52,13 +54,19 @@ export const getCleanedLinodeInterfaceValues = ( */ export const getLinodeInterfacePayload = ( networkInterface: LinodeCreateInterface -): CreateLinodeInterfacePayload => { +): Omit & { + firewall_id: null | number; +} => { // ensure only one interface type is present const cleanedValues = getCleanedLinodeInterfaceValues(networkInterface); if (cleanedValues.vpc) { const vpcValues = omitProps(cleanedValues.vpc, ['vpc_id']); - return { ...omitProps(cleanedValues, ['purpose']), vpc: vpcValues }; + return { + ...omitProps(cleanedValues, ['purpose']), + firewall_id: cleanedValues.firewall_id, + vpc: vpcValues, + }; } // The API errors saying address is invalid if we pass an empty string. @@ -68,7 +76,10 @@ export const getLinodeInterfacePayload = ( cleanedValues.vlan.ipam_address = null; } - return omitProps(cleanedValues, ['purpose']); + return { + ...omitProps(cleanedValues, ['purpose']), + firewall_id: cleanedValues.firewall_id, + }; }; /** diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.tsx index 62642e6213c..01558429a41 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.tsx @@ -109,7 +109,7 @@ export const Summary = ({ isAlertsBetaMode }: SummaryProps) => { const hasFirewall = interfaceGeneration === 'linode' - ? linodeInterfaces.some((i) => i.firewall_id) + ? linodeInterfaces.some((i) => i.firewall_id && i.firewall_id !== -1) : firewallId; const hasBetaAclpAlertsAssigned = diff --git a/packages/manager/src/features/Linodes/LinodeCreate/resolvers.ts b/packages/manager/src/features/Linodes/LinodeCreate/resolvers.ts index dedae51d7de..d5a48faf454 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/resolvers.ts +++ b/packages/manager/src/features/Linodes/LinodeCreate/resolvers.ts @@ -38,6 +38,22 @@ export const getLinodeCreateResolver = ( values.linodeInterfaces = values.linodeInterfaces.map( getCleanedLinodeInterfaceValues ); + if (tab === 'Clone Linode' && !values.firewall_id) { + // The Clone Linode flow does not have the firewall_id field under interfaces object, so we set firewall_id to -1 to bypass the firewall requirement in the validation schema. + values.firewall_id = -1; + } + if ( + values.interface_generation === 'legacy_config' || + tab === 'Clone Linode' + ) { + // firewall_id is required in the form under interfaces object when using linode interfaces, but not when using legacy interfaces. + // If the user selects legacy interfaces, we set firewall_id to -1 to bypass the firewall requirement in the validation schema. + values.linodeInterfaces.forEach((linodeInterface) => { + linodeInterface.firewall_id = -1; + }); + } else { + values.firewall_id = -1; + } } else { values.linodeInterfaces = []; values.interfaces = diff --git a/packages/manager/src/features/Linodes/LinodeCreate/utilities.test.tsx b/packages/manager/src/features/Linodes/LinodeCreate/utilities.test.tsx index 10ffa39e821..ffddf4a9bed 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/utilities.test.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/utilities.test.tsx @@ -1,11 +1,4 @@ -import { - createLinodeRequestFactory, - getIsLegacyInterfaceArray, - linodeConfigInterfaceFactory, - linodeInterfaceFactoryPublic, - linodeInterfaceFactoryVlan, - linodeInterfaceFactoryVPC, -} from '@linode/utilities'; +import { createLinodeRequestFactory } from '@linode/utilities'; import { getDefaultInterfaceGenerationFromAccountSetting, @@ -364,26 +357,6 @@ describe('getInterfacesPayload', () => { }); }); -describe('getIsLegacyInterfaceArray', () => { - it('determines the given interfaces are legacy', () => { - const legacyInterfaces = linodeConfigInterfaceFactory.buildList(3); - expect(getIsLegacyInterfaceArray(legacyInterfaces)).toBe(true); - expect(getIsLegacyInterfaceArray(undefined)).toBe(true); - expect(getIsLegacyInterfaceArray([])).toBe(true); - }); - - it('returns false if the given interfaces are new Linode Interfaces', () => { - const linodeInterfacesVlan = linodeInterfaceFactoryVlan.buildList(3); - expect(getIsLegacyInterfaceArray(linodeInterfacesVlan)).toBe(false); - - const linodeInterfacesVPC = linodeInterfaceFactoryVPC.buildList(3); - expect(getIsLegacyInterfaceArray(linodeInterfacesVPC)).toBe(false); - - const linodeInterfacesPublic = linodeInterfaceFactoryPublic.buildList(3); - expect(getIsLegacyInterfaceArray(linodeInterfacesPublic)).toBe(false); - }); -}); - describe('getLinodeLabelFromLabelParts', () => { it('should join items', () => { expect(getLinodeLabelFromLabelParts(['my-linode', 'us-east'])).toBe( @@ -489,6 +462,7 @@ describe('getDoesEmployeeNeedToAssignFirewall', () => { vpc: null, default_route: null, vlan: null, + firewall_id: null, }, ], 'linode' @@ -545,6 +519,7 @@ describe('getDoesEmployeeNeedToAssignFirewall', () => { public: null, default_route: null, vlan: { vlan_label: 'my-vlan-1' }, + firewall_id: null, }, ], 'linode' @@ -565,6 +540,7 @@ describe('getDoesEmployeeNeedToAssignFirewall', () => { vpc: null, vlan: null, default_route: null, + firewall_id: null, }, { vpc: null, @@ -572,6 +548,7 @@ describe('getDoesEmployeeNeedToAssignFirewall', () => { public: null, default_route: null, vlan: { vlan_label: 'my-vlan-1' }, + firewall_id: null, }, ], 'linode' diff --git a/packages/manager/src/features/Linodes/LinodeCreate/utilities.ts b/packages/manager/src/features/Linodes/LinodeCreate/utilities.ts index a30cf672123..cd044d73637 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/utilities.ts +++ b/packages/manager/src/features/Linodes/LinodeCreate/utilities.ts @@ -25,6 +25,7 @@ import { getDefaultUDFData } from './Tabs/StackScripts/UserDefinedFields/utiliti import type { LinodeCreateInterface } from './Networking/utilities'; import type { AccountSettings, + CreateLinodeInterfacePayload, CreateLinodeRequest, FirewallSettings, InterfaceGenerationType, @@ -97,8 +98,8 @@ export const getLinodeCreatePayload = ( if (shouldUseNewInterfaces) { values.interfaces = formValues.linodeInterfaces.map( getLinodeInterfacePayload - ); - values.firewall_id = undefined; + ) as CreateLinodeInterfacePayload[]; + values.firewall_id = -1; } else { values.interfaces = formValues.backup_id ? undefined diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/AddInterfaceForm.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/AddInterfaceForm.tsx index 82b2370844e..659a6b8a55d 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/AddInterfaceForm.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/AddInterfaceForm.tsx @@ -23,6 +23,7 @@ import { VLANInterface } from './VLAN/VLANInterface'; import { VPCInterface } from './VPC/VPCInterface'; import type { CreateInterfaceFormValues } from './utilities'; +import type { CreateLinodeInterfacePayload } from '@linode/api-v4'; interface Props { linodeId: number; @@ -45,7 +46,7 @@ export const AddInterfaceForm = (props: Props) => { ) ?? []; const form = useForm({ defaultValues: { - firewall_id: null, + firewall_id: undefined, public: {}, vlan: {}, vpc: { @@ -62,7 +63,11 @@ export const AddInterfaceForm = (props: Props) => { const { errors, values } = await yupResolver( CreateLinodeInterfaceFormSchema - )(valuesWithOnlySelectedInterface, context, options); + )( + valuesWithOnlySelectedInterface as CreateInterfaceFormValues, + context, + options + ); if (errors) { return { errors, values }; @@ -74,7 +79,10 @@ export const AddInterfaceForm = (props: Props) => { const onSubmit = async (values: CreateInterfaceFormValues) => { try { - await mutateAsync(getLinodeInterfacePayload(values)); + const payload = getLinodeInterfacePayload( + values + ) as CreateLinodeInterfacePayload; + await mutateAsync(payload); enqueueSnackbar('Successfully added network interface.', { variant: 'success', diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/InterfaceFirewall.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/InterfaceFirewall.tsx index e697993df09..a1e98f02870 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/InterfaceFirewall.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/InterfaceFirewall.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useController } from 'react-hook-form'; import { FirewallSelect } from 'src/features/Firewalls/components/FirewallSelect'; +import { WARNING_MESSAGE_FOR_NO_FIREWALL_OPTION } from 'src/features/Linodes/constants'; import type { CreateInterfaceFormValues } from './utilities'; @@ -18,8 +19,9 @@ export const InterfaceFirewall = () => { errorText={fieldState.error?.message} onBlur={field.onBlur} onChange={(e, firewall) => field.onChange(firewall?.id ?? null)} - placeholder="None" + placeholder="Select a Firewall" value={field.value} + warningMessageForNoFirewallOption={WARNING_MESSAGE_FOR_NO_FIREWALL_OPTION} /> ); }; diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/InterfaceType.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/InterfaceType.tsx index 83465ed7068..d09a81afcbc 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/InterfaceType.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/InterfaceType.tsx @@ -39,9 +39,9 @@ export const InterfaceType = (props: Props) => { field.onChange(value); // VLAN interfaces do not support Firewalls, so set - // the Firewall ID to `null` to be safe and early return. + // the Firewall ID to `-1` to be safe and early return. if (value === 'vlan') { - setValue('firewall_id', null); + setValue('firewall_id', -1); return; } diff --git a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/EditInterfaceFirewall.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/EditInterfaceFirewall.tsx index c83e38e881d..0a38715062d 100644 --- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/EditInterfaceFirewall.tsx +++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/EditInterfaceDrawer/EditInterfaceFirewall.tsx @@ -33,6 +33,7 @@ export const EditInterfaceFirewall = ({ showSuccessNotice }: Props) => { field.onChange(firewall?.id ?? null)} + showNoFirewallOption={false} value={field.value} /> )} diff --git a/packages/manager/src/features/Linodes/constants.ts b/packages/manager/src/features/Linodes/constants.ts index b1326b91364..63a2cd6c581 100644 --- a/packages/manager/src/features/Linodes/constants.ts +++ b/packages/manager/src/features/Linodes/constants.ts @@ -51,3 +51,6 @@ export const LINODE_LOCKED_DELETE_INTERFACE_TOOLTIP = export const LINODE_REBUILD_LOCKED_NOTICE_TEXT = 'This Linode is currently locked and cannot be rebuilt. Please remove the lock to proceed.'; + +export const WARNING_MESSAGE_FOR_NO_FIREWALL_OPTION = + 'This Linode, or its Linode interface, is not secured with a Cloud Firewall. Add a firewall to help protect your resources and simplify security management.'; diff --git a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx index 89d105ae4c8..6787031e6dc 100644 --- a/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx +++ b/packages/manager/src/features/VPCs/VPCDetail/SubnetAssignLinodesDrawer.tsx @@ -29,6 +29,7 @@ import { Link } from 'src/components/Link'; import { RemovableSelectionsListTable } from 'src/components/RemovableSelectionsList/RemovableSelectionsListTable'; import { FirewallSelect } from 'src/features/Firewalls/components/FirewallSelect'; import { useGetAllUserEntitiesByPermission } from 'src/features/IAM/hooks/useGetAllUserEntitiesByPermission'; +import { WARNING_MESSAGE_FOR_NO_FIREWALL_OPTION } from 'src/features/Linodes/constants'; import { getDefaultFirewallForInterfacePurpose } from 'src/features/Linodes/LinodeCreate/Networking/utilities'; import { REMOVABLE_SELECTIONS_LINODES_TABLE_HEADERS, @@ -753,6 +754,9 @@ export const SubnetAssignLinodesDrawer = ( setFieldValue('selectedFirewall', firewall?.id) } value={values.selectedFirewall} + warningMessageForNoFirewallOption={ + WARNING_MESSAGE_FOR_NO_FIREWALL_OPTION + } /> )} diff --git a/packages/manager/src/features/VPCs/utils.ts b/packages/manager/src/features/VPCs/utils.ts index 5e96fb4388e..7ff4ba93ef9 100644 --- a/packages/manager/src/features/VPCs/utils.ts +++ b/packages/manager/src/features/VPCs/utils.ts @@ -151,7 +151,7 @@ export const getVPCInterfacePayload = (inputs: { if (isLinodeInterface) { return { - firewall_id: firewallId, + firewall_id: firewallId!, vpc: { subnet_id: subnetId ?? -1, ipv4: { diff --git a/packages/manager/src/utilities/codesnippets/generate-cli.test.ts b/packages/manager/src/utilities/codesnippets/generate-cli.test.ts index 1cfa1080314..34af7810cad 100644 --- a/packages/manager/src/utilities/codesnippets/generate-cli.test.ts +++ b/packages/manager/src/utilities/codesnippets/generate-cli.test.ts @@ -41,6 +41,7 @@ const linodeDataForCLI = ` --label ${linodeRequest.label} \\ --region ${linodeRequest.region} \\ --root_pass ${linodeRequest.root_pass} \\ + --firewall_id -1 \\ --type ${linodeRequest.type} \\ --authorized_users Linny \\ --authorized_users Gritty \\ diff --git a/packages/utilities/src/factories/linodes.ts b/packages/utilities/src/factories/linodes.ts index 3c0d0b6467e..2bb366dcfb4 100644 --- a/packages/utilities/src/factories/linodes.ts +++ b/packages/utilities/src/factories/linodes.ts @@ -515,6 +515,7 @@ export const createLinodeRequestFactory = label: Factory.each((i) => `linode-${i}`), region: 'us-southeast', root_pass: 'linode-root-password', + firewall_id: -1, type: 'g6-standard-1', }); diff --git a/packages/validation/src/linodes.schema.ts b/packages/validation/src/linodes.schema.ts index 05e4c460a5b..2ba005c581f 100644 --- a/packages/validation/src/linodes.schema.ts +++ b/packages/validation/src/linodes.schema.ts @@ -720,7 +720,9 @@ export const CreateVPCInterfaceSchema = object({ }); export const CreateLinodeInterfaceSchema = object({ - firewall_id: number().nullable(), + firewall_id: number() + .nullable() + .required('Select an option or create a new Firewall.'), default_route: object({ ipv4: boolean(), ipv6: boolean(), @@ -872,7 +874,9 @@ export const CreateLinodeSchema = object({ }), }), metadata: MetadataSchema.notRequired().default(undefined), - firewall_id: number().nullable().notRequired(), + firewall_id: number() + .nullable() + .required('Select an option or create a new Firewall.'), placement_group: PlacementGroupPayloadSchema.notRequired().default(undefined), disk_encryption: DiskEncryptionSchema, maintenance_policy: string() From 3926c9e2f7b92a1cface6075c24cf263bdff2bf2 Mon Sep 17 00:00:00 2001 From: Ganesh Revanakar Date: Fri, 20 Feb 2026 22:31:50 +0530 Subject: [PATCH 2/2] Fixed e2e test cases --- .../e2e/core/general/gdpr-agreement.spec.ts | 16 +++++++++++- .../open-support-ticket.spec.ts | 9 +++++++ .../e2e/core/linodes/create-linode.spec.ts | 6 +++++ .../core/oneClickApps/one-click-apps.spec.ts | 17 ++++++++++-- .../stackscripts/create-stackscripts.spec.ts | 26 ++++++++++++++++++- .../smoke-community-stackscripts.spec.ts | 16 ++++++++++-- 6 files changed, 84 insertions(+), 6 deletions(-) diff --git a/packages/manager/cypress/e2e/core/general/gdpr-agreement.spec.ts b/packages/manager/cypress/e2e/core/general/gdpr-agreement.spec.ts index c89c6cbc6c9..9aaa67132c6 100644 --- a/packages/manager/cypress/e2e/core/general/gdpr-agreement.spec.ts +++ b/packages/manager/cypress/e2e/core/general/gdpr-agreement.spec.ts @@ -1,9 +1,12 @@ import { linodeFactory, regionFactory } from '@linode/utilities'; +import { firewallFactory } from '@src/factories'; import { mockGetAccountAgreements } from 'support/intercepts/account'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinode } from 'support/intercepts/linodes'; import { mockGetRegions } from 'support/intercepts/regions'; import { ui } from 'support/ui'; -import { randomLabel, randomString } from 'support/util/random'; +import { linodeCreatePage } from 'support/ui/pages'; +import { randomLabel, randomNumber, randomString } from 'support/util/random'; import type { Region } from '@linode/api-v4'; @@ -100,6 +103,11 @@ describe('GDPR agreement', () => { }); it('needs the agreement checked to submit the form', () => { + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); mockGetRegions(mockRegions).as('getRegions'); mockGetAccountAgreements({ billing_agreement: false, @@ -127,6 +135,12 @@ describe('GDPR agreement', () => { cy.findByLabelText('Root Password').type(rootpass); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); + cy.get('[data-testid="eu-agreement-checkbox"]') .as('euAgreement') .scrollIntoView(); diff --git a/packages/manager/cypress/e2e/core/helpAndSupport/open-support-ticket.spec.ts b/packages/manager/cypress/e2e/core/helpAndSupport/open-support-ticket.spec.ts index f979b0cd28a..b41149900de 100644 --- a/packages/manager/cypress/e2e/core/helpAndSupport/open-support-ticket.spec.ts +++ b/packages/manager/cypress/e2e/core/helpAndSupport/open-support-ticket.spec.ts @@ -6,6 +6,7 @@ import 'cypress-file-upload'; import { mockGetAccount } from 'support/intercepts/account'; import { mockGetDomains } from 'support/intercepts/domains'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockCreateLinodeAccountLimitError, mockGetLinodeDetails, @@ -31,6 +32,7 @@ import { } from 'support/util/random'; import { chooseRegion } from 'support/util/regions'; +import { firewallFactory } from 'src/factories'; import { accountFactory, domainFactory, @@ -378,6 +380,10 @@ describe('open support tickets', () => { planLabel: 'Nanode 1 GB', planId: 'g6-nanode-1', }; + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); const mockLinode = linodeFactory.build(); @@ -393,6 +399,7 @@ describe('open support tickets', () => { mockGetSupportTicket(mockAccountLimitTicket); mockGetSupportTicketReplies(mockAccountLimitTicket.id, []); mockGetLinodes([mockLinode]); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); cy.visitWithLogin('/linodes/create'); @@ -401,6 +408,8 @@ describe('open support tickets', () => { linodeCreatePage.selectRegionById(mockRegion.id); linodeCreatePage.selectPlan(mockPlan.planType, mockPlan.planLabel); linodeCreatePage.setRootPassword(randomString(32)); + // Select a firewall + linodeCreatePage.selectFirewall(mockFirewall.label, 'Assign Firewall'); // Attempt to create Linode and confirm mocked account limit error with support link is present. ui.button diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts index ce8d8c931fb..726267b67b8 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode.spec.ts @@ -62,6 +62,12 @@ describe('Create Linode', () => { describe('End-to-end', () => { // Run an end-to-end test to create a basic Linode for each plan type described below. describe('By plan type', () => { + beforeEach(() => { + mockAppendFeatureFlags({ + linodeInterfaces: { enabled: false }, + iam: { enabled: false }, + }); + }); [ { planId: 'g6-nanode-1', diff --git a/packages/manager/cypress/e2e/core/oneClickApps/one-click-apps.spec.ts b/packages/manager/cypress/e2e/core/oneClickApps/one-click-apps.spec.ts index 8c1e15ab9bd..f1779baa308 100644 --- a/packages/manager/cypress/e2e/core/oneClickApps/one-click-apps.spec.ts +++ b/packages/manager/cypress/e2e/core/oneClickApps/one-click-apps.spec.ts @@ -1,4 +1,5 @@ import { linodeFactory } from '@linode/utilities'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { mockGetAllImages } from 'support/intercepts/images'; import { mockCreateLinode } from 'support/intercepts/linodes'; import { @@ -7,11 +8,12 @@ import { mockGetStackScripts, } from 'support/intercepts/stackscripts'; import { ui } from 'support/ui'; +import { linodeCreatePage } from 'support/ui/pages'; import { getRandomOCAId } from 'support/util/one-click-apps'; -import { randomLabel, randomString } from 'support/util/random'; +import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { chooseRegion } from 'support/util/regions'; -import { imageFactory } from 'src/factories'; +import { firewallFactory, imageFactory } from 'src/factories'; import { stackScriptFactory } from 'src/factories/stackscripts'; import { getMarketplaceAppLabel } from 'src/features/Linodes/LinodeCreate/Tabs/Marketplace/utilities'; import { oneClickApps } from 'src/features/OneClickApps/oneClickApps'; @@ -166,10 +168,15 @@ describe('OneClick Apps (OCA)', () => { const linode = linodeFactory.build({ label: linodeLabel, }); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); mockGetAllImages(images); mockGetStackScripts([stackscript]).as('getStackScripts'); mockGetStackScript(stackscript.id, stackscript); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); cy.visitWithLogin(`/linodes/create/marketplace`); @@ -242,6 +249,12 @@ describe('OneClick Apps (OCA)', () => { // Create the Linode mockCreateLinode(linode).as('createLinode'); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); + ui.button .findByTitle('Create Linode') .should('be.visible') diff --git a/packages/manager/cypress/e2e/core/stackscripts/create-stackscripts.spec.ts b/packages/manager/cypress/e2e/core/stackscripts/create-stackscripts.spec.ts index 2d793df73f5..dda948424b3 100644 --- a/packages/manager/cypress/e2e/core/stackscripts/create-stackscripts.spec.ts +++ b/packages/manager/cypress/e2e/core/stackscripts/create-stackscripts.spec.ts @@ -2,6 +2,7 @@ import { createImage, getLinodeDisks, resizeLinodeDisk } from '@linode/api-v4'; import { createLinodeRequestFactory } from '@linode/utilities'; import { authenticate } from 'support/api/authentication'; import { interceptGetAccountAvailability } from 'support/intercepts/account'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { interceptGetAllImages } from 'support/intercepts/images'; import { interceptCreateLinode } from 'support/intercepts/linodes'; import { @@ -9,6 +10,7 @@ import { interceptGetStackScripts, } from 'support/intercepts/stackscripts'; import { ui } from 'support/ui'; +import { linodeCreatePage } from 'support/ui/pages'; import { SimpleBackoffMethod } from 'support/util/backoff'; import { cleanUp } from 'support/util/cleanup'; import { chooseImage } from 'support/util/images'; @@ -18,10 +20,16 @@ import { pollLinodeDiskSize, pollLinodeStatus, } from 'support/util/polling'; -import { randomLabel, randomPhrase, randomString } from 'support/util/random'; +import { + randomLabel, + randomNumber, + randomPhrase, + randomString, +} from 'support/util/random'; import { chooseRegion, getRegionByLabel } from 'support/util/regions'; import { getFilteredImagesForImageSelect } from 'src/components/ImageSelect/utilities'; +import { firewallFactory } from 'src/factories'; import type { Image } from '@linode/api-v4'; @@ -179,6 +187,11 @@ const createLinodeAndImage = async () => { return image; }; +const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), +}); + authenticate(); describe('Create stackscripts', () => { before(() => { @@ -186,6 +199,7 @@ describe('Create stackscripts', () => { }); beforeEach(() => { cy.tag('method:e2e', 'purpose:dcTesting'); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); }); /* @@ -288,6 +302,11 @@ describe('Create stackscripts', () => { cy.findByLabelText('Example Title').should('be.visible').click(); cy.focused().type('{selectall}{backspace}'); cy.focused().type(randomString(12)); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); ui.button .findByTitle('Create Linode') @@ -389,6 +408,11 @@ describe('Create stackscripts', () => { cy.findByText(privateImage.label).as('qaPrivateImage').scrollIntoView(); cy.get('@qaPrivateImage').should('be.visible').click(); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); interceptCreateLinode().as('createLinode'); fillOutLinodeForm( linodeLabel, diff --git a/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscripts.spec.ts b/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscripts.spec.ts index 7e137192517..629683d748d 100644 --- a/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscripts.spec.ts +++ b/packages/manager/cypress/e2e/core/stackscripts/smoke-community-stackscripts.spec.ts @@ -1,5 +1,6 @@ import { getProfile } from '@linode/api-v4'; import { authenticate } from 'support/api/authentication'; +import { mockGetFirewalls } from 'support/intercepts/firewalls'; import { interceptCreateLinode } from 'support/intercepts/linodes'; import { mockGetUserPreferences } from 'support/intercepts/profile'; import { @@ -8,11 +9,12 @@ import { mockGetStackScripts, } from 'support/intercepts/stackscripts'; import { ui } from 'support/ui'; +import { linodeCreatePage } from 'support/ui/pages'; import { cleanUp } from 'support/util/cleanup'; -import { randomLabel, randomString } from 'support/util/random'; +import { randomLabel, randomNumber, randomString } from 'support/util/random'; import { chooseRegion } from 'support/util/regions'; -import { stackScriptFactory } from 'src/factories'; +import { firewallFactory, stackScriptFactory } from 'src/factories'; import { formatDate } from 'src/utilities/formatDate'; import type { Profile, StackScript } from '@linode/api-v4'; @@ -284,11 +286,16 @@ describe('Community Stackscripts integration tests', () => { const image = 'AlmaLinux 9'; const region = chooseRegion({ capabilities: ['Linodes', 'Vlans'] }); const linodeLabel = randomLabel(); + const mockFirewall = firewallFactory.build({ + id: randomNumber(), + label: randomLabel(), + }); // Ensure that the Primary Nav is open mockGetUserPreferences({ desktop_sidebar_open: false }).as( 'getPreferences' ); + mockGetFirewalls([mockFirewall]).as('getFirewalls'); interceptGetStackScripts().as('getStackScripts'); cy.visitWithLogin('/stackscripts/community'); cy.wait(['@getStackScripts', '@getPreferences']); @@ -422,6 +429,11 @@ describe('Community Stackscripts integration tests', () => { cy.get('[id="root-password"]').type(rootPassword); interceptCreateLinode().as('createLinode'); + // Select a firewall + linodeCreatePage.selectFirewall( + mockFirewall.label, + 'Public Interface Firewall' + ); ui.button .findByTitle('Create Linode') .should('be.visible')