Skip to content

Commit

Permalink
fix: list existing secret in build time secret modal
Browse files Browse the repository at this point in the history
feat: create only non existing build time secrets

feat: alternative solution for list existing secret

test: add missging tests
  • Loading branch information
JoaoPedroPP committed Oct 29, 2024
1 parent ec397ae commit 15feee3
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 33 deletions.
16 changes: 13 additions & 3 deletions src/components/ImportForm/SecretSection/SecretSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { TextInputTypes, GridItem, Grid, FormSection } from '@patternfly/react-core';
import { PlusCircleIcon } from '@patternfly/react-icons/dist/js/icons/plus-circle-icon';
import { useFormikContext } from 'formik';
import { Base64 } from 'js-base64';
import { useSecrets } from '../../../hooks/useSecrets';
import { SecretModel } from '../../../models';
import { InputField, TextColumnField } from '../../../shared';
Expand All @@ -27,9 +28,18 @@ const SecretSection = () => {
const partnerTaskNames = getSupportedPartnerTaskSecrets().map(({ label }) => label);
const partnerTaskSecrets: string[] =
secrets && secretsLoaded
? secrets
?.filter((rs) => partnerTaskNames.includes(rs.metadata.name))
?.map((s) => s.metadata.name) || []
? secrets?.map((secret) => ({
type: secret.type,
name: secret.metadata.name,
providerUrl: '',
tokenKeyName: secret.metadata.name,
keyValuePairs: Object.keys(secret.data).map((key) => ({
key,
value: Base64.decode(secret.data[key]),
readOnlyKey: true,
readOnlyValue: true,
})),
})) || []
: [];

const onSubmit = React.useCallback(
Expand Down
10 changes: 10 additions & 0 deletions src/components/ImportForm/__tests__/submit-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createImageRepository,
} from '../../../utils/create-utils';
import { createIntegrationTest } from '../../IntegrationTest/IntegrationTestForm/utils/create-utils';
import { getSecretResource } from '../../Secrets/utils/secret-utils';
import { createResources } from '../submit-utils';

jest.mock('@redhat-cloud-services/frontend-components-notifications/redux');
Expand All @@ -19,10 +20,16 @@ jest.mock('../../IntegrationTest/IntegrationTestForm/utils/create-utils', () =>
createIntegrationTest: jest.fn(),
}));

jest.mock('../../Secrets/utils/secret-utils', () => ({
...(jest.requireActual('../../Secrets/utils/secret-utils') as object),
getSecretResource: jest.fn(),
}));

const createApplicationMock = createApplication as jest.Mock;
const createComponentMock = createComponent as jest.Mock;
const createIntegrationTestMock = createIntegrationTest as jest.Mock;
const createImageRepositoryMock = createImageRepository as jest.Mock;
const getSecretResourceMock = getSecretResource as jest.Mock;

