diff --git a/src/components/Secrets/SecretForm.tsx b/src/components/Secrets/SecretForm.tsx index 1024f6e4f..81ddfae12 100644 --- a/src/components/Secrets/SecretForm.tsx +++ b/src/components/Secrets/SecretForm.tsx @@ -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, @@ -29,13 +30,13 @@ const SecretForm: React.FC> = ({ 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]); @@ -43,12 +44,14 @@ const SecretForm: React.FC> = ({ existi 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 (
> = ({ existi values.secretName && isPartnerTask(values.secretName) && setFieldValue('secretName', ''); + } + if (type === SecretTypeDropdownLabel.source) { + resetKeyValues(); + values.secretName && + isPartnerTask(values.secretName) && + setFieldValue('secretName', ''); } else { setOptions(initialOptions); clearKeyValues(); @@ -87,19 +96,25 @@ const SecretForm: React.FC> = ({ 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); }} /> - + {currentType === SecretTypeDropdownLabel.source && } + {currentType !== SecretTypeDropdownLabel.source && ( + + )} ); }; diff --git a/src/components/Secrets/SecretModal.tsx b/src/components/Secrets/SecretModal.tsx index 4640e32b9..9789586f9 100644 --- a/src/components/Secrets/SecretModal.tsx +++ b/src/components/Secrets/SecretModal.tsx @@ -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'; @@ -42,7 +43,15 @@ const SecretModal: React.FC> = ({ const initialValues: SecretModalValues = { secretName: '', type: SecretTypeDropdownLabel.opaque, - keyValues: defaultKeyValues, + opaque: { + keyValues: defaultKeyValues, + }, + image: { + keyValues: defaultKeyValues, + }, + source: { + authType: 'Basic authentication', + }, existingSecrets, }; @@ -68,6 +77,7 @@ const SecretModal: React.FC> = ({ onClick={() => { props.handleSubmit(); }} + isDisabled={!props.dirty || !isEmpty(props.errors) || props.isSubmitting} > Create , diff --git a/src/types/secret.ts b/src/types/secret.ts index 486f93707..69ae3fa9d 100644 --- a/src/types/secret.ts +++ b/src/types/secret.ts @@ -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 { diff --git a/src/utils/create-utils.ts b/src/utils/create-utils.ts index 231e15f84..a2a557e9b 100644 --- a/src/utils/create-utils.ts +++ b/src/utils/create-utils.ts @@ -4,8 +4,10 @@ import { k8sUpdateResource, commonFetch, } from '@openshift/dynamic-plugin-sdk-utils'; +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, @@ -38,6 +40,9 @@ import { ImportSecret, ImageRepositoryKind, ImageRepositoryVisibility, + SecretTypeDropdownLabel, + SourceSecretType, + SecretFormValues, } from '../types'; import { ComponentSpecs } from './../types/component'; import { BuildRequest, BUILD_REQUEST_ANNOTATION } from './component-utils'; @@ -336,26 +341,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( diff --git a/src/utils/validation-utils.ts b/src/utils/validation-utils.ts index c8544fd5b..583ae3690 100644 --- a/src/utils/validation-utils.ts +++ b/src/utils/validation-utils.ts @@ -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.~/?[\]!$&'()*+,;=:@%]*?:?))$/; @@ -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'), + }), + ), + }), + }), });