Skip to content

Commit

Permalink
ARTESCA-3019: Add oracle cloud object storage support
Browse files Browse the repository at this point in the history
  • Loading branch information
ChengYanJin committed Jul 4, 2024
1 parent 501942e commit f9a27cb
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 2 deletions.
211 changes: 211 additions & 0 deletions src/react/locations/LocationDetails/LocationDetailsOracle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import { Input } from '@scality/core-ui/dist/components/inputv2/inputv2';
import React, { useEffect, useState } from 'react';
import { LocationDetailsFormProps } from '.';
import { XDM_FEATURE } from '../../../js/config';
import { HelpLocationCreationAsyncNotification } from '../../ui-elements/Help';
import { isIngestSource } from '../../utils/storageOptions';
import { storageOptions } from './storageOptions';
import {
FormGroup,
FormSection,
} from '@scality/core-ui/dist/components/form/Form.component';
import { Checkbox } from '@scality/core-ui/dist/components/checkbox/Checkbox.component';
import { useConfig } from '../../next-architecture/ui/ConfigProvider';

type State = {
bucketMatch: boolean;
accessKey: string;
secretKey: string;
bucketName: string;
namespace: string;
region: string;
endpoint: string;
};
const INIT_STATE: State = {
bucketMatch: false,
accessKey: '',
secretKey: '',
bucketName: '',
namespace: '',
region: '',
endpoint: '',
};

export const oracleCloudEndpointBuilder = (namespace: string, region: string) =>
`https://${namespace}.compat.objectstorage.${region}.oraclecloud.com`;

export default function LocationDetailsOracle({
capabilities,
details,
editingExisting,
locationType,
onChange,
}: LocationDetailsFormProps) {
const [formState, setFormState] = useState<State>(() => {
return {
...Object.assign({}, INIT_STATE, details, { secretKey: '' }),
};
});
const onFormItemChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const target = e.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
if (target.name === 'namespace' || target.name === 'region') {
setFormState({
...formState,
[target.name]: value,
endpoint: oracleCloudEndpointBuilder(
target.name === 'namespace' ? (value as string) : formState.namespace,
target.name === 'region' ? (value as string) : formState.region,
),
});
} else {
setFormState({ ...formState, [target.name]: value });
}

if (onChange) {
//remove the namespace and region from the formState
//as it is not part of the LocationS3Details
const { namespace, region, ...rest } = formState;
onChange({ ...rest, [target.name]: value });
}
};

//TODO check why the tests expect onChange to be called on mount
useEffect(() => {
const { namespace, region, ...rest } = formState;
onChange({ ...rest, endpoint: formState.endpoint });
}, []);
const isIngest = isIngestSource(storageOptions, locationType, capabilities);
const { features } = useConfig();

return (
<>
<FormSection>
<FormGroup
id="accessKey"
content={
<Input
name="accessKey"
id="accessKey"
type="text"
placeholder="AKI5HMPCLRB86WCKTN2C"
value={formState.accessKey}
onChange={onFormItemChange}
autoComplete="off"
/>
}
required
label="Access Key"
helpErrorPosition="bottom"
/>

<FormGroup
id="secretKey"
label="Secret Key"
required
labelHelpTooltip="Your credentials are encrypted in transit, then at rest using your instance's RSA key pair so that we're unable to see them."
helpErrorPosition="bottom"
content={
<Input
name="secretKey"
id="secretKey"
type="password"
placeholder="QFvIo6l76oe9xgCAw1N/zlPFtdTSZXMMUuANeXc6"
value={formState.secretKey}
onChange={onFormItemChange}
autoComplete="new-password"
/>
}
/>
<FormGroup
id="bucketName"
label="Target Bucket Name"
help="The Target Bucket on your location needs to have Versioning enabled."
required
content={
<Input
name="bucketName"
id="bucketName"
type="text"
placeholder="bucket-name"
value={formState.bucketName}
onChange={onFormItemChange}
autoComplete="off"
disabled={editingExisting}
/>
}
helpErrorPosition="bottom"
/>
<FormGroup
id="namespace"
label="Namespace"
content={
<Input
id="namespace"
name="namespace"
type="text"
value={formState.namespace}
onChange={onFormItemChange}
placeholder="object_storage_namespace"
/>
}
required
labelHelpTooltip="The namespace of the object storage."
helpErrorPosition="bottom"
/>
<FormGroup
id="region"
label="Region"
content={
<Input
id="region"
name="region"
type="text"
value={formState.region}
onChange={onFormItemChange}
placeholder="eu-paris-1"
/>
}
required
labelHelpTooltip="The region of the object storage."
helpErrorPosition="bottom"
/>
</FormSection>
{(isIngest && features.includes(XDM_FEATURE)) || !isIngest ? (
<FormSection>
<FormGroup
label=""
direction="vertical"
id="bucketMatch"
content={
<Checkbox
name="bucketMatch"
disabled={editingExisting}
checked={formState.bucketMatch}
onChange={onFormItemChange}
//@ts-expect-error fix this when you are working on it
label={
isIngest ? (
<>
Async Metadata updates Ready{' '}
<HelpLocationCreationAsyncNotification />{' '}
</>
) : (
'Write objects without prefix'
)
}
/>
}
helpErrorPosition="bottom"
help="Your objects will be stored in the target bucket without a source-bucket prefix."
error={
formState.bucketMatch
? 'Storing multiple buckets in a location with this option enabled can lead to data loss.'
: undefined
}
/>
</FormSection>
) : null}
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Wrapper } from '../../../utils/testUtil';
import LocationDetailsOracle from '../LocationDetailsOracle';
import { ORACLE_CLOUD_LOCATION_KEY } from '../../../../types/config';

