Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upcoming: [M3-9519] - Allow LKE-E IP ACL addresses to be optional with an explicit acknowledgement #11856

Merged
merged 13 commits into from
Mar 19, 2025
Merged
12 changes: 2 additions & 10 deletions packages/api-v4/src/kubernetes/kubernetes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {
createKubeClusterSchema,
createKubeEnterpriseClusterSchema,
} from '@linode/validation/lib/kubernetes.schema';
import { createKubeClusterSchema } from '@linode/validation/lib/kubernetes.schema';
import { API_ROOT, BETA_API_ROOT } from '../constants';
import Request, {
setData,
Expand Down Expand Up @@ -97,12 +94,7 @@ export const createKubernetesClusterBeta = (data: CreateKubeClusterPayload) => {
return Request<KubernetesCluster>(
setMethod('POST'),
setURL(`${BETA_API_ROOT}/lke/clusters`),
setData(
data,
data.tier === 'enterprise'
? createKubeEnterpriseClusterSchema
: createKubeClusterSchema
)
setData(data, createKubeClusterSchema)
);
};

Expand Down
254 changes: 238 additions & 16 deletions packages/manager/cypress/e2e/core/kubernetes/lke-create.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,223 @@ describe('LKE Cluster Creation with ACL', () => {
.should('be.enabled');
});

/**
* - Confirms create flow for LKE-E cluster with ACL enabled by default
* - Confirms at least one IP must be provided for ACL unless acknowledgement is checked
* - Confirms the cluster details page shows ACL is enabled
*/
it('creates an LKE cluster with ACL enabled by default and handles IP address validation', () => {
const clusterLabel = randomLabel();
const mockedEnterpriseCluster = kubernetesClusterFactory.build({
k8s_version: latestEnterpriseTierKubernetesVersion.id,
label: clusterLabel,
region: 'us-iad',
tier: 'enterprise',
});
const mockedEnterpriseClusterPools = [nanodeMemoryPool];
const mockACL = kubernetesControlPlaneACLFactory.build({
acl: {
addresses: {
ipv4: [],
ipv6: [],
},
enabled: true,
'revision-id': '',
},
});
mockGetControlPlaneACL(mockedEnterpriseCluster.id, mockACL).as(
'getControlPlaneACL'
);
mockGetAccount(
accountFactory.build({
capabilities: [
'Kubernetes Enterprise',
'LKE HA Control Planes',
'LKE Network Access Control List (IP ACL)',
],
})
).as('getAccount');
mockGetTieredKubernetesVersions('enterprise', [
latestEnterpriseTierKubernetesVersion,
]).as('getTieredKubernetesVersions');
mockGetKubernetesVersions([latestKubernetesVersion]).as(
'getKubernetesVersions'
);
mockGetLinodeTypes(mockedLKEClusterTypes).as('getLinodeTypes');
mockGetLKEClusterTypes(mockedLKEEnterprisePrices).as(
'getLKEEnterpriseClusterTypes'
);
mockGetRegions([
regionFactory.build({
capabilities: ['Linodes', 'Kubernetes', 'Kubernetes Enterprise'],
id: 'us-iad',
label: 'Washington, DC',
}),
]).as('getRegions');
mockGetCluster(mockedEnterpriseCluster).as('getCluster');
mockCreateCluster(mockedEnterpriseCluster).as('createCluster');
mockGetClusters([mockedEnterpriseCluster]).as('getClusters');
mockGetClusterPools(
mockedEnterpriseCluster.id,
mockedEnterpriseClusterPools
).as('getClusterPools');
mockGetDashboardUrl(mockedEnterpriseCluster.id).as('getDashboardUrl');
mockGetApiEndpoints(mockedEnterpriseCluster.id).as('getApiEndpoints');

cy.visitWithLogin('/kubernetes/clusters');
cy.wait(['@getAccount']);

ui.button
.findByTitle('Create Cluster')
.should('be.visible')
.should('be.enabled')
.click();

cy.url().should('endWith', '/kubernetes/create');
cy.wait(['@getKubernetesVersions', '@getTieredKubernetesVersions']);

// Select enterprise tier.
cy.get(`[data-qa-select-card-heading="LKE Enterprise"]`)
.closest('[data-qa-selection-card]')
.click();

cy.wait(['@getLKEEnterpriseClusterTypes', '@getRegions']);

// Select a supported region.
ui.regionSelect.find().clear().type('Washington, DC{enter}');

// Select an enterprise version.
ui.autocomplete
.findByLabel('Kubernetes Version')
.should('be.visible')
.click();

clusterPlans.forEach((clusterPlan) => {
const nodeCount = clusterPlan.nodeCount;
const planName = clusterPlan.planName;
// Click the right tab for the plan, and add a node pool with the desired
// number of nodes.
cy.findByText(clusterPlan.tab).should('be.visible').click();
const quantityInput = '[name="Quantity"]';
cy.findByText(planName)
.should('be.visible')
.closest('tr')
.within(() => {
cy.get(quantityInput).should('be.visible');
cy.get(quantityInput).click();
cy.get(quantityInput).type(`{selectall}${nodeCount}`);

ui.button
.findByTitle('Add')
.should('be.visible')
.should('be.enabled')
.click();
});
});

// Confirm ACL is enabled by default.
cy.contains('Control Plane ACL').should('be.visible');
ui.toggle
.find()
.should('have.attr', 'data-qa-toggle', 'true')
.should('be.visible');
cy.findByRole('checkbox', { name: /Provide an ACL later/ }).should(
'not.be.checked'
);

// Try to submit the form without the ACL acknowledgement checked.
cy.get('[data-testid="kube-checkout-bar"]')
.should('be.visible')
.within(() => {
ui.button
.findByTitle('Create Cluster')
.should('be.visible')
.should('be.enabled')
.click();
});

// Confirm error validation requires an ACL IP.
cy.findByText(
'At least one IP address or CIDR range is required for LKE Enterprise.'
).should('be.visible');

// Add an IP,
cy.findByLabelText('IPv4 Addresses or CIDRs ip-address-0')
.should('be.visible')
.click();
cy.focused().clear();
cy.focused().type('10.0.0.0/24');

cy.get('[data-testid="kube-checkout-bar"]')
.should('be.visible')
.within(() => {
// Try to submit the form again.
ui.button
.findByTitle('Create Cluster')
.should('be.visible')
.should('be.enabled')
.click();
});

// Confirm the validation message is gone.
cy.findByText(
'At least one IP address or CIDR range is required for LKE Enterprise.'
).should('not.exist');

// Check the acknowledgement to prevent IP validation.
cy.findByRole('checkbox', { name: /Provide an ACL later/ }).check();

// Clear the IP address field and check the acknowledgement to confirm the form can now submit without IP address validation.
cy.findByLabelText('IPv4 Addresses or CIDRs ip-address-0')
.should('be.visible')
.click();
cy.focused().clear();
cy.findByRole('checkbox', { name: /Provide an ACL later/ }).check();

// Finally, add a label, so the form will submit.
cy.findByLabelText('Cluster Label').should('be.visible').click();
cy.focused().type(`${clusterLabel}{enter}`);

cy.get('[data-testid="kube-checkout-bar"]')
.should('be.visible')
.within(() => {
// Try to submit the form.
ui.button
.findByTitle('Create Cluster')
.should('be.visible')
.should('be.enabled')
.click();
});

// Confirm the validation message is gone.
cy.findByText(
'At least one IP address or CIDR range is required for LKE Enterprise.'
).should('not.exist');

cy.wait([
'@getCluster',
'@getClusterPools',
'@createCluster',
'@getLKEEnterpriseClusterTypes',
'@getLinodeTypes',
'@getDashboardUrl',
'@getApiEndpoints',
'@getControlPlaneACL',
]);

cy.url().should(
'endWith',
`/kubernetes/clusters/${mockedEnterpriseCluster.id}/summary`
);

// Confirms Summary panel displays as expected
cy.contains('Control Plane ACL').should('be.visible');
ui.button
.findByTitle('Enabled (0 IP Addresses)')
.should('be.visible')
.should('be.enabled');
});

/**
* - Confirms IP validation error appears when a bad IP is entered
* - Confirms IP validation error disappears when a valid IP is entered
Expand Down Expand Up @@ -1089,7 +1306,7 @@ describe('LKE Cluster Creation with LKE-E', () => {
* - Confirms an LKE-E supported region can be selected
* - Confirms an LKE-E supported k8 version can be selected
* - Confirms the APL section is disabled while it remains unsupported
* - Confirms at least one IP must be provided for ACL
* - Confirms ACL is enabled by default
* - Confirms the checkout bar displays the correct LKE-E info
* - Confirms an enterprise cluster can be created with the correct chip, version, and price
* - Confirms that the total node count for each pool is displayed
Expand All @@ -1103,7 +1320,20 @@ describe('LKE Cluster Creation with LKE-E', () => {
tier: 'enterprise',
});
const mockedEnterpriseClusterPools = [nanodeMemoryPool, dedicatedCpuPool];
const mockACL = kubernetesControlPlaneACLFactory.build({
acl: {
addresses: {
ipv4: ['10.0.0.0/24'],
ipv6: [],
},
enabled: true,
'revision-id': '',
},
});

mockGetControlPlaneACL(mockedEnterpriseCluster.id, mockACL).as(
'getControlPlaneACL'
);
mockGetAccountBeta(
accountBetaFactory.build({
id: 'apl',
Expand Down Expand Up @@ -1287,19 +1517,14 @@ describe('LKE Cluster Creation with LKE-E', () => {
cy.findByText('Linode 2 GB Plan').should('be.visible');
cy.findByText('$15.00').should('be.visible');
cy.findByText('$459.00').should('be.visible');

// Try to submit the form
ui.button
.findByTitle('Create Cluster')
.should('be.visible')
.should('be.enabled')
.click();
});

// Confirm error validation requires an ACL IP
cy.findByText(
'At least one IP address or CIDR range is required for LKE Enterprise.'
).should('be.visible');
// Confirms ACL is enabled by default.
cy.contains('Control Plane ACL').should('be.visible');
ui.toggle
.find()
.should('have.attr', 'data-qa-toggle', 'true')
.should('be.visible');

// Add an IP
cy.findByLabelText('IPv4 Addresses or CIDRs ip-address-0')
Expand All @@ -1319,10 +1544,6 @@ describe('LKE Cluster Creation with LKE-E', () => {
.click();
});

cy.findByText(
'At least one IP address or CIDR range is required for LKE Enterprise.'
).should('not.exist');

// Wait for LKE cluster to be created and confirm that we are redirected
// to the cluster summary page.
cy.wait([
Expand All @@ -1333,6 +1554,7 @@ describe('LKE Cluster Creation with LKE-E', () => {
'@getLinodeTypes',
'@getDashboardUrl',
'@getApiEndpoints',
'@getControlPlaneACL',
]);

cy.url().should(
Expand Down
Loading