From 831901a3a6a7ec1a8bfc2dce6d8a5c03e23847c5 Mon Sep 17 00:00:00 2001 From: Abdhesh Nayak Date: Sun, 10 Mar 2024 13:51:33 +0530 Subject: [PATCH] :sparkles: Refactored cloud provider --- web/Taskfile.yaml | 4 +- web/gql-queries-generator/doc/queries.graphql | 26 +-- web/src/apps/console/components/code-view.tsx | 4 +- .../console/page-components/new-cluster.tsx | 15 +- .../$a+/$cloudprovider+/validate-cp.tsx | 148 ++++++++++++--- .../onboarding+/$a+/new-cloud-provider.tsx | 58 +----- .../$cluster+/settings+/general/route.tsx | 25 ++- .../cloud-providers/handle-provider.tsx | 30 ++-- .../cloud-providers/provider-resources.tsx | 168 ++++++++++++++---- .../server/gql/queries/account-queries.ts | 11 +- .../server/gql/queries/cluster-queries.ts | 17 +- .../gql/queries/provider-secret-queries.ts | 9 +- web/src/apps/console/server/r-utils/common.ts | 2 +- web/src/generated/gql/sdl.graphql | 133 +++++++++++--- web/src/generated/gql/server.ts | 89 +++++++--- 15 files changed, 506 insertions(+), 233 deletions(-) diff --git a/web/Taskfile.yaml b/web/Taskfile.yaml index b210e68ff..bdac9c97d 100644 --- a/web/Taskfile.yaml +++ b/web/Taskfile.yaml @@ -8,7 +8,7 @@ tasks: interactive: true cmds: - | - BASE_URL=dev.kloudlite.io + BASE_URL=gcp-production.kloudlite.io COOKIE_DOMAIN=".kloudlite.io" GATEWAY_URL="http://gateway.kloudlite.svc.cluster.local" case {{.app}} in @@ -55,7 +55,7 @@ tasks: esac - REMIX_DEV_ORIGIN="https://{{.app}}$URL_SUFFIX.dev.kloudlite.io" + REMIX_DEV_ORIGIN="https://{{.app}}$URL_SUFFIX.$BASE_URL" cp -r ./static/common/. ./public/ cp -r ./static/{{.app}}/. ./public/ diff --git a/web/gql-queries-generator/doc/queries.graphql b/web/gql-queries-generator/doc/queries.graphql index 7b22173d6..0413536dc 100644 --- a/web/gql-queries-generator/doc/queries.graphql +++ b/web/gql-queries-generator/doc/queries.graphql @@ -77,6 +77,7 @@ query consoleGetAccount($accountName: String!) { name annotations } + targetNamespace updateTime contactEmail displayName @@ -300,10 +301,6 @@ query consoleListClusters($search: SearchCluster, $pagination: CursorPaginationI spec { messageQueueTopicName kloudliteRelease - credentialsRef { - namespace - name - } clusterTokenRef { key name @@ -398,18 +395,6 @@ query consoleGetCluster($name: String!) { name namespace } - credentialKeys { - keyAccessKey - keyAWSAccountId - keyAWSAssumeRoleExternalID - keyAWSAssumeRoleRoleARN - keyIAMInstanceProfileRole - keySecretKey - } - credentialsRef { - name - namespace - } kloudliteRelease messageQueueTopicName output { @@ -480,15 +465,15 @@ query consoleListProviderSecrets($search: SearchProviderSecret, $pagination: Cur edges { cursor node { - aws { - awsAccountId - } cloudProviderName createdBy { userEmail userId userName } + aws { + authMechanism + } creationTime displayName lastUpdatedBy { @@ -528,9 +513,6 @@ mutation consoleDeleteProviderSecret($secretName: String!) { query consoleGetProviderSecret($name: String!) { infra_getProviderSecret(name: $name) { - aws { - awsAccountId - } cloudProviderName createdBy { userEmail diff --git a/web/src/apps/console/components/code-view.tsx b/web/src/apps/console/components/code-view.tsx index 290460987..232b74597 100644 --- a/web/src/apps/console/components/code-view.tsx +++ b/web/src/apps/console/components/code-view.tsx @@ -14,7 +14,7 @@ interface ICodeView { const CodeView = ({ data, copy, - showShellPrompt, + showShellPrompt: _, language = 'shell', title, }: ICodeView) => { @@ -55,7 +55,7 @@ const CodeView = ({ }} className="group/sha cursor-pointer p-lg rounded-md bodyMd flex flex-row gap-xl items-center hljs w-full" > -
+          
             {data}
           
diff --git a/web/src/apps/console/page-components/new-cluster.tsx b/web/src/apps/console/page-components/new-cluster.tsx index 27ed67069..31a2fef63 100644 --- a/web/src/apps/console/page-components/new-cluster.tsx +++ b/web/src/apps/console/page-components/new-cluster.tsx @@ -1,4 +1,4 @@ -import { useNavigate, useParams } from '@remix-run/react'; +import { useNavigate, useOutletContext, useParams } from '@remix-run/react'; import { useMemo, useState } from 'react'; import Select from '~/components/atoms/select'; import { toast } from '~/components/molecule/toast'; @@ -28,6 +28,7 @@ import MultiStepProgressWrapper from '../components/multi-step-progress-wrapper' import { TitleBox } from '../components/raw-wrapper'; import { BottomNavigation, ReviewComponent } from '../components/commons'; import FillerCluster from '../assets/filler-cluster'; +import { IAccountContext } from '../routes/_main+/$account+/_layout'; type props = | { @@ -50,6 +51,8 @@ export const NewCluster = ({ providerSecrets, cloudProvider }: props) => { [providerSecrets] ); + const { account } = useOutletContext(); + const options = useMapper(cloudProviders, (provider) => ({ value: parseName(provider), label: provider.displayName, @@ -122,15 +125,19 @@ export const NewCluster = ({ providerSecrets, cloudProvider }: props) => { spec: { cloudProvider: validateClusterCloudProvider(val.cloudProvider), aws: { + credentials: { + authMechanism: 'secret_keys', + secretRef: { + name: val.credentialsRef, + namespace: account.targetNamespace, + }, + }, region: selectedRegion.Name, k3sMasters: { nvidiaGpuEnabled: true, instanceType: 'c6a.xlarge', }, }, - credentialsRef: { - name: val.credentialsRef, - }, availabilityMode: validateAvailabilityMode( val.availabilityMode ), diff --git a/web/src/apps/console/routes/_a+/onboarding+/$a+/$cloudprovider+/validate-cp.tsx b/web/src/apps/console/routes/_a+/onboarding+/$a+/$cloudprovider+/validate-cp.tsx index dd9c55132..ba5f98eaa 100644 --- a/web/src/apps/console/routes/_a+/onboarding+/$a+/$cloudprovider+/validate-cp.tsx +++ b/web/src/apps/console/routes/_a+/onboarding+/$a+/$cloudprovider+/validate-cp.tsx @@ -1,5 +1,3 @@ -/* eslint-disable react/no-unescaped-entities */ -/* eslint-disable no-nested-ternary */ import { IRemixCtx } from '~/root/lib/types/common'; import { useLoaderData, useNavigate, useOutletContext } from '@remix-run/react'; import { defer } from '@remix-run/node'; @@ -22,6 +20,10 @@ import MultiStepProgress, { } from '~/console/components/multi-step-progress'; import { Check } from '~/console/components/icons'; import { BottomNavigation } from '~/console/components/commons'; +import useForm from '~/root/lib/client/hooks/use-form'; +import Yup from '~/root/lib/server/helpers/yup'; +import { PasswordInput } from '~/components/atoms/input'; +import FillerCloudProvider from '~/console/assets/filler-cloud-provider'; import { IAccountContext } from '../../../../_main+/$account+/_layout'; export const loader = async (ctx: IRemixCtx) => { @@ -64,25 +66,90 @@ const Validator = ({ cloudProvider }: { cloudProvider: any }) => { const [isLoading, setIsLoading] = useState(false); const { data, isLoading: il } = useCustomSwr( - () => cloudProvider.metadata!.name + isLoading, + () => parseName(cloudProvider) + isLoading, async () => { - if (!cloudProvider.metadata!.name) { + if (!parseName(cloudProvider.metadata!.name)) { throw new Error('Invalid cloud provider name'); } return api.checkAwsAccess({ - cloudproviderName: cloudProvider.metadata.name, + cloudproviderName: parseName(cloudProvider), }); } ); + const { values, handleChange, errors, handleSubmit } = useForm({ + initialValues: { + accessKey: '', + secretKey: '', + }, + validationSchema: Yup.object({ + accessKey: Yup.string().test( + 'provider', + 'access key is required', + // @ts-ignores + // eslint-disable-next-line react/no-this-in-sfc + function (item) { + return data?.result || item; + } + ), + secretKey: Yup.string().test( + 'provider', + 'secret key is required', + // eslint-disable-next-line func-names + // @ts-ignore + function (item) { + return data?.result || item; + } + ), + }), + onSubmit: async (val) => { + if (data?.result) { + navigate( + `/onboarding/${parseName(account)}/${parseName( + cloudProvider + )}/new-cluster` + ); + return; + } + + try { + const { errors } = await api.updateProviderSecret({ + secret: { + metadata: { + name: parseName(cloudProvider), + }, + cloudProviderName: cloudProvider.cloudProviderName, + displayName: cloudProvider.displayName, + aws: { + authMechanism: 'secret_keys', + authSecretKeys: { + accessKey: val.accessKey, + secretKey: val.secretKey, + }, + }, + }, + }); + + if (errors) { + throw errors[0]; + } + + setIsLoading((s) => !s); + } catch (err) { + handleError(err); + } + }, + }); + const { currentStep, jumpStep } = useMultiStepProgress({ defaultStep: 3, totalSteps: 4, }); return ( -
+
} title="Setup your account!" subTitle="Simplify Collaboration and Enhance Productivity with Kloudlite teams" @@ -101,10 +168,11 @@ const Validator = ({ cloudProvider }: { cloudProvider: any }) => { /> -
+
- Validate your cloud provider's credentials + Validate your cloud provider's credentials
+ {/* eslint-disable-next-line no-nested-ternary */} {il ? (
{ ) : (
- Account ID + + {cloudProvider.displayName} + - {cloudProvider.aws?.awsAccountId} + ({parseName(cloudProvider)})
@@ -161,27 +231,65 @@ const Validator = ({ cloudProvider }: { cloudProvider: any }) => { to create AWS cloudformation stack
+ + {!data?.result && ( + <> +
+ Once you have created the cloudformation stack, please + enter the access key and secret key below to validate + your cloud Provider, you can get the access key and + secret key from the output of the cloudformation stack. +
+ + + + + + )}
)} { + navigate( + `/onboarding/${parseName(account)}/${parseName( + cloudProvider + )}/new-cluster` + ); + }, + } + } primaryButton={{ variant: 'primary', - content: data?.result ? 'Next' : 'Skip', - onClick: () => { - navigate( - `/onboarding/${parseName(account)}/${parseName( - cloudProvider - )}/new-cluster` - ); - }, + content: data?.result ? 'Continue' : 'Update', + type: 'submit', }} /> -
+ - +
); }; diff --git a/web/src/apps/console/routes/_a+/onboarding+/$a+/new-cloud-provider.tsx b/web/src/apps/console/routes/_a+/onboarding+/$a+/new-cloud-provider.tsx index d84ab8f2a..454a25db2 100644 --- a/web/src/apps/console/routes/_a+/onboarding+/$a+/new-cloud-provider.tsx +++ b/web/src/apps/console/routes/_a+/onboarding+/$a+/new-cloud-provider.tsx @@ -1,5 +1,4 @@ import { useNavigate, useParams } from '@remix-run/react'; -import { PasswordInput } from '~/components/atoms/input'; import Select from '~/components/atoms/select'; import { toast } from '~/components/molecule/toast'; import useForm, { dummyEvent } from '~/root/lib/client/hooks/use-form'; @@ -29,42 +28,12 @@ const NewCloudProvider = () => { displayName: '', name: '', provider: providers[0].value, - accessKey: '', - secretKey: '', isNameError: false, }, validationSchema: Yup.object({ displayName: Yup.string().required(), name: Yup.string().required(), provider: Yup.string().required(), - accessKey: Yup.string().test( - 'provider', - 'access key is required', - function (item) { - return ( - // @ts-ignores - // eslint-disable-next-line react/no-this-in-sfc - this.parent.provider && - // eslint-disable-next-line react/no-this-in-sfc - this.parent.provider === 'aws' && - item - ); - } - ), - secretKey: Yup.string().test( - 'provider', - 'secret key is required', - function (item) { - return ( - // @ts-ignores - // eslint-disable-next-line react/no-this-in-sfc - this.parent.provider && - // eslint-disable-next-line react/no-this-in-sfc - this.parent.provider === 'aws' && - item - ); - } - ), }), onSubmit: async (val) => { const addProvider = async () => { @@ -77,8 +46,7 @@ const NewCloudProvider = () => { name: val.name, }, aws: { - secretKey: val.secretKey, - accessKey: val.accessKey, + authMechanism: 'secret_keys', }, cloudProviderName: validateCloudProvider(val.provider), }, @@ -102,7 +70,7 @@ const NewCloudProvider = () => { toast.success('provider secret created successfully'); - navigate(`/onboarding/${accountName}/${values.name}/new-cluster`); + navigate(`/onboarding/${accountName}/${values.name}/validate-cp`); } catch (err) { handleError(err); } @@ -163,28 +131,6 @@ const NewCloudProvider = () => { }} options={async () => providers} /> - - {values.provider === 'aws' && ( - <> - - - - - )}
ps.value === cluster.spec?.credentialsRef.name - ); + // const defaultProvider = providerSecrets.find((ps) => cluster.spec?.aws); const defaultRegion = awsRegions.find( (r) => r.Name === cluster.spec?.aws?.region @@ -203,16 +200,16 @@ const Layout = ({
-
- {' '} - providerSecrets} */} + {/* /> */} + {/*
*/}
{ /> )} - {!isUpdate && values?.provider === 'aws' && ( + {isUpdate && values?.provider === 'aws' && ( <> { const api = useConsoleApi(); - const checkAwsAccess = async () => { - const { data, errors } = await api.checkAwsAccess({ - cloudproviderName: item.metadata?.name || '', - }); - if (errors) { - throw errors[0]; - } - return data; - }; + // const checkAwsAccess = async () => { + // const { data, errors } = await api.checkAwsAccess({ + // cloudproviderName: item.metadata?.name || '', + // }); + // if (errors) { + // throw errors[0]; + // } + // return data; + // }; const [isLoading, setIsLoading] = useState(false); + const { data, isLoading: il } = useCustomSwr( + () => parseName(item) + isLoading, + async () => { + if (!parseName(item)) { + throw new Error('Invalid cloud provider name'); + } + return api.checkAwsAccess({ + cloudproviderName: parseName(item), + }); + } + ); + + const { values, handleChange, errors, handleSubmit } = useForm({ + initialValues: { + accessKey: '', + secretKey: '', + }, + validationSchema: Yup.object({ + accessKey: Yup.string().test( + 'provider', + 'access key is required', + // @ts-ignores + // eslint-disable-next-line react/no-this-in-sfc + function (item) { + return data?.result || item; + } + ), + secretKey: Yup.string().test( + 'provider', + 'secret key is required', + // eslint-disable-next-line func-names + // @ts-ignore + function (item) { + return data?.result || item; + } + ), + }), + onSubmit: async (val) => { + if (data?.result) { + // navigate( + // `/onboarding/${parseName(account)}/${parseName( + // cloudProvider + // )}/new-cluster` + // ); + toast.success('Provider validated successfully'); + + onClose(); + return; + } + + try { + const { errors } = await api.updateProviderSecret({ + secret: { + metadata: { + name: parseName(item), + }, + cloudProviderName: item.cloudProviderName, + displayName: item.displayName, + aws: { + authMechanism: 'secret_keys', + authSecretKeys: { + accessKey: val.accessKey, + secretKey: val.secretKey, + }, + }, + }, + }); + + if (errors) { + throw errors[0]; + } + + setIsLoading((s) => !s); + } catch (err) { + handleError(err); + } + }, + }); + return ( Validate Aws Provider -
-
- Account ID - {item.aws?.awsAccountId} -
+
+ {/*
*/} + {/* Account ID */} + {/* {item.aws?.awsAccountId} */} + {/*
*/}
@@ -78,20 +160,14 @@ const AwsValidationPopup = ({ visit the link above and click on the button to validate your AWS account, or
-
+ + {!data?.result && ( + <> +
+ Once you have created the cloudformation stack, please enter the + access key and secret key below to validate your cloud Provider, + you can get the access key and secret key from the output of the + cloudformation stack. +
+ + + + + +