Skip to content

Commit

Permalink
metal3: support edit for Bare Metal Hosts
Browse files Browse the repository at this point in the history
  • Loading branch information
suomiy committed Jan 22, 2020
1 parent ecfaba1 commit 5ebd000
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,74 @@ import * as _ from 'lodash';
import { Formik } from 'formik';
import { history, resourcePathFromModel, FirehoseResult } from '@console/internal/components/utils';
import { nameValidationSchema } from '@console/dev-console/src/components/import/validation-schema';
import { getName } from '@console/shared';
import { createBareMetalHost } from '../../../k8s/requests/bare-metal-host';
import { getName } from '@console/shared/src';
import { K8sResourceKind } from '@console/internal/module/k8s';
import { createBareMetalHost, updateBareMetalHost } from '../../../k8s/requests/bare-metal-host';
import { BareMetalHostModel } from '../../../models';
import { BareMetalHostKind } from '../../../types';
import {
buildBareMetalHostSecret,
buildBareMetalHostObject,
} from '../../../k8s/objects/bare-metal-host';
getHostBMCAddress,
getHostBootMACAddress,
getHostDescription,
isHostOnline,
} from '../../../selectors';
import { getSecretPassword, getSecretUsername } from '../../../selectors/secret';
import { getLoadedData } from '../../../utils';
import { usePrevious } from '../../../hooks';
import AddBareMetalHostForm from './AddBareMetalHostForm';
import { AddBareMetalHostFormValues } from './types';
import { MAC_REGEX, BMC_ADDRESS_REGEX } from './utils';

const initialValues: AddBareMetalHostFormValues = {
name: '',
BMCAddress: '',
username: '',
password: '',
bootMACAddress: '',
online: true,
description: '',
};
const getInitialValues = (
host: BareMetalHostKind,
secret: K8sResourceKind,
): AddBareMetalHostFormValues => ({
name: getName(host) || '',
BMCAddress: getHostBMCAddress(host) || '',
username: getSecretUsername(secret) || '',
password: getSecretPassword(secret) || '',
bootMACAddress: getHostBootMACAddress(host) || '',
online: isHostOnline(host) || true,
description: getHostDescription(host) || '',
});

type AddBareMetalHostProps = {
namespace: string;
isEditing: boolean;
loaded?: boolean;
hosts?: FirehoseResult<BareMetalHostKind[]>;
host?: FirehoseResult<BareMetalHostKind>;
secret?: FirehoseResult<K8sResourceKind>;
};

