diff --git a/packages/api-v4/src/object-storage/buckets.ts b/packages/api-v4/src/object-storage/buckets.ts index 20e71533d77..2007df79c11 100644 --- a/packages/api-v4/src/object-storage/buckets.ts +++ b/packages/api-v4/src/object-storage/buckets.ts @@ -37,12 +37,12 @@ import type { * * Get one Object Storage Bucket. */ -export const getBucket = (clusterId: string, bucketName: string) => +export const getBucket = (regionId: string, bucketName: string) => Request( setMethod('GET'), setURL( `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - clusterId, + regionId, )}/${encodeURIComponent(bucketName)}`, ), ); @@ -84,7 +84,7 @@ export const getBucketsInRegion = ( * * Creates a new Bucket on your account. * - * @param data { object } The label and clusterId of the new Bucket. + * @param data { object } The label and region of the new Bucket. * */ export const createBucket = (data: CreateObjectStorageBucketPayload) => @@ -102,42 +102,13 @@ export const createBucket = (data: CreateObjectStorageBucketPayload) => * NOTE: Attempting to delete a non-empty bucket will result in an error. */ export const deleteBucket = ({ - cluster, + regionId, label, }: DeleteObjectStorageBucketPayload) => Request( setURL( `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - cluster, - )}/${encodeURIComponent(label)}`, - ), - setMethod('DELETE'), - ); - -/** - * deleteBucketWithRegion - * - * Removes a Bucket from your account with region. - * - * NOTE: Attempting to delete a non-empty bucket will result in an error. - */ -/* - @TODO OBJ Multicluster: deleteBucketWithRegion is a function, - once feature is rolled out we replace it with existing deleteBucket - by updating it with region instead of cluster. - */ - -export const deleteBucketWithRegion = ({ - region, - label, -}: { - label: string; - region: string; -}) => - Request( - setURL( - `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - region, + regionId, )}/${encodeURIComponent(label)}`, ), setMethod('DELETE'), @@ -147,7 +118,7 @@ export const deleteBucketWithRegion = ({ * Returns a list of Objects in a given Bucket. */ export const getObjectList = ({ - clusterId, + regionId, bucket: bucketName, params, }: GetObjectStorageObjectListPayload) => @@ -156,7 +127,7 @@ export const getObjectList = ({ setParams(params), setURL( `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - clusterId, + regionId, )}/${encodeURIComponent(bucketName)}/object-list`, ), ); @@ -165,7 +136,7 @@ export const getObjectList = ({ * uploadSSLCert */ export const uploadSSLCert = ( - clusterId: string, + regionId: string, bucketName: string, data: CreateObjectStorageBucketSSLPayload, ) => @@ -174,7 +145,7 @@ export const uploadSSLCert = ( setData(data, UploadCertificateSchema), setURL( `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - clusterId, + regionId, )}/${encodeURIComponent(bucketName)}/ssl`, ), ); @@ -185,12 +156,12 @@ export const uploadSSLCert = ( * Returns { ssl: true } if there is an SSL certificate available for * the specified bucket, { ssl: false } otherwise. */ -export const getSSLCert = (clusterId: string, bucketName: string) => +export const getSSLCert = (regionId: string, bucketName: string) => Request( setMethod('GET'), setURL( `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - clusterId, + regionId, )}/${encodeURIComponent(bucketName)}/ssl`, ), ); @@ -202,12 +173,12 @@ export const getSSLCert = (clusterId: string, bucketName: string) => * removed automatically when a bucket is deleted; this endpoint is only * for removing certs without altering the bucket. */ -export const deleteSSLCert = (clusterId: string, bucketName: string) => +export const deleteSSLCert = (regionId: string, bucketName: string) => Request<{}>( setMethod('DELETE'), setURL( `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - clusterId, + regionId, )}/${encodeURIComponent(bucketName)}/ssl`, ), ); @@ -217,12 +188,12 @@ export const deleteSSLCert = (clusterId: string, bucketName: string) => * * Returns access information (ACL, CORS) for a given Bucket. */ -export const getBucketAccess = (clusterId: string, bucketName: string) => +export const getBucketAccess = (regionId: string, bucketName: string) => Request( setMethod('GET'), setURL( `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - clusterId, + regionId, )}/${encodeURIComponent(bucketName)}/access`, ), ); @@ -233,7 +204,7 @@ export const getBucketAccess = (clusterId: string, bucketName: string) => * Updates access information (ACL, CORS) for a given Bucket. */ export const updateBucketAccess = ( - clusterId: string, + regionId: string, bucketName: string, params: UpdateObjectStorageBucketAccessPayload, ) => @@ -241,7 +212,7 @@ export const updateBucketAccess = ( setMethod('PUT'), setURL( `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - clusterId, + regionId, )}/${encodeURIComponent(bucketName)}/access`, ), setData(params, UpdateBucketAccessSchema), diff --git a/packages/api-v4/src/object-storage/clusters.ts b/packages/api-v4/src/object-storage/clusters.ts deleted file mode 100644 index 091764107ae..00000000000 --- a/packages/api-v4/src/object-storage/clusters.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { API_ROOT } from '../constants'; -import Request, { setMethod, setParams, setURL, setXFilter } from '../request'; - -import type { Filter, ResourcePage as Page, Params } from '../types'; -import type { ObjectStorageCluster } from './types'; - -/** - * @deprecated This method returns legacy clusterId values representing regions - * used in older API versions. It is maintained for backward compatibility only. - * Please use the "getRegions" endpoint instead for up-to-date information. - */ -export const getClusters = (params?: Params, filters?: Filter) => - Request>( - setMethod('GET'), - setParams(params), - setXFilter(filters), - setURL(`${API_ROOT}/object-storage/clusters`), - ); diff --git a/packages/api-v4/src/object-storage/index.ts b/packages/api-v4/src/object-storage/index.ts index 36e29f03304..9e266dd1edc 100644 --- a/packages/api-v4/src/object-storage/index.ts +++ b/packages/api-v4/src/object-storage/index.ts @@ -2,8 +2,6 @@ export * from './account'; export * from './buckets'; -export * from './clusters'; - export * from './objects'; export * from './objectStorageKeys'; diff --git a/packages/api-v4/src/object-storage/objects.ts b/packages/api-v4/src/object-storage/objects.ts index 33a105d9623..e60cde16548 100644 --- a/packages/api-v4/src/object-storage/objects.ts +++ b/packages/api-v4/src/object-storage/objects.ts @@ -15,8 +15,8 @@ import type { * HTTP method in your request body's method parameter. */ export const getObjectURL = ( - clusterId: string, - bucketName: string, + regionId: string, + bucket: string, name: string, method: 'DELETE' | 'GET' | 'POST' | 'PUT', options?: CreateObjectStorageObjectURLPayload, @@ -25,8 +25,8 @@ export const getObjectURL = ( setMethod('POST'), setURL( `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - clusterId, - )}/${encodeURIComponent(bucketName)}/object-url`, + regionId, + )}/${encodeURIComponent(bucket)}/object-url`, ), setData({ name, method, ...options }), ); @@ -38,7 +38,7 @@ export const getObjectURL = ( * Gets the ACL for a given Object. */ export const getObjectACL = ({ - clusterId, + regionId, bucket, params, }: GetObjectStorageACLPayload) => @@ -46,7 +46,7 @@ export const getObjectACL = ({ setMethod('GET'), setURL( `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - clusterId, + regionId, )}/${encodeURIComponent(bucket)}/object-acl?name=${encodeURIComponent( params.name, )}`, @@ -60,7 +60,7 @@ export const getObjectACL = ({ * Updates the ACL for a given Object. */ export const updateObjectACL = ( - clusterId: string, + regionId: string, bucketName: string, name: string, acl: Omit, @@ -69,7 +69,7 @@ export const updateObjectACL = ( setMethod('PUT'), setURL( `${API_ROOT}/object-storage/buckets/${encodeURIComponent( - clusterId, + regionId, )}/${encodeURIComponent(bucketName)}/object-acl`, ), setData({ acl, name }), diff --git a/packages/api-v4/src/object-storage/types.ts b/packages/api-v4/src/object-storage/types.ts index 0bb6fd6b507..e0ac7c2d4ef 100644 --- a/packages/api-v4/src/object-storage/types.ts +++ b/packages/api-v4/src/object-storage/types.ts @@ -55,7 +55,7 @@ export interface ObjectStorageKeyBucketAccess { bucket_name: string; cluster: string; permissions: ObjectStorageKeyBucketAccessPermissions; - region?: string; // @TODO OBJ Multicluster: Remove optional indicator when API changes get released to prod + region: string; } export interface CreateObjectStorageKeyPayload { @@ -71,30 +71,21 @@ export interface UpdateObjectStorageKeyPayload { export interface CreateObjectStorageBucketPayload { acl?: 'authenticated-read' | 'private' | 'public-read' | 'public-read-write'; - cluster?: string; cors_enabled?: boolean; /** * To explicitly create a bucket on a specific endpoint type. */ endpoint_type?: ObjectStorageEndpointTypes; label: string; - region?: string; + region: string; /** * Used to create a bucket on a specific already-assigned S3 endpoint. */ s3_endpoint?: string; - /* - @TODO OBJ Multicluster: 'region' will become required, and the 'cluster' field will be deprecated - once the feature is fully rolled out in production as part of the process of cleaning up the 'objMultiCluster' - feature flag. - - Until then, the API will accept either cluster or region, or both (provided they are the same value). - The payload requires at least one of them though, which will be enforced via validation. - */ } export interface DeleteObjectStorageBucketPayload { - cluster: string; + regionId: string; label: string; } @@ -168,7 +159,7 @@ export interface ObjectStorageCluster { export interface GetObjectStorageObjectListPayload { bucket: string; - clusterId: string; + regionId: string; params?: ObjectStorageObjectListParams; } @@ -202,7 +193,7 @@ export interface UpdateObjectStorageBucketAccessPayload { export interface GetObjectStorageACLPayload { bucket: string; - clusterId: string; + regionId: string; params: { name: string; }; diff --git a/packages/manager/cypress.config.ts b/packages/manager/cypress.config.ts index c122e309fd2..59cab91a940 100644 --- a/packages/manager/cypress.config.ts +++ b/packages/manager/cypress.config.ts @@ -9,7 +9,6 @@ import { configureMultiReporters } from './cypress/support/plugins/configure-mul import { discardPassedTestRecordings } from './cypress/support/plugins/discard-passed-test-recordings'; import { featureFlagOverrides } from './cypress/support/plugins/feature-flag-override'; import { fetchAccount } from './cypress/support/plugins/fetch-account'; -import { fetchLinodeClusters } from './cypress/support/plugins/fetch-linode-clusters'; import { fetchLinodeImages } from './cypress/support/plugins/fetch-linode-images'; import { fetchLinodeRegions } from './cypress/support/plugins/fetch-linode-regions'; import { generateTestWeights } from './cypress/support/plugins/generate-weights'; @@ -102,7 +101,6 @@ export default defineConfig({ discardPassedTestRecordings, fetchAccount, fetchLinodeRegions, - fetchLinodeClusters, fetchLinodeImages, resetUserPreferences, regionOverrideCheck, diff --git a/packages/manager/cypress/e2e/core/objectStorage/object-storage.smoke.spec.ts b/packages/manager/cypress/e2e/core/objectStorage/object-storage.smoke.spec.ts index c11d6fd890f..c25f53bd498 100644 --- a/packages/manager/cypress/e2e/core/objectStorage/object-storage.smoke.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorage/object-storage.smoke.spec.ts @@ -8,7 +8,7 @@ import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags'; import { mockCreateBucket, mockDeleteBucket, - mockDeleteBucketObject, + mockCreateObjectUrl, mockDeleteBucketObjectS3, mockGetBucketObjects, mockGetBuckets, @@ -142,7 +142,7 @@ describe('object storage smoke tests', () => { bucketContents.forEach((bucketFile) => { const filename = bucketFile.split('/')[1]; - mockDeleteBucketObject(bucketLabel, bucketCluster, filename).as( + mockCreateObjectUrl(bucketLabel, bucketCluster, filename).as( 'deleteBucketObject' ); mockDeleteBucketObjectS3(bucketLabel, bucketCluster, filename).as( diff --git a/packages/manager/cypress/support/api/objectStorage.ts b/packages/manager/cypress/support/api/objectStorage.ts index eead9dbdc93..42955f8a24b 100644 --- a/packages/manager/cypress/support/api/objectStorage.ts +++ b/packages/manager/cypress/support/api/objectStorage.ts @@ -44,7 +44,7 @@ export const deleteAllTestBucketObjects = async ( // @TODO Improve object retrieval to account for pagination for buckets with many objects. const storageObjects = await getObjectList({ bucket: bucketLabel, - clusterId, + regionId: clusterId, }); const storageObjectDeletePromises = storageObjects.data.map( async (storageObject: ObjectStorageObject) => { @@ -104,7 +104,7 @@ export const deleteAllTestBuckets = async () => { bucket.label ); return deleteBucket({ - cluster: bucket.region || bucket.cluster, + regionId: bucket.region || bucket.cluster, label: bucket.label, }); } diff --git a/packages/manager/cypress/support/intercepts/object-storage.ts b/packages/manager/cypress/support/intercepts/object-storage.ts index 3064e25b892..00d10c1739b 100644 --- a/packages/manager/cypress/support/intercepts/object-storage.ts +++ b/packages/manager/cypress/support/intercepts/object-storage.ts @@ -15,7 +15,6 @@ import type { CreateObjectStorageBucketPayload, ObjectStorageBucket, ObjectStorageBucketAccess, - ObjectStorageCluster, ObjectStorageEndpoint, ObjectStorageKey, PriceType, @@ -97,6 +96,7 @@ export const mockGetBucketsForRegion = ( /** * Intercepts POST request to create a bucket and mocks an error response. * + * @param regionId - Region for which to mock buckets. * @param errorMessage - Optional error message with which to mock response. * @param statusCode - HTTP status code with which to mock response. * @@ -167,34 +167,34 @@ export const mockCreateBucketError = ( /** * Intercepts DELETE request to delete bucket. * - * If a bucket label and cluster are provided, only requests to delete the - * given bucket in the given cluster are intercepted. + * If a bucket label and regionId are provided, only requests to delete the + * given bucket in the given regionId are intercepted. * - * If only a cluster is provided, only requests to delete buckets in the - * given cluster are intercepted. + * If only a regionId is provided, only requests to delete buckets in the + * given regionId are intercepted. * - * If no cluster or label are provided, all requests to delete buckets are + * If no regionId or label are provided, all requests to delete buckets are * intercepted. * * @param label - Optional label for bucket deletion to intercept. - * @param cluster - Optional cluster for bucket deletion to intercept. + * @param regionId - Optional regionId for bucket deletion to intercept. * * @returns Cypress chainable. */ export const interceptDeleteBucket = ( label?: string, - cluster?: string + regionId?: string ): Cypress.Chainable => { - if (label && cluster) { + if (label && regionId) { return cy.intercept( 'DELETE', - apiMatcher(`object-storage/buckets/${cluster}/${label}`) + apiMatcher(`object-storage/buckets/${regionId}/${label}`) ); } - if (cluster) { + if (regionId) { return cy.intercept( 'DELETE', - apiMatcher(`object-storage/buckets/${cluster}/*`) + apiMatcher(`object-storage/buckets/${regionId}/*`) ); } return cy.intercept('DELETE', apiMatcher('object-storage/buckets/*')); @@ -204,18 +204,19 @@ export const interceptDeleteBucket = ( * Intercepts DELETE request to delete bucket and mocks response. * * @param label - Object storage bucket label. - * @param cluster - Object storage bucket cluster. + * @param regionId - Region with Object Storage capability. + * @param statusCode - HTTP status code with which to mock response, 200 as default. * * @returns Cypress chainable. */ export const mockDeleteBucket = ( label: string, - cluster: string, + regionId: string, statusCode: number = 200 ): Cypress.Chainable => { return cy.intercept( 'DELETE', - apiMatcher(`object-storage/buckets/${cluster}/${label}`), + apiMatcher(`object-storage/buckets/${regionId}/${label}`), { body: {}, statusCode, @@ -227,7 +228,7 @@ export const mockDeleteBucket = ( * Intercepts GET request to fetch bucket objects and mocks response. * * @param label - Object storage bucket label. - * @param cluster - Object storage bucket cluster. + * @param regionId - Region with Object Storage capability. * @param data - Mocked response data. * @param statusCode - Mocked response status code. * @@ -235,14 +236,14 @@ export const mockDeleteBucket = ( */ export const mockGetBucketObjects = ( label: string, - cluster: string, + regionId: string, data: any, statusCode: number = 200 ): Cypress.Chainable => { return cy.intercept( 'GET', apiMatcher( - `object-storage/buckets/${cluster}/${label}/object-list?delimiter=%2F&prefix=` + `object-storage/buckets/${regionId}/${label}/object-list?delimiter=%2F&prefix=` ), { body: { @@ -262,7 +263,7 @@ export const mockGetBucketObjects = ( * is mocked. * * @param label - Object storage bucket label. - * @param cluster - Object storage bucket cluster. + * @param regionId - Region with Object Storage capability. * @param filename - Mocked response object filename. * @param data - Optional mocked response data. * @param statusCode - Opiontal mocked response status code. @@ -271,7 +272,7 @@ export const mockGetBucketObjects = ( */ export const mockUploadBucketObject = ( label: string, - cluster: string, + regionId: string, filename: string, data?: any, statusCode: number = 200 @@ -279,14 +280,14 @@ export const mockUploadBucketObject = ( const mockResponse = { body: data || { exists: false, - url: `https://${cluster}.linodeobjects.com:443/${label}/${filename}`, + url: `https://${regionId}.linodeobjects.com:443/${label}/${filename}`, }, statusCode, }; return cy.intercept( 'POST', - apiMatcher(`object-storage/buckets/${cluster}/${label}/object-url`), + apiMatcher(`object-storage/buckets/${regionId}/${label}/object-url`), mockResponse ); }; @@ -295,7 +296,7 @@ export const mockUploadBucketObject = ( * Intercepts S3 PUT request to upload bucket object. * * @param label - Object storage bucket label. - * @param domain - Object storage bucket cluster. + * @param domain - Object storage domain. * @param filename - Object filename. * * @returns Cypress chainable. @@ -312,7 +313,7 @@ export const interceptUploadBucketObjectS3 = ( * Intercepts S3 PUT request to upload bucket object and mocks response. * * @param label - Object storage bucket label. - * @param cluster - Object storage bucket cluster. + * @param cluster - Region with Object Storage capability. * @param filename - Object filename. * * @returns Cypress chainable. @@ -330,25 +331,25 @@ export const mockUploadBucketObjectS3 = ( }; /** - * Intercepts POST request to delete bucket object and mocks response. + * Intercepts POST request to create an object URL and mocks response. * * @param label - Object storage bucket label. - * @param cluster - Object storage bucket cluster. + * @param regionId - Region with Object Storage capability. * @param filename - Object filename. * * @returns Cypress chainable. */ -export const mockDeleteBucketObject = ( +export const mockCreateObjectUrl = ( label: string, - cluster: string, + regionId: string, filename: string ): Cypress.Chainable => { return cy.intercept( 'POST', - apiMatcher(`object-storage/buckets/${cluster}/${label}/object-url`), + apiMatcher(`object-storage/buckets/${regionId}/${label}/object-url`), { exists: true, - url: `https://${cluster}.linodeobjects.com:443/${label}/${filename}`, + url: `https://${regionId}-1.linodeobjects.com:443/${label}/${filename}`, } ); }; @@ -357,7 +358,7 @@ export const mockDeleteBucketObject = ( * Intercepts S3 DELETE request to delete bucket object and mocks response. * * @param label - Object storage bucket label. - * @param cluster - Object storage bucket cluster. + * @param cluster - Region with Object Storage capability. * @param filename - Object filename. * @param status - Response status. * @@ -390,7 +391,7 @@ export const interceptGetAccessKeys = (): Cypress.Chainable => { /** * Intercepts GET request to fetch object storage access keys, and mocks response. * - * @param response - Mocked response. + * @param accessKeys - Mocked response. * * @returns Cypress chainable. */ @@ -470,38 +471,21 @@ export const mockCancelObjectStorage = (): Cypress.Chainable => { return cy.intercept('POST', apiMatcher('object-storage/cancel'), {}); }; -/** - * Intercepts GET request to fetch Object Storage clusters and mocks response. - * - * @param clusters - Clusters with which to mock response. - * - * @returns Cypress chainable. - */ -export const mockGetClusters = ( - clusters: ObjectStorageCluster[] -): Cypress.Chainable => { - return cy.intercept( - 'GET', - apiMatcher('object-storage/clusters*'), - paginateResponse(clusters) - ); -}; - /** * Intercepts GET request to fetch access information (ACL, CORS) for a given Bucket. * * @param label - Object storage bucket label. - * @param cluster - Object storage bucket cluster. + * @param regionId - Region with Object Storage capability. * * @returns Cypress chainable. */ export const interceptGetBucketAccess = ( label: string, - cluster: string + regionId: string ): Cypress.Chainable => { return cy.intercept( 'GET', - apiMatcher(`object-storage/buckets/${cluster}/${label}/access`) + apiMatcher(`object-storage/buckets/${regionId}/${label}/access`) ); }; @@ -509,17 +493,17 @@ export const interceptGetBucketAccess = ( * Intercepts PUT request to update access information (ACL, CORS) for a given Bucket. * * @param label - Object storage bucket label. - * @param cluster - Object storage bucket cluster. + * @param regionId - Region with Object Storage capability. * * @returns Cypress chainable. */ export const interceptUpdateBucketAccess = ( label: string, - cluster: string + regionId: string ): Cypress.Chainable => { return cy.intercept( 'PUT', - apiMatcher(`object-storage/buckets/${cluster}/${label}/access`) + apiMatcher(`object-storage/buckets/${regionId}/${label}/access`) ); }; @@ -545,20 +529,20 @@ export const mockGetObjectStorageEndpoints = ( * * * @param label - Object storage bucket label. - * @param cluster - Object storage bucket cluster. + * @param regionId - Region with Object Storage capability. * @param bucketFilename - uploaded bucketFilename * * @returns Cypress chainable. */ export const mockGetBucketObjectFilename = ( label: string, - cluster: string, + regionId: string, bucketFilename: string ): Cypress.Chainable => { return cy.intercept( 'GET', apiMatcher( - `object-storage/buckets/${cluster}/${label}/object-acl?name=${bucketFilename}` + `object-storage/buckets/${regionId}/${label}/object-acl?name=${bucketFilename}` ), { body: {}, @@ -569,11 +553,11 @@ export const mockGetBucketObjectFilename = ( export const mockGetBucket = ( label: string, - cluster: string + regionId: string ): Cypress.Chainable => { return cy.intercept( 'GET', - apiMatcher(`object-storage/buckets/${cluster}/${label}`), + apiMatcher(`object-storage/buckets/${regionId}/${label}`), { body: {}, statusCode: 200, @@ -584,19 +568,19 @@ export const mockGetBucket = ( /* Intercepts GET request to fetch access information (ACL, CORS) for a given Bucket, and mocks response. * * @param label - Object storage bucket label. - * @param cluster - Object storage bucket cluster. + * @param regionId - Region with Object Storage capability. * @param bucketAccess - Access details for which to mock the response * * @returns Cypress chainable. */ export const mockGetBucketAccess = ( label: string, - cluster: string, + regionId: string, bucketAccess: ObjectStorageBucketAccess ): Cypress.Chainable => { return cy.intercept( 'GET', - apiMatcher(`object-storage/buckets/${cluster}/${label}/access`), + apiMatcher(`object-storage/buckets/${regionId}/${label}/access`), makeResponse(bucketAccess) ); }; diff --git a/packages/manager/cypress/support/plugins/fetch-linode-clusters.ts b/packages/manager/cypress/support/plugins/fetch-linode-clusters.ts deleted file mode 100644 index f9bd2e29bde..00000000000 --- a/packages/manager/cypress/support/plugins/fetch-linode-clusters.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { getClusters } from '@linode/api-v4'; - -import type { CypressPlugin } from './plugin'; -import type { ObjectStorageCluster, ResourcePage } from '@linode/api-v4'; - -/** - * Fetches Linode clusters and stores data in Cypress `cloudManagerClusters` env. - * - * Throws an error if no OAuth token (used for regions API request) is defined. - */ -export const fetchLinodeClusters: CypressPlugin = async (on, config) => { - const clusters: ResourcePage = await getClusters({ - page_size: 500, - }); - return { - ...config, - env: { - ...config.env, - cloudManagerClusters: clusters.data, - }, - }; -}; diff --git a/packages/manager/src/factories/objectStorage.ts b/packages/manager/src/factories/objectStorage.ts index 4a8ea82d443..15e2469c480 100644 --- a/packages/manager/src/factories/objectStorage.ts +++ b/packages/manager/src/factories/objectStorage.ts @@ -11,6 +11,7 @@ import type { export const objectStorageBucketFactory = Factory.Sync.makeFactory({ + region: 'us-east', cluster: 'us-east-1', created: '2019-12-12T00:00:00', hostname: Factory.each( @@ -18,7 +19,6 @@ export const objectStorageBucketFactory = ), label: Factory.each((i) => `obj-bucket-${i}`), objects: 103, - region: 'us-east', size: 999999, }); diff --git a/packages/manager/src/featureFlags.ts b/packages/manager/src/featureFlags.ts index 8c24c608d0d..afc103debde 100644 --- a/packages/manager/src/featureFlags.ts +++ b/packages/manager/src/featureFlags.ts @@ -266,7 +266,6 @@ export interface Flags { objectStorageContextualMetrics: boolean; objectStorageGen2: BaseFeatureFlag; objectStorageGlobalQuotas: boolean; - objMultiCluster: boolean; objSummaryPage: boolean; placementGroupPolicyUpdate: boolean; privateImageSharing: boolean; diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyDrawer.test.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyDrawer.test.tsx deleted file mode 100644 index 6bf0c7863fd..00000000000 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyDrawer.test.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { screen } from '@testing-library/react'; -import * as React from 'react'; - -import { objectStorageBucketFactory } from 'src/factories/objectStorage'; -import { renderWithTheme } from 'src/utilities/testHelpers'; - -import { AccessKeyDrawer, getDefaultScopes } from './AccessKeyDrawer'; -import { getUpdatedScopes } from './AccessTable'; - -import type { AccessKeyDrawerProps } from './AccessKeyDrawer'; -import type { MODE } from './types'; -import type { ObjectStorageKeyBucketAccess } from '@linode/api-v4/lib/object-storage/types'; - -describe('AccessKeyDrawer', () => { - const props: AccessKeyDrawerProps = { - isRestrictedUser: false, - mode: 'creating' as MODE, - onClose: vi.fn(), - onSubmit: vi.fn(), - open: true, - }; - renderWithTheme(); - it('renders without crashing', () => { - expect(screen.getByTestId('drawer-title')).toBeInTheDocument(); - }); - - describe('default scopes helper method', () => { - const mockBuckets = objectStorageBucketFactory.buildList(5); - it('should return an item for each bucket', () => { - expect(getDefaultScopes(mockBuckets)).toHaveLength(mockBuckets.length); - }); - - it('should return objects with the correct shape', () => { - const bucket = mockBuckets[0]; - expect(getDefaultScopes([bucket])[0]).toEqual({ - bucket_name: bucket.label, - cluster: bucket.cluster, - permissions: 'none', - region: 'us-east', - }); - }); - - it('should sort the permissions by cluster', () => { - const usaBucket = objectStorageBucketFactory.build({ - cluster: 'us-east-1', - }); - const germanBucket = objectStorageBucketFactory.build({ - cluster: 'eu-central-1', - }); - const asiaBucket = objectStorageBucketFactory.build({ - cluster: 'ap-south-1', - }); - const unsortedBuckets = [usaBucket, germanBucket, asiaBucket]; - expect( - getDefaultScopes(unsortedBuckets).map((scope) => scope.cluster) - ).toEqual(['ap-south-1', 'eu-central-1', 'us-east-1']); - }); - }); - - describe('Updating scopes', () => { - const mockBuckets = objectStorageBucketFactory.buildList(3); - - const mockScopes = getDefaultScopes(mockBuckets); - - it('should update the correct scope', () => { - const newScope = { - ...mockScopes[2], - permissions: 'read_write', - } as ObjectStorageKeyBucketAccess; - expect(getUpdatedScopes(mockScopes, newScope)[2]).toHaveProperty( - 'permissions', - 'read_write' - ); - }); - - it('should leave other scopes unchanged', () => { - const newScope = { - ...mockScopes[2], - access: 'read_write', - } as ObjectStorageKeyBucketAccess; - const updatedScopes = getUpdatedScopes(mockScopes, newScope); - expect(updatedScopes[0]).toEqual(mockScopes[0]); - expect(updatedScopes[1]).toEqual(mockScopes[1]); - expect(updatedScopes.length).toEqual(mockScopes.length); - }); - - it('should handle crappy input', () => { - const newScope = { - bucket_name: 'not-real', - cluster: 'totally-fake', - permissions: 'read_only', - region: 'us-east', - } as ObjectStorageKeyBucketAccess; - expect(getUpdatedScopes(mockScopes, newScope)).toEqual(mockScopes); - }); - }); -}); diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyDrawer.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyDrawer.tsx deleted file mode 100644 index 49999cb275e..00000000000 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyDrawer.tsx +++ /dev/null @@ -1,294 +0,0 @@ -import { useAccountSettings } from '@linode/queries'; -import { - ActionsPanel, - CircleProgress, - Drawer, - Notice, - TextField, - Typography, -} from '@linode/ui'; -import { createObjectStorageKeysSchema } from '@linode/validation/lib/objectStorageKeys.schema'; -import { Formik } from 'formik'; -import * as React from 'react'; - -import { Link } from 'src/components/Link'; -import { useObjectStorageBuckets } from 'src/queries/object-storage/queries'; - -import { EnableObjectStorageModal } from '../EnableObjectStorageModal'; -import { confirmObjectStorage } from '../utilities'; -import { LimitedAccessControls } from './LimitedAccessControls'; - -import type { MODE } from './types'; -import type { - CreateObjectStorageKeyPayload, - ObjectStorageBucket, - ObjectStorageKey, - ObjectStorageKeyBucketAccess, - ObjectStorageKeyBucketAccessPermissions, - UpdateObjectStorageKeyPayload, -} from '@linode/api-v4/lib/object-storage'; -import type { FormikProps } from 'formik'; - -export interface AccessKeyDrawerProps { - isRestrictedUser: boolean; - mode: MODE; - // If the mode is 'editing', we should have an ObjectStorageKey to edit - objectStorageKey?: ObjectStorageKey; - onClose: () => void; - onSubmit: ( - values: CreateObjectStorageKeyPayload | UpdateObjectStorageKeyPayload, - formikProps: FormikProps - ) => void; - open: boolean; -} - -interface FormState { - bucket_access: null | ObjectStorageKeyBucketAccess[]; - label: string; -} - -/** - * Helpers for converting a list of buckets - * on the user's account into a list of - * bucket_access in the shape the API will expect, - * sorted by cluster. - */ -export const sortByCluster = ( - a: ObjectStorageKeyBucketAccess, - b: ObjectStorageKeyBucketAccess -) => { - if (a.cluster > b.cluster) { - return 1; - } - if (a.cluster < b.cluster) { - return -1; - } - return 0; -}; - -export const getDefaultScopes = ( - buckets: ObjectStorageBucket[] -): ObjectStorageKeyBucketAccess[] => - buckets - .map((thisBucket) => ({ - bucket_name: thisBucket.label, - cluster: thisBucket.cluster, - permissions: 'none' as ObjectStorageKeyBucketAccessPermissions, - region: thisBucket.region ?? '', - })) - .sort(sortByCluster); - -export const AccessKeyDrawer = (props: AccessKeyDrawerProps) => { - const { isRestrictedUser, mode, objectStorageKey, onClose, onSubmit, open } = - props; - - const { data: accountSettings } = useAccountSettings(); - - const { - data: objectStorageBucketsResponse, - error: bucketsError, - isLoading: areBucketsLoading, - } = useObjectStorageBuckets(); - - const buckets = objectStorageBucketsResponse?.buckets || []; - - const hasBuckets = buckets?.length > 0; - - const hidePermissionsTable = - bucketsError || objectStorageBucketsResponse?.buckets.length === 0; - - const createMode = mode === 'creating'; - - const [dialogOpen, setDialogOpen] = React.useState(false); - // This is for local display management only, not part of the payload - // and so not included in Formik's types - const [limitedAccessChecked, setLimitedAccessChecked] = React.useState(false); - - React.useEffect(() => { - if (open) { - setLimitedAccessChecked(false); - } - }, [open]); - - const title = createMode ? 'Create Access Key' : 'Edit Access Key Label'; - - const initialLabelValue = - !createMode && objectStorageKey ? objectStorageKey.label : ''; - - const initialValues: FormState = { - bucket_access: getDefaultScopes(buckets), - label: initialLabelValue, - }; - - const handleSubmit = ( - values: CreateObjectStorageKeyPayload, - formikProps: FormikProps - ) => { - // If the user hasn't toggled the Limited Access button, - // don't include any bucket_access information in the payload. - - // If any/all values are 'none', don't include them in the response. - let payload = {}; - if ( - mode === 'creating' && - values?.bucket_access !== null && - limitedAccessChecked - ) { - const access = values?.bucket_access ?? []; - payload = { - ...values, - bucket_access: access.filter( - (thisAccess) => thisAccess.permissions !== 'none' - ), - }; - } else { - payload = { ...values, bucket_access: null }; - } - - if (mode === 'editing') { - payload = { - label: values.label, - }; - } - return onSubmit(payload, formikProps); - }; - - return ( - - {areBucketsLoading ? ( - - ) : ( - - {(formikProps) => { - const { - errors, - handleBlur, - handleChange, - handleSubmit, - isSubmitting, - setFieldValue, - status, - values, - } = formikProps; - - const beforeSubmit = () => { - confirmObjectStorage( - accountSettings?.object_storage || 'active', - formikProps, - () => setDialogOpen(true) - ); - }; - - const handleScopeUpdate = ( - newScopes: ObjectStorageKeyBucketAccess[] - ) => { - setFieldValue('bucket_access', newScopes); - }; - - const handleToggleAccess = () => { - setLimitedAccessChecked((checked) => !checked); - // Reset scopes - setFieldValue('bucket_access', getDefaultScopes(buckets)); - }; - - return ( - <> - {status && ( - - )} - - {isRestrictedUser && ( - - )} - - {/* Explainer copy if we're in 'creating' mode */} - {createMode && ( - - Generate an Access Key for use with an{' '} - - S3-compatible client - - . - - )} - - {!hasBuckets ? ( - - This key will have unlimited access to all buckets on your - account. The option to create a limited access key is only - available after creating one or more buckets. - - ) : null} - - - {createMode && !hidePermissionsTable ? ( - - ) : null} - - setDialogOpen(false)} - open={dialogOpen} - /> - - ); - }} - - )} - - ); -}; diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx index e624b21443f..4b12b239223 100644 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx @@ -20,10 +20,8 @@ import { } from 'src/utilities/analytics/customEventAnalytics'; import { getAPIErrorOrDefault, getErrorMap } from 'src/utilities/errorUtils'; -import { useIsObjMultiClusterEnabled } from '../hooks/useIsObjectStorageGen2Enabled'; -import { AccessKeyDrawer } from './AccessKeyDrawer'; -import { AccessKeyTable } from './AccessKeyTable/AccessKeyTable'; import { OMC_AccessKeyDrawer } from './OMC_AccessKeyDrawer'; +import { AccessKeyTable } from './AccessKeyTable/AccessKeyTable'; import { RevokeAccessKeyDialog } from './RevokeAccessKeyDialog'; import { ViewPermissionsDrawer } from './ViewPermissionsDrawer'; @@ -88,8 +86,6 @@ export const AccessKeyLanding = (props: Props) => { const displayKeysDialog = useOpenClose(); const revokeKeysDialog = useOpenClose(); - const { isObjMultiClusterEnabled } = useIsObjMultiClusterEnabled(); - // Redirect to base access keys route if current page has no data // TODO: Remove this implementation and replace `usePagination` with `usePaginate` hook. See [M3-10442] React.useEffect(() => { @@ -193,10 +189,7 @@ export const AccessKeyLanding = (props: Props) => { setSubmitting(true); - updateObjectStorageKey( - keyToEdit.id, - isObjMultiClusterEnabled ? values : { label: values.label } - ) + updateObjectStorageKey(keyToEdit.id, values) .then((_) => { setSubmitting(false); @@ -301,25 +294,14 @@ export const AccessKeyLanding = (props: Props) => { pageSize={pagination.pageSize} /> - {isObjMultiClusterEnabled ? ( - - ) : ( - - )} + { openRevokeDialog, } = props; - const { isObjMultiClusterEnabled } = useIsObjMultiClusterEnabled(); - - const isSmallViewport = useMediaQuery((theme) => - theme.breakpoints.down('md') - ); - const actions = [ { onClick: () => { openDrawer('editing', objectStorageKey); }, - title: isObjMultiClusterEnabled ? 'Edit' : 'Edit Label', + title: 'Edit', }, { onClick: () => { @@ -47,14 +35,10 @@ export const AccessKeyActionMenu = (props: Props) => { }, title: 'Permissions', }, - ...(isObjMultiClusterEnabled - ? [ - { - onClick: openHostnamesDrawer, - title: 'View Regions/S3 Hostnames', - }, - ] - : []), + { + onClick: openHostnamesDrawer, + title: 'View Regions/S3 Hostnames', + }, { onClick: () => { openRevokeDialog(objectStorageKey); @@ -63,24 +47,10 @@ export const AccessKeyActionMenu = (props: Props) => { }, ]; - if (isObjMultiClusterEnabled || isSmallViewport) { - return ( - - ); - } - return ( - - {actions.map((action) => ( - - ))} - + ); }; diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.tsx index 535da66a385..4388846ff0a 100644 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.tsx +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTable.tsx @@ -1,6 +1,5 @@ import { useAccount } from '@linode/queries'; import { Hidden } from '@linode/ui'; -import { isFeatureEnabledV2 } from '@linode/utilities'; import React, { useState } from 'react'; import { Table } from 'src/components/Table'; @@ -46,12 +45,6 @@ export const AccessKeyTable = (props: AccessKeyTableProps) => { const flags = useFlags(); const { data: account } = useAccount(); - const isObjMultiClusterEnabled = isFeatureEnabledV2( - 'Object Storage Access Key Regions', - Boolean(flags.objMultiCluster), - account?.capabilities ?? [] - ); - return ( <> { Label Access Key - {isObjMultiClusterEnabled && ( - - Regions/S3 Hostnames - - )} + + Regions/S3 Hostnames + @@ -84,7 +75,6 @@ export const AccessKeyTable = (props: AccessKeyTableProps) => { data={data} error={error} isLoading={isLoading} - isObjMultiClusterEnabled={isObjMultiClusterEnabled} isRestrictedUser={isRestrictedUser} openDrawer={openDrawer} openRevokeDialog={openRevokeDialog} @@ -93,13 +83,11 @@ export const AccessKeyTable = (props: AccessKeyTableProps) => { />
- {isObjMultiClusterEnabled && ( - setShowHostNamesDrawers(false)} - open={showHostNamesDrawer} - regions={hostNames} - /> - )} + setShowHostNamesDrawers(false)} + open={showHostNamesDrawer} + regions={hostNames} + /> ); }; diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx index 34c0c9493a2..28383b3d8c1 100644 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx @@ -17,7 +17,6 @@ interface Props { data: ObjectStorageKey[] | undefined; error: APIError[] | null | undefined; isLoading: boolean; - isObjMultiClusterEnabled: boolean; isRestrictedUser: boolean; openDrawer: OpenAccessDrawer; openRevokeDialog: (objectStorageKey: ObjectStorageKey) => void; @@ -30,7 +29,6 @@ export const AccessKeyTableBody = (props: Props) => { data, error, isLoading, - isObjMultiClusterEnabled, isRestrictedUser, openDrawer, openRevokeDialog, @@ -38,7 +36,7 @@ export const AccessKeyTableBody = (props: Props) => { setShowHostNamesDrawers, } = props; - const cols = isObjMultiClusterEnabled ? 4 : 3; + const cols = 4; if (isRestrictedUser) { return ; @@ -46,10 +44,7 @@ export const AccessKeyTableBody = (props: Props) => { if (isLoading) { return ( - + ); } diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRow.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRow.tsx index 735e987349f..79a45f17f20 100644 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRow.tsx +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRow.tsx @@ -34,15 +34,6 @@ export const AccessKeyTableRow = (props: Props) => { storageKeyData, } = props; - const { data: account } = useAccount(); - const flags = useFlags(); - - const isObjMultiClusterEnabled = isFeatureEnabledV2( - 'Object Storage Access Key Regions', - Boolean(flags.objMultiCluster), - account?.capabilities ?? [] - ); - return ( {storageKeyData.label} @@ -54,15 +45,13 @@ export const AccessKeyTableRow = (props: Props) => { - {isObjMultiClusterEnabled && ( - - - - )} + + + { const { checked, handleToggle, ...rest } = props; - const { isObjMultiClusterEnabled } = useIsObjMultiClusterEnabled(); - return ( <> { /> } label={ - isObjMultiClusterEnabled ? ( - - ) : ( - 'Limited Access' - ) + } sx={(theme) => ({ marginBottom: theme.spacing(0.5), @@ -68,11 +60,7 @@ export const LimitedAccessControls = React.memo((props: Props) => { also create new buckets, but will not have access to the buckets they create. - {isObjMultiClusterEnabled ? ( - - ) : ( - - )} + ); }); diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/ViewPermissionsDrawer.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/ViewPermissionsDrawer.tsx index 9bdfc6f8ebc..ca0fa60982c 100644 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/ViewPermissionsDrawer.tsx +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/ViewPermissionsDrawer.tsx @@ -1,8 +1,6 @@ import { Drawer, Typography } from '@linode/ui'; import * as React from 'react'; -import { useIsObjMultiClusterEnabled } from '../hooks/useIsObjectStorageGen2Enabled'; -import { AccessTable } from './AccessTable'; import { BucketPermissionsTable } from './BucketPermissionsTable'; import type { ObjectStorageKey } from '@linode/api-v4'; @@ -16,8 +14,6 @@ export interface Props { export const ViewPermissionsDrawer = (props: Props) => { const { objectStorageKey, onClose, open } = props; - const { isObjMultiClusterEnabled } = useIsObjMultiClusterEnabled(); - return ( { This access key has the following permissions: - {isObjMultiClusterEnabled ? ( - null} - /> - ) : ( - null} - /> - )} + null} + /> )} diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectsTab/BucketBreadcrumb.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectsTab/BucketBreadcrumb.tsx index 7c2ad2ceed3..99ed61feea6 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectsTab/BucketBreadcrumb.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectsTab/BucketBreadcrumb.tsx @@ -20,8 +20,8 @@ interface Props { export const BucketBreadcrumb = (props: Props) => { const { bucketName, prefix } = props; const navigate = useNavigate(); - const { clusterId } = useParams({ - from: '/object-storage/buckets/$clusterId/$bucketName', + const { regionId } = useParams({ + from: '/object-storage/buckets/$regionId/$bucketName', }); const { width } = useWindowDimensions(); const bucketPath = bucketName + '/' + prefix; @@ -43,8 +43,8 @@ export const BucketBreadcrumb = (props: Props) => { { navigate({ - to: '/object-storage/buckets/$clusterId/$bucketName', - params: { clusterId, bucketName }, + to: '/object-storage/buckets/$regionId/$bucketName', + params: { regionId, bucketName }, search: { prefix: '' }, }); }} @@ -75,8 +75,8 @@ export const BucketBreadcrumb = (props: Props) => { const prefixString = prefixArrayToString(prefixArray, idx); navigate({ - to: '/object-storage/buckets/$clusterId/$bucketName', - params: { clusterId, bucketName }, + to: '/object-storage/buckets/$regionId/$bucketName', + params: { regionId, bucketName }, search: { prefix: prefixString }, }); }} diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectsTab/BucketDetail.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectsTab/BucketDetail.tsx index f843b4caa61..b29a3f05ffd 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectsTab/BucketDetail.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/ObjectsTab/BucketDetail.tsx @@ -63,30 +63,21 @@ export const BucketDetail = () => { * the bucket's region id */ const { enqueueSnackbar } = useSnackbar(); - const { bucketName, clusterId } = useParams({ - from: '/object-storage/buckets/$clusterId/$bucketName', + const { bucketName, regionId } = useParams({ + from: '/object-storage/buckets/$regionId/$bucketName', }); const { prefix = '' } = useSearch({ - from: '/object-storage/buckets/$clusterId/$bucketName', + from: '/object-storage/buckets/$regionId/$bucketName', }); const queryClient = useQueryClient(); const flags = useFlags(); const { data: account } = useAccount(); - const isObjMultiClusterEnabled = isFeatureEnabledV2( - 'Object Storage Access Key Regions', - Boolean(flags.objMultiCluster), - account?.capabilities ?? [] - ); - const { data: buckets } = useObjectStorageBuckets(); const bucket = buckets?.buckets.find((bucket) => { - if (isObjMultiClusterEnabled) { - return bucket.label === bucketName && bucket.region === clusterId; - } - return bucket.label === bucketName && bucket.cluster === clusterId; + return bucket.label === bucketName && bucket.region === regionId; }); const { @@ -97,7 +88,7 @@ export const BucketDetail = () => { isFetching, isFetchingNextPage, isLoading, - } = useObjectBucketObjectsInfiniteQuery(clusterId, bucketName, prefix); + } = useObjectBucketObjectsInfiniteQuery(regionId, bucketName, prefix); const [isCreateFolderDrawerOpen, setIsCreateFolderDrawerOpen] = React.useState(false); const [objectToDelete, setObjectToDelete] = React.useState(); @@ -114,7 +105,7 @@ export const BucketDetail = () => { const handleDownload = async (objectName: string) => { try { const { url } = await getObjectURL( - clusterId, + regionId, bucketName, objectName, 'GET', @@ -147,7 +138,7 @@ export const BucketDetail = () => { // we don't want to fetch for every delete action. Debounce // the updateBucket call by 3 seconds. const debouncedUpdateBucket = debounce(3000, false, () => { - fetchBucketAndUpdateCache(clusterId, bucketName, queryClient); + fetchBucketAndUpdateCache(regionId, bucketName, queryClient); }); const deleteObject = async () => { @@ -161,7 +152,7 @@ export const BucketDetail = () => { if (objectToDelete.endsWith('/')) { const itemsInFolderData = await getObjectList({ bucket: bucketName, - clusterId, + regionId: regionId, params: { delimiter: OBJECT_STORAGE_DELIMITER, prefix: objectToDelete, @@ -185,7 +176,7 @@ export const BucketDetail = () => { try { const { url } = await getObjectURL( - clusterId, + regionId, bucketName, objectToDelete, 'DELETE' @@ -213,7 +204,7 @@ export const BucketDetail = () => { pageParams: string[]; pages: ObjectStorageObjectList[]; }>( - getObjectBucketObjectsQueryKey(clusterId, bucketName, prefix), + getObjectBucketObjectsQueryKey(regionId, bucketName, prefix), (data) => ({ pageParams: data?.pageParams || [], pages, @@ -253,7 +244,7 @@ export const BucketDetail = () => { const addOneFile = (objectName: string, sizeInBytes: number) => { const currentData = queryClient.getQueryData< InfiniteData - >(getObjectBucketObjectsQueryKey(clusterId, bucketName, prefix)); + >(getObjectBucketObjectsQueryKey(regionId, bucketName, prefix)); if (!currentData) { return; @@ -300,7 +291,7 @@ export const BucketDetail = () => { const addOneFolder = (objectName: string) => { const currentData = queryClient.getQueryData< InfiniteData - >(getObjectBucketObjectsQueryKey(clusterId, bucketName, prefix)); + >(getObjectBucketObjectsQueryKey(regionId, bucketName, prefix)); if (!currentData) { return; @@ -320,7 +311,7 @@ export const BucketDetail = () => { // prefix. Due to how invalidateQueries works, all subdirectories also get invalidated. queryClient.invalidateQueries({ queryKey: [ - ...objectStorageQueries.bucket(clusterId, bucketName)._ctx.objects + ...objectStorageQueries.bucket(regionId, bucketName)._ctx.objects .queryKey, ...`${prefix}${objectName}`.split('/'), ], @@ -349,7 +340,7 @@ export const BucketDetail = () => { const numOfDisplayedObjects = data?.pages.map((page) => page.data.length).reduce((a, b) => a + b, 0) || 0; - if (!bucketName || !clusterId) { + if (!bucketName || !regionId) { return null; } @@ -360,7 +351,7 @@ export const BucketDetail = () => { @@ -448,7 +439,7 @@ export const BucketDetail = () => { { /> setIsCreateFolderDrawerOpen(false)} open={isCreateFolderDrawerOpen} diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/bucketDetailLandingLazyRoute.ts b/packages/manager/src/features/ObjectStorage/BucketDetail/bucketDetailLandingLazyRoute.ts index 46086c4a867..3805736da40 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/bucketDetailLandingLazyRoute.ts +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/bucketDetailLandingLazyRoute.ts @@ -3,7 +3,7 @@ import { createLazyRoute } from '@tanstack/react-router'; import { BucketDetailLanding } from 'src/features/ObjectStorage/BucketDetail'; export const bucketDetailLandingLazyRoute = createLazyRoute( - '/object-storage/buckets/$clusterId/$bucketName' + '/object-storage/buckets/$regionId/$bucketName' )({ component: BucketDetailLanding, }); diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx index 7320121a1ed..280486cfda4 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx @@ -38,7 +38,7 @@ const BucketMetrics = React.lazy(() => })) ); -const BUCKET_DETAILS_URL = '/object-storage/buckets/$clusterId/$bucketName'; +const BUCKET_DETAILS_URL = '/object-storage/buckets/$regionId/$bucketName'; const ENDPOINT_TYPES_WITH_NO_METRICS_SUPPORT = ['E0', 'E1']; export const BucketDetailLanding = React.memo(() => { diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.tsx index 6ffcf11205f..3d1ef450d70 100644 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketDetailsDrawer.tsx @@ -7,11 +7,9 @@ import * as React from 'react'; import { CopyTooltip } from 'src/components/CopyTooltip/CopyTooltip'; import { Link } from 'src/components/Link'; import { MaskableText } from 'src/components/MaskableText/MaskableText'; -import { useObjectStorageClusters } from 'src/queries/object-storage/queries'; import { formatDate } from 'src/utilities/formatDate'; import { AccessSelect } from '../BucketDetail/AccessTab/AccessSelect'; -import { useIsObjMultiClusterEnabled } from '../hooks/useIsObjectStorageGen2Enabled'; import type { ObjectStorageBucket } from '@linode/api-v4/lib/object-storage'; @@ -36,22 +34,9 @@ export const BucketDetailsDrawer = React.memo( size, } = selectedBucket ?? {}; - const { isObjMultiClusterEnabled } = useIsObjMultiClusterEnabled(); - - // @TODO OBJGen2 - We could clean this up when OBJ Gen2 is in GA. - const { data: clusters } = useObjectStorageClusters( - !isObjMultiClusterEnabled - ); - const { data: regions } = useRegionsQuery(); const { data: currentRegion } = useRegionQuery(region ?? ''); const { data: profile } = useProfile(); - // @TODO OBJGen2 - We could clean this up when OBJ Gen2 is in GA. - const selectedCluster = clusters?.find((c) => c.id === cluster); - const regionFromCluster = regions?.find( - (r) => r.id === selectedCluster?.region - ); - let formattedCreated; try { @@ -78,15 +63,9 @@ export const BucketDetailsDrawer = React.memo( Endpoint Type: {endpoint_type} )} - {isObjMultiClusterEnabled ? ( - - {currentRegion?.label} - - ) : cluster ? ( - - {regionFromCluster?.label ?? cluster} - - ) : null} + + {currentRegion?.label} + {hostname && ( @@ -105,13 +84,8 @@ export const BucketDetailsDrawer = React.memo( {readableBytes(size).formatted} )} - {/* @TODO OBJ Multicluster: use region instead of cluster if isObjMultiClusterEnabled. */} {typeof objects === 'number' && ( - + {pluralize('object', 'objects', objects)} )} @@ -120,11 +94,7 @@ export const BucketDetailsDrawer = React.memo( )} {cluster && label && ( { const { data: regions } = useRegionsQuery(); - const { isObjMultiClusterEnabled } = useIsObjMultiClusterEnabled(); - - const { data: clusters } = useObjectStorageClusters( - !isObjMultiClusterEnabled - ); - - const actualCluster = clusters?.find((c) => c.id === cluster); - const clusterRegion = regions?.find((r) => r.id === actualCluster?.region); - const regionsLookup = regions && getRegionsByRegionId(regions); const isLegacy = endpoint_type === 'E0'; @@ -61,11 +50,7 @@ export const BucketTableRow = (props: BucketTableRowProps) => { - + {label} {hostname} @@ -75,9 +60,7 @@ export const BucketTableRow = (props: BucketTableRowProps) => { - {isObjMultiClusterEnabled && regionsLookup && region - ? regionsLookup[region].label - : (clusterRegion?.label ?? cluster)} + {regionsLookup?.[region]?.label} diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/ClusterSelect.test.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/ClusterSelect.test.tsx deleted file mode 100644 index 91a6aa04c49..00000000000 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/ClusterSelect.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; - -import { renderWithTheme } from 'src/utilities/testHelpers'; - -import ClusterSelect from './ClusterSelect'; - -describe('ClusterSelect', () => { - it('Renders a select with object storage clusters', () => { - const { getByText } = renderWithTheme( - null} - onChange={() => null} - selectedCluster={''} - /> - ); - getByText('Region'); - }); -}); diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/ClusterSelect.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/ClusterSelect.tsx deleted file mode 100644 index bcd6d9e77ba..00000000000 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/ClusterSelect.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useRegionsQuery } from '@linode/queries'; -import { useIsGeckoEnabled } from '@linode/shared'; -import * as React from 'react'; - -import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; -import { useFlags } from 'src/hooks/useFlags'; -import { useObjectStorageClusters } from 'src/queries/object-storage/queries'; - -import type { Region } from '@linode/api-v4/lib/regions'; - -interface Props { - disabled?: boolean; - error?: string; - onBlur: (e: any) => void; - onChange: (value: string) => void; - required?: boolean; - selectedCluster: string | undefined; -} - -export const ClusterSelect: React.FC = (props) => { - const { disabled, error, onBlur, onChange, required, selectedCluster } = - props; - - const { data: clusters, error: clustersError } = useObjectStorageClusters(); - const { data: regions } = useRegionsQuery(); - - const flags = useFlags(); - const { isGeckoLAEnabled } = useIsGeckoEnabled( - flags.gecko2?.enabled, - flags.gecko2?.la - ); - - const regionOptions = clusters?.reduce((acc, cluster) => { - const region = regions?.find((r) => r.id === cluster.region); - if (region) { - acc.push({ ...region, id: cluster.id }); - } - return acc; - }, []); - - // Error could be: 1. General Clusters error, 2. Field error, 3. Nothing - const errorText = clustersError - ? 'Error loading regions' - : error - ? error - : undefined; - - return ( - onChange(region.id)} - placeholder="Select a Region" - regions={regionOptions ?? []} - required={required} - value={selectedCluster ?? null} - /> - ); -}; - -export default ClusterSelect; diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.test.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.test.tsx deleted file mode 100644 index 78667fa9c7c..00000000000 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.test.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { regionFactory } from '@linode/utilities'; -import { waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import * as React from 'react'; - -import { - accountSettingsFactory, - objectStorageClusterFactory, -} from 'src/factories'; -import { makeResourcePage } from 'src/mocks/serverHandlers'; -import { http, HttpResponse, server } from 'src/mocks/testServer'; -import { renderWithTheme } from 'src/utilities/testHelpers'; - -import { CreateBucketDrawer } from './CreateBucketDrawer'; - -const props = { - isOpen: true, - onClose: vi.fn(), -}; - -describe('CreateBucketDrawer', () => { - it.skip('Should show a general error notice if the API returns one', async () => { - server.use( - http.post('*/object-storage/buckets', () => { - return HttpResponse.json( - { errors: [{ reason: 'Object Storage is offline!' }] }, - { - status: 500, - } - ); - }), - http.get('*/regions', async () => { - return HttpResponse.json( - makeResourcePage( - regionFactory.buildList(1, { id: 'us-east', label: 'Newark, NJ' }) - ) - ); - }), - http.get('*object-storage/clusters', () => { - return HttpResponse.json( - makeResourcePage( - objectStorageClusterFactory.buildList(1, { - id: 'us-east-1', - region: 'us-east', - }) - ) - ); - }), - http.get('*/account/settings', () => { - return HttpResponse.json( - accountSettingsFactory.build({ object_storage: 'active' }) - ); - }) - ); - - const { findByText, getByLabelText, getByPlaceholderText, getByTestId } = - renderWithTheme(); - - await userEvent.type( - getByLabelText('Label', { exact: false }), - 'my-test-bucket' - ); - - // We must waitFor because we need to load region and cluster data from the API - await waitFor(() => - userEvent.selectOptions( - getByPlaceholderText('Select a Region'), - 'Newark, NJ (us-east-1)' - ) - ); - - const saveButton = getByTestId('create-bucket-button'); - - await userEvent.click(saveButton); - - await findByText('Object Storage is offline!'); - }); -}); diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.tsx deleted file mode 100644 index fc2e4d3f96a..00000000000 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import { yupResolver } from '@hookform/resolvers/yup'; -import { - useAccountAgreements, - useAccountSettings, - useMutateAccountAgreements, - useNetworkTransferPricesQuery, - useProfile, - useRegionsQuery, -} from '@linode/queries'; -import { ActionsPanel, Drawer, Notice, TextField } from '@linode/ui'; -import { CreateBucketSchema } from '@linode/validation'; -import { styled } from '@mui/material/styles'; -import * as React from 'react'; -import { Controller, useForm } from 'react-hook-form'; - -import { EUAgreementCheckbox } from 'src/features/Account/Agreements/EUAgreementCheckbox'; -import { - useCreateBucketMutation, - useObjectStorageBuckets, - useObjectStorageTypesQuery, -} from 'src/queries/object-storage/queries'; -import { sendCreateBucketEvent } from 'src/utilities/analytics/customEventAnalytics'; -import { getGDPRDetails } from 'src/utilities/formatRegion'; -import { PRICES_RELOAD_ERROR_NOTICE_TEXT } from 'src/utilities/pricing/constants'; -import { reportAgreementSigningError } from 'src/utilities/reportAgreementSigningError'; - -import { EnableObjectStorageModal } from '../EnableObjectStorageModal'; -import { QuotasInfoNotice } from '../QuotasInfoNotice'; -import ClusterSelect from './ClusterSelect'; -import { OveragePricing } from './OveragePricing'; - -import type { CreateObjectStorageBucketPayload } from '@linode/api-v4'; - -interface Props { - isOpen: boolean; - onClose: () => void; -} - -export const CreateBucketDrawer = (props: Props) => { - const { data: profile } = useProfile(); - const { isOpen, onClose } = props; - const isRestrictedUser = profile?.restricted; - - const { data: regions } = useRegionsQuery(); - - const { data: bucketsData } = useObjectStorageBuckets(); - - const { - data: objTypes, - isError: isErrorObjTypes, - isInitialLoading: isLoadingObjTypes, - } = useObjectStorageTypesQuery(isOpen); - const { - data: transferTypes, - isError: isErrorTransferTypes, - isInitialLoading: isLoadingTransferTypes, - } = useNetworkTransferPricesQuery(isOpen); - - const isErrorTypes = isErrorTransferTypes || isErrorObjTypes; - const isLoadingTypes = isLoadingTransferTypes || isLoadingObjTypes; - const isInvalidPrice = - !objTypes || !transferTypes || isErrorTypes || isErrorTransferTypes; - - const { isPending, mutateAsync: createBucket } = useCreateBucketMutation(); - const { data: agreements } = useAccountAgreements(); - const { mutateAsync: updateAccountAgreements } = useMutateAccountAgreements(); - const { data: accountSettings } = useAccountSettings(); - const [isEnableObjDialogOpen, setIsEnableObjDialogOpen] = - React.useState(false); - const [hasSignedAgreement, setHasSignedAgreement] = - React.useState(false); - - const { - control, - formState: { errors }, - handleSubmit, - reset, - setError, - watch, - } = useForm({ - context: { buckets: bucketsData?.buckets ?? [] }, - defaultValues: { - cluster: '', - cors_enabled: true, - label: '', - }, - mode: 'onBlur', - resolver: yupResolver(CreateBucketSchema), - }); - - const watchCluster = watch('cluster'); - - const onSubmit = async (data: CreateObjectStorageBucketPayload) => { - try { - await createBucket(data); - - if (data.cluster) { - sendCreateBucketEvent(data.cluster); - } - - if (hasSignedAgreement) { - try { - await updateAccountAgreements({ eu_model: true }); - } catch (error) { - reportAgreementSigningError(error); - } - } - - handleClose(); - } catch (errors) { - for (const error of errors) { - setError(error?.field ?? 'root', { message: error.reason }); - } - } - }; - - const handleBucketFormSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (accountSettings?.object_storage !== 'active') { - setIsEnableObjDialogOpen(true); - } else { - handleSubmit(onSubmit)(); - } - }; - - const clusterRegion = watchCluster - ? regions?.find((region) => watchCluster.includes(region.id)) - : undefined; - - const { showGDPRCheckbox } = getGDPRDetails({ - agreements, - profile, - regions, - selectedRegionId: clusterRegion?.id ?? '', - }); - - const handleClose = () => { - reset(); - onClose(); - }; - - return ( - -
- - {isRestrictedUser && ( - - )} - {errors.root?.message && ( - - )} - ( - - )} - rules={{ required: 'Bucket name is required' }} - /> - ( - field.onChange(value)} - required - selectedCluster={field.value ?? undefined} - /> - )} - rules={{ required: 'Cluster is required' }} - /> - {clusterRegion?.id && } - {showGDPRCheckbox && ( - setHasSignedAgreement(e.target.checked)} - /> - )} - - setIsEnableObjDialogOpen(false)} - open={isEnableObjDialogOpen} - regionId={clusterRegion?.id} - /> - -
- ); -}; - -const StyledEUAgreementCheckbox = styled(EUAgreementCheckbox, { - label: 'StyledEUAgreementCheckbox', -})(({ theme }) => ({ - marginButton: theme.spacing(3), - marginTop: theme.spacing(3), -})); diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_CreateBucketDrawer.test.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_CreateBucketDrawer.test.tsx index 5e3050f2f14..5933b4a28bd 100644 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_CreateBucketDrawer.test.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketLanding/OMC_CreateBucketDrawer.test.tsx @@ -24,7 +24,6 @@ describe('OMC_CreateBucketDrawer', () => { component: , options: { flags: { - objMultiCluster: true, objectStorageGen2: { enabled: true }, }, }, @@ -59,7 +58,6 @@ describe('OMC_CreateBucketDrawer', () => { component: , options: { flags: { - objMultiCluster: true, objectStorageGen2: { enabled: true }, }, }, diff --git a/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx b/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx index 00dd326b68f..2e9fd5777de 100644 --- a/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx +++ b/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx @@ -18,10 +18,8 @@ import { useObjectStorageBuckets } from 'src/queries/object-storage/queries'; import { getRestrictedResourceText } from '../Account/utils'; import { BillingNotice } from './BillingNotice'; -import { CreateBucketDrawer } from './BucketLanding/CreateBucketDrawer'; -import { OMC_BucketLanding } from './BucketLanding/OMC_BucketLanding'; import { OMC_CreateBucketDrawer } from './BucketLanding/OMC_CreateBucketDrawer'; -import { useIsObjMultiClusterEnabled } from './hooks/useIsObjectStorageGen2Enabled'; +import { BucketLanding } from './BucketLanding/BucketLanding'; import type { MODE } from './AccessKeyLanding/types'; @@ -43,8 +41,6 @@ export const ObjectStorageLanding = () => { const [mode, setMode] = React.useState('creating'); - const { isObjMultiClusterEnabled } = useIsObjMultiClusterEnabled(); - const { data: profile } = useProfile(); const { data: accountSettings } = useAccountSettings(); @@ -174,9 +170,7 @@ export const ObjectStorageLanding = () => { )} - + { - - {isObjMultiClusterEnabled ? ( - navigate({ to: '/object-storage/buckets' })} - /> - ) : ( - navigate({ to: '/object-storage/buckets' })} - /> - )} + navigate({ to: '/object-storage/buckets' })} + /> ); diff --git a/packages/manager/src/features/ObjectStorage/hooks/useIsObjectStorageGen2Enabled.tsx b/packages/manager/src/features/ObjectStorage/hooks/useIsObjectStorageGen2Enabled.tsx index 53f770b415a..cc63202a9f6 100644 --- a/packages/manager/src/features/ObjectStorage/hooks/useIsObjectStorageGen2Enabled.tsx +++ b/packages/manager/src/features/ObjectStorage/hooks/useIsObjectStorageGen2Enabled.tsx @@ -23,16 +23,3 @@ export const useIsObjectStorageGen2Enabled = (): { return { isObjectStorageGen2Enabled }; }; - -export const useIsObjMultiClusterEnabled = () => { - const flags = useFlags(); - const { data: account } = useAccount(); - - const isObjMultiClusterEnabled = isFeatureEnabledV2( - 'Object Storage Access Key Regions', - Boolean(flags.objMultiCluster), - account?.capabilities ?? [] - ); - - return { isObjMultiClusterEnabled }; -}; diff --git a/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx b/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx index 59d80ad8e62..1f63a0ce9b4 100644 --- a/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx +++ b/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx @@ -8,7 +8,6 @@ import { ConfirmationDialog } from 'src/components/ConfirmationDialog/Confirmati import { CopyableTextField } from 'src/components/CopyableTextField/CopyableTextField'; import { CopyAllHostnames } from 'src/features/ObjectStorage/AccessKeyLanding/CopyAllHostnames'; import { HostNamesList } from 'src/features/ObjectStorage/AccessKeyLanding/HostNamesList'; -import { useIsObjMultiClusterEnabled } from 'src/features/ObjectStorage/hooks/useIsObjectStorageGen2Enabled'; import type { ObjectStorageKey } from '@linode/api-v4/lib/object-storage'; @@ -39,8 +38,6 @@ export const SecretTokenDialog = (props: Props) => { const { data: regionsData } = useRegionsQuery(); const regionsLookup = regionsData && getRegionsByRegionId(regionsData); - const { isObjMultiClusterEnabled } = useIsObjMultiClusterEnabled(); - const modalConfirmationButtonText = objectStorageKey ? 'I Have Saved My Secret Key' : `I Have Saved My ${title}`; @@ -71,38 +68,22 @@ export const SecretTokenDialog = (props: Props) => { } once, after which it can\u{2019}t be recovered. Be sure to keep it in a safe place.`} variant="warning" /> - {/* @TODO OBJ Multicluster: The objectStorageKey check is a temporary fix - to handle error cases when the feature flag is enabled without Mock - Service Worker (MSW). This can be removed during the feature flag cleanup. */} - {isObjMultiClusterEnabled && - objectStorageKey && - objectStorageKey?.regions?.length > 0 && ( -
- - `${regionsLookup?.[region.id]?.label}: ${ - region.s3_endpoint - }` - ) - .join('\n') ?? '' - } - /> -
- )} - {/* @TODO OBJ Multicluster: The objectStorageKey check is a temporary fix - to handle error cases when the feature flag is enabled without Mock - Service Worker (MSW). This can be removed during the feature flag cleanup. */} - {isObjMultiClusterEnabled && - objectStorageKey && - objectStorageKey?.regions?.length > 0 && ( - - )} +
+ + `${regionsLookup?.[region.id]?.label}: ${region.s3_endpoint}` + ) + .join('\n') ?? '' + } + /> +
+ {objectStorageKey ? ( <> diff --git a/packages/manager/src/queries/object-storage/queries.ts b/packages/manager/src/queries/object-storage/queries.ts index 7670e05273b..18d8aae451a 100644 --- a/packages/manager/src/queries/object-storage/queries.ts +++ b/packages/manager/src/queries/object-storage/queries.ts @@ -2,7 +2,6 @@ import { cancelObjectStorage, createBucket, deleteBucket, - deleteBucketWithRegion, deleteSSLCert, getBucketAccess, getObjectACL, @@ -38,7 +37,6 @@ import { useFlags } from 'src/hooks/useFlags'; import { getAllBucketsFromEndpoints, getAllBucketsFromRegions, - getAllObjectStorageClusters, getAllObjectStorageEndpoints, getAllObjectStorageTypes, } from './requests'; @@ -54,7 +52,6 @@ import type { ObjectStorageBucket, ObjectStorageBucketAccess, ObjectStorageBucketSSL, - ObjectStorageCluster, ObjectStorageEndpoint, ObjectStorageKey, ObjectStorageObjectACL, @@ -71,10 +68,10 @@ export const objectStorageQueries = createQueryKeys('object-storage', { queryFn: () => getObjectStorageKeys(params), queryKey: [params], }), - bucket: (clusterOrRegion: string, bucketName: string) => ({ + bucket: (regionId: string, bucketName: string) => ({ contextQueries: { access: { - queryFn: () => getBucketAccess(clusterOrRegion, bucketName), + queryFn: () => getBucketAccess(regionId, bucketName), queryKey: null, }, objects: { @@ -83,7 +80,7 @@ export const objectStorageQueries = createQueryKeys('object-storage', { queryFn: () => getObjectACL({ bucket: bucketName, - clusterId: clusterOrRegion, + regionId, params: { name }, }), queryKey: [name], @@ -94,20 +91,16 @@ export const objectStorageQueries = createQueryKeys('object-storage', { queryKey: null, }, ssl: { - queryFn: () => getSSLCert(clusterOrRegion, bucketName), + queryFn: () => getSSLCert(regionId, bucketName), queryKey: null, }, }, - queryKey: [clusterOrRegion, bucketName], + queryKey: [regionId, bucketName], }), buckets: { queryFn: () => null, // This is a placeholder queryFn. Look at `useObjectStorageBuckets` for the actual logic. queryKey: null, }, - clusters: { - queryFn: getAllObjectStorageClusters, - queryKey: null, - }, endpoints: { queryFn: getAllObjectStorageEndpoints, queryKey: null, @@ -135,17 +128,6 @@ export const useObjectStorageEndpoints = (enabled = true) => { }); }; -/** - * - * @deprecated This will be replaced by useObjectStorageEndpoints - */ -export const useObjectStorageClusters = (enabled: boolean = true) => - useQuery({ - ...objectStorageQueries.clusters, - ...queryPresets.oneTimeFetch, - enabled, - }); - export const useObjectStorageBuckets = (enabled: boolean = true) => { const flags = useFlags(); const { data: account, isLoading: accountIsLoading } = useAccount(enabled); @@ -203,39 +185,38 @@ export const useObjectStorageAccessKeys = (params: Params) => }); export const useBucketAccess = ( - clusterOrRegion: string, + regionId: string, bucket: string, queryEnabled: boolean ) => useQuery({ - ...objectStorageQueries.bucket(clusterOrRegion, bucket)._ctx.access, + ...objectStorageQueries.bucket(regionId, bucket)._ctx.access, enabled: queryEnabled, }); export const useObjectAccess = ( bucket: string, - clusterId: string, + regionId: string, params: { name: string }, queryEnabled: boolean ) => useQuery({ enabled: queryEnabled, ...objectStorageQueries - .bucket(clusterId, bucket) + .bucket(regionId, bucket) ._ctx.objects._ctx.acl(params.name), }); export const useUpdateBucketAccessMutation = ( - clusterOrRegion: string, + regionId: string, bucket: string ) => { const queryClient = useQueryClient(); return useMutation<{}, APIError[], UpdateObjectStorageBucketAccessPayload>({ - mutationFn: (data) => updateBucketAccess(clusterOrRegion, bucket, data), + mutationFn: (data) => updateBucketAccess(regionId, bucket, data), onSuccess: (_, variables) => { queryClient.setQueryData( - objectStorageQueries.bucket(clusterOrRegion, bucket)._ctx.access - .queryKey, + objectStorageQueries.bucket(regionId, bucket)._ctx.access.queryKey, (oldData) => ({ acl: variables?.acl ?? 'private', acl_xml: oldData?.acl_xml ?? '', @@ -248,7 +229,7 @@ export const useUpdateBucketAccessMutation = ( }; export const useUpdateObjectAccessMutation = ( - clusterId: string, + regionId: string, bucketName: string, name: string ) => { @@ -256,12 +237,12 @@ export const useUpdateObjectAccessMutation = ( const options = queryOptions( objectStorageQueries - .bucket(clusterId, bucketName) + .bucket(regionId, bucketName) ._ctx.objects._ctx.acl(name) ); return useMutation<{}, APIError[], ACLType>({ - mutationFn: (data) => updateObjectACL(clusterId, bucketName, name, data), + mutationFn: (data) => updateObjectACL(regionId, bucketName, name, data), onSuccess(_, acl) { queryClient.setQueryData(options.queryKey, (oldData) => ({ acl, @@ -311,36 +292,8 @@ export const useCreateBucketMutation = () => { export const useDeleteBucketMutation = () => { const queryClient = useQueryClient(); - return useMutation<{}, APIError[], { cluster: string; label: string }>({ + return useMutation<{}, APIError[], { label: string; regionId: string }>({ mutationFn: deleteBucket, - onSuccess: (_, variables) => { - queryClient.setQueryData( - objectStorageQueries.buckets.queryKey, - (oldData) => ({ - buckets: - oldData?.buckets.filter( - (bucket) => - !( - bucket.cluster === variables.cluster && - bucket.label === variables.label - ) - ) ?? [], - errors: oldData?.errors ?? [], - }) - ); - }, - }); -}; - -/* - @TODO OBJ Multicluster: useDeleteBucketWithRegionMutation is a temporary hook, - once feature is rolled out we replace it with existing useDeleteBucketMutation - by updating it with region instead of cluster. -*/ -export const useDeleteBucketWithRegionMutation = () => { - const queryClient = useQueryClient(); - return useMutation<{}, APIError[], { label: string; region: string }>({ - mutationFn: deleteBucketWithRegion, onSuccess: (_, variables) => { queryClient.setQueryData( objectStorageQueries.buckets.queryKey, @@ -349,7 +302,7 @@ export const useDeleteBucketWithRegionMutation = () => { oldData?.buckets.filter( (bucket: ObjectStorageBucket) => !( - bucket.region === variables.region && + bucket.region === variables.regionId && bucket.label === variables.label ) ) ?? [], @@ -361,16 +314,16 @@ export const useDeleteBucketWithRegionMutation = () => { }; export const getObjectBucketObjectsQueryKey = ( - clusterId: string, + regionId: string, bucket: string, prefix: string ) => [ - ...objectStorageQueries.bucket(clusterId, bucket)._ctx.objects.queryKey, + ...objectStorageQueries.bucket(regionId, bucket)._ctx.objects.queryKey, ...prefixToQueryKey(prefix), ]; export const useObjectBucketObjectsInfiniteQuery = ( - clusterId: string, + regionId: string, bucket: string, prefix: string ) => @@ -380,16 +333,13 @@ export const useObjectBucketObjectsInfiniteQuery = ( queryFn: ({ pageParam }) => getObjectList({ bucket, - clusterId, + regionId, params: { delimiter, marker: pageParam as string | undefined, prefix }, }), - queryKey: getObjectBucketObjectsQueryKey(clusterId, bucket, prefix), + queryKey: getObjectBucketObjectsQueryKey(regionId, bucket, prefix), }); -export const useCreateObjectUrlMutation = ( - clusterId: string, - bucketName: string -) => +export const useCreateObjectUrlMutation = (regionId: string, bucket: string) => useMutation< ObjectStorageObjectURL, APIError[], @@ -400,15 +350,15 @@ export const useCreateObjectUrlMutation = ( } >({ mutationFn: ({ method, name, options }) => - getObjectURL(clusterId, bucketName, name, method, options), + getObjectURL(regionId, bucket, name, method, options), }); -export const useBucketSSLQuery = (cluster: string, bucket: string) => +export const useBucketSSLQuery = (region: string, bucket: string) => useQuery( - objectStorageQueries.bucket(cluster, bucket)._ctx.ssl + objectStorageQueries.bucket(region, bucket)._ctx.ssl ); -export const useBucketSSLMutation = (cluster: string, bucket: string) => { +export const useBucketSSLMutation = (region: string, bucket: string) => { const queryClient = useQueryClient(); return useMutation< @@ -416,24 +366,27 @@ export const useBucketSSLMutation = (cluster: string, bucket: string) => { APIError[], CreateObjectStorageBucketSSLPayload >({ - mutationFn: (data) => uploadSSLCert(cluster, bucket, data), + mutationFn: (data) => uploadSSLCert(region, bucket, data), onSuccess(data) { queryClient.setQueryData( - objectStorageQueries.bucket(cluster, bucket)._ctx.ssl.queryKey, + objectStorageQueries.bucket(region, bucket)._ctx.ssl.queryKey, data ); }, }); }; -export const useBucketSSLDeleteMutation = (cluster: string, bucket: string) => { +export const useBucketSSLDeleteMutation = ( + regionId: string, + bucket: string +) => { const queryClient = useQueryClient(); return useMutation<{}, APIError[]>({ - mutationFn: () => deleteSSLCert(cluster, bucket), + mutationFn: () => deleteSSLCert(regionId, bucket), onSuccess() { queryClient.setQueryData( - objectStorageQueries.bucket(cluster, bucket)._ctx.ssl.queryKey, + objectStorageQueries.bucket(regionId, bucket)._ctx.ssl.queryKey, { ssl: false } ); }, diff --git a/packages/manager/src/queries/object-storage/requests.ts b/packages/manager/src/queries/object-storage/requests.ts index 4d9170f4c9c..9833c4166c0 100644 --- a/packages/manager/src/queries/object-storage/requests.ts +++ b/packages/manager/src/queries/object-storage/requests.ts @@ -1,7 +1,6 @@ import { getBuckets, getBucketsInRegion, - getClusters, getObjectStorageEndpoints, getObjectStorageTypes, } from '@linode/api-v4'; @@ -16,12 +15,6 @@ import type { Region, } from '@linode/api-v4'; -/** - * @deprecated This will be replaced with `getAllObjectStorageEndpoints` when OBJ Gen2 is in GA. - */ -export const getAllObjectStorageClusters = () => - getAll(() => getClusters())().then((data) => data.data); - export const getAllObjectStorageBuckets = () => getAll(() => getBuckets())().then((data) => data.data); diff --git a/packages/manager/src/routes/objectStorage/index.ts b/packages/manager/src/routes/objectStorage/index.ts index ad2d05b180c..8ffc8e0ac69 100644 --- a/packages/manager/src/routes/objectStorage/index.ts +++ b/packages/manager/src/routes/objectStorage/index.ts @@ -72,7 +72,7 @@ const objectStorageAccessKeyCreateRoute = createRoute({ const objectStorageBucketDetailRoute = createRoute({ getParentRoute: () => objectStorageRoute, - path: 'buckets/$clusterId/$bucketName', + path: 'buckets/$regionId/$bucketName', validateSearch: (search: ObjectStorageDetailSearchParams) => search, }).lazy(() => import(