Skip to content

Commit

Permalink
feat(secrets): add source secrets to modal
Browse files Browse the repository at this point in the history
  • Loading branch information
abhinandan13jan committed Sep 25, 2024
1 parent 516fdbe commit 86bb953
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 34 deletions.
35 changes: 25 additions & 10 deletions src/components/Secrets/SecretForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DropdownItemObject, SelectInputField } from '../../shared';
import KeyValueFileInputField from '../../shared/components/formik-fields/key-value-file-input-field/KeyValueFileInputField';
import { SecretFormValues, SecretTypeDropdownLabel } from '../../types';
import { RawComponentProps } from '../modal/createModalLauncher';
import { SourceSecretForm } from './SecretsForm/SourceSecretForm';
import SecretTypeSelector from './SecretTypeSelector';
import {
getSupportedPartnerTaskKeyValuePairs,
Expand All @@ -29,26 +30,28 @@ const SecretForm: React.FC<React.PropsWithChildren<SecretFormProps>> = ({ existi
const currentTypeRef = React.useRef(values.type);

const clearKeyValues = () => {
const newKeyValues = values.keyValues.filter((kv) => !kv.readOnlyKey);
const newKeyValues = values.opaque.keyValues.filter((kv) => !kv.readOnlyKey);
setFieldValue('keyValues', [...(newKeyValues.length ? newKeyValues : defaultKeyValues)]);
};

const resetKeyValues = () => {
setOptions([]);
const newKeyValues = values.keyValues.filter(
const newKeyValues = values.opaque.keyValues.filter(
(kv) => !kv.readOnlyKey && (!!kv.key || !!kv.value),
);
setFieldValue('keyValues', [...newKeyValues, ...defaultImageKeyValues]);
};

const dropdownItems: DropdownItemObject[] = Object.entries(SecretTypeDropdownLabel).reduce(
(acc, [key, value]) => {
value !== SecretTypeDropdownLabel.source && acc.push({ key, value });
acc.push({ key, value });
return acc;
},
[],
);

const currentType = currentTypeRef.current;

return (
<Form>
<SecretTypeSelector
Expand All @@ -60,6 +63,12 @@ const SecretForm: React.FC<React.PropsWithChildren<SecretFormProps>> = ({ existi
values.secretName &&
isPartnerTask(values.secretName) &&
setFieldValue('secretName', '');
}
if (type === SecretTypeDropdownLabel.source) {
resetKeyValues();
values.secretName &&
isPartnerTask(values.secretName) &&
setFieldValue('secretName', '');
} else {
setOptions(initialOptions);
clearKeyValues();
Expand Down Expand Up @@ -87,19 +96,25 @@ const SecretForm: React.FC<React.PropsWithChildren<SecretFormProps>> = ({ existi
onSelect={(e, value) => {
if (isPartnerTask(value)) {
setFieldValue('keyValues', [
...values.keyValues.filter((kv) => !kv.readOnlyKey && (!!kv.key || !!kv.value)),
...values.opaque.keyValues.filter(
(kv) => !kv.readOnlyKey && (!!kv.key || !!kv.value),
),
...getSupportedPartnerTaskKeyValuePairs(value),
]);
}
setFieldValue('secretName', value);
}}
/>
<KeyValueFileInputField
name="keyValues"
data-test="secret-key-value-pair"
entries={defaultKeyValues}
disableRemoveAction={values.keyValues.length === 1}
/>
{currentType === SecretTypeDropdownLabel.source && <SourceSecretForm />}
{currentType !== SecretTypeDropdownLabel.source && (
<KeyValueFileInputField
required
name="keyValues"
data-test="secret-key-value-pair"
entries={defaultKeyValues}
disableRemoveAction={values.opaque.keyValues.length === 1}
/>
)}
</Form>
);
};
Expand Down
12 changes: 11 additions & 1 deletion src/components/Secrets/SecretModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ModalVariant,
} from '@patternfly/react-core';
import { Formik } from 'formik';
import { isEmpty } from 'lodash-es';
import { ImportSecret, SecretTypeDropdownLabel } from '../../types';
import { SecretFromSchema } from '../../utils/validation-utils';
import { RawComponentProps } from '../modal/createModalLauncher';
Expand Down Expand Up @@ -42,7 +43,15 @@ const SecretModal: React.FC<React.PropsWithChildren<SecretModalProps>> = ({
const initialValues: SecretModalValues = {
secretName: '',
type: SecretTypeDropdownLabel.opaque,
keyValues: defaultKeyValues,
opaque: {
keyValues: defaultKeyValues,
},
image: {
keyValues: defaultKeyValues,
},
source: {
authType: 'Basic authentication',
},
existingSecrets,
};

Expand All @@ -68,6 +77,7 @@ const SecretModal: React.FC<React.PropsWithChildren<SecretModalProps>> = ({
onClick={() => {
props.handleSubmit();
}}
isDisabled={!props.dirty || !isEmpty(props.errors) || props.isSubmitting}
>
Create
</Button>,
Expand Down
20 changes: 15 additions & 5 deletions src/types/secret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@ export const SecretByUILabel = 'ui.appstudio.redhat.com/secret-for';
export type ImportSecret = {
secretName: string;
type: string;
keyValues: {
key: string;
value: string;
readOnlyKey?: boolean;
}[];
source: Source;
opaque: {
keyValues: {
key: string;
value: string;
readOnlyKey?: boolean;
}[];
};
image: {
keyValues: {
key: string;
value: string;
readOnlyKey?: boolean;
}[];
};
};

export enum SecretSPILabel {
Expand Down
51 changes: 38 additions & 13 deletions src/utils/create-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import {
commonFetch,
} from '@openshift/dynamic-plugin-sdk-utils';
import gitUrlParse from 'git-url-parse';
import { Base64 } from 'js-base64';
import isEqual from 'lodash/isEqual';
import isNumber from 'lodash/isNumber';
import { pick } from 'lodash-es';
import { v4 as uuidv4 } from 'uuid';
import {
getRandomSvgNumber,
Expand Down Expand Up @@ -40,6 +42,9 @@ import {
ImportSecret,
ImageRepositoryKind,
ImageRepositoryVisibility,
SecretTypeDropdownLabel,
SourceSecretType,
SecretFormValues,
} from '../types';
import { ComponentSpecs } from './../types/component';
import {
Expand Down Expand Up @@ -356,26 +361,46 @@ export const addSecret = async (values: any, workspace: string, namespace: strin
return await createSecretResource(values, workspace, namespace, false);
};

export const createSecret = async (
secret: ImportSecret,
workspace: string,
namespace: string,
dryRun: boolean,
) => {
const secretResource = {
export const getSecretObject = (values: SecretFormValues, namespace: string): SecretKind => {
let data = {};
if (values.type === SecretTypeDropdownLabel.source) {
if (values.source.authType === SourceSecretType.basic) {
const authObj = pick(values.source, ['username', 'password']);
data = Object.entries(authObj).reduce((acc, [key, value]) => {
acc[key] = Base64.encode(value);
return acc;
}, {});
} else {
const SSH_KEY = 'ssh-privatekey';
data[SSH_KEY] = values.source[SSH_KEY];
}
} else {
data = values.opaque.keyValues.reduce((acc, s) => {
acc[s.key] = s.value ? s.value : '';
return acc;
}, {});
}
const secretResource: SecretKind = {
apiVersion: SecretModel.apiVersion,
kind: SecretModel.kind,
metadata: {
name: secret.secretName,
name: values.secretName,
namespace,
},
type: K8sSecretType[secret.type],
stringData: secret.keyValues.reduce((acc, s) => {
acc[s.key] = s.value ? s.value : '';
return acc;
}, {}),
type: K8sSecretType[values.type],
data,
};

return secretResource;
};
export const createSecret = async (
secret: ImportSecret,
workspace: string,
namespace: string,
dryRun: boolean,
) => {
const secretResource = getSecretObject(secret, namespace);

// Todo: K8sCreateResource appends the resource name and errors out.
// Fix the below code when this sdk-utils issue is resolved https://issues.redhat.com/browse/RHCLOUD-21655.
return await commonFetch(
Expand Down
35 changes: 30 additions & 5 deletions src/utils/validation-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as yup from 'yup';
import { SecretTypeDropdownLabel, SourceSecretType } from '../types';

export const GIT_URL_REGEX =
/^((((ssh|git|https?:?):\/\/:?)(([^\s@]+@|[^@]:?)[-\w.]+(:\d\d+:?)?(\/[-\w.~/?[\]!$&'()*+,;=:@%]*:?)?:?))|([^\s@]+@[-\w.]+:[-\w.~/?[\]!$&'()*+,;=:@%]*?:?))$/;
Expand Down Expand Up @@ -28,10 +29,34 @@ export const SecretFromSchema = yup.object({
return !existingSecrets.includes(value);
},
),
keyValues: yup.array().of(
yup.object({
key: yup.string().required('Required'),
value: yup.string().required('Required'),
type: yup.string(),
source: yup.object().when('type', {
is: SecretTypeDropdownLabel.source,
then: yup.object({
authType: yup.string(),
username: yup.string().when('authType', {
is: SourceSecretType.basic,
then: yup.string().required('Required'),
}),
password: yup.string().when('authType', {
is: SourceSecretType.basic,
then: yup.string().required('Required'),
}),
['ssh-privatekey']: yup.string().when('authType', {
is: SourceSecretType.ssh,
then: yup.string().required('Required'),
}),
}),
),
}),
opaque: yup.object().when('type', {
is: SecretTypeDropdownLabel.opaque,
then: yup.object({
keyValues: yup.array().of(
yup.object({
key: yup.string().required('Required'),
value: yup.string().required('Required'),
}),
),
}),
}),
});

0 comments on commit 86bb953

Please sign in to comment.