Skip to content

Commit

Permalink
upcoming: [M3-8296] - APIv4, Validation & Endpoints for OBJGen2 (lino…
Browse files Browse the repository at this point in the history
…de#10677)

Co-authored-by: Jaalah Ramos <[email protected]>
  • Loading branch information
jaalah-akamai and jaalah authored Jul 17, 2024
1 parent 0822ae6 commit e60faef
Show file tree
Hide file tree
Showing 19 changed files with 161 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Upcoming Features
---

Added new /v4/object-storage/endpoints endpoint ([#10677](https://github.com/linode/manager/pull/10677))
19 changes: 18 additions & 1 deletion packages/api-v4/src/object-storage/objects.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { API_ROOT } from '../constants';
import Request, { setData, setMethod, setURL } from '../request';
import Request, {
setData,
setMethod,
setParams,
setURL,
setXFilter,
} from '../request';
import {
ACLType,
ObjectStorageEndpointsResponse,
ObjectStorageObjectACL,
ObjectStorageObjectURL,
ObjectStorageObjectURLOptions,
} from './types';

import type { ResourcePage, RequestOptions } from '../types';

/**
* Gets a URL to upload/download/delete Objects from a Bucket.
*/
Expand Down Expand Up @@ -70,3 +79,11 @@ export const updateObjectACL = (
),
setData({ acl, name })
);

export const getObjectStorageEndpoints = ({ filter, params }: RequestOptions) =>
Request<ResourcePage<ObjectStorageEndpointsResponse[]>>(
setMethod('GET'),
setURL(`${API_ROOT}/object-storage/endpoints`),
setParams(params),
setXFilter(filter)
);
34 changes: 27 additions & 7 deletions packages/api-v4/src/object-storage/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export interface RegionS3EndpointAndID {
export type ObjEndpointTypes = 'E0' | 'E1' | 'E2' | 'E3';

export interface ObjAccessKeyRegionsResponse {
id: string;
s3_endpoint: string;
endpoint_type?: ObjEndpointTypes;
}

export interface ObjectStorageKey {
Expand All @@ -9,7 +12,7 @@ export interface ObjectStorageKey {
id: number;
label: string;
limited: boolean;
regions: RegionS3EndpointAndID[];
regions: ObjAccessKeyRegionsResponse[];
secret_key: string;
}

Expand Down Expand Up @@ -45,6 +48,7 @@ export interface ObjectStorageBucketRequestPayload {
cors_enabled?: boolean;
label: string;
region?: string;
endpoint_type?: ObjEndpointTypes;
/*
@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'
Expand Down Expand Up @@ -73,6 +77,8 @@ export interface ObjectStorageBucket {
hostname: string;
objects: number;
size: number; // Size of bucket in bytes
s3_endpoint?: string;
endpoint_type?: ObjEndpointTypes;
}

export interface ObjectStorageObject {
Expand All @@ -88,16 +94,23 @@ export interface ObjectStorageObjectURL {
url: string;
}

export interface ObjectStorageEndpointsResponse {
region: string;
endpoint_type: ObjEndpointTypes;
s3_endpoint: string | null;
}

export type ACLType =
| 'private'
| 'public-read'
| 'authenticated-read'
| 'public-read-write'
| 'custom';

// Gen2 endpoints ('E2', 'E3') are not supported and will return null.
export interface ObjectStorageObjectACL {
acl: ACLType;
acl_xml: string;
acl: ACLType | null;
acl_xml: string | null;
}

export interface ObjectStorageObjectURLOptions {
Expand Down Expand Up @@ -142,18 +155,25 @@ export interface ObjectStorageBucketSSLRequest {
private_key: string;
}

// Gen2 endpoints ('E2', 'E3') are not supported and will return null.
export interface ObjectStorageBucketSSLResponse {
ssl: boolean;
ssl: boolean | null;
}

export interface ObjectStorageBucketAccessRequest {
acl?: Omit<ACLType, 'custom'>;
cors_enabled?: boolean;
}

export interface ObjBucketAccessPayload {
acl: ACLType;
cors_enabled?: boolean;
}

// Gen2 endpoints ('E2', 'E3') are not supported and will return null.
export interface ObjectStorageBucketAccessResponse {
acl: ACLType;
acl_xml: string;
cors_enabled: boolean;
cors_xml: string;
cors_enabled: boolean | null;
cors_xml: string | null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Object Storage Gen2 cors_enabled and type updates ([#10677](https://github.com/linode/manager/pull/10677))
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @file End-to-end tests for Object Storage Access Key operations.
*/

import { objectStorageBucketFactory } from 'src/factories/objectStorage';
import { createObjectStorageBucketFactory } from 'src/factories/objectStorage';
import { authenticate } from 'support/api/authentication';
import { createBucket } from '@linode/api-v4/lib/object-storage';
import {
Expand Down Expand Up @@ -120,7 +120,7 @@ describe('object storage access key end-to-end tests', () => {
it('can create an access key with limited access - e2e', () => {
const bucketLabel = randomLabel();
const bucketCluster = 'us-east-1';
const bucketRequest = objectStorageBucketFactory.build({
const bucketRequest = createObjectStorageBucketFactory.build({
label: bucketLabel,
cluster: bucketCluster,
// Default factory sets `cluster` and `region`, but API does not accept `region` yet.
Expand Down
17 changes: 15 additions & 2 deletions packages/manager/src/factories/objectStorage.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import {
import Factory from 'src/factories/factoryProxy';

import type {
ObjectStorageBucket,
ObjectStorageBucketRequestPayload,
ObjectStorageCluster,
ObjectStorageKey,
ObjectStorageObject,
} from '@linode/api-v4/lib/object-storage/types';
import Factory from 'src/factories/factoryProxy';

export const objectStorageBucketFactory = Factory.Sync.makeFactory<ObjectStorageBucket>(
{
Expand All @@ -20,6 +22,17 @@ export const objectStorageBucketFactory = Factory.Sync.makeFactory<ObjectStorage
}
);

export const createObjectStorageBucketFactory = Factory.Sync.makeFactory<ObjectStorageBucketRequestPayload>(
{
acl: 'private',
cluster: 'us-east-1',
cors_enabled: true,
endpoint_type: 'E1',
label: Factory.each((i) => `obj-bucket-${i}`),
region: 'us-east',
}
);

export const objectStorageClusterFactory = Factory.Sync.makeFactory<ObjectStorageCluster>(
{
domain: Factory.each((id) => `cluster-${id}.linodeobjects.com`),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
ObjectStorageKey,
RegionS3EndpointAndID,
ObjAccessKeyRegionsResponse,
} from '@linode/api-v4/lib/object-storage';
import { APIError } from '@linode/api-v4/lib/types';
import { styled } from '@mui/material/styles';
Expand Down Expand Up @@ -41,7 +41,7 @@ export const AccessKeyTable = (props: AccessKeyTableProps) => {
const [showHostNamesDrawer, setShowHostNamesDrawers] = useState<boolean>(
false
);
const [hostNames, setHostNames] = useState<RegionS3EndpointAndID[]>([]);
const [hostNames, setHostNames] = useState<ObjAccessKeyRegionsResponse[]>([]);

const flags = useFlags();
const { account } = useAccountManagement();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
ObjectStorageKey,
RegionS3EndpointAndID,
ObjAccessKeyRegionsResponse,
} from '@linode/api-v4/lib/object-storage';
import { APIError } from '@linode/api-v4/lib/types';
import React from 'react';
Expand All @@ -19,7 +19,7 @@ type Props = {
isRestrictedUser: boolean;
openDrawer: OpenAccessDrawer;
openRevokeDialog: (objectStorageKey: ObjectStorageKey) => void;
setHostNames: (hostNames: RegionS3EndpointAndID[]) => void;
setHostNames: (hostNames: ObjAccessKeyRegionsResponse[]) => void;
setShowHostNamesDrawers: (show: boolean) => void;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
ObjectStorageKey,
RegionS3EndpointAndID,
ObjAccessKeyRegionsResponse,
} from '@linode/api-v4/lib/object-storage';
import { styled } from '@mui/material/styles';
import React from 'react';
Expand All @@ -20,7 +20,7 @@ import { HostNameTableCell } from './HostNameTableCell';
type Props = {
openDrawer: OpenAccessDrawer;
openRevokeDialog: (storageKeyData: ObjectStorageKey) => void;
setHostNames: (hostNames: RegionS3EndpointAndID[]) => void;
setHostNames: (hostNames: ObjAccessKeyRegionsResponse[]) => void;
setShowHostNamesDrawers: (show: boolean) => void;
storageKeyData: ObjectStorageKey;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { getRegionsByRegionId } from 'src/utilities/regions';

import type {
ObjectStorageKey,
RegionS3EndpointAndID,
ObjAccessKeyRegionsResponse,
} from '@linode/api-v4/lib/object-storage';

type Props = {
setHostNames: (hostNames: RegionS3EndpointAndID[]) => void;
setHostNames: (hostNames: ObjAccessKeyRegionsResponse[]) => void;
setShowHostNamesDrawers: (show: boolean) => void;
storageKeyData: ObjectStorageKey;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RegionS3EndpointAndID } from '@linode/api-v4';
import { ObjAccessKeyRegionsResponse } from '@linode/api-v4';
import * as React from 'react';

import { Box } from 'src/components/Box';
Expand All @@ -12,7 +12,7 @@ import { CopyAllHostnames } from './CopyAllHostnames';
interface Props {
onClose: () => void;
open: boolean;
regions: RegionS3EndpointAndID[];
regions: ObjAccessKeyRegionsResponse[];
}

export const HostNamesDrawer = (props: Props) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ACLType } from '@linode/api-v4/lib/object-storage';
import { Theme, styled } from '@mui/material/styles';
import { styled } from '@mui/material/styles';
import * as React from 'react';

import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
Expand All @@ -18,18 +17,26 @@ import { getErrorStringOrDefault } from 'src/utilities/errorUtils';
import { bucketACLOptions, objectACLOptions } from '../utilities';
import { copy } from './AccessSelect.data';

interface AccessPayload {
acl: ACLType;
cors_enabled?: boolean;
}
import type {
ACLType,
ObjBucketAccessPayload,
ObjectStorageObjectACL,
} from '@linode/api-v4/lib/object-storage';
import type { Theme } from '@mui/material/styles';

export interface Props {
getAccess: () => Promise<AccessPayload>;
getAccess: () => Promise<ObjBucketAccessPayload | ObjectStorageObjectACL>;
name: string;
updateAccess: (acl: ACLType, cors_enabled?: boolean) => Promise<{}>;
variant: 'bucket' | 'object';
}

function isObjBucketAccessPayload(
payload: ObjBucketAccessPayload | ObjectStorageObjectACL
): payload is ObjBucketAccessPayload {
return 'cors_enabled' in payload;
}

export const AccessSelect = React.memo((props: Props) => {
const { getAccess, name, updateAccess, variant } = props;
// Access data for this Object (from the API).
Expand All @@ -40,7 +47,7 @@ export const AccessSelect = React.memo((props: Props) => {
// The ACL Option currently selected in the <EnhancedSelect /> component.
const [selectedACL, setSelectedACL] = React.useState<ACLType | null>(null);
// The CORS Option currently selected in the <Toggle /> component.
const [selectedCORSOption, setSelectedCORSOption] = React.useState(true);
const [selectedCORSOption, setSelectedCORSOption] = React.useState(true); // TODO: OBJGen2 - We need to handle this in upcoming PR
// State for submitting access options.
const [updateAccessLoading, setUpdateAccessLoading] = React.useState(false);
const [updateAccessError, setUpdateAccessError] = React.useState('');
Expand All @@ -55,17 +62,22 @@ export const AccessSelect = React.memo((props: Props) => {
setUpdateAccessSuccess(false);
setAccessLoading(true);
getAccess()
.then(({ acl, cors_enabled }) => {
.then((payload) => {
setAccessLoading(false);
const { acl } = payload;
// Don't show "public-read-write" for Objects here; use "custom" instead
// since "public-read-write" Objects are basically the same as "public-read".
const _acl =
variant === 'object' && acl === 'public-read-write' ? 'custom' : acl;
setACLData(_acl);
setSelectedACL(_acl);
if (typeof cors_enabled !== 'undefined') {
setCORSData(cors_enabled);
setSelectedCORSOption(cors_enabled);

if (isObjBucketAccessPayload(payload)) {
const { cors_enabled } = payload;
if (typeof cors_enabled === 'boolean') {
setCORSData(cors_enabled);
setSelectedCORSOption(cors_enabled);
}
}
})
.catch((err) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
ACLType,
getBucketAccess,
updateBucketAccess,
} from '@linode/api-v4/lib/object-storage';
Expand All @@ -11,6 +10,8 @@ import { Typography } from 'src/components/Typography';

import { AccessSelect } from './AccessSelect';

import type { ACLType } from '@linode/api-v4/lib/object-storage';

export const StyledRootContainer = styled(Paper, {
label: 'StyledRootContainer',
})(({ theme }) => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
ACLType,
getObjectACL,
updateObjectACL,
} from '@linode/api-v4/lib/object-storage';
Expand All @@ -18,6 +17,8 @@ import { readableBytes } from 'src/utilities/unitConversions';

import { AccessSelect } from './AccessSelect';

import type { ACLType } from '@linode/api-v4/lib/object-storage';

export interface ObjectDetailsDrawerProps {
bucketName: string;
clusterId: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Region } from '@linode/api-v4';
import {
ACLType,
getBucketAccess,
updateBucketAccess,
} from '@linode/api-v4/lib/object-storage';
Expand All @@ -24,6 +22,9 @@ import { truncateMiddle } from 'src/utilities/truncate';
import { readableBytes } from 'src/utilities/unitConversions';

import { AccessSelect } from '../BucketDetail/AccessSelect';

import type { Region } from '@linode/api-v4';
import type { ACLType } from '@linode/api-v4/lib/object-storage';
export interface BucketDetailsDrawerProps {
bucketLabel?: string;
bucketRegion?: Region;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const CreateBucketDrawer = (props: Props) => {
const formik = useFormik({
initialValues: {
cluster: '',
cors_enabled: true, // For Gen1, CORS is always enabled
label: '',
},
async onSubmit(values) {
Expand Down
Loading

0 comments on commit e60faef

Please sign in to comment.