diff --git a/packages/manager/CHANGELOG.md b/packages/manager/CHANGELOG.md index 867ad0a063e..c422e9236ae 100644 --- a/packages/manager/CHANGELOG.md +++ b/packages/manager/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [2025-02-27] - v1.137.2 + +### Fixed: + +- Disk Encryption logic preventing Linode deployment in distributed regions ([#11760](https://github.com/linode/manager/pull/11760) + ## [2025-02-25] - v1.137.1 ### Fixed: 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 08d178ccd06..72ac16d81a2 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 @@ -1,6 +1,14 @@ import { ui } from 'support/ui'; -import { accountFactory, regionFactory } from '@src/factories'; -import { mockGetRegions } from 'support/intercepts/regions'; +import { + linodeFactory, + accountFactory, + linodeTypeFactory, + regionFactory, +} from '@src/factories'; +import { + mockGetRegionAvailability, + mockGetRegions, +} from 'support/intercepts/regions'; import { mockGetAccount } from 'support/intercepts/account'; import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; import { makeFeatureFlagData } from 'support/util/feature-flags'; @@ -8,6 +16,14 @@ import { checkboxTestId, headerTestId, } from 'src/components/Encryption/constants'; +import { extendRegion } from 'support/util/regions'; +import { linodeCreatePage } from 'support/ui/pages'; +import { + mockCreateLinode, + mockGetLinodeTypes, +} from 'support/intercepts/linodes'; +import { randomLabel, randomString } from 'support/util/random'; +import type { Region } from '@linode/api-v4'; describe('Create Linode with Disk Encryption', () => { it('should not have a "Disk Encryption" section visible if the feature flag is off and user does not have capability', () => { @@ -77,4 +93,123 @@ describe('Create Linode with Disk Encryption', () => { cy.get(`[data-testid="${checkboxTestId}"]`).should('be.enabled'); }); + + // Confirm Linode Disk Encryption features when using Distributed Regions. + describe('Distributed regions', () => { + const encryptionTooltipMessage = + 'Distributed Compute Instances are encrypted. This setting can not be changed.'; + + const mockDistributedRegionWithoutCapability = regionFactory.build({ + capabilities: [ + 'Linodes', + 'Cloud Firewall', + 'Distributed Plans', + 'Placement Group', + ], + site_type: 'distributed', + }); + + const mockDistributedRegionWithCapability = regionFactory.build({ + capabilities: [ + 'Linodes', + 'Cloud Firewall', + 'Distributed Plans', + 'Placement Group', + 'Disk Encryption', + ], + site_type: 'distributed', + }); + + const mockDistributedRegions: Region[] = [ + mockDistributedRegionWithCapability, + mockDistributedRegionWithoutCapability, + ]; + + const mockLinodeType = linodeTypeFactory.build({ + id: 'nanode-edge-1', + label: 'Nanode 1GB', + class: 'nanode', + }); + + /* + * Right now there's some ambiguity over the 'Disk Encryption' capability + * and whether it's expected to be present for Distributed Regions. We'll + * test Cloud against both scenarios -- when distributed regions do and do + * not have the capability -- and confirm that the Linode Create flow works + * as expected in both cases. + */ + mockDistributedRegions.forEach((distributedRegion) => { + const suffix = distributedRegion.capabilities.includes('Disk Encryption') + ? '(with region capability)' + : '(without region capability)'; + + /* + * - Confirms that disk encryption works as expected for distributed regions. Specifically: + * - Encrypted checkbox is always checked, is disabled, and therefore cannot be changed. + * - Outgoing Linode create API request payload does NOT contain encryption property. + */ + it(`creates a Linode with Disk Encryption in a distributed region ${suffix}`, () => { + const mockRegions = [distributedRegion]; + const mockLinode = linodeFactory.build({ + label: randomLabel(), + region: distributedRegion.id, + }); + + mockAppendFeatureFlags({ + gecko2: { + enabled: true, + }, + }); + + mockGetRegions(mockRegions); + mockGetLinodeTypes([mockLinodeType]); + mockGetRegionAvailability(distributedRegion.id, []); + mockCreateLinode(mockLinode).as('createLinode'); + cy.visitWithLogin('/linodes/create'); + + cy.get('[data-qa-linode-region]').within(() => { + ui.tabList.find().within(() => { + cy.findByText('Distributed').click(); + }); + + cy.findByLabelText('Region').type(distributedRegion.label); + ui.regionSelect + .findItemByRegionLabel( + extendRegion(distributedRegion).label, + mockRegions + ) + .click(); + }); + + linodeCreatePage.setLabel(mockLinode.label); + linodeCreatePage.setRootPassword(randomString(32)); + + // Select mock Nanode plan type. + cy.get('[data-qa-plan-row="Nanode 1 GB"]').click(); + + cy.findByLabelText('Encrypt Disk') + .should('be.disabled') + .should('be.checked'); + + cy.findByLabelText(encryptionTooltipMessage).click(); + ui.tooltip.findByText(encryptionTooltipMessage).should('be.visible'); + + // Click "Create Linode" and confirm outgoing API request payload. + ui.button + .findByTitle('Create Linode') + .should('be.visible') + .should('be.enabled') + .click(); + + // Submit form to create Linode and confirm that outgoing API request + // contains expected user data. + cy.wait('@createLinode').then((xhr) => { + const requestPayload = xhr.request.body; + const regionId = requestPayload['region']; + expect(regionId).to.equal(mockLinode.region); + expect(requestPayload['disk_encryption']).to.be.undefined; + }); + }); + }); + }); }); diff --git a/packages/manager/package.json b/packages/manager/package.json index 0ccb21757a3..2f7f2218c2b 100644 --- a/packages/manager/package.json +++ b/packages/manager/package.json @@ -2,7 +2,7 @@ "name": "linode-manager", "author": "Linode", "description": "The Linode Manager website", - "version": "1.137.1", + "version": "1.137.2", "private": true, "type": "module", "bugs": { diff --git a/packages/manager/src/components/AccessPanel/AccessPanel.tsx b/packages/manager/src/components/AccessPanel/AccessPanel.tsx index 0f87f6e16c9..3a9bf425801 100644 --- a/packages/manager/src/components/AccessPanel/AccessPanel.tsx +++ b/packages/manager/src/components/AccessPanel/AccessPanel.tsx @@ -218,7 +218,9 @@ export const AccessPanel = (props: Props) => { linodeIsInDistributedRegion, regionSupportsDiskEncryption, })} - isEncryptEntityChecked={diskEncryptionEnabled ?? false} + isEncryptEntityChecked={ + linodeIsInDistributedRegion || (diskEncryptionEnabled ?? false) + } onChange={() => toggleDiskEncryptionEnabled()} /> diff --git a/packages/manager/src/components/Encryption/Encryption.tsx b/packages/manager/src/components/Encryption/Encryption.tsx index 1b90722ce39..84daa821289 100644 --- a/packages/manager/src/components/Encryption/Encryption.tsx +++ b/packages/manager/src/components/Encryption/Encryption.tsx @@ -72,7 +72,7 @@ export const Encryption = (props: EncryptionProps) => { flexDirection="row" > onChange(checked)} diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Region.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Region.tsx index 73fa3c83fd3..decfebeb1eb 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Region.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Region.tsx @@ -139,14 +139,19 @@ export const Region = React.memo(() => { } if (isDiskEncryptionFeatureEnabled) { - // Enable disk encryption by default if the region supports it - const defaultDiskEncryptionValue = region.capabilities.includes( - 'Disk Encryption' - ) - ? 'enabled' - : undefined; - - setValue('disk_encryption', defaultDiskEncryptionValue); + if (region.site_type === 'distributed') { + // If a distributed region is selected, make sure we don't send disk_encryption in the payload. + setValue('disk_encryption', undefined); + } else { + // Enable disk encryption by default if the region supports it + const defaultDiskEncryptionValue = region.capabilities.includes( + 'Disk Encryption' + ) + ? 'enabled' + : undefined; + + setValue('disk_encryption', defaultDiskEncryptionValue); + } } if (!isLabelFieldDirty) { @@ -208,7 +213,7 @@ export const Region = React.memo(() => { } return ( - + Region { expect(heading.tagName).toBe('H3'); }); - it('should disable disk encryption if the selected region does not support it', async () => { + it('should disable disk encryption if the selected core region does not support it', async () => { const region = regionFactory.build({ capabilities: [], + site_type: 'core', }); const account = accountFactory.build({ capabilities: ['Disk Encryption'] }); @@ -158,4 +159,40 @@ describe('Security', () => { 'Disk encryption is not available in the selected region. Select another region to use Disk Encryption.' ); }); + + it('should disable the disk encryption checkbox (but show it as enabled) if the selected region is a distributed region', async () => { + const region = regionFactory.build({ + capabilities: ['Disk Encryption'], + site_type: 'distributed', + }); + + const account = accountFactory.build({ capabilities: ['Disk Encryption'] }); + + server.use( + http.get('*/v4/account', () => { + return HttpResponse.json(account); + }), + http.get('*/v4/regions', () => { + return HttpResponse.json(makeResourcePage([region])); + }) + ); + + const { + findByLabelText, + getByLabelText, + } = renderWithThemeAndHookFormContext({ + component: , + options: { flags: { linodeDiskEncryption: true } }, + useFormOptions: { defaultValues: { region: region.id } }, + }); + + await findByLabelText( + 'Distributed Compute Instances are encrypted. This setting can not be changed.' + ); + + const checkbox = getByLabelText('Encrypt Disk'); + + expect(checkbox).toBeChecked(); + expect(checkbox).toBeDisabled(); + }); }); diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Security.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Security.tsx index 2a5c39c0f20..eb36828828c 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Security.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Security.tsx @@ -107,12 +107,14 @@ export const Security = () => { ? DISK_ENCRYPTION_DEFAULT_DISTRIBUTED_INSTANCES : DISK_ENCRYPTION_UNAVAILABLE_IN_REGION_COPY } + isEncryptEntityChecked={ + isDistributedRegion || field.value === 'enabled' + } onChange={(checked) => field.onChange(checked ? 'enabled' : 'disabled') } - disabled={!regionSupportsDiskEncryption} + disabled={isDistributedRegion || !regionSupportsDiskEncryption} error={fieldState.error?.message} - isEncryptEntityChecked={field.value === 'enabled'} /> )} control={control} diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.test.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.test.tsx index dc151f343a1..156dad3ed8d 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.test.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.test.tsx @@ -264,4 +264,21 @@ describe('Linode Create Summary', () => { await findByText(`5 Nodes - $10/month $2.50/hr`); }); + + it('should render "Encrypted" if a distributed region is selected', async () => { + const region = regionFactory.build({ site_type: 'distributed' }); + + server.use( + http.get('*/v4/regions', () => { + return HttpResponse.json(makeResourcePage([region])); + }) + ); + + const { findByText } = renderWithThemeAndHookFormContext({ + component: , + useFormOptions: { defaultValues: { region: region.id } }, + }); + + await findByText('Encrypted'); + }); }); diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.tsx index 8d3eead884a..c06fdeaecc6 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Summary/Summary.tsx @@ -125,7 +125,7 @@ export const Summary = () => { item: { title: 'Encrypted', }, - show: diskEncryption === 'enabled', + show: diskEncryption === 'enabled' || region?.site_type === 'distributed', }, ]; diff --git a/packages/manager/src/features/Linodes/LinodeCreate/TwoStepRegion.tsx b/packages/manager/src/features/Linodes/LinodeCreate/TwoStepRegion.tsx index b5d001191c5..61030500e43 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/TwoStepRegion.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/TwoStepRegion.tsx @@ -75,7 +75,7 @@ export const TwoStepRegion = (props: CombinedProps) => { const { params } = useLinodeCreateQueryParams(); return ( - + Region