const selectors = {
namespaceSelector: () => screen.getByLabelText(/Namespace/),
regionSelector: () => screen.getByLabelText(/Region/),
targetBucketSelector: () => screen.getByLabelText(/Target Bucket Name/),
accessKeySelector: () => screen.getByLabelText(/Access Key/),
secretKeySelector: () => screen.getByLabelText(/Secret Key/),
};
const props = {
details: {},
onChange: () => {},
locationType: ORACLE_CLOUD_LOCATION_KEY,
};
const namespace = 'namespace';
const region = 'eu-paris-1';
describe('class <LocationDetailsOracle />', () => {
it('should call onChange on mount', async () => {
//S
let location = {};
render(
//@ts-ignore
<LocationDetailsOracle {...props} onChange={(l) => (location = l)} />,
{ wrapper: Wrapper },
);
waitFor(() => {
expect(selectors.namespaceSelector()).toBeInTheDocument();
});
//E
await userEvent.type(selectors.namespaceSelector(), namespace);
await userEvent.type(selectors.regionSelector(), region);
await userEvent.type(selectors.targetBucketSelector(), 'target-bucket');
await userEvent.type(selectors.accessKeySelector(), 'accessKey');
await userEvent.type(selectors.secretKeySelector(), 'secretKey');
expect(location).toEqual({
bucketMatch: false,
endpoint: `https://${namespace}.compat.objectstorage.${region}.oraclecloud.com`,
bucketName: 'target-bucket',
accessKey: 'accessKey',
secretKey: 'secretKey',
});
});
});
11 changes: 11 additions & 0 deletions src/react/locations/LocationDetails/storageOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import LocationDetailsDOSpaces from './LocationDetailsDOSpaces';
import LocationDetailsGcp from './LocationDetailsGcp';
import LocationDetailsHyperdriveV2 from './LocationDetailsHyperdriveV2';
import LocationDetailsNFS from './LocationDetailsNFS';
import LocationDetailsOracle from './LocationDetailsOracle';
import LocationDetailsSproxyd from './LocationDetailsSproxyd';
import LocationDetailsTapeDMF from './LocationDetailsTapeDMF';
import LocationDetailsWasabi from './LocationDetailsWasabi';
Expand Down Expand Up @@ -213,4 +214,14 @@ export const storageOptions: Record<LocationTypeKey, StorageOptionValues> = {
supportsReplicationSource: true,
hasIcon: false,
},
'location-oracle-ring-s3-v1': {
name: 'Oracle Cloud Object Storage',
short: 'Oracle',
formDetails: LocationDetailsOracle,
supportsVersioning: true,
supportsReplicationTarget: true,
supportsReplicationSource: true,
hasIcon: false,
checkCapability: 'locationTypeS3Custom',
},
};
4 changes: 3 additions & 1 deletion src/react/locations/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
JAGUAR_S3_LOCATION_KEY,
Location as LegacyLocation,
LocationTypeKey,
ORACLE_CLOUD_LOCATION_KEY,
ORANGE_S3_LOCATION_KEY,
OUTSCALE_PUBLIC_S3_LOCATION_KEY,
OUTSCALE_SNC_S3_LOCATION_KEY,
Expand Down Expand Up @@ -174,7 +175,8 @@ export const checkIsRingS3Reseller = (locationType: LocationTypeKey) => {
locationType === JAGUAR_S3_LOCATION_KEY ||
locationType === ORANGE_S3_LOCATION_KEY ||
locationType === OUTSCALE_PUBLIC_S3_LOCATION_KEY ||
locationType === OUTSCALE_SNC_S3_LOCATION_KEY
locationType === OUTSCALE_SNC_S3_LOCATION_KEY ||
locationType === ORACLE_CLOUD_LOCATION_KEY
);
};

Expand Down
4 changes: 3 additions & 1 deletion src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const OUTSCALE_SNC_S3_ENDPOINT =
'https://oos.cloudgouv-eu-west-1.outscale.com';
export const OUTSCALE_SNC_S3_LOCATION_KEY = 'location-3ds-outscale-oos-snc';

export const ORACLE_CLOUD_LOCATION_KEY = 'location-oracle-ring-s3-v1';
export type LocationName = string;

type LocationS3Type =
Expand All @@ -26,7 +27,8 @@ type LocationS3Type =
| 'location-orange-ring-s3-v1'
| 'location-aws-s3-v1'
| 'location-3ds-outscale-oos-public'
| 'location-3ds-outscale-oos-snc';
| 'location-3ds-outscale-oos-snc'
| 'location-oracle-ring-s3-v1';
type LocationFSType =
| 'location-scality-hdclient-v2'
| 'location-aws-s3-v1'
Expand Down

0 comments on commit f9a27cb

Please sign in to comment.