describe('Submit Utils: createResources', () => {
it('should create application and components', async () => {
Expand All @@ -46,6 +53,7 @@ describe('Submit Utils: createResources', () => {
'test-ws',
'url.bombino',
);
expect(getSecretResourceMock).toHaveBeenCalledTimes(1);
expect(createApplicationMock).toHaveBeenCalledTimes(2);
expect(createIntegrationTestMock).toHaveBeenCalledTimes(2);
expect(createComponentMock).toHaveBeenCalledTimes(2);
Expand Down Expand Up @@ -76,6 +84,7 @@ describe('Submit Utils: createResources', () => {
expect(createApplicationMock).toHaveBeenCalledTimes(2);
expect(createIntegrationTestMock).toHaveBeenCalledTimes(2);
expect(createComponentMock).toHaveBeenCalledTimes(0);
expect(getSecretResourceMock).toHaveBeenCalledTimes(0);
expect(createImageRepositoryMock).toHaveBeenCalledTimes(0);
});

Expand All @@ -102,6 +111,7 @@ describe('Submit Utils: createResources', () => {
);
expect(createApplicationMock).toHaveBeenCalledTimes(0);
expect(createIntegrationTestMock).toHaveBeenCalledTimes(0);
expect(getSecretResourceMock).toHaveBeenCalledTimes(1);
expect(createComponentMock).toHaveBeenCalledTimes(2);
expect(createImageRepositoryMock).toHaveBeenCalledTimes(2);
});
Expand Down
9 changes: 7 additions & 2 deletions src/components/ImportForm/submit-utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getSecretResource } from '../../components/Secrets/utils/secret-utils';
import { ApplicationKind, ImportSecret } from '../../types';
import {
createApplication,
Expand Down Expand Up @@ -93,7 +94,11 @@ export const createResources = async (

let createdComponent;
if (showComponent) {
await createSecrets(importSecrets, workspace, namespace, true);
const allSecrets = await getSecretResource(namespace);
const secretsToCreate = importSecrets.filter((secret) =>
allSecrets.items.find((existing) => secret.secretName in existing.data) ? false : true,
);
await createSecrets(secretsToCreate, workspace, namespace, true);

createdComponent = await createComponent(
{ componentName, application, gitProviderAnnotation, source, gitURLAnnotation },
Expand All @@ -113,7 +118,7 @@ export const createResources = async (
isPrivate: isPrivateRepo,
bombinoUrl,
});
await createSecrets(importSecrets, workspace, namespace, false);
await createSecrets(secretsToCreate, workspace, namespace, false);
}

return {
Expand Down
50 changes: 36 additions & 14 deletions src/components/Secrets/SecretForm.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Form } from '@patternfly/react-core';
import { SelectVariant } from '@patternfly/react-core/deprecated';
import { useFormikContext } from 'formik';
import { DropdownItemObject, SelectInputField } from '../../shared';
import KeyValueFileInputField from '../../shared/components/formik-fields/key-value-file-input-field/KeyValueFileInputField';
import { SecretFormValues, SecretTypeDropdownLabel } from '../../types';
import { SecretFormValues, SecretTypeDropdownLabel, K8sSecretType } from '../../types';
import { RawComponentProps } from '../modal/createModalLauncher';
import SecretTypeSelector from './SecretTypeSelector';
import {
supportedPartnerTasksSecrets,
getSupportedPartnerTaskKeyValuePairs,
isPartnerTask,
getSupportedPartnerTaskSecrets,
} from './utils/secret-utils';

type SecretFormProps = RawComponentProps & {
Expand All @@ -19,14 +19,37 @@ type SecretFormProps = RawComponentProps & {

const SecretForm: React.FC<React.PropsWithChildren<SecretFormProps>> = ({ existingSecrets }) => {
const { values, setFieldValue } = useFormikContext<SecretFormValues>();
const [currentType, setType] = React.useState(values.type);
const defaultKeyValues = [{ key: '', value: '', readOnlyKey: false }];
const defaultImageKeyValues = [{ key: '.dockerconfigjson', value: '', readOnlyKey: true }];
const [options, setOptions] = React.useState([]);
const [optionsValues, setOptionsValues] = React.useState([]);

const initialOptions = getSupportedPartnerTaskSecrets().filter(
(secret) => !existingSecrets.includes(secret.value),
);
const [options, setOptions] = React.useState(initialOptions);
const currentTypeRef = React.useRef(values.type);
useEffect(() => {
const initialOptions = existingSecrets
.filter((secret) => secret.type === K8sSecretType[currentType])
.concat(
currentType === SecretTypeDropdownLabel.opaque &&
existingSecrets.find((s) => s.name === 'snyk-secret') === undefined
? [supportedPartnerTasksSecrets.snyk]
: [],
)
.filter((secret) => secret.type !== K8sSecretType[SecretTypeDropdownLabel.image])
.map((secret) => ({ value: secret.name, lable: secret.name }));
const initialOptionsValues = existingSecrets
.filter((secret) => secret.type === K8sSecretType[currentType])
.filter((secret) => secret.type !== K8sSecretType[SecretTypeDropdownLabel.image])
.reduce(
(dictOfSecrets, secret) => {
dictOfSecrets[secret.name] = secret;
return dictOfSecrets;
},
{ 'snyk-secret': supportedPartnerTasksSecrets.snyk },
);

setOptions(initialOptions);
setOptionsValues(initialOptionsValues);
}, [currentType, existingSecrets]);

const clearKeyValues = () => {
const newKeyValues = values.keyValues.filter((kv) => !kv.readOnlyKey);
Expand Down Expand Up @@ -54,14 +77,13 @@ const SecretForm: React.FC<React.PropsWithChildren<SecretFormProps>> = ({ existi
<SecretTypeSelector
dropdownItems={dropdownItems}
onChange={(type) => {
currentTypeRef.current = type;
setType(type);
if (type === SecretTypeDropdownLabel.image) {
resetKeyValues();
values.secretName &&
isPartnerTask(values.secretName) &&
isPartnerTask(values.secretName, optionsValues) &&
setFieldValue('secretName', '');
} else {
setOptions(initialOptions);
clearKeyValues();
}
}}
Expand All @@ -80,15 +102,15 @@ const SecretForm: React.FC<React.PropsWithChildren<SecretFormProps>> = ({ existi
toggleId="secret-name-toggle"
toggleAriaLabel="secret-name-dropdown"
onClear={() => {
if (currentTypeRef.current !== values.type || isPartnerTask(values.secretName)) {
if (currentType !== values.type || isPartnerTask(values.secretName, optionsValues)) {
clearKeyValues();
}
}}
onSelect={(e, value) => {
if (isPartnerTask(value)) {
if (isPartnerTask(value, optionsValues)) {
setFieldValue('keyValues', [
...values.keyValues.filter((kv) => !kv.readOnlyKey && (!!kv.key || !!kv.value)),
...getSupportedPartnerTaskKeyValuePairs(value),
...getSupportedPartnerTaskKeyValuePairs(value, optionsValues),
]);
}
setFieldValue('secretName', value);
Expand Down
4 changes: 2 additions & 2 deletions src/components/Secrets/__tests___/SecretModal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ describe('SecretForm', () => {
});
});

it('should not show the secrets in the select dropdown if it is already existing', async () => {
it('should show the secrets in the select dropdown if it is already existing', async () => {
const onClose = jest.fn();
formikRenderer(
<SecretModal
Expand All @@ -104,7 +104,7 @@ describe('SecretForm', () => {
const modal = screen.queryByTestId('build-secret-modal');
fireEvent.click(modal.querySelector('#secret-name-toggle-select-typeahead'));
});
expect(screen.queryByText('snyk-secret')).not.toBeInTheDocument();
expect(screen.queryByText('snyk-secret')).toBeInTheDocument();
});

it('should remove the selected value with clearn button is clicked', async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Secrets/__tests___/secret-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('getSupportedPartnerTaskKeyValuePairs', () => {

it('should return snyk secret values ', () => {
expect(getSupportedPartnerTaskKeyValuePairs('snyk-secret')).toEqual([
{ key: 'snyk_token', readOnlyKey: true, value: '' },
{ key: 'snyk_token', readOnlyKey: true, value: '', readOnlyValue: false },
]);
});
});
Expand Down
29 changes: 18 additions & 11 deletions src/components/Secrets/utils/secret-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { k8sCreateResource } from '@openshift/dynamic-plugin-sdk-utils';
import { k8sCreateResource, k8sGetResource } from '@openshift/dynamic-plugin-sdk-utils';
import { Base64 } from 'js-base64';
import { pick } from 'lodash-es';
import { SecretModel } from '../../../models';
Expand Down Expand Up @@ -46,19 +46,18 @@ export const getSupportedPartnerTaskSecrets = () => {
value: secret.name,
}));
};
export const isPartnerTaskAvailable = (type: string) =>
!!Object.values(supportedPartnerTasksSecrets).find(
(secret) => secret.type === K8sSecretType[type],
);
export const isPartnerTaskAvailable = (type: string, arr = supportedPartnerTasksSecrets) =>
!!Object.values(arr).find((secret) => secret.type === K8sSecretType[type]);

export const isPartnerTask = (secretName: string) => {
return !!Object.values(supportedPartnerTasksSecrets).find((secret) => secret.name === secretName);
export const isPartnerTask = (secretName: string, arr = supportedPartnerTasksSecrets) => {
return !!Object.values(arr).find((secret) => secret.name === secretName);
};

export const getSupportedPartnerTaskKeyValuePairs = (secretName?: string) => {
const partnerTask = Object.values(supportedPartnerTasksSecrets).find(
(secret) => secret.name === secretName,
);
export const getSupportedPartnerTaskKeyValuePairs = (
secretName?: string,
arr = supportedPartnerTasksSecrets,
) => {
const partnerTask = Object.values(arr).find((secret) => secret.name === secretName);
return partnerTask ? partnerTask.keyValuePairs : [];
};

Expand Down Expand Up @@ -257,3 +256,11 @@ export const getAddSecretBreadcrumbs = () => {
{ path: '#', name: 'Add secret' },
];
};

export const getSecretResource = async (namespace: string): Promise<SecretKind> =>
k8sGetResource({
model: SecretModel,
queryOptions: {
ns: namespace,
},
});

0 comments on commit 15feee3

Please sign in to comment.