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/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..726267b67b8 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(() => {
@@ -54,22 +62,19 @@ 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',
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 +86,7 @@ describe('Create Linode', () => {
const linodeRegion = chooseRegion({
capabilities: ['Linodes', 'Vlans'],
});
+ mockGetFirewalls([mockFirewall]).as('getFirewalls');
const linodeLabel = randomLabel();
@@ -108,6 +114,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 +238,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 +255,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 +311,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 +321,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/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/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/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')
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()