Skip to content

Commit

Permalink
Merge pull request #11761 from linode/staging
Browse files Browse the repository at this point in the history
Release v1.137.2 - `staging` → `master`
  • Loading branch information
dwiley-akamai authored Feb 28, 2025
2 parents b5db23f + 7172f57 commit e91da44
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 19 deletions.
6 changes: 6 additions & 0 deletions packages/manager/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
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';
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', () => {
Expand Down Expand Up @@ -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;
});
});
});
});
});
2 changes: 1 addition & 1 deletion packages/manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
4 changes: 3 additions & 1 deletion packages/manager/src/components/AccessPanel/AccessPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,9 @@ export const AccessPanel = (props: Props) => {
linodeIsInDistributedRegion,
regionSupportsDiskEncryption,
})}
isEncryptEntityChecked={diskEncryptionEnabled ?? false}
isEncryptEntityChecked={
linodeIsInDistributedRegion || (diskEncryptionEnabled ?? false)
}
onChange={() => toggleDiskEncryptionEnabled()}
/>
</>
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/src/components/Encryption/Encryption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const Encryption = (props: EncryptionProps) => {
flexDirection="row"
>
<Checkbox
checked={disabled ? false : isEncryptEntityChecked} // in Create flows, this will be defaulted to be checked. Otherwise, we will rely on the current encryption status for the initial value
checked={isEncryptEntityChecked}
data-testid={checkboxTestId}
disabled={disabled}
onChange={(e, checked) => onChange(checked)}
Expand Down
23 changes: 14 additions & 9 deletions packages/manager/src/features/Linodes/LinodeCreate/Region.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -208,7 +213,7 @@ export const Region = React.memo(() => {
}

return (
<Paper>
<Paper data-qa-linode-region>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="h2">Region</Typography>
<DocsLink
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,10 @@ describe('Security', () => {
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'] });
Expand All @@ -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<LinodeCreateFormValues>({
component: <Security />,
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();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: <Summary />,
useFormOptions: { defaultValues: { region: region.id } },
});

await findByText('Encrypted');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export const Summary = () => {
item: {
title: 'Encrypted',
},
show: diskEncryption === 'enabled',
show: diskEncryption === 'enabled' || region?.site_type === 'distributed',
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const TwoStepRegion = (props: CombinedProps) => {
const { params } = useLinodeCreateQueryParams();

return (
<Paper data-testid="region">
<Paper data-testid="region" data-qa-linode-region>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="h2">Region</Typography>
<DocsLink
Expand Down

0 comments on commit e91da44

Please sign in to comment.