diff --git a/.jest-setup.js b/.jest-setup.js
index fac76fbe3..cfd16edec 100644
--- a/.jest-setup.js
+++ b/.jest-setup.js
@@ -4,3 +4,6 @@ import 'core-js/stable';
import 'regenerator-runtime/runtime';
Enzyme.configure({ adapter: new Adapter() });
+HTMLCanvasElement.prototype.getContext = () => {
+ // return whatever getContext has to return
+};
diff --git a/package-lock.json b/package-lock.json
index 26eb46402..eb21b1c9b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,7 @@
"@hapi/joi-date": "^2.0.1",
"@hookform/resolvers": "^2.8.8",
"@monaco-editor/react": "^4.4.5",
- "@scality/core-ui": "0.121.0",
+ "@scality/core-ui": "0.122.0",
"@scality/module-federation": "github:scality/module-federation#1.1.0",
"@types/react-table": "^7.7.10",
"@types/react-virtualized": "^9.21.20",
@@ -3322,9 +3322,9 @@
"dev": true
},
"node_modules/@scality/core-ui": {
- "version": "0.121.0",
- "resolved": "https://registry.npmjs.org/@scality/core-ui/-/core-ui-0.121.0.tgz",
- "integrity": "sha512-hmGLm8KrhRAAoKM+43egEt5qRrQrvRSVXN7VPav3ReFEvUDsbdWqvbI2xQO0zn9A/H9Y1biz4X2jvHMbWtYzPg==",
+ "version": "0.122.0",
+ "resolved": "https://registry.npmjs.org/@scality/core-ui/-/core-ui-0.122.0.tgz",
+ "integrity": "sha512-61rXypM9Z845lN7+5jqeuof3ZLZe8tIfcdaVj2FoFLP97nXUuNaKRyBEoWUc0ESSFl9U87ReU1jMOKIOJlWnsg==",
"dependencies": {
"@floating-ui/dom": "^1.6.3",
"@fortawesome/fontawesome-free": "^5.10.2",
@@ -3359,46 +3359,6 @@
"vega-tooltip": "0.27.0"
}
},
- "node_modules/@scality/core-ui/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@scality/core-ui/node_modules/cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^7.0.0"
- }
- },
- "node_modules/@scality/core-ui/node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/@scality/core-ui/node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
- },
"node_modules/@scality/core-ui/node_modules/compute-scroll-into-view": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
@@ -3530,47 +3490,6 @@
"resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.16.1.tgz",
"integrity": "sha512-FdgD72fmZMPJE99FxvFXth0IL4BbLA93WmBg/lvcJmfkK4Uf90WIlvGwaIUdSePIsdpkZjBPyQcHMQ8OcS8Smg=="
},
- "node_modules/@scality/core-ui/node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/@scality/core-ui/node_modules/yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
- "dependencies": {
- "cliui": "^7.0.2",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@scality/core-ui/node_modules/yargs-parser": {
- "version": "20.2.9",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
- "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/@scality/module-federation": {
"version": "1.1.0",
"resolved": "git+ssh://git@github.com/scality/module-federation.git#fcced6f064ecd8f152daf5137113d8fd38648ef6",
@@ -19984,7 +19903,6 @@
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
- "dev": true,
"dependencies": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
@@ -22367,9 +22285,9 @@
"dev": true
},
"@scality/core-ui": {
- "version": "0.121.0",
- "resolved": "https://registry.npmjs.org/@scality/core-ui/-/core-ui-0.121.0.tgz",
- "integrity": "sha512-hmGLm8KrhRAAoKM+43egEt5qRrQrvRSVXN7VPav3ReFEvUDsbdWqvbI2xQO0zn9A/H9Y1biz4X2jvHMbWtYzPg==",
+ "version": "0.122.0",
+ "resolved": "https://registry.npmjs.org/@scality/core-ui/-/core-ui-0.122.0.tgz",
+ "integrity": "sha512-61rXypM9Z845lN7+5jqeuof3ZLZe8tIfcdaVj2FoFLP97nXUuNaKRyBEoWUc0ESSFl9U87ReU1jMOKIOJlWnsg==",
"requires": {
"@floating-ui/dom": "^1.6.3",
"@fortawesome/fontawesome-free": "^5.10.2",
@@ -22404,37 +22322,6 @@
"vega-tooltip": "0.27.0"
},
"dependencies": {
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
- "requires": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^7.0.0"
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
- },
"compute-scroll-into-view": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
@@ -22536,35 +22423,6 @@
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.16.1.tgz",
"integrity": "sha512-FdgD72fmZMPJE99FxvFXth0IL4BbLA93WmBg/lvcJmfkK4Uf90WIlvGwaIUdSePIsdpkZjBPyQcHMQ8OcS8Smg=="
- },
- "wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "requires": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- }
- },
- "yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
- "requires": {
- "cliui": "^7.0.2",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- }
- },
- "yargs-parser": {
- "version": "20.2.9",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
- "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="
}
}
},
@@ -35081,7 +34939,6 @@
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
- "dev": true,
"requires": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
diff --git a/package.json b/package.json
index 6f64d4248..d01a5e126 100644
--- a/package.json
+++ b/package.json
@@ -71,7 +71,7 @@
"@hapi/joi-date": "^2.0.1",
"@hookform/resolvers": "^2.8.8",
"@monaco-editor/react": "^4.4.5",
- "@scality/core-ui": "0.121.0",
+ "@scality/core-ui": "0.122.0",
"@scality/module-federation": "github:scality/module-federation#1.1.0",
"@types/react-table": "^7.7.10",
"@types/react-virtualized": "^9.21.20",
diff --git a/src/react/DataServiceRoleProvider.tsx b/src/react/DataServiceRoleProvider.tsx
index 45a4b0327..b31ec9256 100644
--- a/src/react/DataServiceRoleProvider.tsx
+++ b/src/react/DataServiceRoleProvider.tsx
@@ -5,6 +5,7 @@ import { getRoleArnStored, setRoleArnStored } from './utils/localStorage';
import { useMutation } from 'react-query';
import {
S3ClientProvider,
+ S3ClientWithoutReduxProvider,
useAssumeRoleQuery,
useS3ConfigFromAssumeRoleResult,
} from './next-architecture/ui/S3ClientProvider';
@@ -94,7 +95,18 @@ export const useCurrentAccount = () => {
};
};
-const DataServiceRoleProvider = ({ children }: { children: JSX.Element }) => {
+const DataServiceRoleProvider = ({
+ children,
+ /**
+ * DoNotChangePropsWithRedux is a static props.
+ * When set, it must not be changed, otherwise it will break the hook rules.
+ * To be removed when we remove redux.
+ */
+ DoNotChangePropsWithRedux = true,
+}: {
+ children: JSX.Element;
+ DoNotChangePropsWithRedux?: boolean;
+}) => {
const [role, setRoleState] = useState<{ roleArn: string }>({
roleArn: '',
});
@@ -121,7 +133,7 @@ const DataServiceRoleProvider = ({ children }: { children: JSX.Element }) => {
const storedRole = getRoleArnStored();
if (accountName) {
const account = accounts.find((account) => account.Name === accountName);
- if (account) {
+ if (account && !role.roleArn) {
setRoleState({ roleArn: account?.Roles[0].Arn });
}
} else if (!role.roleArn && storedRole && accounts.length) {
@@ -138,6 +150,7 @@ const DataServiceRoleProvider = ({ children }: { children: JSX.Element }) => {
} else if (!storedRole && !role.roleArn && accounts.length) {
setRoleState({ roleArn: accounts[0].Roles[0].Arn });
}
+
if (role.roleArn) {
assumeRoleMutation.mutate(role.roleArn);
}
@@ -171,8 +184,25 @@ const DataServiceRoleProvider = ({ children }: { children: JSX.Element }) => {
return Loading...;
}
+ if (DoNotChangePropsWithRedux) {
+ return (
+
+ <_DataServiceRoleContext.Provider
+ value={{
+ role,
+ setRole,
+ setRolePromise,
+ assumedRole,
+ }}
+ >
+ {children}
+
+
+ );
+ }
+
return (
-
+
<_DataServiceRoleContext.Provider
value={{
role,
@@ -183,7 +213,7 @@ const DataServiceRoleProvider = ({ children }: { children: JSX.Element }) => {
>
{children}
-
+
);
};
diff --git a/src/react/endpoint/EndpointList.tsx b/src/react/endpoint/EndpointList.tsx
index 6970b13f1..52d85c489 100644
--- a/src/react/endpoint/EndpointList.tsx
+++ b/src/react/endpoint/EndpointList.tsx
@@ -14,6 +14,7 @@ import {
cloudServerDashboard,
} from './AdvancedMetricsButton';
import { DeleteEndpoint } from './DeleteEndpoint';
+
type CellProps = {
row: {
original: Endpoint;
diff --git a/src/react/next-architecture/adapters/accessible-accounts/IAMPensieveAccessibleAccounts.ts b/src/react/next-architecture/adapters/accessible-accounts/IAMPensieveAccessibleAccounts.ts
index 0253b8b82..7f4fdfaf4 100644
--- a/src/react/next-architecture/adapters/accessible-accounts/IAMPensieveAccessibleAccounts.ts
+++ b/src/react/next-architecture/adapters/accessible-accounts/IAMPensieveAccessibleAccounts.ts
@@ -1,4 +1,4 @@
-import { useAccounts } from '../../../utils/hooks';
+import { noopBasedEventDispatcher, useAccounts } from '../../../utils/hooks';
import { useAccountsLocationsAndEndpoints } from '../../domain/business/accounts';
import { AccountInfo, Role } from '../../domain/entities/account';
import { PromiseResult } from '../../domain/entities/promise';
@@ -8,6 +8,7 @@ import { IAccountsLocationsEndpointsAdapter } from '../accounts-locations/IAccou
export class IAMPensieveAccessibleAccounts implements IAccessibleAccounts {
constructor(
private accountsLocationsAndEndpointsAdapter: IAccountsLocationsEndpointsAdapter,
+ private withEventDispatcher = true,
) {}
useListAccessibleAccounts(): {
accountInfos: PromiseResult<(AccountInfo & { assumableRoles: Role[] })[]>;
@@ -17,7 +18,11 @@ export class IAMPensieveAccessibleAccounts implements IAccessibleAccounts {
accountsLocationsEndpointsAdapter:
this.accountsLocationsAndEndpointsAdapter,
});
- const { accounts: accessibleAccounts, status } = useAccounts();
+ const eventDispatcher = this.withEventDispatcher
+ ? undefined
+ : noopBasedEventDispatcher;
+ const { accounts: accessibleAccounts, status } =
+ useAccounts(eventDispatcher);
if (accountStatus === 'error' || status === 'error') {
return {
diff --git a/src/react/next-architecture/ui/AccessibleAccountsAdapterProvider.tsx b/src/react/next-architecture/ui/AccessibleAccountsAdapterProvider.tsx
index 52cf8d2ae..1597a462e 100644
--- a/src/react/next-architecture/ui/AccessibleAccountsAdapterProvider.tsx
+++ b/src/react/next-architecture/ui/AccessibleAccountsAdapterProvider.tsx
@@ -21,12 +21,20 @@ export const useAccessibleAccountsAdapter = (): IAccessibleAccounts => {
export const AccessibleAccountsAdapterProvider = ({
children,
+ /**
+ * DoNotChangePropsWithEventDispatcher is a static props.
+ * When set, it must not be changed, otherwise it will break the hook rules.
+ * To be removed when we remove redux.
+ */
+ DoNotChangePropsWithEventDispatcher = true,
}: {
children: JSX.Element;
+ DoNotChangePropsWithEventDispatcher?: boolean;
}) => {
const accountAdapter = useAccountsLocationsEndpointsAdapter();
const accessibleAccountsAdapter = new IAMPensieveAccessibleAccounts(
accountAdapter,
+ DoNotChangePropsWithEventDispatcher,
);
return (
diff --git a/src/react/next-architecture/ui/AlertProvider.tsx b/src/react/next-architecture/ui/AlertProvider.tsx
index 205252553..5b9696afb 100644
--- a/src/react/next-architecture/ui/AlertProvider.tsx
+++ b/src/react/next-architecture/ui/AlertProvider.tsx
@@ -96,10 +96,19 @@ const AlertProvider = ({ children }: { children: React.ReactNode }) => {
const metalk8sUI = deployedApps.find(
(app: { kind: string }) => app.kind === 'metalk8s-ui',
);
- const metalk8sUIConfig = retrieveConfiguration({
- configType: 'run',
- name: metalk8sUI.name,
- });
+
+ const metalk8sUIConfig = metalk8sUI
+ ? retrieveConfiguration({
+ configType: 'run',
+ name: metalk8sUI.name,
+ })
+ : {
+ spec: {
+ selfConfiguration: {
+ url_alertmanager: '',
+ },
+ },
+ };
return (
diff --git a/src/react/next-architecture/ui/S3ClientProvider.tsx b/src/react/next-architecture/ui/S3ClientProvider.tsx
index 200824f6b..83e92bc47 100644
--- a/src/react/next-architecture/ui/S3ClientProvider.tsx
+++ b/src/react/next-architecture/ui/S3ClientProvider.tsx
@@ -92,9 +92,64 @@ export const S3ClientProvider = ({
);
};
+export const S3ClientWithoutReduxProvider = ({
+ configuration,
+ children,
+}: PropsWithChildren<{
+ configuration: S3.Types.ClientConfiguration;
+}>) => {
+ const { iamEndpoint, iamInternalFQDN, s3InternalFQDN, basePath } =
+ useConfig();
+ const { s3Client, zenkoClient, iamClient } = useMemo(() => {
+ const s3Config = {
+ ...configuration,
+ endpoint: genClientEndpoint(configuration.endpoint as string),
+ };
+ const s3Client = new S3(s3Config);
+ const zenkoClient = new ZenkoClient(
+ s3Config.endpoint,
+ iamInternalFQDN,
+ s3InternalFQDN,
+ process.env.NODE_ENV === 'development' ? '' : basePath,
+ );
+ const iamClient = new IAMClient(iamEndpoint);
+
+ if (
+ configuration.credentials?.accessKeyId &&
+ configuration.credentials?.secretAccessKey &&
+ configuration.credentials?.sessionToken
+ ) {
+ zenkoClient.login({
+ accessKey: configuration.credentials.accessKeyId,
+ secretKey: configuration.credentials.secretAccessKey,
+ sessionToken: configuration.credentials.sessionToken,
+ });
+
+ iamClient.login({
+ accessKey: configuration.credentials.accessKeyId,
+ secretKey: configuration.credentials.secretAccessKey,
+ sessionToken: configuration.credentials.sessionToken,
+ });
+ }
+
+ return { s3Client, zenkoClient, iamClient };
+ }, [configuration]);
+
+ return (
+
+
+ <_IAMContext.Provider value={{ iamClient }}>
+ {children}
+
+
+
+ );
+};
+
export const useAssumeRoleQuery = () => {
const { stsEndpoint } = useConfig();
const token = useAccessToken();
+
const user = useAuth();
const roleSessionName = `ui-${user.userData?.id}`;
const stsClient = new STSClient({ endpoint: stsEndpoint });
@@ -102,20 +157,22 @@ export const useAssumeRoleQuery = () => {
return {
queryKey,
- getQuery: (roleArn: string) => ({
- queryKey,
- queryFn: () =>
- stsClient.assumeRoleWithWebIdentity({
- idToken: notFalsyTypeGuard(token),
- roleArn: roleArn,
- RoleSessionName: roleSessionName,
- }),
-
- refetchOnMount: false,
- refetchOnWindowFocus: false,
- refetchOnReconnect: false,
- enabled: !!token && !!roleArn,
- }),
+ getQuery: (roleArn: string) => {
+ return {
+ queryKey,
+ queryFn: () =>
+ stsClient.assumeRoleWithWebIdentity({
+ idToken: notFalsyTypeGuard(token),
+ roleArn: roleArn,
+ RoleSessionName: roleSessionName,
+ }),
+
+ refetchOnMount: false,
+ refetchOnWindowFocus: false,
+ refetchOnReconnect: false,
+ enabled: !!token && !!roleArn,
+ };
+ },
};
};
diff --git a/src/react/ui-elements/SelectAccountIAMRole.tsx b/src/react/ui-elements/SelectAccountIAMRole.tsx
new file mode 100644
index 000000000..d41e04047
--- /dev/null
+++ b/src/react/ui-elements/SelectAccountIAMRole.tsx
@@ -0,0 +1,298 @@
+import { Stack } from '@scality/core-ui';
+import { Select } from '@scality/core-ui/dist/next';
+import { IAM } from 'aws-sdk';
+import { Bucket } from 'aws-sdk/clients/s3';
+import { PropsWithChildren, useState } from 'react';
+import { useQuery, useQueryClient } from 'react-query';
+import { MemoryRouter, Route, useHistory, useParams } from 'react-router-dom';
+import DataServiceRoleProvider, {
+ useAssumedRole,
+ useSetAssumedRole,
+} from '../DataServiceRoleProvider';
+import { useIAMClient } from '../IAMProvider';
+import { IMetricsAdapter } from '../next-architecture/adapters/metrics/IMetricsAdapter';
+import { useListAccounts } from '../next-architecture/domain/business/accounts';
+import { Account } from '../next-architecture/domain/entities/account';
+import { LatestUsedCapacity } from '../next-architecture/domain/entities/metrics';
+import {
+ AccessibleAccountsAdapterProvider,
+ useAccessibleAccountsAdapter,
+} from '../next-architecture/ui/AccessibleAccountsAdapterProvider';
+import { AccountsLocationsEndpointsAdapterProvider } from '../next-architecture/ui/AccountsLocationsEndpointsAdapterProvider';
+import { getListRolesQuery } from '../queries';
+import { regexArn } from '../utils/hooks';
+
+class NoOpMetricsAdapter implements IMetricsAdapter {
+ async listBucketsLatestUsedCapacity(
+ buckets: Bucket[],
+ ): Promise> {
+ return {};
+ }
+ async listLocationsLatestUsedCapacity(
+ locationIds: string[],
+ ): Promise> {
+ return {};
+ }
+ async listAccountLocationsLatestUsedCapacity(
+ accountCanonicalId: string,
+ ): Promise> {
+ return {};
+ }
+ async listAccountsLatestUsedCapacity(
+ accountCanonicalIds: string[],
+ ): Promise> {
+ return {};
+ }
+}
+
+const filterRoles = (
+ accountName: string,
+ roles: IAM.Role[],
+ hideAccountRoles: { accountName: string; roleName: string }[],
+) => {
+ return roles.filter(
+ (role) =>
+ !hideAccountRoles.find(
+ (hideRole) =>
+ hideRole.accountName === accountName &&
+ hideRole.roleName === role.RoleName,
+ ),
+ );
+};
+
+export const extractAccountIdFromARN = (arn: string) => {
+ return regexArn.exec(arn)?.groups?.['account_id'] ?? '';
+};
+
+/**
+ * DataServiceRoleProvider is using the path to figure out what is the current account.
+ * In order to reuse this logic, we need to have a router and set DataServiceRoleProvider under
+ * the path /accounts/:accountName
+ * Without this INTERNAL_DEFAULT_ACCOUNT_NAME_FOR_INITIALIZATION, it won't render.
+ *
+ * We assume the user won't have an account with this name.
+ */
+const INTERNAL_DEFAULT_ACCOUNT_NAME_FOR_INITIALIZATION =
+ '__INTERNAL_DEFAULT_ACCOUNT_NAME_FOR_INITIALIZATION__';
+
+const AssumeDefaultIAMRole = ({
+ defaultValue,
+}: Pick) => {
+ const accessibleAccountsAdapter = useAccessibleAccountsAdapter();
+ const metricsAdapter = new NoOpMetricsAdapter();
+ const accounts = useListAccounts({
+ accessibleAccountsAdapter,
+ metricsAdapter,
+ });
+ const history = useHistory();
+ const setAssumeRole = useSetAssumedRole();
+
+ const isInternalDefaultAccountSelected =
+ history.location.pathname ===
+ '/accounts/' + INTERNAL_DEFAULT_ACCOUNT_NAME_FOR_INITIALIZATION;
+
+ if (
+ accounts.accounts.status === 'success' &&
+ defaultValue &&
+ isInternalDefaultAccountSelected
+ ) {
+ const acc = accounts.accounts.value.find(
+ (acc) => acc.name === defaultValue?.accountName,
+ );
+
+ /**
+ * This set state will trigger a warning because it's not in a useEffect.
+ * This is fine because the set state is under an if and it should not be called too many times.
+ * The only time it could break is if for some reason the user use an account that is named like
+ * INTERNAL_DEFAULT_ACCOUNT_NAME_FOR_INITIALIZATION and use the component with a defaultValue.
+ */
+ setAssumeRole({
+ roleArn: acc?.preferredAssumableRoleArn ?? '',
+ });
+ history.replace('/accounts/' + defaultValue?.accountName);
+ }
+
+ return <>>;
+};
+
+const InternalProvider = ({
+ children,
+ defaultValue,
+}: PropsWithChildren<
+ Pick
+>) => {
+ return (
+
+
+
+
+
+ <>
+
+ {children}
+ >
+
+
+
+
+
+ );
+};
+
+type SelectAccountIAMRoleProps = {
+ onChange: (account: Account, role: IAM.Role) => void;
+ defaultValue?: { accountName: string; roleName: string };
+ hideAccountRoles?: { accountName: string; roleName: string }[];
+};
+
+type SelectAccountIAMRoleWithAccountProps = SelectAccountIAMRoleProps & {
+ accounts: Account[];
+};
+
+const SelectAccountIAMRoleWithAccount = (
+ props: SelectAccountIAMRoleWithAccountProps,
+) => {
+ const history = useHistory();
+ const IAMClient = useIAMClient();
+ const setAssumedRole = useSetAssumedRole();
+ const { accounts, defaultValue, hideAccountRoles, onChange } = props;
+ const defaultAccountName = useParams<{ accountName: string }>().accountName;
+ const defaultAccount =
+ accounts.find((account) => account.name === defaultAccountName) ?? null;
+ const [account, setAccount] = useState(defaultAccount);
+ const [role, setRole] = useState(null);
+ const assumedRole = useAssumedRole();
+
+ const accountName = account ? account.name : '';
+ const rolesQuery = getListRolesQuery(accountName, IAMClient);
+ const queryClient = useQueryClient();
+
+ const assumedRoleAccountId = extractAccountIdFromARN(
+ assumedRole?.AssumedRoleUser?.Arn,
+ );
+ const selectedAccountId = extractAccountIdFromARN(
+ account?.preferredAssumableRoleArn,
+ );
+
+ /**
+ * When we change account, it will take some time to assume the role for the new account.
+ * We need this check to make sure we don't show the roles for the old account.
+ */
+ const assumedRoleAccountMatchSelectedAccount =
+ assumedRoleAccountId === selectedAccountId;
+
+ const listRolesQuery = {
+ ...rolesQuery,
+ enabled:
+ !!IAMClient &&
+ !!IAMClient.client &&
+ accountName !== '' &&
+ assumedRoleAccountMatchSelectedAccount,
+ };
+ const roleQueryData = useQuery(listRolesQuery);
+
+ const roles = filterRoles(
+ accountName,
+ roleQueryData?.data?.Roles ?? [],
+ hideAccountRoles,
+ );
+
+ const isDefaultAccountSelected = account?.name === defaultValue?.accountName;
+ const defaultRole = isDefaultAccountSelected ? defaultValue?.roleName : null;
+
+ return (
+
+
+
+ {roles.length > 0 ? (
+
+ ) : null}
+
+ );
+};
+
+const defaultOnChange = () => ({});
+export const _SelectAccountIAMRole = (props: SelectAccountIAMRoleProps) => {
+ const {
+ onChange = defaultOnChange,
+ hideAccountRoles = [],
+ defaultValue,
+ } = props;
+
+ const accessibleAccountsAdapter = useAccessibleAccountsAdapter();
+ const metricsAdapter = new NoOpMetricsAdapter();
+ const accounts = useListAccounts({
+ accessibleAccountsAdapter,
+ metricsAdapter,
+ });
+
+ if (accounts.accounts.status === 'success') {
+ return (
+
+ );
+ } else {
+ return Loading accounts...
;
+ }
+};
+
+export const SelectAccountIAMRole = (props: SelectAccountIAMRoleProps) => {
+ return (
+
+ <_SelectAccountIAMRole {...props} />
+
+ );
+};
diff --git a/src/react/ui-elements/__tests__/SelectAccountIAMRole.test.tsx b/src/react/ui-elements/__tests__/SelectAccountIAMRole.test.tsx
new file mode 100644
index 000000000..aed481d40
--- /dev/null
+++ b/src/react/ui-elements/__tests__/SelectAccountIAMRole.test.tsx
@@ -0,0 +1,618 @@
+import { render, screen, waitFor } from '@testing-library/react';
+import { rest } from 'msw';
+import { setupServer } from 'msw/node';
+import { QueryClient, QueryClientProvider } from 'react-query';
+import { TEST_API_BASE_URL } from '../../../react/utils/testUtil';
+import {
+ SelectAccountIAMRole,
+ extractAccountIdFromARN,
+} from '../SelectAccountIAMRole';
+
+import userEvent from '@testing-library/user-event';
+import { debug } from 'jest-preview';
+import {
+ USERS,
+ getConfigOverlay,
+} from '../../../js/mock/managementClientMSWHandlers';
+import { INSTANCE_ID } from '../../../react/actions/__tests__/utils/testUtil';
+
+const testAccountId1 = '064609833007';
+const testAccountId2 = '377232323695';
+
+const genFn = (getPayloadFn: jest.Mock) => {
+ return rest.post(`${TEST_API_BASE_URL}/`, (req, res, ctx) => {
+ //@ts-ignore
+ const params = new URLSearchParams(req.body);
+ getPayloadFn(params);
+
+ if (params.get('Action') === 'AssumeRoleWithWebIdentity') {
+ const accountId = extractAccountIdFromARN(params.get('RoleArn'));
+
+ if (accountId === testAccountId1) {
+ return res(
+ ctx.status(200),
+ ctx.xml(`
+
+
+
+ arn:aws:sts::${testAccountId1}:assumed-role/storage-manager-role/ui-9160673b-2c2a-4a6f-a1ef-a3cb6ce25d7f
+ OES3SPDIYW4L92S8K1QE6MINE31LQG04:ui-9160673b-2c2a-4a6f-a1ef-a3cb6ce25d7f
+
+
+ v/0Nq1YMw4nNbvtgQlgi0l6m/PXWjlk1VLmn2I5q
+ 72SPRZFF71WPWXXUG6XF
+ eyJzYWx0IjoicVIvVGdIdS9FVjJ4TjN5RmtXSnVLZGE0M0krK0g1L3lFVDU5UkV0enpYYz0iLCJ0YWciOiI4d05WRTIwTlQxWTVKbWtZemo2ZGJ3PT0iLCJjaXBoZXJ0ZXh0IjoiQVNIanI0M0VZc3dzK0QwWDFkVXRXQ2JMbzlFOVZ5SzF5WWt6a21lRjRXOUpCU3hwbmNxS21zWnpIU3ZvYlZEYjNKaDRNTm16bW1yVUd6dTU1bmRwMTk0eTVlVjFSVWMzaHZnSTFxZTRuYmJxNHBPdit5V3VZQ3RtSExUbE5BTHpDK3VhYW1tZDdzWk9BVXNKQlhRcmVHUG5sTFphb0kySTFveXJjbk10QlVpb1AvYnNjNUd6RHFqdTFWMjVQRE9PQWgzM2JFSktHdmorbEoyL2lWV0x5UHBQU1pLZmdZUnd1QjRXczdGaG81dHhaem9uWWhpaG9ocnFtdmFnNUJSNytiN2lGN3ZxZjBVSnFPZXI5Wm9ldDk1dlpqL01qTU04aGhGQXI1MmZnTHpzOHAzVlN3dHV0OENFSTBoVEJJNlVycUY4SWxiUmhFOUtlaHo0cnRiZHRKQzVmVHFRSkVPZWltb0RIbGpZZXZqOVlIZzZPVFhDR2ZhVzRIWDc3T0g5M1BRa0dHc1RCSjVpRTEyZEdYQjhYWWdSM1VackIwUzdQejdLQnpvSUVodTZOWUkrK1NPZ2pwMlFaUmhaWGtkbDdDdU5EMWg2UE9qN2twREY0QXhHbWdwcjBMbmpOdVp1UzJaWlJTck5OZG1WL3B5dWpUM3BtcFNJNUZkNW5Wby9SV1dTSGhoR0FVcWRJS0EyV00xdVJ2TkVFS25rb25keWNuVHRrSHpDVUwrN0RtTXNuL202eTcyZjFReHY2VFQyejRzRVFSUDFhWUcwdnBWSTlXbUpWdW5yTFFVNmxSUmpsb1VFSFVkZ2xCMGd1eTZGZTNYR29YQjdVc1J5UUpxbEJ0elpvdFdkR1AvSjZaMllNODFDSy8zZjJZTXVnNTZlbXQxTmJJZ0hrVWxnaGxpclRsNVdrckVRbG5XTW4zT2dzRk9wMjJKSTV1UGoxSENUMlNhTlBXZEQrY0VCcTZycC9tc1FDOW02Q3prSkMwTWMyclZ0RmdnZitaSEV5dVZvdEVzeUFtY1V5QTdFZzJtY3BXd1pnbHZrYkZQQmI5M2NDN0ZhZGhpNEUzQ0hQbm9BczF5eVNKNkxIOTZZTHJoaXk1Q1h3VWloSTlRdmxuSDNlV3EwaElBZTFGc2N6bThzVWRCTGU2SWlVc3ZJVDlpMjJzcTZnaVpmdld5czVlaU9NZzRQMFBaZCtPK0VDRmdmd3dxdUhYcFdEL1F5RnR1RENVb0xxblNyMU9lOHdCQ2lXNDFYaGtacmEzWTdtVW1QYXlNWTN6MXpOZm1XRllJV0dWZzlBNVFUaUZLWGlZTngxdUtWZGJ3Qk54SmowVmxlTTE4azlDNFR3Z2U3dVYyKzEzcWVkTW5xOUpLa2Uwa1NsNmMxMWM5N1RUbGJ4TUx5YS9WY1JLWkNkbHJaTGZNK0hjSTJWaGdkSzNzWHJIVEN6UENFRC9lMVBRTkg1RVZBVThLRlNHWGEzb1dPWm9VcmlSYlk1L3R5eTQvbHRKTkVhNnV3R2hra0ljR3JLMjltUndkaDJHSE94R1laYmdGL0VVUDYreUs5cjQrVzc5Y1RYc3NRcEpSM1M1bkZpUHE2bHR6NXM1ZlNYalNkcUxSM0gvTVZlcXV6K3RON0czMk1ieW9halZvcVJxcks2WjZIVm1vM3pDZ1M4TURQQk9jVkY3Ymc0QmhXaXFUTjc5a0ZqV0xkWWZSVlB5Qk1VaXBHNmZCcGlBdUZCZEV1S2lLMHBwVkhQNUpZL0h5ZXRBbVgxMzdVK1U5d3prbmw3eXhyOEQ0TkdNL05yaVhBT21hSDN4YVEifQ==
+ 2023-11-28T10:16:13Z
+
+ www.scality.com
+
+
+ 8e94c64ebf4486567b0e
+
+ `),
+ );
+ } else if (accountId === testAccountId2) {
+ return res(
+ ctx.status(200),
+ ctx.xml(`
+
+
+
+ arn:aws:sts::${testAccountId2}:assumed-role/storage-manager-role/ui-9160673b-2c2a-4a6f-a1ef-a3cb6ce25d7f
+ OES3SPDIYW4L92S8K1QE6MINE31LQG04:ui-9160673b-2c2a-4a6f-a1ef-a3cb6ce25d7f
+
+
+ v/0Nq1YMw4nNbvtgQlgi0l6m/PXWjlk1VLmn2I5q
+ 72SPRZFF71WPWXXUG6XF
+ eyJzYWx0IjoicVIvVGdIdS9FVjJ4TjN5RmtXSnVLZGE0M0krK0g1L3lFVDU5UkV0enpYYz0iLCJ0YWciOiI4d05WRTIwTlQxWTVKbWtZemo2ZGJ3PT0iLCJjaXBoZXJ0ZXh0IjoiQVNIanI0M0VZc3dzK0QwWDFkVXRXQ2JMbzlFOVZ5SzF5WWt6a21lRjRXOUpCU3hwbmNxS21zWnpIU3ZvYlZEYjNKaDRNTm16bW1yVUd6dTU1bmRwMTk0eTVlVjFSVWMzaHZnSTFxZTRuYmJxNHBPdit5V3VZQ3RtSExUbE5BTHpDK3VhYW1tZDdzWk9BVXNKQlhRcmVHUG5sTFphb0kySTFveXJjbk10QlVpb1AvYnNjNUd6RHFqdTFWMjVQRE9PQWgzM2JFSktHdmorbEoyL2lWV0x5UHBQU1pLZmdZUnd1QjRXczdGaG81dHhaem9uWWhpaG9ocnFtdmFnNUJSNytiN2lGN3ZxZjBVSnFPZXI5Wm9ldDk1dlpqL01qTU04aGhGQXI1MmZnTHpzOHAzVlN3dHV0OENFSTBoVEJJNlVycUY4SWxiUmhFOUtlaHo0cnRiZHRKQzVmVHFRSkVPZWltb0RIbGpZZXZqOVlIZzZPVFhDR2ZhVzRIWDc3T0g5M1BRa0dHc1RCSjVpRTEyZEdYQjhYWWdSM1VackIwUzdQejdLQnpvSUVodTZOWUkrK1NPZ2pwMlFaUmhaWGtkbDdDdU5EMWg2UE9qN2twREY0QXhHbWdwcjBMbmpOdVp1UzJaWlJTck5OZG1WL3B5dWpUM3BtcFNJNUZkNW5Wby9SV1dTSGhoR0FVcWRJS0EyV00xdVJ2TkVFS25rb25keWNuVHRrSHpDVUwrN0RtTXNuL202eTcyZjFReHY2VFQyejRzRVFSUDFhWUcwdnBWSTlXbUpWdW5yTFFVNmxSUmpsb1VFSFVkZ2xCMGd1eTZGZTNYR29YQjdVc1J5UUpxbEJ0elpvdFdkR1AvSjZaMllNODFDSy8zZjJZTXVnNTZlbXQxTmJJZ0hrVWxnaGxpclRsNVdrckVRbG5XTW4zT2dzRk9wMjJKSTV1UGoxSENUMlNhTlBXZEQrY0VCcTZycC9tc1FDOW02Q3prSkMwTWMyclZ0RmdnZitaSEV5dVZvdEVzeUFtY1V5QTdFZzJtY3BXd1pnbHZrYkZQQmI5M2NDN0ZhZGhpNEUzQ0hQbm9BczF5eVNKNkxIOTZZTHJoaXk1Q1h3VWloSTlRdmxuSDNlV3EwaElBZTFGc2N6bThzVWRCTGU2SWlVc3ZJVDlpMjJzcTZnaVpmdld5czVlaU9NZzRQMFBaZCtPK0VDRmdmd3dxdUhYcFdEL1F5RnR1RENVb0xxblNyMU9lOHdCQ2lXNDFYaGtacmEzWTdtVW1QYXlNWTN6MXpOZm1XRllJV0dWZzlBNVFUaUZLWGlZTngxdUtWZGJ3Qk54SmowVmxlTTE4azlDNFR3Z2U3dVYyKzEzcWVkTW5xOUpLa2Uwa1NsNmMxMWM5N1RUbGJ4TUx5YS9WY1JLWkNkbHJaTGZNK0hjSTJWaGdkSzNzWHJIVEN6UENFRC9lMVBRTkg1RVZBVThLRlNHWGEzb1dPWm9VcmlSYlk1L3R5eTQvbHRKTkVhNnV3R2hra0ljR3JLMjltUndkaDJHSE94R1laYmdGL0VVUDYreUs5cjQrVzc5Y1RYc3NRcEpSM1M1bkZpUHE2bHR6NXM1ZlNYalNkcUxSM0gvTVZlcXV6K3RON0czMk1ieW9halZvcVJxcks2WjZIVm1vM3pDZ1M4TURQQk9jVkY3Ymc0QmhXaXFUTjc5a0ZqV0xkWWZSVlB5Qk1VaXBHNmZCcGlBdUZCZEV1S2lLMHBwVkhQNUpZL0h5ZXRBbVgxMzdVK1U5d3prbmw3eXhyOEQ0TkdNL05yaVhBT21hSDN4YVEifQ==
+ 2023-11-28T10:16:13Z
+
+ www.scality.com
+
+
+ 8e94c64ebf4486567b0e
+
+ `),
+ );
+ }
+ }
+ if (params.get('Action') === 'ListRoles') {
+ return res(
+ ctx.xml(`
+
+
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-gc-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ backbeat-gc-1
+ NPP7LHXVP8THSFDX9J58KJED1VKO5WIZ
+ arn:aws:iam::232853836441:role/scality-internal/backbeat-gc-1
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-lifecycle-bp-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ backbeat-lifecycle-bp-1
+ B5HBXF8G2DQ7Z7N13LJA87JRJ1SU40QS
+ arn:aws:iam::232853836441:role/scality-internal/backbeat-lifecycle-bp-1
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-lifecycle-conductor-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ backbeat-lifecycle-conductor-1
+ SBPV35W7A65Q5OCCWR1FD203538EELDB
+ arn:aws:iam::232853836441:role/scality-internal/backbeat-lifecycle-conductor-1
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-lifecycle-op-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ backbeat-lifecycle-op-1
+ WHS10HK95B2PN9RK8UY2D9Z8377F9E5X
+ arn:aws:iam::232853836441:role/scality-internal/backbeat-lifecycle-op-1
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-lifecycle-tp-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ backbeat-lifecycle-tp-1
+ YSXDD002ETBE0CJEZQYFDAYJQTWOVJ51
+ arn:aws:iam::232853836441:role/scality-internal/backbeat-lifecycle-tp-1
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fsorbet-fwd-2%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ cold-storage-archive-role-2
+ DMELLEKK4LI9B3F5EWGTXEMRBKU35R3E
+ arn:aws:iam::232853836441:role/scality-internal/cold-storage-archive-role-2
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fsorbet-fwd-2%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ cold-storage-restore-role-2
+ H3Y58C2OQTKRH4M1EBXASEKSLOMEGRI1
+ arn:aws:iam::232853836441:role/scality-internal/cold-storage-restore-role-2
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Agroups%22%3A%22100%3A%3ADataConsumer%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2Fui.pod-choco.local%2Fauth%2Frealms%2Fartesca%22%7D%7D%2C%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Agroups%22%3A%22100%3A%3ADataConsumer%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2F13.48.197.10%3A8443%2Fauth%2Frealms%2Fartesca%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ Has S3 read and write accesses to 100 S3 Buckets. Cannot create or delete S3 Buckets.
+ /scality-internal/
+ data-consumer-role
+ YGEX9QWC7RI9KMBQEKS4RA9OND4JZ35U
+ arn:aws:iam::232853836441:role/scality-internal/data-consumer-role
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Agroups%22%3A%22100%3A%3AStorageAccountOwner%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2Fui.pod-choco.local%2Fauth%2Frealms%2Fartesca%22%7D%7D%2C%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Agroups%22%3A%22100%3A%3AStorageAccountOwner%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2F13.48.197.10%3A8443%2Fauth%2Frealms%2Fartesca%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ Manages the 100 account (Policies, Users, Roles, Groups).
+ /scality-internal/
+ storage-account-owner-role
+ OYYDW5GLCETHME90KWAZCG5Z8KNZA1OT
+ arn:aws:iam::232853836441:role/scality-internal/storage-account-owner-role
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Aroles%22%3A%22StorageManager%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2Fui.pod-choco.local%2Fauth%2Frealms%2Fartesca%22%7D%7D%2C%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Aroles%22%3A%22StorageManager%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2F13.48.197.10%3A8443%2Fauth%2Frealms%2Fartesca%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ Has all permissions and full access to the 100 account resources and manages ARTESCA users.
+ /scality-internal/
+ storage-manager-role
+ YRA3NTDUTWN6DRN76LSSDM6HA22RWBO9
+ arn:aws:iam::232853836441:role/scality-internal/storage-manager-role
+ 2024-04-17T16:31:36Z
+
+
+ false
+
+
+ 148012f42345b8eb7c29
+
+
+ `),
+ );
+ }
+
+ if (params.get('Action') === 'GetRolesForWebIdentity') {
+ const testAccount1 = USERS.find((user) => user.id === testAccountId1);
+ const testAccount2 = USERS.find((user) => user.id === testAccountId2);
+ const accounts = [testAccount1, testAccount2];
+
+ return res(
+ ctx.json({
+ IsTruncated: false,
+ Accounts: accounts.map((account) => {
+ return {
+ Name: account.userName,
+ CreationDate: account.createDate,
+ Roles: [
+ {
+ Name: 'storage-manager-role',
+ Arn: `arn:aws:iam::${account.id}:role/scality-internal/storage-manager-role`,
+ },
+ ],
+ };
+ }),
+ }),
+ );
+ }
+ });
+};
+
+const server = setupServer(getConfigOverlay(TEST_API_BASE_URL, INSTANCE_ID));
+
+const LocalWrapper = ({ children }) => {
+ const queryClient = new QueryClient({
+ // In test environnement, we don't want to retry queries
+ // because we may test the error case
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ mutations: {
+ retry: false,
+ },
+ },
+ });
+
+ return (
+ {children}
+ );
+};
+
+describe('SelectAccountIAMRole', () => {
+ const seletors = {
+ accountSelect: () => screen.getByLabelText(/select account/i),
+ roleSelect: () => screen.getByLabelText(/select role/i),
+ selectOption: (name: string | RegExp) =>
+ screen.getByRole('option', {
+ name: new RegExp(name, 'i'),
+ }),
+ };
+ beforeAll(() => {
+ server.listen({ onUnhandledRequest: 'error' });
+ });
+ afterEach(() => {
+ server.resetHandlers();
+ });
+ afterAll(() => {
+ server.close();
+ });
+ it('renders with normal props', async () => {
+ const getPayloadFn = jest.fn();
+ server.use(genFn(getPayloadFn));
+ const onChange = jest.fn();
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(seletors.accountSelect()).toBeInTheDocument();
+ });
+
+ await userEvent.click(seletors.accountSelect());
+
+ expect(screen.getByText('no-bucket')).toBeInTheDocument();
+
+ await userEvent.click(seletors.selectOption(/no-bucket/i));
+
+ await waitFor(() => {
+ expect(seletors.roleSelect()).toBeInTheDocument();
+ });
+
+ await userEvent.click(seletors.roleSelect());
+ await userEvent.click(seletors.selectOption(/backbeat-gc-1/i));
+
+ const account = {
+ assumableRoles: [
+ {
+ Arn: `arn:aws:iam::${testAccountId1}:role/scality-internal/storage-manager-role`,
+ Name: 'storage-manager-role',
+ },
+ ],
+ canManageAccount: true,
+ canonicalId:
+ '1e3492312ab47ab0785e3411824352a8fa8aab68cece94973af04167926b8f2c',
+ creationDate: '2022-03-18T12:51:44.000Z',
+ id: testAccountId1,
+ name: 'no-bucket',
+ preferredAssumableRoleArn: `arn:aws:iam::${testAccountId1}:role/scality-internal/storage-manager-role`,
+ usedCapacity: {
+ status: 'unknown',
+ },
+ };
+ const role = {
+ Arn: 'arn:aws:iam::232853836441:role/scality-internal/backbeat-gc-1',
+ AssumeRolePolicyDocument:
+ '%7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-gc-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D',
+ CreateDate: new Date('2024-04-17T16:31:36.000Z'),
+ Description: 'undefined',
+ Path: '/scality-internal/',
+ RoleId: 'NPP7LHXVP8THSFDX9J58KJED1VKO5WIZ',
+ RoleName: 'backbeat-gc-1',
+ Tags: [],
+ };
+ expect(onChange).toHaveBeenCalledWith(account, role);
+ });
+
+ it('test the change of account and role', async () => {
+ // Select Account A, check that role B does not exist and select role A
+ // Change Account to Account B and Role B
+
+ const getPayloadFn = jest.fn();
+ server.use(genFn(getPayloadFn));
+ const onChange = jest.fn();
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(seletors.accountSelect()).toBeInTheDocument();
+ });
+
+ await userEvent.click(seletors.accountSelect());
+
+ await userEvent.click(seletors.selectOption(/no-bucket/i));
+
+ await waitFor(() => {
+ expect(seletors.roleSelect()).toBeInTheDocument();
+ });
+
+ await userEvent.click(seletors.roleSelect());
+ await userEvent.click(seletors.selectOption(/backbeat-gc-1/i));
+
+ expect(onChange).toHaveBeenNthCalledWith(
+ 1,
+ {
+ assumableRoles: [
+ {
+ Arn: 'arn:aws:iam::064609833007:role/scality-internal/storage-manager-role',
+ Name: 'storage-manager-role',
+ },
+ ],
+ canManageAccount: true,
+ canonicalId:
+ '1e3492312ab47ab0785e3411824352a8fa8aab68cece94973af04167926b8f2c',
+ creationDate: '2022-03-18T12:51:44.000Z',
+ id: '064609833007',
+ name: 'no-bucket',
+ preferredAssumableRoleArn:
+ 'arn:aws:iam::064609833007:role/scality-internal/storage-manager-role',
+ usedCapacity: {
+ status: 'unknown',
+ },
+ },
+ {
+ Arn: 'arn:aws:iam::232853836441:role/scality-internal/backbeat-gc-1',
+ AssumeRolePolicyDocument:
+ '%7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-gc-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D',
+ CreateDate: new Date('2024-04-17T16:31:36.000Z'),
+ Description: 'undefined',
+ Path: '/scality-internal/',
+ RoleId: 'NPP7LHXVP8THSFDX9J58KJED1VKO5WIZ',
+ RoleName: 'backbeat-gc-1',
+ Tags: [],
+ },
+ );
+
+ await userEvent.click(seletors.accountSelect());
+
+ expect(screen.getByText('yanjin')).toBeInTheDocument();
+
+ server.use(
+ rest.post(`${TEST_API_BASE_URL}/`, (req, res, ctx) => {
+ //@ts-ignore
+ const params = new URLSearchParams(req.body);
+ getPayloadFn(params);
+
+ if (params.get('Action') === 'ListRoles') {
+ return res(
+ ctx.xml(`
+
+
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-gc-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ yanjin-custom-role
+ NPP7LHXVP8THSFDX9J58KJED1VKO5WIZ
+ arn:aws:iam::232853836441:role/scality-internal/yanjin-custom-role
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-lifecycle-bp-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ backbeat-lifecycle-bp-1
+ B5HBXF8G2DQ7Z7N13LJA87JRJ1SU40QS
+ arn:aws:iam::232853836441:role/scality-internal/backbeat-lifecycle-bp-1
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-lifecycle-conductor-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ backbeat-lifecycle-conductor-1
+ SBPV35W7A65Q5OCCWR1FD203538EELDB
+ arn:aws:iam::232853836441:role/scality-internal/backbeat-lifecycle-conductor-1
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-lifecycle-op-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ backbeat-lifecycle-op-1
+ WHS10HK95B2PN9RK8UY2D9Z8377F9E5X
+ arn:aws:iam::232853836441:role/scality-internal/backbeat-lifecycle-op-1
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-lifecycle-tp-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ backbeat-lifecycle-tp-1
+ YSXDD002ETBE0CJEZQYFDAYJQTWOVJ51
+ arn:aws:iam::232853836441:role/scality-internal/backbeat-lifecycle-tp-1
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fsorbet-fwd-2%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ cold-storage-archive-role-2
+ DMELLEKK4LI9B3F5EWGTXEMRBKU35R3E
+ arn:aws:iam::232853836441:role/scality-internal/cold-storage-archive-role-2
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fsorbet-fwd-2%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ undefined
+ /scality-internal/
+ cold-storage-restore-role-2
+ H3Y58C2OQTKRH4M1EBXASEKSLOMEGRI1
+ arn:aws:iam::232853836441:role/scality-internal/cold-storage-restore-role-2
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Agroups%22%3A%22100%3A%3ADataConsumer%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2Fui.pod-choco.local%2Fauth%2Frealms%2Fartesca%22%7D%7D%2C%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Agroups%22%3A%22100%3A%3ADataConsumer%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2F13.48.197.10%3A8443%2Fauth%2Frealms%2Fartesca%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ Has S3 read and write accesses to 100 S3 Buckets. Cannot create or delete S3 Buckets.
+ /scality-internal/
+ data-consumer-role
+ YGEX9QWC7RI9KMBQEKS4RA9OND4JZ35U
+ arn:aws:iam::232853836441:role/scality-internal/data-consumer-role
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Agroups%22%3A%22100%3A%3AStorageAccountOwner%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2Fui.pod-choco.local%2Fauth%2Frealms%2Fartesca%22%7D%7D%2C%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Agroups%22%3A%22100%3A%3AStorageAccountOwner%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2F13.48.197.10%3A8443%2Fauth%2Frealms%2Fartesca%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ Manages the 100 account (Policies, Users, Roles, Groups).
+ /scality-internal/
+ storage-account-owner-role
+ OYYDW5GLCETHME90KWAZCG5Z8KNZA1OT
+ arn:aws:iam::232853836441:role/scality-internal/storage-account-owner-role
+ 2024-04-17T16:31:36Z
+
+
+ %7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Aroles%22%3A%22StorageManager%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2Fui.pod-choco.local%2Fauth%2Frealms%2Fartesca%22%7D%7D%2C%7B%22Action%22%3A%22sts%3AAssumeRoleWithWebIdentity%22%2C%22Condition%22%3A%7B%22StringEquals%22%3A%7B%22keycloak%3Aroles%22%3A%22StorageManager%22%7D%7D%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Federated%22%3A%22https%3A%2F%2F13.48.197.10%3A8443%2Fauth%2Frealms%2Fartesca%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D
+ Has all permissions and full access to the 100 account resources and manages ARTESCA users.
+ /scality-internal/
+ storage-manager-role
+ YRA3NTDUTWN6DRN76LSSDM6HA22RWBO9
+ arn:aws:iam::232853836441:role/scality-internal/storage-manager-role
+ 2024-04-17T16:31:36Z
+
+
+ false
+
+
+ 148012f42345b8eb7c29
+
+
+ `),
+ );
+ }
+ }),
+ );
+ await userEvent.click(seletors.selectOption(/yanjin/i));
+
+ await waitFor(() => {
+ expect(seletors.roleSelect()).toBeInTheDocument();
+ });
+
+ await userEvent.click(seletors.roleSelect());
+
+ await userEvent.click(seletors.selectOption(/yanjin-custom-role/i));
+
+ expect(onChange).toHaveBeenNthCalledWith(
+ 2,
+ {
+ assumableRoles: [
+ {
+ Arn: 'arn:aws:iam::377232323695:role/scality-internal/storage-manager-role',
+ Name: 'storage-manager-role',
+ },
+ ],
+ canManageAccount: true,
+ canonicalId:
+ '8c3b89e95e9768755365a8c2d528e71bc7b1cab781ac118b0824cefe21abaf29',
+ creationDate: '2022-04-29T09:35:35.000Z',
+ id: '377232323695',
+ name: 'yanjin',
+ preferredAssumableRoleArn:
+ 'arn:aws:iam::377232323695:role/scality-internal/storage-manager-role',
+ usedCapacity: {
+ status: 'unknown',
+ },
+ },
+ {
+ Arn: 'arn:aws:iam::232853836441:role/scality-internal/yanjin-custom-role',
+ AssumeRolePolicyDocument:
+ '%7B%22Statement%22%3A%5B%7B%22Action%22%3A%22sts%3AAssumeRole%22%2C%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22AWS%22%3A%22arn%3Aaws%3Aiam%3A%3A000000000000%3Auser%2Fscality-internal%2Fbackbeat-gc-1%22%7D%7D%5D%2C%22Version%22%3A%222012-10-17%22%7D',
+ CreateDate: new Date('2024-04-17T16:31:36.000Z'),
+ Description: 'undefined',
+ Path: '/scality-internal/',
+ RoleId: 'NPP7LHXVP8THSFDX9J58KJED1VKO5WIZ',
+ RoleName: 'yanjin-custom-role',
+ Tags: [],
+ },
+ );
+ debug();
+ });
+
+ it('renders with default value', async () => {
+ const getPayloadFn = jest.fn();
+ server.use(genFn(getPayloadFn));
+ const onChange = jest.fn();
+ render(
+
+
+ ,
+ );
+ await waitFor(() => {
+ expect(seletors.accountSelect()).toBeInTheDocument();
+ });
+
+ expect(screen.getByText('no-bucket')).toBeInTheDocument();
+ });
+
+ it('renders with wrong default value', async () => {
+ const getPayloadFn = jest.fn();
+ server.use(genFn(getPayloadFn));
+ const onChange = jest.fn();
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(seletors.accountSelect()).toBeInTheDocument();
+ });
+
+ expect(seletors.accountSelect()).toBeInTheDocument();
+ });
+
+ it('renders with hidden account roles', async () => {
+ const getPayloadFn = jest.fn();
+ server.use(genFn(getPayloadFn));
+ const onChange = jest.fn();
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(seletors.accountSelect()).toBeInTheDocument();
+ });
+
+ await userEvent.click(seletors.accountSelect());
+
+ await userEvent.click(seletors.selectOption(/no-bucket/i));
+
+ await waitFor(() => {
+ expect(seletors.roleSelect()).toBeInTheDocument();
+ });
+
+ await userEvent.click(seletors.roleSelect());
+ await userEvent.type(seletors.roleSelect(), 'data-consumer');
+
+ expect(screen.getByText(/no options/i)).toBeInTheDocument();
+ });
+});
diff --git a/src/react/utils/hooks.ts b/src/react/utils/hooks.ts
index b50820d97..5a0a4086b 100644
--- a/src/react/utils/hooks.ts
+++ b/src/react/utils/hooks.ts
@@ -146,9 +146,10 @@ export function useQueryWithUnmountSupport<
});
return query;
}
-
+// arn:aws:sts::142222634614:assumed-role/storage-manager-role/ui-9160673b-2c2a-4a6f-a1ef-a3cb6ce25d7f
+// arn:aws:iam::142222634614:role/scality-internal/storage-manager-role
export const regexArn =
- /arn:aws:iam::(?\d{12}):(?role|policy)\/(?(?:[^/]*\/)*)(?[^/]+)$/;
+ /arn:aws:(?:iam|sts)::(?\d{12}):(?role|policy|assumed-role)\/(?(?:[^/]*\/)*)(?[^/]+)$/;
export const STORAGE_MANAGER_ROLE = 'storage-manager-role';
export const STORAGE_ACCOUNT_OWNER_ROLE = 'storage-account-owner-role';
@@ -233,6 +234,7 @@ export const useAccounts = (
},
(data) => data.Accounts,
);
+
const uniqueAccountsWithRoles = Object.values(
data?.reduce(
(agg, current) => ({
diff --git a/webpack.common.js b/webpack.common.js
index 8a219c2a2..dc1e2995b 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -81,6 +81,8 @@ module.exports = {
'./FederableApp': './src/react/FederableApp.tsx',
'./VeeamWelcomeModal':
'./src/react/ui-elements/Veeam/VeeamWelcomeModal.tsx',
+ './SelectAccountIAMRole':
+ './src/react/ui-elements/SelectAccountIAMRole.tsx',
},
shared: {
...Object.fromEntries(