const AddBareMetalHost: React.FC<AddBareMetalHostProps> = ({ namespace, hosts }) => {
const hostNames = _.flatMap(_.get(hosts, 'data', []), (host) => getName(host));
const AddBareMetalHost: React.FC<AddBareMetalHostProps> = ({
namespace,
isEditing,
hosts,
host: resultHost,
secret: resultSecret,
}) => {
const [reload, setReload] = React.useState<boolean>(false);
const hostNames = _.flatMap(getLoadedData(hosts, []), (host) => getName(host));
const initialHost = getLoadedData(resultHost);
const initialSecret = getLoadedData(resultSecret);
const prevInitialHost = usePrevious(initialHost);
const prevInitialSecret = usePrevious(initialSecret);

const initialValues = getInitialValues(initialHost, initialSecret);
const prevInitialValues = getInitialValues(prevInitialHost, prevInitialSecret);

React.useEffect(() => {
if (reload) {
setReload(false);
}
}, [reload, setReload]);

const showUpdated =
isEditing &&
prevInitialHost &&
prevInitialSecret &&
!_.isEqual(prevInitialValues, initialValues);

const addHostValidationSchema = Yup.object().shape({
name: Yup.mixed()
Expand All @@ -52,23 +91,16 @@ const AddBareMetalHost: React.FC<AddBareMetalHostProps> = ({ namespace, hosts })
.required('Required.'),
});

const handleSubmit = (
{ name, BMCAddress, username, password, bootMACAddress, online, description },
actions,
) => {
const secret = buildBareMetalHostSecret(name, namespace, username, password);
const bareMetalHost = buildBareMetalHostObject(
name,
namespace,
BMCAddress,
bootMACAddress,
online,
description,
);
createBareMetalHost(bareMetalHost, secret)
const handleSubmit = (values, actions) => {
const opts = { ...values, namespace };
const promise = isEditing
? updateBareMetalHost(initialHost, initialSecret, opts)
: createBareMetalHost(opts);

promise
.then(() => {
actions.setSubmitting(false);
history.push(resourcePathFromModel(BareMetalHostModel, name, namespace));
history.push(resourcePathFromModel(BareMetalHostModel, values.name, namespace));
})
.catch((error) => {
actions.setSubmitting(false);
Expand All @@ -79,11 +111,15 @@ const AddBareMetalHost: React.FC<AddBareMetalHostProps> = ({ namespace, hosts })
return (
<Formik
initialValues={initialValues}
enableReinitialize={isEditing && (reload || !prevInitialHost || !prevInitialSecret)}
onSubmit={handleSubmit}
onReset={history.goBack}
onReset={() => setReload(true)}
validationSchema={addHostValidationSchema}
component={AddBareMetalHostForm}
/>
>
{(props) => (
<AddBareMetalHostForm {...props} isEditing={isEditing} showUpdated={showUpdated} />
)}
</Formik>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import * as React from 'react';
import * as _ from 'lodash';
import { history } from '@console/internal/components/utils';
import { FormikProps } from 'formik';
import { Form, TextInputTypes } from '@patternfly/react-core';
import { InputField, TextAreaField, SwitchField, FormFooter } from '@console/shared';
import { InputField, TextAreaField, SwitchField, FormFooter } from '@console/shared/src';
import { AddBareMetalHostFormValues } from './types';

type AddBareMetalHostFormProps = FormikProps<AddBareMetalHostFormValues>;
type AddBareMetalHostFormProps = FormikProps<AddBareMetalHostFormValues> & {
isEditing: boolean;
showUpdated: boolean;
};

const AddBareMetalHostForm: React.FC<AddBareMetalHostFormProps> = ({
errors,
Expand All @@ -14,6 +18,8 @@ const AddBareMetalHostForm: React.FC<AddBareMetalHostFormProps> = ({
status,
isSubmitting,
dirty,
isEditing,
showUpdated,
}) => (
<Form onSubmit={handleSubmit}>
<InputField
Expand All @@ -24,6 +30,7 @@ const AddBareMetalHostForm: React.FC<AddBareMetalHostFormProps> = ({
placeholder="openshift-worker"
helpText="Provide unique name for the new Bare Metal Host."
required
isDisabled={isEditing}
/>
<TextAreaField
data-test-id="add-baremetal-host-form-description-input"
Expand Down Expand Up @@ -60,17 +67,23 @@ const AddBareMetalHostForm: React.FC<AddBareMetalHostFormProps> = ({
helpText="The MAC address of the NIC connected to the network that will be used to provision the host."
required
/>
<SwitchField
name="online"
data-test-id="add-baremetal-host-form-online-switch"
label="Power host on after creation"
/>
{!isEditing && (
<SwitchField
name="online"
data-test-id="add-baremetal-host-form-online-switch"
label="Power host on after creation"
/>
)}
<FormFooter
isSubmitting={isSubmitting}
handleCancel={handleReset}
submitLabel="Create"
handleReset={showUpdated && handleReset}
handleCancel={history.goBack}
submitLabel={isEditing ? 'Save' : 'Create'}
errorMessage={status && status.submitError}
disableSubmit={isSubmitting || !dirty || !_.isEmpty(errors)}
infoTitle={'Bare Metal Host has been updated'}
infoMessage={'Click reload to see the recent changes'}
showAlert={showUpdated}
/>
</Form>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,47 @@ import { RouteComponentProps } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { Firehose, FirehoseResource } from '@console/internal/components/utils';
import { referenceForModel } from '@console/internal/module/k8s';
import { SecretModel } from '@console/internal/models';
import { BareMetalHostModel } from '../../../models';
import { getSecretName } from '../../../k8s/objects/bare-metal-host';
import AddBareMetalHost from './AddBareMetalHost';

export type AddBareMetalHostPageProps = RouteComponentProps<{ ns?: string }>;
export type AddBareMetalHostPageProps = RouteComponentProps<{ ns?: string; name?: string }>;

const AddBareMetalHostPage: React.FunctionComponent<AddBareMetalHostPageProps> = ({ match }) => {
const namespace = match.params.ns;
const resources: FirehoseResource[] = [
{
const { name, ns: namespace } = match.params;
const resources: FirehoseResource[] = [];

const isEditing = !!name;
if (isEditing) {
resources.push(
{
kind: referenceForModel(BareMetalHostModel),
namespaced: true,
namespace,
name,
isList: false,
prop: 'host',
},
{
kind: SecretModel.kind,
namespaced: true,
namespace,
name: getSecretName(name),
isList: false,
prop: 'secret',
},
);
} else {
resources.push({
kind: referenceForModel(BareMetalHostModel),
namespaced: true,
namespace,
isList: true,
prop: 'hosts',
},
];
const title = 'Add Bare Metal Host';
});
}
const title = `${isEditing ? 'Edit' : 'Add'} Bare Metal Host`;
return (
<>
<Helmet>
Expand All @@ -31,11 +55,13 @@ const AddBareMetalHostPage: React.FunctionComponent<AddBareMetalHostPageProps> =
<h1 className="co-m-pane__heading co-m-pane__heading--baseline">
<div className="co-m-pane__name">{title}</div>
</h1>
<p className="co-m-pane__explanation">
Expand the hardware inventory by registering new Bare Metal Host.
</p>
{!isEditing && (
<p className="co-m-pane__explanation">
Expand the hardware inventory by registering new Bare Metal Host.
</p>
)}
<Firehose resources={resources}>
<AddBareMetalHost namespace={namespace} />
<AddBareMetalHost namespace={namespace} isEditing={isEditing} />
</Firehose>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ import {
MachineKind,
MachineSetKind,
NodeKind,
referenceForModel,
} from '@console/internal/module/k8s';
import { getMachineNode, getMachineNodeName, getName, getAnnotations } from '@console/shared';
import {
getMachineNode,
getMachineNodeName,
getName,
getNamespace,
getAnnotations,
} from '@console/shared';
import { confirmModal, deleteModal } from '@console/internal/components/modals';
import { MachineModel, MachineSetModel } from '@console/internal/models';
import { findNodeMaintenance, getHostMachine, getHostPowerStatus } from '../../selectors';
Expand Down Expand Up @@ -42,6 +49,11 @@ type ActionArgs = {
status: string;
};

export const Edit = (kindObj: K8sKind, host: BareMetalHostKind): KebabOption => ({
label: `Edit ${kindObj.label}`,
href: `/k8s/ns/${getNamespace(host)}/${referenceForModel(kindObj)}/${getName(host)}/edit`,
});

export const SetNodeMaintenance = (
kindObj: K8sKind,
host: BareMetalHostKind,
Expand Down Expand Up @@ -149,7 +161,7 @@ export const menuActions = [
PowerOff,
Kebab.factory.ModifyLabels,
Kebab.factory.ModifyAnnotations,
Kebab.factory.Edit,
Edit,
Delete,
];

Expand Down
9 changes: 9 additions & 0 deletions frontend/packages/metal3-plugin/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useEffect, useRef } from 'react';

export const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Base64 } from 'js-base64';
import { SecretModel } from '@console/internal/models';
import { BareMetalHostModel } from '../../models';
import { BareMetalHostKind } from '../../types';

const getSecretName = (name: string): string => `${name}-bmc-secret`;
export const getSecretName = (name: string): string => `${name}-bmc-secret`;

export const buildBareMetalHostSecret = (name, namespace, username, password) => ({
apiVersion: SecretModel.apiVersion,
Expand All @@ -13,8 +12,8 @@ export const buildBareMetalHostSecret = (name, namespace, username, password) =>
name: getSecretName(name),
},
data: {
username: Base64.encode(username),
password: Base64.encode(password),
username: btoa(username),
password: btoa(password),
},
type: 'Opaque',
});
Expand Down
Loading

0 comments on commit 5ebd000

Please sign in to comment.