diff --git a/src/k8s/groups/ArgoCD/Application/hooks/useArgoApplicationCRUD.ts b/src/k8s/groups/ArgoCD/Application/hooks/useArgoApplicationCRUD.ts new file mode 100644 index 00000000..8af68e07 --- /dev/null +++ b/src/k8s/groups/ArgoCD/Application/hooks/useArgoApplicationCRUD.ts @@ -0,0 +1,81 @@ +import React from 'react'; +import { CRUD_TYPES } from '../../../../../constants/crudTypes'; +import { useResourceCRUDMutation } from '../../../../../hooks/useResourceCRUDMutation'; +import { ApplicationKubeObject } from '../index'; +import { ApplicationKubeObjectInterface } from '../types'; + +interface DeleteArgoApplicationProps { + argoApplication: ApplicationKubeObjectInterface; +} + +interface EditArgoApplicationProps { + argoApplication: ApplicationKubeObjectInterface; +} + +interface CreateArgoApplicationProps { + argoApplication: ApplicationKubeObjectInterface; +} + +export const useArgoApplicationCRUD = () => { + const argoApplicationCreateMutation = useResourceCRUDMutation< + ApplicationKubeObjectInterface, + CRUD_TYPES.CREATE + >('argoApplicationCreateMutation', ApplicationKubeObject, CRUD_TYPES.CREATE, { + customMessages: { + onMutate: 'Creating application...', + onError: 'Failed to create application', + onSuccess: 'Start creating application', + }, + }); + + const argoApplicationDeleteMutation = useResourceCRUDMutation< + ApplicationKubeObjectInterface, + CRUD_TYPES.DELETE + >('argoApplicationDeleteMutation', ApplicationKubeObject, CRUD_TYPES.DELETE, { + customMessages: { + onMutate: 'Uninstalling application...', + onError: 'Failed to uninstall application', + onSuccess: 'Start uninstalling application', + }, + }); + + const argoApplicationEditMutation = useResourceCRUDMutation< + ApplicationKubeObjectInterface, + CRUD_TYPES.EDIT + >('argoApplicationEditMutation', ApplicationKubeObject, CRUD_TYPES.EDIT, { + customMessages: { + onMutate: 'Updating application...', + onError: 'Failed to update application', + onSuccess: 'Start updating application', + }, + }); + + const createArgoApplication = React.useCallback( + async ({ argoApplication }: CreateArgoApplicationProps): Promise => { + argoApplicationCreateMutation.mutate(argoApplication); + }, + [argoApplicationCreateMutation] + ); + + const deleteArgoApplication = React.useCallback( + async ({ argoApplication }: DeleteArgoApplicationProps): Promise => { + argoApplicationDeleteMutation.mutate(argoApplication); + }, + [argoApplicationDeleteMutation] + ); + + const editArgoApplication = React.useCallback( + async ({ argoApplication }: EditArgoApplicationProps): Promise => { + argoApplicationEditMutation.mutate(argoApplication); + }, + [argoApplicationEditMutation] + ); + + const mutations = { + argoApplicationCreateMutation, + argoApplicationEditMutation, + argoApplicationDeleteMutation, + }; + + return { createArgoApplication, editArgoApplication, deleteArgoApplication, mutations }; +}; diff --git a/src/k8s/groups/ArgoCD/Application/hooks/useCreateArgoApplication.ts b/src/k8s/groups/ArgoCD/Application/hooks/useCreateArgoApplication.ts deleted file mode 100644 index 0fbaec09..00000000 --- a/src/k8s/groups/ArgoCD/Application/hooks/useCreateArgoApplication.ts +++ /dev/null @@ -1,153 +0,0 @@ -import React from 'react'; -import { CRUD_TYPES } from '../../../../../constants/crudTypes'; -import { useResourceCRUDMutation } from '../../../../../hooks/useResourceCRUDMutation'; -import { CDPipelineKubeObjectInterface } from '../../../EDP/CDPipeline/types'; -import { CodebaseKubeObjectInterface } from '../../../EDP/Codebase/types'; -import { CodebaseImageStreamKubeObjectInterface } from '../../../EDP/CodebaseImageStream/types'; -import { GitServerKubeObjectInterface } from '../../../EDP/GitServer/types'; -import { StageKubeObjectInterface } from '../../../EDP/Stage/types'; -import { ApplicationKubeObject } from '../index'; -import { ApplicationKubeObjectInterface } from '../types'; -import { createArgoApplicationInstance } from '../utils/createArgoApplicationInstance'; -import { editApplicationInstance } from '../utils/editApplicationInstance'; - -interface CreateArgoApplicationProps { - gitServers: GitServerKubeObjectInterface[]; - CDPipeline: CDPipelineKubeObjectInterface; - currentCDPipelineStage: StageKubeObjectInterface; - application: CodebaseKubeObjectInterface; - imageStream: CodebaseImageStreamKubeObjectInterface; - imageTag: string; - valuesOverride: boolean; - gitOpsCodebase: CodebaseKubeObjectInterface; -} - -interface EditArgoApplicationProps { - argoApplication: ApplicationKubeObjectInterface; - gitServers: GitServerKubeObjectInterface[]; - CDPipeline: CDPipelineKubeObjectInterface; - currentCDPipelineStage: StageKubeObjectInterface; - application: CodebaseKubeObjectInterface; - imageStream: CodebaseImageStreamKubeObjectInterface; - imageTag: string; - valuesOverride: boolean; - gitOpsCodebase: CodebaseKubeObjectInterface; -} - -interface DeleteArgoApplicationProps { - argoApplication: ApplicationKubeObjectInterface; -} - -export const useCreateArgoApplication = () => { - const argoApplicationCreateMutation = useResourceCRUDMutation< - ApplicationKubeObjectInterface, - CRUD_TYPES.CREATE - >('argoApplicationCreateMutation', ApplicationKubeObject, CRUD_TYPES.CREATE, { - customMessages: { - onMutate: 'Creating application...', - onError: 'Failed to deploy application', - onSuccess: 'Start deploying application', - }, - }); - - const argoApplicationEditMutation = useResourceCRUDMutation< - ApplicationKubeObjectInterface, - CRUD_TYPES.EDIT - >('argoApplicationEditMutation', ApplicationKubeObject, CRUD_TYPES.EDIT, { - customMessages: { - onMutate: 'Applying changes...', - onError: 'Failed to update application', - onSuccess: 'Start updating application', - }, - }); - - const argoApplicationDeleteMutation = useResourceCRUDMutation< - ApplicationKubeObjectInterface, - CRUD_TYPES.DELETE - >('argoApplicationDeleteMutation', ApplicationKubeObject, CRUD_TYPES.DELETE, { - customMessages: { - onMutate: 'Uninstalling application...', - onError: 'Failed to uninstall application', - onSuccess: 'Start uninstalling application', - }, - }); - - const createArgoApplication = React.useCallback( - async ({ - gitServers, - CDPipeline, - currentCDPipelineStage, - application, - imageStream, - imageTag, - valuesOverride, - gitOpsCodebase, - }: CreateArgoApplicationProps): Promise => { - const [gitServer] = gitServers.filter( - (el) => el.metadata.name === application.spec.gitServer - ); - - const argoApplicationData = createArgoApplicationInstance({ - CDPipeline, - currentCDPipelineStage, - application, - imageStream, - imageTag, - gitServer, - valuesOverride, - gitOpsCodebase, - }); - - argoApplicationCreateMutation.mutate(argoApplicationData); - }, - [argoApplicationCreateMutation] - ); - - const editArgoApplication = React.useCallback( - async ({ - CDPipeline, - currentCDPipelineStage, - argoApplication, - application, - imageStream, - imageTag, - gitServers, - valuesOverride, - gitOpsCodebase, - }: EditArgoApplicationProps): Promise => { - const [gitServer] = gitServers.filter( - (el) => el.metadata.name === application.spec.gitServer - ); - - const argoApplicationData: ApplicationKubeObjectInterface = editApplicationInstance({ - CDPipeline, - currentCDPipelineStage, - argoApplication, - application, - imageStream, - imageTag, - gitServer, - valuesOverride, - gitOpsCodebase, - }); - - argoApplicationEditMutation.mutate(argoApplicationData); - }, - [argoApplicationEditMutation] - ); - - const deleteArgoApplication = React.useCallback( - async ({ argoApplication }: DeleteArgoApplicationProps): Promise => { - argoApplicationDeleteMutation.mutate(argoApplication); - }, - [argoApplicationDeleteMutation] - ); - - const mutations = { - argoApplicationCreateMutation, - argoApplicationEditMutation, - argoApplicationDeleteMutation, - }; - - return { createArgoApplication, editArgoApplication, deleteArgoApplication, mutations }; -}; diff --git a/src/k8s/groups/ArgoCD/Application/labels.ts b/src/k8s/groups/ArgoCD/Application/labels.ts index c0fac36a..7e52c2ec 100644 --- a/src/k8s/groups/ArgoCD/Application/labels.ts +++ b/src/k8s/groups/ArgoCD/Application/labels.ts @@ -1,3 +1,5 @@ export const APPLICATION_LABEL_SELECTOR_PIPELINE = 'app.edp.epam.com/pipeline'; export const APPLICATION_LABEL_SELECTOR_STAGE = 'app.edp.epam.com/stage'; export const APPLICATION_LABEL_SELECTOR_APP_NAME = 'app.edp.epam.com/app-name'; +export const APPLICATION_LABEL_SELECTOR_APP_TYPE = 'app.edp.epam.com/app-type'; + diff --git a/src/k8s/groups/ArgoCD/Application/mocks/CDPipeline.mock.ts b/src/k8s/groups/ArgoCD/Application/mocks/CDPipeline.mock.ts deleted file mode 100644 index 353e1d88..00000000 --- a/src/k8s/groups/ArgoCD/Application/mocks/CDPipeline.mock.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { DeepPartial } from '../../../../../types/global'; -import { CDPipelineKubeObjectInterface } from '../../../EDP/CDPipeline/types'; - -export const CDPipelineMock: DeepPartial = { - apiVersion: 'v2.edp.epam.com/v1', - kind: 'CDPipeline', - metadata: { - name: 'test-pipeline-name', - namespace: 'test-namespace', - }, -}; diff --git a/src/k8s/groups/ArgoCD/Application/mocks/CDPipelineStage.mock.ts b/src/k8s/groups/ArgoCD/Application/mocks/CDPipelineStage.mock.ts deleted file mode 100644 index 689b3f51..00000000 --- a/src/k8s/groups/ArgoCD/Application/mocks/CDPipelineStage.mock.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { DeepPartial } from '../../../../../types/global'; -import { StageKubeObjectInterface } from '../../../EDP/Stage/types'; - -export const CDPipelineStageMock: DeepPartial = { - apiVersion: 'v2.edp.epam.com/v1', - kind: 'Stage', - metadata: { - name: 'test-pipeline-name-test-stage-name', - namespace: 'test-namespace', - }, - spec: { - name: 'test-stage-name', - clusterName: 'test-cluster-name', - namespace: 'test-namespace-test-pipeline-name-test-stage-name', - }, -}; diff --git a/src/k8s/groups/ArgoCD/Application/mocks/application.mock.ts b/src/k8s/groups/ArgoCD/Application/mocks/application.mock.ts deleted file mode 100644 index 4700ea29..00000000 --- a/src/k8s/groups/ArgoCD/Application/mocks/application.mock.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { DeepPartial } from '../../../../../types/global'; -import { ApplicationKubeObjectInterface } from '../types'; - -export const expectedApplicationOutputMock: DeepPartial = { - apiVersion: 'argoproj.io/v1alpha1', - kind: 'Application', - metadata: { - name: 'test-app-name-8ygse', - namespace: 'test-namespace', - labels: { - 'app.edp.epam.com/stage': 'test-stage-name', - 'app.edp.epam.com/pipeline': 'test-pipeline-name', - 'app.edp.epam.com/app-name': 'test-app-name', - }, - // @ts-ignore - finalizers: ['resources-finalizer.argocd.argoproj.io'], - ownerReferences: [ - { - apiVersion: 'v2.edp.epam.com/v1', - blockOwnerDeletion: true, - controller: true, - kind: 'Stage', - name: 'test-pipeline-name-test-stage-name', - uid: undefined, - }, - ], - }, - spec: { - project: 'test-namespace', - destination: { - namespace: 'test-namespace-test-pipeline-name-test-stage-name', - name: 'test-cluster-name', - }, - source: { - helm: { - releaseName: 'test-app-name', - parameters: [ - { name: 'image.tag', value: 'test-image-tag' }, - { - name: 'image.repository', - value: 'test-registry/test-namespace/test-app-name', - }, - ], - }, - path: 'deploy-templates', - repoURL: 'ssh://git@github.com:111/test-namespace/test-app-name', - targetRevision: 'test-image-tag', - }, - syncPolicy: { - syncOptions: ['CreateNamespace=true'], - automated: { selfHeal: true, prune: true }, - }, - }, -}; -export const expectedApplicationOutputMockWithValuesOverride: DeepPartial = - { - apiVersion: 'argoproj.io/v1alpha1', - kind: 'Application', - metadata: { - name: 'test-app-name-8ygse', - namespace: 'test-namespace', - labels: { - 'app.edp.epam.com/stage': 'test-stage-name', - 'app.edp.epam.com/pipeline': 'test-pipeline-name', - 'app.edp.epam.com/app-name': 'test-app-name', - }, - // @ts-ignore - finalizers: ['resources-finalizer.argocd.argoproj.io'], - ownerReferences: [ - { - apiVersion: 'v2.edp.epam.com/v1', - blockOwnerDeletion: true, - controller: true, - kind: 'Stage', - name: 'test-pipeline-name-test-stage-name', - uid: undefined, - }, - ], - }, - spec: { - project: 'test-namespace', - destination: { - namespace: 'test-namespace-test-pipeline-name-test-stage-name', - name: 'test-cluster-name', - }, - // @ts-ignore - sources: [ - { - repoURL: 'ssh://git@github.com:111/edp-gitops', - targetRevision: 'main', - ref: 'values', - }, - { - helm: { - valueFiles: ['$values/test-pipeline-name/test-stage-name/test-app-name-values.yaml'], - parameters: [ - { name: 'image.tag', value: 'test-image-tag' }, - { - name: 'image.repository', - value: 'test-registry/test-namespace/test-app-name', - }, - ], - releaseName: 'test-app-name', - }, - path: 'deploy-templates', - repoURL: 'ssh://git@github.com:111/test-namespace/test-app-name', - targetRevision: 'test-image-tag', - }, - ], - syncPolicy: { - syncOptions: ['CreateNamespace=true'], - automated: { selfHeal: true, prune: true }, - }, - }, - }; -export const expectedApplicationAfterEditOutputMock: DeepPartial = { - apiVersion: 'argoproj.io/v1alpha1', - kind: 'Application', - metadata: { - labels: { - 'app.edp.epam.com/app-name': 'test-app-name', - 'app.edp.epam.com/pipeline': 'demo', - 'app.edp.epam.com/stage': 'sit', - }, - name: 'test-app-name-t2yfb', - namespace: 'test-namespace', - }, - spec: { - destination: { name: 'in-cluster', namespace: 'test-namespace-demo-sit' }, - project: 'test-namespace', - syncPolicy: { - automated: { prune: true, selfHeal: true }, - syncOptions: ['CreateNamespace=true'], - }, - source: { - helm: { - releaseName: 'test-app-name', - parameters: [ - { name: 'image.tag', value: 'test-image-tag' }, - { - name: 'image.repository', - value: 'test-registry/test-namespace/test-app-name', - }, - ], - }, - path: 'deploy-templates', - repoURL: 'ssh://git@github.com:111/test-namespace/test-app-name', - targetRevision: 'test-image-tag', - }, - }, -}; -export const expectedApplicationAfterEditOutputMockWithValuesOverride: DeepPartial = - { - apiVersion: 'argoproj.io/v1alpha1', - kind: 'Application', - metadata: { - labels: { - 'app.edp.epam.com/app-name': 'test-app-name', - 'app.edp.epam.com/pipeline': 'demo', - 'app.edp.epam.com/stage': 'sit', - }, - name: 'test-app-name-t2yfb', - namespace: 'test-namespace', - }, - spec: { - destination: { name: 'in-cluster', namespace: 'test-namespace-demo-sit' }, - project: 'test-namespace', - syncPolicy: { - automated: { prune: true, selfHeal: true }, - syncOptions: ['CreateNamespace=true'], - }, - // @ts-ignore - sources: [ - { - repoURL: 'ssh://git@github.com:111/edp-gitops', - targetRevision: 'main', - ref: 'values', - }, - { - helm: { - valueFiles: ['$values/test-pipeline-name/test-stage-name/test-app-name-values.yaml'], - parameters: [ - { name: 'image.tag', value: 'test-image-tag' }, - { - name: 'image.repository', - value: 'test-registry/test-namespace/test-app-name', - }, - ], - releaseName: 'test-app-name', - }, - path: 'deploy-templates', - repoURL: 'ssh://git@github.com:111/test-namespace/test-app-name', - targetRevision: 'test-image-tag', - }, - ], - }, - }; diff --git a/src/k8s/groups/ArgoCD/Application/mocks/enrichedApplication.mock.ts b/src/k8s/groups/ArgoCD/Application/mocks/enrichedApplication.mock.ts deleted file mode 100644 index acc6c3cf..00000000 --- a/src/k8s/groups/ArgoCD/Application/mocks/enrichedApplication.mock.ts +++ /dev/null @@ -1,126 +0,0 @@ -export const enrichedApplicationMock = { - application: { - apiVersion: 'v2.edp.epam.com/v1', - kind: 'Codebase', - metadata: { - name: 'test-app-name', - namespace: 'test-namespace', - }, - spec: { - gitServer: 'github', - gitUrlPath: '/test-namespace/test-app-name', - type: 'application', - versioning: { - type: 'default', - }, - }, - }, - argoApplication: { - apiVersion: 'argoproj.io/v1alpha1', - kind: 'Application', - metadata: { - labels: { - 'app.edp.epam.com/app-name': 'test-app-name', - 'app.edp.epam.com/pipeline': 'demo', - 'app.edp.epam.com/stage': 'sit', - }, - name: 'test-app-name-t2yfb', - namespace: 'test-namespace', - }, - spec: { - destination: { - name: 'in-cluster', - namespace: 'test-namespace-demo-sit', - }, - project: 'test-namespace', - sources: [ - { - ref: 'values', - repoURL: 'ssh://git@git.epam.com:22/epmd-edp/sk-dev/edp-gitops', - targetRevision: 'main', - }, - { - helm: { - parameters: [ - { - name: 'image.tag', - value: '0.1.0-SNAPSHOT.3', - }, - { - name: 'image.repository', - value: - 'registry.eks-sandbox.aws.main.edp.projects.epam.com/test-namespace/test-app-name', - }, - ], - releaseName: 'test-app-name', - valueFiles: ['$values/demo/sit/test-app-name-values.yaml'], - }, - path: 'deploy-templates', - repoURL: 'ssh://git@git.epam.com:22/epmd-edp/sk-dev/test-app-name', - targetRevision: 'build/0.1.0-SNAPSHOT.1', - }, - ], - syncPolicy: { - automated: { - prune: true, - selfHeal: true, - }, - syncOptions: ['CreateNamespace=true'], - }, - }, - }, - applicationImageStream: 'test-app-name-master', - toPromote: true, - applicationImageStreams: [ - { - apiVersion: 'v2.edp.epam.com/v1', - kind: 'CodebaseImageStream', - metadata: { - name: 'test-app-name-master', - namespace: 'test-namespace', - }, - spec: { - codebase: 'test-app-name', - imageName: '01234567890.dkr.ecr.eu-central-1.amazonaws.com/test-namespace/test-app-name', - tags: [ - { - created: "2023-01-10'T'12:49:28", - name: 'master-0.0.1-SNAPSHOT-20230110-124530', - }, - ], - }, - }, - { - apiVersion: 'v2.edp.epam.com/v1', - kind: 'CodebaseImageStream', - metadata: { - creationTimestamp: '2023-01-10T13:08:59Z', - generation: 1, - managedFields: [ - { - apiVersion: 'v2.edp.epam.com/v1', - fieldsType: 'FieldsV1', - fieldsV1: { - 'f:spec': { - '.': {}, - 'f:codebase': {}, - 'f:imageName': {}, - }, - }, - manager: 'cd-pipeline-operator', - operation: 'Update', - time: '2023-01-10T13:08:59Z', - }, - ], - name: 'mm-test-dev-test-app-name-verified', - namespace: 'test-namespace', - resourceVersion: '650269748', - uid: 'e321646f-e814-413d-b636-534a14525448', - }, - spec: { - codebase: 'test-app-name', - imageName: '01234567890.dkr.ecr.eu-central-1.amazonaws.com/test-namespace/test-app-name', - }, - }, - ], -}; diff --git a/src/k8s/groups/ArgoCD/Application/mocks/gitOpsCodebase.mock.ts b/src/k8s/groups/ArgoCD/Application/mocks/gitOpsCodebase.mock.ts deleted file mode 100644 index c159d931..00000000 --- a/src/k8s/groups/ArgoCD/Application/mocks/gitOpsCodebase.mock.ts +++ /dev/null @@ -1,31 +0,0 @@ -export const gitOpsCodebaseMock = { - apiVersion: 'v2.edp.epam.com/v1', - kind: 'Codebase', - metadata: { - labels: { - 'app.edp.epam.com/codebaseType': 'system', - }, - name: 'edp-gitops', - namespace: 'edp-delivery-vp-dev', - }, - spec: { - buildTool: 'helm', - ciTool: 'tekton', - defaultBranch: 'main', - deploymentScript: 'helm-chart', - description: 'Custom values for deploy applications', - emptyProject: false, - framework: 'gitops', - gitServer: 'gerrit', - gitUrlPath: '/edp-gitops', - jiraIssueMetadataPayload: null, - lang: 'helm', - strategy: 'create', - ticketNamePattern: null, - type: 'system', - versioning: { - startFrom: '0.1.0-SNAPSHOT', - type: 'edp', - }, - }, -}; diff --git a/src/k8s/groups/ArgoCD/Application/mocks/imageStream.mock.ts b/src/k8s/groups/ArgoCD/Application/mocks/imageStream.mock.ts deleted file mode 100644 index 66433605..00000000 --- a/src/k8s/groups/ArgoCD/Application/mocks/imageStream.mock.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { DeepPartial } from '../../../../../types/global'; -import { CodebaseImageStreamKubeObjectInterface } from '../../../EDP/CodebaseImageStream/types'; - -export const imageStreamMock: DeepPartial = { - apiVersion: 'v2.edp.epam.com/v1', - kind: 'CodebaseImageStream', - metadata: { - name: 'test-app-name-master', - namespace: 'test-namespace', - }, - spec: { - codebase: 'test-app-name', - imageName: 'test-registry/test-namespace/test-app-name', - tags: [ - { - created: "2023-01-10'T'12:49:28", - name: 'master-0.0.1-SNAPSHOT-20230110-124530', - }, - ], - }, -}; diff --git a/src/k8s/groups/ArgoCD/Application/utils/createArgoApplicationInstance/index.test.ts b/src/k8s/groups/ArgoCD/Application/utils/createArgoApplicationInstance/index.test.ts deleted file mode 100644 index 5dffba69..00000000 --- a/src/k8s/groups/ArgoCD/Application/utils/createArgoApplicationInstance/index.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * @jest-environment jsdom - */ - -import { jest } from '@jest/globals'; -import { CDPipelineKubeObjectInterface } from '../../../../EDP/CDPipeline/types'; -import { CodebaseKubeObjectInterface } from '../../../../EDP/Codebase/types'; -import { CodebaseImageStreamKubeObjectInterface } from '../../../../EDP/CodebaseImageStream/types'; -import { gitServerGithubMock } from '../../../../EDP/GitServer/mocks/gitServer.mock'; -import { GitServerKubeObjectInterface } from '../../../../EDP/GitServer/types'; -import { StageKubeObjectInterface } from '../../../../EDP/Stage/types'; -import { - expectedApplicationOutputMock, - expectedApplicationOutputMockWithValuesOverride, -} from '../../mocks/application.mock'; -import { CDPipelineMock } from '../../mocks/CDPipeline.mock'; -import { CDPipelineStageMock } from '../../mocks/CDPipelineStage.mock'; -import { enrichedApplicationMock } from '../../mocks/enrichedApplication.mock'; -import { gitOpsCodebaseMock } from '../../mocks/gitOpsCodebase.mock'; -import { imageStreamMock } from '../../mocks/imageStream.mock'; -import { createArgoApplicationInstance } from './index'; - -beforeEach(() => { - jest - .spyOn(global.window.crypto, 'getRandomValues') - .mockReturnValue(new Uint32Array([2736861854, 4288701136, 612580786, 3178865852, 3429947584])); -}); - -afterEach(() => { - jest.clearAllMocks(); - jest.spyOn(global.window.crypto, 'getRandomValues').mockRestore(); -}); - -describe('testing createApplicationInstance', () => { - it('should return valid kube object', () => { - const object = createArgoApplicationInstance({ - CDPipeline: CDPipelineMock as CDPipelineKubeObjectInterface, - currentCDPipelineStage: CDPipelineStageMock as unknown as StageKubeObjectInterface, - application: enrichedApplicationMock.application as unknown as CodebaseKubeObjectInterface, - imageStream: imageStreamMock as CodebaseImageStreamKubeObjectInterface, - imageTag: 'test-image-tag', - gitServer: gitServerGithubMock as GitServerKubeObjectInterface, - valuesOverride: false, - gitOpsCodebase: gitOpsCodebaseMock as unknown as CodebaseKubeObjectInterface, - }); - - expect(object).toEqual(expectedApplicationOutputMock); - }); - - it('should return valid kube object with values override', () => { - const object = createArgoApplicationInstance({ - CDPipeline: CDPipelineMock as CDPipelineKubeObjectInterface, - currentCDPipelineStage: CDPipelineStageMock as unknown as StageKubeObjectInterface, - application: enrichedApplicationMock.application as unknown as CodebaseKubeObjectInterface, - imageStream: imageStreamMock as CodebaseImageStreamKubeObjectInterface, - imageTag: 'test-image-tag', - gitServer: gitServerGithubMock as GitServerKubeObjectInterface, - valuesOverride: true, - gitOpsCodebase: gitOpsCodebaseMock as unknown as CodebaseKubeObjectInterface, - }); - - expect(object).toEqual(expectedApplicationOutputMockWithValuesOverride); - }); -}); diff --git a/src/k8s/groups/ArgoCD/Application/utils/createArgoApplicationInstance/index.ts b/src/k8s/groups/ArgoCD/Application/utils/createArgoApplicationInstance/index.ts deleted file mode 100644 index 888c3f1a..00000000 --- a/src/k8s/groups/ArgoCD/Application/utils/createArgoApplicationInstance/index.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { - CODEBASE_COMMON_BUILD_TOOLS, - CODEBASE_COMMON_FRAMEWORKS, - CODEBASE_COMMON_LANGUAGES, -} from '../../../../../../configs/codebase-mappings'; -import { CODEBASE_VERSIONING_TYPES } from '../../../../../../constants/codebaseVersioningTypes'; -import { GIT_PROVIDERS } from '../../../../../../constants/gitProviders'; -import { createRandomString } from '../../../../../../utils/createRandomString'; -import { CDPipelineKubeObjectInterface } from '../../../../EDP/CDPipeline/types'; -import { CodebaseKubeObjectInterface } from '../../../../EDP/Codebase/types'; -import { CodebaseImageStreamKubeObjectInterface } from '../../../../EDP/CodebaseImageStream/types'; -import { GitServerKubeObjectInterface } from '../../../../EDP/GitServer/types'; -import { StageKubeObjectInterface } from '../../../../EDP/Stage/types'; -import { ApplicationKubeObjectConfig } from '../../config'; -import { ApplicationKubeObjectInterface } from '../../types'; - -const { kind, group, version } = ApplicationKubeObjectConfig; - -export const createArgoApplicationInstance = ({ - CDPipeline, - currentCDPipelineStage, - application, - imageStream, - imageTag, - gitServer, - valuesOverride, - gitOpsCodebase, -}: { - CDPipeline: CDPipelineKubeObjectInterface; - currentCDPipelineStage: StageKubeObjectInterface; - application: CodebaseKubeObjectInterface; - imageStream: CodebaseImageStreamKubeObjectInterface; - imageTag: string; - gitServer: GitServerKubeObjectInterface; - valuesOverride: boolean; - gitOpsCodebase: CodebaseKubeObjectInterface; -}): ApplicationKubeObjectInterface => { - const { - metadata: { namespace, name: pipelineName }, - } = CDPipeline; - - const { - spec: { name: stageName }, - } = currentCDPipelineStage; - - const { - metadata: { name: appName }, - spec: { - versioning: { type: versioningType }, - gitUrlPath, - lang, - framework, - buildTool, - }, - } = application; - - const { - spec: { imageName }, - } = imageStream; - - const { - spec: { gitHost, sshPort, gitProvider }, - } = gitServer; - - const isEDPVersioning = versioningType === CODEBASE_VERSIONING_TYPES.EDP; - const repoUrlUser = gitProvider === GIT_PROVIDERS.GERRIT ? 'argocd' : 'git'; - const valuesRepoURL = `ssh://${repoUrlUser}@${gitHost}:${sshPort}${gitOpsCodebase.spec.gitUrlPath}`; - const repoURL = `ssh://${repoUrlUser}@${gitHost}:${sshPort}${gitUrlPath}`; - const targetRevision = isEDPVersioning ? `build/${imageTag}` : imageTag; - - const isHelmApp = - lang === CODEBASE_COMMON_LANGUAGES.HELM && - framework === CODEBASE_COMMON_FRAMEWORKS.HELM && - buildTool === CODEBASE_COMMON_BUILD_TOOLS.HELM; - - return { - apiVersion: `${group}/${version}`, - kind, - // @ts-ignore - metadata: { - name: `${appName}-${createRandomString()}`, - namespace, - labels: { - 'app.edp.epam.com/stage': stageName, - 'app.edp.epam.com/pipeline': pipelineName, - 'app.edp.epam.com/app-name': appName, - }, - // @ts-ignore - finalizers: ['resources-finalizer.argocd.argoproj.io'], - ownerReferences: [ - { - apiVersion: currentCDPipelineStage.apiVersion, - blockOwnerDeletion: true, - controller: true, - kind: currentCDPipelineStage.kind, - name: currentCDPipelineStage.metadata.name, - uid: currentCDPipelineStage.metadata.uid, - }, - ], - }, - // @ts-ignore - spec: { - project: namespace, - destination: { - namespace: currentCDPipelineStage.spec.namespace, - name: currentCDPipelineStage.spec.clusterName, - }, - ...(valuesOverride - ? { - sources: [ - { - repoURL: valuesRepoURL, - targetRevision: 'main', - ref: 'values', - }, - { - helm: { - valueFiles: [`$values/${pipelineName}/${stageName}/${appName}-values.yaml`], - parameters: [ - { - name: 'image.tag', - value: imageTag, - }, - { - name: 'image.repository', - value: imageName, - }, - ], - releaseName: appName, - }, - path: 'deploy-templates', - repoURL: repoURL, - targetRevision: targetRevision, - }, - ], - } - : { - source: { - helm: { - releaseName: appName, - parameters: [ - ...(isHelmApp - ? [] - : [ - { - name: 'image.tag', - value: imageTag, - }, - { - name: 'image.repository', - value: imageName, - }, - ]), - ], - }, - path: 'deploy-templates', - repoURL: repoURL, - targetRevision: targetRevision, - }, - }), - syncPolicy: { - syncOptions: ['CreateNamespace=true'], - automated: { - selfHeal: true, - prune: true, - }, - }, - }, - }; -}; diff --git a/src/k8s/groups/ArgoCD/Application/utils/createVClusterApplication/index.test.ts b/src/k8s/groups/ArgoCD/Application/utils/createVClusterApplication/index.test.ts new file mode 100644 index 00000000..934c4b65 --- /dev/null +++ b/src/k8s/groups/ArgoCD/Application/utils/createVClusterApplication/index.test.ts @@ -0,0 +1,51 @@ +import { createVClusterApplication } from '.'; + +describe('testing createVClusterApplication', () => { + it('should return valid kube object', () => { + const object = createVClusterApplication({ + namespace: 'test-namespace', + clusterName: 'test-cluster-name', + }); + + expect(object).toEqual({ + apiVersion: 'argoproj.io/v1alpha1', + kind: 'Application', + metadata: { + name: 'test-namespace-test-cluster-name', + namespace: 'test-namespace', + labels: { + 'app.edp.epam.com/app-type': 'cluster', + }, + }, + spec: { + project: 'test-namespace', + source: { + repoURL: 'https://github.com/loft-sh/vcluster', + targetRevision: 'v0.20.0', + path: 'chart', + helm: { + parameters: [ + { + name: 'controlPlane.statefulSet.image.tag', + value: '0.20.0', + }, + { + name: 'exportKubeConfig.server', + value: 'https://test-cluster-name.test-namespace.svc:443', + }, + { + name: 'controlPlane.statefulSet.persistence.volumeClaim.retentionPolicy', + value: 'Delete', + }, + ], + }, + }, + destination: { + server: 'https://kubernetes.default.svc', + namespace: 'test-namespace', + }, + syncPolicy: { automated: { prune: true, selfHeal: true } }, + }, + }); + }); +}); diff --git a/src/k8s/groups/ArgoCD/Application/utils/createVClusterApplication/index.ts b/src/k8s/groups/ArgoCD/Application/utils/createVClusterApplication/index.ts new file mode 100644 index 00000000..1256a2d2 --- /dev/null +++ b/src/k8s/groups/ArgoCD/Application/utils/createVClusterApplication/index.ts @@ -0,0 +1,55 @@ +import { APPLICATION_LABEL_SELECTOR_APP_TYPE } from '../../labels'; + +export const createVClusterApplication = ({ + namespace, + clusterName, +}: { + namespace: string; + clusterName: string; +}) => { + return { + apiVersion: 'argoproj.io/v1alpha1', + kind: 'Application', + metadata: { + name: `${namespace}-${clusterName}`, + namespace: namespace, + labels: { + [APPLICATION_LABEL_SELECTOR_APP_TYPE]: 'cluster', + }, + }, + spec: { + project: namespace, + source: { + repoURL: 'https://github.com/loft-sh/vcluster', + targetRevision: 'v0.20.0', + path: 'chart', + helm: { + parameters: [ + { + name: 'controlPlane.statefulSet.image.tag', + value: '0.20.0', + }, + { + name: 'exportKubeConfig.server', + value: `https://${clusterName}.${namespace}.svc:443`, + }, + { + name: 'controlPlane.statefulSet.persistence.volumeClaim.retentionPolicy', + value: 'Delete', + }, + ], + }, + }, + destination: { + server: 'https://kubernetes.default.svc', + namespace: namespace, + }, + syncPolicy: { + automated: { + prune: true, + selfHeal: true, + }, + }, + }, + }; +}; diff --git a/src/k8s/groups/ArgoCD/Application/utils/editApplicationInstance/index.test.ts b/src/k8s/groups/ArgoCD/Application/utils/editApplicationInstance/index.test.ts deleted file mode 100644 index a816f282..00000000 --- a/src/k8s/groups/ArgoCD/Application/utils/editApplicationInstance/index.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { CDPipelineKubeObjectInterface } from '../../../../EDP/CDPipeline/types'; -import { CodebaseKubeObjectInterface } from '../../../../EDP/Codebase/types'; -import { CodebaseImageStreamKubeObjectInterface } from '../../../../EDP/CodebaseImageStream/types'; -import { gitServerGithubMock } from '../../../../EDP/GitServer/mocks/gitServer.mock'; -import { GitServerKubeObjectInterface } from '../../../../EDP/GitServer/types'; -import { StageKubeObjectInterface } from '../../../../EDP/Stage/types'; -import { - expectedApplicationAfterEditOutputMock, - expectedApplicationAfterEditOutputMockWithValuesOverride, -} from '../../mocks/application.mock'; -import { CDPipelineMock } from '../../mocks/CDPipeline.mock'; -import { CDPipelineStageMock } from '../../mocks/CDPipelineStage.mock'; -import { enrichedApplicationMock } from '../../mocks/enrichedApplication.mock'; -import { gitOpsCodebaseMock } from '../../mocks/gitOpsCodebase.mock'; -import { imageStreamMock } from '../../mocks/imageStream.mock'; -import { ApplicationKubeObjectInterface } from '../../types'; -import { editApplicationInstance } from './index'; - -describe('testing editApplicationInstance', () => { - it('should return valid kube object', () => { - const object = editApplicationInstance({ - CDPipeline: CDPipelineMock as CDPipelineKubeObjectInterface, - currentCDPipelineStage: CDPipelineStageMock as unknown as StageKubeObjectInterface, - application: enrichedApplicationMock.application as unknown as CodebaseKubeObjectInterface, - imageStream: imageStreamMock as CodebaseImageStreamKubeObjectInterface, - imageTag: 'test-image-tag', - gitServer: gitServerGithubMock as GitServerKubeObjectInterface, - valuesOverride: false, - gitOpsCodebase: gitOpsCodebaseMock as unknown as CodebaseKubeObjectInterface, - argoApplication: - enrichedApplicationMock.argoApplication as unknown as ApplicationKubeObjectInterface, - }); - - expect(object).toEqual(expectedApplicationAfterEditOutputMock); - }); - - it('should return valid kube object with values override', () => { - const object = editApplicationInstance({ - CDPipeline: CDPipelineMock as CDPipelineKubeObjectInterface, - currentCDPipelineStage: CDPipelineStageMock as unknown as StageKubeObjectInterface, - application: enrichedApplicationMock.application as unknown as CodebaseKubeObjectInterface, - imageStream: imageStreamMock as CodebaseImageStreamKubeObjectInterface, - imageTag: 'test-image-tag', - gitServer: gitServerGithubMock as GitServerKubeObjectInterface, - valuesOverride: true, - gitOpsCodebase: gitOpsCodebaseMock as unknown as CodebaseKubeObjectInterface, - argoApplication: - enrichedApplicationMock.argoApplication as unknown as ApplicationKubeObjectInterface, - }); - - expect(object).toEqual(expectedApplicationAfterEditOutputMockWithValuesOverride); - }); -}); diff --git a/src/k8s/groups/ArgoCD/Application/utils/editApplicationInstance/index.ts b/src/k8s/groups/ArgoCD/Application/utils/editApplicationInstance/index.ts deleted file mode 100644 index 14b86336..00000000 --- a/src/k8s/groups/ArgoCD/Application/utils/editApplicationInstance/index.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { - CODEBASE_COMMON_BUILD_TOOLS, - CODEBASE_COMMON_FRAMEWORKS, - CODEBASE_COMMON_LANGUAGES, -} from '../../../../../../configs/codebase-mappings'; -import { CODEBASE_VERSIONING_TYPES } from '../../../../../../constants/codebaseVersioningTypes'; -import { GIT_PROVIDERS } from '../../../../../../constants/gitProviders'; -import { CDPipelineKubeObjectInterface } from '../../../../EDP/CDPipeline/types'; -import { CodebaseKubeObjectInterface } from '../../../../EDP/Codebase/types'; -import { CodebaseImageStreamKubeObjectInterface } from '../../../../EDP/CodebaseImageStream/types'; -import { GitServerKubeObjectInterface } from '../../../../EDP/GitServer/types'; -import { StageKubeObjectInterface } from '../../../../EDP/Stage/types'; -import { ApplicationKubeObjectInterface } from '../../types'; - -export const editApplicationInstance = ({ - CDPipeline, - currentCDPipelineStage, - argoApplication, - application, - imageStream, - imageTag, - gitServer, - valuesOverride, - gitOpsCodebase, -}: { - CDPipeline: CDPipelineKubeObjectInterface; - currentCDPipelineStage: StageKubeObjectInterface; - application: CodebaseKubeObjectInterface; - argoApplication: ApplicationKubeObjectInterface; - imageStream: CodebaseImageStreamKubeObjectInterface; - imageTag: string; - gitServer: GitServerKubeObjectInterface; - valuesOverride: boolean; - gitOpsCodebase: CodebaseKubeObjectInterface; -}): ApplicationKubeObjectInterface => { - const { - metadata: { name: pipelineName }, - } = CDPipeline; - - const { - metadata: { name: appName }, - spec: { - versioning: { type: versioningType }, - gitUrlPath, - lang, - framework, - buildTool, - }, - } = application; - - const { - spec: { name: stageName }, - } = currentCDPipelineStage; - - const { - spec: { imageName }, - } = imageStream; - - const { - spec: { gitHost, sshPort, gitProvider }, - } = gitServer; - - const base = { ...argoApplication }; - - const isEDPVersioning = versioningType === CODEBASE_VERSIONING_TYPES.EDP; - const repoUrlUser = gitProvider === GIT_PROVIDERS.GERRIT ? 'argocd' : 'git'; - const valuesRepoURL = `ssh://${repoUrlUser}@${gitHost}:${sshPort}${gitOpsCodebase.spec.gitUrlPath}`; - const repoURL = `ssh://${repoUrlUser}@${gitHost}:${sshPort}${gitUrlPath}`; - const targetRevision = isEDPVersioning ? `build/${imageTag}` : imageTag; - const isHelmApp = - lang === CODEBASE_COMMON_LANGUAGES.HELM && - framework === CODEBASE_COMMON_FRAMEWORKS.HELM && - buildTool === CODEBASE_COMMON_BUILD_TOOLS.HELM; - - const newSource = { - ...(valuesOverride - ? { - sources: [ - { - repoURL: valuesRepoURL, - targetRevision: 'main', - ref: 'values', - }, - { - helm: { - valueFiles: [`$values/${pipelineName}/${stageName}/${appName}-values.yaml`], - parameters: [ - { - name: 'image.tag', - value: imageTag, - }, - { - name: 'image.repository', - value: imageName, - }, - ], - releaseName: appName, - }, - path: 'deploy-templates', - repoURL: repoURL, - targetRevision: targetRevision, - }, - ], - } - : { - source: { - helm: { - releaseName: appName, - parameters: [ - ...(isHelmApp - ? [] - : [ - { - name: 'image.tag', - value: imageTag, - }, - { - name: 'image.repository', - value: imageName, - }, - ]), - ], - }, - path: 'deploy-templates', - repoURL: repoURL, - targetRevision: targetRevision, - }, - }), - }; - - delete base.spec.source; - // @ts-ignore - delete base.spec.sources; - - base.spec = { - ...base.spec, - ...newSource, - }; - - return base; -}; diff --git a/src/pages/configuration/pages/clusters/constants.ts b/src/pages/configuration/pages/clusters/constants.ts index 37adff5d..b1f3812e 100644 --- a/src/pages/configuration/pages/clusters/constants.ts +++ b/src/pages/configuration/pages/clusters/constants.ts @@ -1,4 +1,6 @@ import { EDP_USER_GUIDE } from '../../../../constants/urls'; +import { ApplicationKubeObject } from '../../../../k8s/groups/ArgoCD/Application'; +import { ApplicationKubeObjectConfig } from '../../../../k8s/groups/ArgoCD/Application/config'; import { SecretKubeObject } from '../../../../k8s/groups/default/Secret'; import { SecretKubeObjectConfig } from '../../../../k8s/groups/default/Secret/config'; import { PageDescription } from '../../../../types/pages'; @@ -12,7 +14,16 @@ export const pageDescription: PageDescription = { }; export const pagePermissionsToCheck = { - create: [{ instance: SecretKubeObject, config: SecretKubeObjectConfig }], - update: [{ instance: SecretKubeObject, config: SecretKubeObjectConfig }], - delete: [{ instance: SecretKubeObject, config: SecretKubeObjectConfig }], + create: [ + { instance: SecretKubeObject, config: SecretKubeObjectConfig }, + { instance: ApplicationKubeObject, config: ApplicationKubeObjectConfig }, + ], + update: [ + { instance: SecretKubeObject, config: SecretKubeObjectConfig }, + { instance: ApplicationKubeObject, config: ApplicationKubeObjectConfig }, + ], + delete: [ + { instance: SecretKubeObject, config: SecretKubeObjectConfig }, + { instance: ApplicationKubeObject, config: ApplicationKubeObjectConfig }, + ], }; diff --git a/src/pages/configuration/pages/clusters/view.tsx b/src/pages/configuration/pages/clusters/view.tsx index 5991d48e..0a26af7b 100644 --- a/src/pages/configuration/pages/clusters/view.tsx +++ b/src/pages/configuration/pages/clusters/view.tsx @@ -1,22 +1,37 @@ import { Icon } from '@iconify/react'; +import { EditorDialog } from '@kinvolk/headlamp-plugin/lib/CommonComponents'; +import { KubeObjectInterface } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster'; import { Accordion, AccordionDetails, AccordionSummary, + Button, Grid, + IconButton, + Paper, + Stack, Tooltip, Typography, } from '@mui/material'; import React from 'react'; +import { ConditionalWrapper } from '../../../../components/ConditionalWrapper'; import { EmptyList } from '../../../../components/EmptyList'; import { ErrorContent } from '../../../../components/ErrorContent'; import { LoadingWrapper } from '../../../../components/LoadingWrapper'; +import { StatusIcon } from '../../../../components/StatusIcon'; import { ICONS } from '../../../../icons/iconify-icons-mapping'; +import { ApplicationKubeObject } from '../../../../k8s/groups/ArgoCD/Application'; +import { useArgoApplicationCRUD } from '../../../../k8s/groups/ArgoCD/Application/hooks/useArgoApplicationCRUD'; +import { APPLICATION_LABEL_SELECTOR_APP_TYPE } from '../../../../k8s/groups/ArgoCD/Application/labels'; +import { ApplicationKubeObjectInterface } from '../../../../k8s/groups/ArgoCD/Application/types'; import { SecretKubeObject } from '../../../../k8s/groups/default/Secret'; import { SECRET_LABEL_SECRET_TYPE } from '../../../../k8s/groups/default/Secret/labels'; +import { useDialogContext } from '../../../../providers/Dialog/hooks'; import { FORM_MODES } from '../../../../types/forms'; import { getDefaultNamespace } from '../../../../utils/getDefaultNamespace'; import { getForbiddenError } from '../../../../utils/getForbiddenError'; +import { DeleteKubeObjectDialog } from '../../../../widgets/dialogs/DeleteKubeObject'; +import { ManageVClusterDialog } from '../../../../widgets/dialogs/ManageVCluster'; import { ManageClusterSecret } from '../../../../widgets/ManageClusterSecret'; import { ConfigurationPageContent } from '../../components/ConfigurationPageContent'; import { pageDescription } from './constants'; @@ -28,6 +43,11 @@ export const PageView = () => { labelSelector: `${SECRET_LABEL_SECRET_TYPE}=cluster`, }); + const [vClusterApps, vClusterAppsError] = ApplicationKubeObject.useList({ + namespace: getDefaultNamespace(), + labelSelector: `${APPLICATION_LABEL_SELECTOR_APP_TYPE}=cluster`, + }); + const isLoading = clusterSecrets === null && !clusterSecretsError; const [isCreateDialogOpen, setCreateDialogOpen] = React.useState(false); @@ -40,7 +60,54 @@ export const PageView = () => { setExpandedPanel(isExpanded ? panel : null); }; + const [editor, setEditor] = React.useState<{ + open: boolean; + data: KubeObjectInterface | undefined; + }>({ + open: false, + data: undefined, + }); + + const handleOpenEditor = (data: KubeObjectInterface) => { + setEditor({ open: true, data }); + }; + + const handleCloseEditor = () => { + setEditor({ open: false, data: undefined }); + }; + + const { + mutations: { argoApplicationEditMutation }, + } = useArgoApplicationCRUD(); + + const handleEditorSave = (data: ApplicationKubeObjectInterface[]) => { + const [item] = data; + + argoApplicationEditMutation.mutate(item, { + onSuccess: () => { + handleCloseEditor(); + }, + }); + }; + const permissions = useTypedPermissions(); + const { setDialog } = useDialogContext(); + const handleDeleteApplication = React.useCallback( + (application: ApplicationKubeObjectInterface) => { + if (!permissions.delete.Application.allowed) { + return; + } + + setDialog(DeleteKubeObjectDialog, { + kubeObject: ApplicationKubeObject, + kubeObjectData: application, + objectName: application?.metadata.name, + description: `Confirm the deletion of the application`, + }); + }, + [permissions.delete.Application.allowed, setDialog] + ); + const renderPageContent = React.useCallback(() => { const forbiddenError = getForbiddenError(clusterSecretsError); @@ -64,7 +131,7 @@ export const PageView = () => { return ( - + {clusterSecrets?.map((el) => { const secret = el.jsonData; const ownerReference = secret?.metadata?.ownerReferences?.[0]?.kind; @@ -74,53 +141,157 @@ export const PageView = () => { const isExpanded = expandedPanel === secretName || singleItem; return ( - - - } - style={{ - cursor: singleItem ? 'default' : 'pointer', + + } + style={{ + cursor: singleItem ? 'default' : 'pointer', + }} + > + + + + {secret.metadata.name} + + + {!!ownerReference && ( + + + + + + )} + + + + + + + ); + })} + + + ); + }, [clusterSecrets, clusterSecretsError, expandedPanel, isLoading, permissions]); + + const renderExtraContent = React.useCallback(() => { + const forbiddenError = getForbiddenError(vClusterAppsError); + + if (forbiddenError) { + return ; + } + + if (!vClusterApps?.length && vClusterApps !== null && !vClusterAppsError) { + return ( + <> + setDialog(ManageVClusterDialog, {})} + /> + + ); + } + + return ( + + + {vClusterApps?.map((application) => { + const health = application?.status?.health?.status; + + const [icon, color, isRotating] = ApplicationKubeObject.getHealthStatusIcon(health); + + return ( + + `${t.typography.pxToRem(10)} ${t.typography.pxToRem(20)}` }}> + - - - - {secret.metadata.name} - - - {!!ownerReference && ( - - - + + + {application.metadata.name} + + + ( + +
{children}
-
- )} -
- - - - - + )} + > + handleDeleteApplication(application)} + disabled={!permissions.delete.Application.allowed} + size="large" + > + + + + ( + +
{children}
+
+ )} + > + +
+
+ +
); })}
); - }, [clusterSecrets, clusterSecretsError, expandedPanel, isLoading, permissions]); + }, [ + handleDeleteApplication, + permissions.delete.Application.allowed, + permissions.delete.Application.reason, + permissions.update.Application.allowed, + permissions.update.Application.reason, + setDialog, + vClusterApps, + vClusterAppsError, + ]); return ( { }} pageDescription={pageDescription} > - {renderPageContent()} + + {renderPageContent()} + t.typography.pxToRem(28)} color="primary.dark" gutterBottom> + Virtual clusters + + Manage Virtual clusters + + + ( + +
{children}
+
+ )} + > + +
+
+ {renderExtraContent()} +
+
+ {editor.open && editor.data?.jsonData && ( + + )}
); }; diff --git a/src/pages/configuration/pages/codemie/view.tsx b/src/pages/configuration/pages/codemie/view.tsx index cc91aa0a..903520f8 100644 --- a/src/pages/configuration/pages/codemie/view.tsx +++ b/src/pages/configuration/pages/codemie/view.tsx @@ -309,14 +309,6 @@ export const PageView = () => { ); })} - {editor.open && editor.data?.jsonData && ( - - )} t.typography.pxToRem(40) }}> @@ -381,17 +373,17 @@ export const PageView = () => { ); })} - {editor.open && editor.data?.jsonData && ( - - )} + {editor.open && editor.data?.jsonData && ( + + )} ); }; diff --git a/src/pages/stage-details/components/Applications/index.tsx b/src/pages/stage-details/components/Applications/index.tsx index c2ba8238..b57c7400 100644 --- a/src/pages/stage-details/components/Applications/index.tsx +++ b/src/pages/stage-details/components/Applications/index.tsx @@ -8,7 +8,7 @@ import { Table } from '../../../../components/Table'; import { TableProps } from '../../../../components/Table/types'; import { TabSection } from '../../../../components/TabSection'; import { ICONS } from '../../../../icons/iconify-icons-mapping'; -import { useCreateArgoApplication } from '../../../../k8s/groups/ArgoCD/Application/hooks/useCreateArgoApplication'; +import { useArgoApplicationCRUD } from '../../../../k8s/groups/ArgoCD/Application/hooks/useArgoApplicationCRUD'; import { APPLICATIONS_TABLE_MODE } from '../../constants'; import { useTypedPermissions } from '../../hooks/useTypedPermissions'; import { EnrichedApplicationWithArgoApplication } from '../../types'; @@ -43,7 +43,7 @@ export const Applications = ({ const { deleteArgoApplication, mutations: { argoApplicationDeleteMutation }, - } = useCreateArgoApplication(); + } = useArgoApplicationCRUD(); const someArgoApplicationMutationIsLoading = React.useMemo( () => argoApplicationDeleteMutation.isLoading, diff --git a/src/widgets/dialogs/ManageVCluster/components/Create/components/DialogHeader/index.tsx b/src/widgets/dialogs/ManageVCluster/components/Create/components/DialogHeader/index.tsx new file mode 100644 index 00000000..e4c93190 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/components/Create/components/DialogHeader/index.tsx @@ -0,0 +1,16 @@ +import { Stack, Typography, useTheme } from '@mui/material'; +import React from 'react'; + +export const DialogHeader = () => { + const theme = useTheme(); + + return ( + + + + Create Virtual Cluster + + + + ); +}; diff --git a/src/widgets/dialogs/ManageVCluster/components/Create/components/Form/index.tsx b/src/widgets/dialogs/ManageVCluster/components/Create/components/Form/index.tsx new file mode 100644 index 00000000..02dda492 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/components/Create/components/Form/index.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { Name } from '../../../fields'; + +export const Form = () => { + return ; +}; diff --git a/src/widgets/dialogs/ManageVCluster/components/Create/components/FormActions/index.tsx b/src/widgets/dialogs/ManageVCluster/components/Create/components/FormActions/index.tsx new file mode 100644 index 00000000..916b1e43 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/components/Create/components/FormActions/index.tsx @@ -0,0 +1,77 @@ +import { Box, Button, Stack, useTheme } from '@mui/material'; +import React from 'react'; +import { useArgoApplicationCRUD } from '../../../../../../../k8s/groups/ArgoCD/Application/hooks/useArgoApplicationCRUD'; +import { ApplicationKubeObjectInterface } from '../../../../../../../k8s/groups/ArgoCD/Application/types'; +import { createVClusterApplication } from '../../../../../../../k8s/groups/ArgoCD/Application/utils/createVClusterApplication'; +import { useTypedFormContext } from '../../../../hooks/useFormContext'; +import { useCurrentDialog } from '../../../../providers/CurrentDialog/hooks'; +import { ManageVClusterFormValues } from '../../../../types'; + +export const FormActions = () => { + const { + state: { closeDialog }, + extra: { EDPConfigMap }, + } = useCurrentDialog(); + + const { + reset, + formState: { isDirty }, + handleSubmit, + } = useTypedFormContext(); + + const handleClose = React.useCallback(() => { + closeDialog(); + reset(); + }, [closeDialog, reset]); + + const handleResetFields = React.useCallback(() => { + reset(); + }, [reset]); + + const { + createArgoApplication, + mutations: { argoApplicationCreateMutation }, + } = useArgoApplicationCRUD(); + + const isLoading = argoApplicationCreateMutation.isLoading; + + const onSubmit = React.useCallback( + async (values: ManageVClusterFormValues) => { + const newVCluster = createVClusterApplication({ + namespace: EDPConfigMap?.data.edp_name, + clusterName: values.name, + }); + await createArgoApplication({ + argoApplication: newVCluster as unknown as ApplicationKubeObjectInterface, + }); + handleClose(); + }, + [EDPConfigMap?.data.edp_name, createArgoApplication, handleClose] + ); + + const theme = useTheme(); + + return ( + + + + + + + + + + ); +}; diff --git a/src/widgets/dialogs/ManageVCluster/components/Create/index.tsx b/src/widgets/dialogs/ManageVCluster/components/Create/index.tsx new file mode 100644 index 00000000..170eaea7 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/components/Create/index.tsx @@ -0,0 +1,26 @@ +import { DialogActions, DialogContent, DialogTitle } from '@mui/material'; +import React from 'react'; +import { FormContextProvider } from '../../../../../providers/Form/provider'; +import { DialogHeader } from './components/DialogHeader'; +import { Form } from './components/Form'; +import { FormActions } from './components/FormActions'; + +export const Create = () => { + return ( + + + + + +
+ + + + + + ); +}; diff --git a/src/widgets/dialogs/ManageVCluster/components/fields/Name/index.tsx b/src/widgets/dialogs/ManageVCluster/components/fields/Name/index.tsx new file mode 100644 index 00000000..436e5908 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/components/fields/Name/index.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { FormTextField } from '../../../../../../providers/Form/components/FormTextField'; +import { useTypedFormContext } from '../../../hooks/useFormContext'; +import { V_CLUSTER_FORM_NAMES } from '../../../names'; + +export const Name = () => { + const { + register, + control, + formState: { errors }, + } = useTypedFormContext(); + + return ( + + ); +}; diff --git a/src/widgets/dialogs/ManageVCluster/components/fields/index.ts b/src/widgets/dialogs/ManageVCluster/components/fields/index.ts new file mode 100644 index 00000000..219e48c6 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/components/fields/index.ts @@ -0,0 +1 @@ +export * from './Name'; diff --git a/src/widgets/dialogs/ManageVCluster/constants.ts b/src/widgets/dialogs/ManageVCluster/constants.ts new file mode 100644 index 00000000..36acca1d --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/constants.ts @@ -0,0 +1 @@ +export const DIALOG_NAME = 'MANAGE_V_CLUSTER_DIALOG'; diff --git a/src/widgets/dialogs/ManageVCluster/hooks/useFormContext.ts b/src/widgets/dialogs/ManageVCluster/hooks/useFormContext.ts new file mode 100644 index 00000000..ecb3fc10 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/hooks/useFormContext.ts @@ -0,0 +1,4 @@ +import { useFormContext } from 'react-hook-form'; +import { ManageVClusterFormValues } from '../types'; + +export const useTypedFormContext = () => useFormContext(); diff --git a/src/widgets/dialogs/ManageVCluster/index.tsx b/src/widgets/dialogs/ManageVCluster/index.tsx new file mode 100644 index 00000000..fcc23ad4 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/index.tsx @@ -0,0 +1,18 @@ +import { Dialog } from '@mui/material'; +import React from 'react'; +import { Create } from './components/Create'; +import { DIALOG_NAME } from './constants'; +import { CurrentDialogContextProvider } from './providers/CurrentDialog/provider'; +import { ManageVClusterDialogProps } from './types'; + +export const ManageVClusterDialog: React.FC = ({ props, state }) => { + return ( + + + + + + ); +}; + +ManageVClusterDialog.displayName = DIALOG_NAME; diff --git a/src/widgets/dialogs/ManageVCluster/names.ts b/src/widgets/dialogs/ManageVCluster/names.ts new file mode 100644 index 00000000..b5393480 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/names.ts @@ -0,0 +1,9 @@ +const NAMES = { + NAME: 'name', +} as const; + +export const V_CLUSTER_FORM_NAMES = { + [NAMES.NAME]: { + name: NAMES.NAME, + }, +}; diff --git a/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/context.ts b/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/context.ts new file mode 100644 index 00000000..776d8d9b --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/context.ts @@ -0,0 +1,20 @@ +import React from 'react'; +import { CurrentDialogContextProviderValue } from './types'; + +const dialogInitialState = { + open: false, + openDialog: () => { + // + }, + closeDialog: () => { + // + }, +}; + +export const CurrentDialogContext = React.createContext({ + props: {}, + state: dialogInitialState, + extra: { + EDPConfigMap: null, + }, +}); diff --git a/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/hooks.ts b/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/hooks.ts new file mode 100644 index 00000000..5b0d0877 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/hooks.ts @@ -0,0 +1,4 @@ +import React from 'react'; +import { CurrentDialogContext } from './context'; + +export const useCurrentDialog = () => React.useContext(CurrentDialogContext); diff --git a/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/provider.tsx b/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/provider.tsx new file mode 100644 index 00000000..e954f7c6 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/provider.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { ConfigMapKubeObject } from '../../../../../k8s/groups/default/ConfigMap'; +import { EDP_CONFIG_CONFIG_MAP_NAME } from '../../../../../k8s/groups/default/ConfigMap/constants'; +import { CurrentDialogContext } from './context'; +import { CurrentDialogContextProviderProps } from './types'; + +export const CurrentDialogContextProvider: React.FC = ({ + children, + props, + state, +}) => { + const [configMaps] = ConfigMapKubeObject.useList(); + + const EDPConfigMap = configMaps?.find( + (item) => item.metadata.name === EDP_CONFIG_CONFIG_MAP_NAME + ); + const CurrentDialogContextValue = React.useMemo( + () => ({ + props, + state, + extra: { + EDPConfigMap, + }, + }), + [EDPConfigMap, props, state] + ); + + return ( + + {children} + + ); +}; diff --git a/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/types.ts b/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/types.ts new file mode 100644 index 00000000..5e4ab442 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/providers/CurrentDialog/types.ts @@ -0,0 +1,10 @@ +import { ConfigMapKubeObjectInterface } from '../../../../../k8s/groups/default/ConfigMap/types'; +import { ManageVClusterDialogProps } from '../../types'; + +export interface CurrentDialogContextProviderProps extends ManageVClusterDialogProps {} + +export interface CurrentDialogContextProviderValue extends CurrentDialogContextProviderProps { + extra: { + EDPConfigMap: ConfigMapKubeObjectInterface; + }; +} diff --git a/src/widgets/dialogs/ManageVCluster/types.ts b/src/widgets/dialogs/ManageVCluster/types.ts new file mode 100644 index 00000000..c2d00f02 --- /dev/null +++ b/src/widgets/dialogs/ManageVCluster/types.ts @@ -0,0 +1,7 @@ +import { DialogProps } from '../../../providers/Dialog/types'; +import { FormValues } from '../../../types/forms'; +import { V_CLUSTER_FORM_NAMES } from './names'; + +export interface ManageVClusterDialogProps extends DialogProps<{}> {} + +export type ManageVClusterFormValues = FormValues;