diff --git a/packages/manager/.changeset/pr-13410-changed-1772435694557.md b/packages/manager/.changeset/pr-13410-changed-1772435694557.md
new file mode 100644
index 00000000000..dcd3b8c1141
--- /dev/null
+++ b/packages/manager/.changeset/pr-13410-changed-1772435694557.md
@@ -0,0 +1,5 @@
+---
+"@linode/manager": Changed
+---
+
+Make firewall selection mandatory while creating linode and its interfaces ([#13410](https://github.com/linode/manager/pull/13410))
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..b3aa9d9f892 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
+ 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();
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..dd43845b810 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,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: true },
+ });
+ mockGetFirewalls([mockFirewall]).as('getFirewalls');
+ });
[
{
planId: 'g6-nanode-1',
@@ -81,6 +95,7 @@ describe('Create Linode', () => {
const linodeRegion = chooseRegion({
capabilities: ['Linodes', 'Vlans'],
});
+ mockGetFirewalls([mockFirewall]).as('getFirewalls');
const linodeLabel = randomLabel();
@@ -108,6 +123,11 @@ describe('Create Linode', () => {
planConfig.planLabel
);
linodeCreatePage.setRootPassword(randomString(32));
+ // Select a firewall
+ linodeCreatePage.selectFirewall(
+ 'No firewall - traffic is unprotected (not recommended)',
+ 'Public Interface Firewall'
+ );
// Confirm information in summary is shown as expected.
cy.get('[data-qa-linode-create-summary]').scrollIntoView();
@@ -227,6 +247,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 +264,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 +320,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 +330,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..903f449da50 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(
+ 'No firewall - traffic is unprotected (not recommended)',
+ '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(
+ 'No firewall - traffic is unprotected (not recommended)',
+ '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..e87e040420b 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']);
@@ -392,6 +399,12 @@ describe('Community Stackscripts integration tests', () => {
cy.get('[data-qa-radio]').click({ force: true });
});
+ // Select a firewall
+ linodeCreatePage.selectFirewall(
+ mockFirewall.label,
+ 'Public Interface Firewall'
+ );
+
// Input root password
// Weak or fair root password cannot rebuild the linode
cy.get('[id="root-password"]').clear();
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..3b038dd20b8 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(
+
+ );
+
+ await findByDisplayValue(NO_FIREWALL_LABEL);
+ });
});
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/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..dd798b23e45 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/resolvers.ts
+++ b/packages/manager/src/features/Linodes/LinodeCreate/resolvers.ts
@@ -38,6 +38,18 @@ export const getLinodeCreateResolver = (
values.linodeInterfaces = values.linodeInterfaces.map(
getCleanedLinodeInterfaceValues
);
+ 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 =
@@ -52,6 +64,12 @@ export const getLinodeCreateResolver = (
values.metadata = undefined;
}
+ // For the Clone Linode flow, we need not send firewall_id in the payload as API will take care of assigning the firewall_id based on the source Linode's configuration.
+ 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;
+ }
+
const { errors } = await yupResolver(
schema,
{},
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/schemas.ts b/packages/manager/src/features/Linodes/LinodeCreate/schemas.ts
index 2bc6cc15084..f51432af0fd 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/schemas.ts
+++ b/packages/manager/src/features/Linodes/LinodeCreate/schemas.ts
@@ -7,6 +7,7 @@ import { array, boolean, number, object, string } from 'yup';
import { CreateLinodeInterfaceFormSchema } from '../LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/utilities';
import type { LinodeCreateFormValues } from './utilities';
+import type { InterfaceGenerationType } from '@linode/api-v4/lib/linodes/types';
import type { ObjectSchema } from 'yup';
/**
@@ -17,6 +18,12 @@ import type { ObjectSchema } from 'yup';
export const CreateLinodeSchema: ObjectSchema =
BaseCreateLinodeSchema.concat(
object({
+ firewall_id: number().when('interface_generation', {
+ is: (value: InterfaceGenerationType) => value === 'legacy_config',
+ then: (schema) =>
+ schema.required('Select an option or create a new Firewall.'),
+ otherwise: (schema) => schema.nullable().notRequired(),
+ }),
firewallOverride: boolean(),
hasSignedEUAgreement: boolean(),
interfaces: array(ConfigProfileInterfaceSchema).required(),
diff --git a/packages/manager/src/features/Linodes/LinodeCreate/utilities.test.tsx b/packages/manager/src/features/Linodes/LinodeCreate/utilities.test.tsx
index 10ffa39e821..8dc0a441f17 100644
--- a/packages/manager/src/features/Linodes/LinodeCreate/utilities.test.tsx
+++ b/packages/manager/src/features/Linodes/LinodeCreate/utilities.test.tsx
@@ -489,6 +489,7 @@ describe('getDoesEmployeeNeedToAssignFirewall', () => {
vpc: null,
default_route: null,
vlan: null,
+ firewall_id: null,
},
],
'linode'
@@ -545,6 +546,7 @@ describe('getDoesEmployeeNeedToAssignFirewall', () => {
public: null,
default_route: null,
vlan: { vlan_label: 'my-vlan-1' },
+ firewall_id: null,
},
],
'linode'
@@ -565,6 +567,7 @@ describe('getDoesEmployeeNeedToAssignFirewall', () => {
vpc: null,
vlan: null,
default_route: null,
+ firewall_id: null,
},
{
vpc: null,
@@ -572,6 +575,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/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/AddInterfaceForm.tsx b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/AddInterfaceForm.tsx
index 82b2370844e..d51e8701512 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
@@ -45,7 +45,7 @@ export const AddInterfaceForm = (props: Props) => {
) ?? [];
const form = useForm({
defaultValues: {
- firewall_id: null,
+ firewall_id: undefined,
public: {},
vlan: {},
vpc: {
@@ -62,7 +62,14 @@ export const AddInterfaceForm = (props: Props) => {
const { errors, values } = await yupResolver(
CreateLinodeInterfaceFormSchema
- )(valuesWithOnlySelectedInterface, context, options);
+ )(
+ {
+ ...valuesWithOnlySelectedInterface,
+ firewall_id: valuesWithOnlySelectedInterface.firewall_id!,
+ },
+ context,
+ options
+ );
if (errors) {
return { errors, values };
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/AddInterfaceDrawer/utilities.ts b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/utilities.ts
index 9e3c41835f3..7f464ce0fba 100644
--- a/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/utilities.ts
+++ b/packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeInterfaces/AddInterfaceDrawer/utilities.ts
@@ -9,6 +9,9 @@ import type { InferType } from 'yup';
export const CreateLinodeInterfaceFormSchema =
CreateLinodeInterfaceSchema.concat(
object({
+ firewall_id: number().required(
+ 'Select an option or create a new Firewall.'
+ ),
purpose: string()
.oneOf(['vpc', 'vlan', 'public'])
.required('You must selected an Interface type.'),
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
+ }
/>
>
)}