Skip to content

Commit

Permalink
[PLAT-14958][PLAT-14959] Make ssh fields optional if skipProvisioning…
Browse files Browse the repository at this point in the history
… is true

Summary:
**Context**
For fully manual provisioning of onprem DB nodes, we require customer to
install node agent and register the node agent with YBA. From then on, all YBA
to YBDB node communication will happen over TLS/GRPC on port 9070 via node
agent.
YBA does not require ssh access to the node after setting up node agent.

**Change**
This diff makes the ssh fields for onprem providers optional if the user opts for fully manual provisioning.
We also disable the `connect` option for onprem nodes if we don't have ssh user and key information.
The universe creation form now no longer requires ssh key if the provider is onprem with skipProvisioning set to true.

Test Plan:
Verify user can create/edit onprem providers with no ssh credentials specified.
Verify universe creation form doesn't require ssh key

Reviewers: svarshney, lsangappa, kkannan

Reviewed By: kkannan

Subscribers: yugaware

Differential Revision: https://phorge.dev.yugabyte.com/D37435
  • Loading branch information
Jethro-M committed Sep 5, 2024
1 parent 16262f7 commit 6614afb
Show file tree
Hide file tree
Showing 14 changed files with 136 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,10 @@ public void configure(Customer customer, UniverseConfigureTaskParams taskParams)

userIntent.masterGFlags = trimFlags(userIntent.masterGFlags);
userIntent.tserverGFlags = trimFlags(userIntent.tserverGFlags);
if (StringUtils.isEmpty(userIntent.accessKeyCode)) {
Provider provider = Provider.getOrBadRequest(UUID.fromString(userIntent.provider));
if (StringUtils.isEmpty(userIntent.accessKeyCode)
&& !(userIntent.providerType.equals(Common.CloudType.onprem)
&& provider.getDetails().skipProvisioning)) {
userIntent.accessKeyCode = appConfig.getString("yb.security.default.access.key");
}
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,14 @@ const VALIDATION_SCHEMA = object().shape({
ACCEPTABLE_CHARS,
'Provider name cannot contain special characters other than "-", and "_"'
),
sshUser: string().required('SSH user is required.'),
sshPrivateKeyContent: mixed().required('SSH private key is required.'),
sshUser: string().when('skipProvisioning', {
is: false,
then: string().required('SSH user is required.')
}),
sshPrivateKeyContent: mixed().when('skipProvisioning', {
is: false,
then: mixed().required('SSH private key is required.')
}),
ntpServers: array().when('ntpSetupType', {
is: NTPSetupType.SPECIFIED,
then: array().of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,12 @@ const VALIDATION_SCHEMA = object().shape({
ACCEPTABLE_CHARS,
'Provider name cannot contain special characters other than "-", and "_"'
),
sshUser: string().required('SSH user is required.'),
sshPrivateKeyContent: mixed().when('editSSHKeypair', {
is: true,
sshUser: string().when('skipProvisioning', {
is: false,
then: string().required('SSH user is required.')
}),
sshPrivateKeyContent: mixed().when(['editSSHKeypair', 'skipProvisioning'], {
is: (editSSHKeypair, skipProvisioning) => editSSHKeypair && !skipProvisioning,
then: mixed().required('SSH private key is required.')
}),
ntpServers: array().when('ntpSetupType', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ export default class NodeAction extends Component {
disabled,
clusterType,
isKubernetes,
isOnPremManuallyProvisioned
isOnPremManuallyProvisioned,
cluster,
accessKeys
} = this.props;

const allowedActions =
Expand Down Expand Up @@ -363,6 +365,10 @@ export default class NodeAction extends Component {
);
}

const accessKeyCode = cluster.userIntent.accessKeyCode;
const accessKey = accessKeys.data.find(
(key) => key.idKey.providerUUID === providerUUID && key.idKey.keyCode === accessKeyCode
);
return (
<DropdownButton
className="btn btn-default"
Expand All @@ -382,6 +388,7 @@ export default class NodeAction extends Component {
label={this.getLabel('CONNECT', currentRow.dedicatedTo)}
clusterType={clusterType}
universeUUID={universeUUID}
disabled={accessKey === undefined}
/>
)}
{isNonEmptyArray(nodeAllowedActions) ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class NodeConnectModal extends Component {
currentUniverse,
clusterType,
universeUUID,
providers
providers,
disabled
} = this.props;
const nodeIPs = { privateIP: currentRow.privateIP, publicIP: currentRow.publicIP };
let accessCommand = null;
Expand All @@ -69,7 +70,9 @@ class NodeConnectModal extends Component {
}

const providerUsed = _.find(providers?.data, { uuid: providerUUID });
const imageBundleUsed = _.find(providerUsed?.imageBundles, { uuid: cluster?.userIntent?.imageBundleUUID });
const imageBundleUsed = _.find(providerUsed?.imageBundles, {
uuid: cluster?.userIntent?.imageBundleUUID
});

const tectiaSSH = runtimeConfigs?.data?.configEntries?.find(
(c) => c.key === 'yb.security.ssh2_enabled'
Expand Down Expand Up @@ -107,11 +110,13 @@ class NodeConnectModal extends Component {
const accessKeyInfo = accessKey?.keyInfo;
const sshPort = imageBundleUsed?.details?.sshPort ?? (accessKeyInfo?.sshPort || 22);
if (!isTectiaSSHEnabled) {
accessCommand = `sudo ssh -i ${accessKeyInfo?.privateKey ?? '<private key>'
} -ostricthostkeychecking=no -p ${sshPort} yugabyte@${nodeIPs.privateIP}`;
accessCommand = `sudo ssh -i ${
accessKeyInfo?.privateKey ?? '<private key>'
} -ostricthostkeychecking=no -p ${sshPort} yugabyte@${nodeIPs.privateIP}`;
} else {
accessCommand = `sshg3 -K ${accessKeyInfo?.privateKey ?? '<private key>'
} -ostricthostkeychecking=no -p ${sshPort} yugabyte@${nodeIPs.privateIP}`;
accessCommand = `sshg3 -K ${
accessKeyInfo?.privateKey ?? '<private key>'
} -ostricthostkeychecking=no -p ${sshPort} yugabyte@${nodeIPs.privateIP}`;
}
}

Expand All @@ -128,7 +133,8 @@ class NodeConnectModal extends Component {
<MenuItem
eventKey={btnId}
data-testid="NodeAction-CONNECT"
onClick={() => this.toggleConnectModal(true)}
onSelect={() => this.toggleConnectModal(true)}
disabled={disabled}
>
{label}
</MenuItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export default class NodeDetails extends Component {
universeMasterInfo
},
customer,
providers
providers,
accessKeys
} = this.props;
const universeDetails = currentUniverse.data.universeDetails;
const nodeDetails = universeDetails.nodeDetailsSet;
Expand Down Expand Up @@ -220,6 +221,7 @@ export default class NodeDetails extends Component {
customer={customer}
currentUniverse={currentUniverse}
providers={providers}
accessKeys={accessKeys}
/>
{readOnlyCluster && (
<NodeDetailsTable
Expand All @@ -231,6 +233,7 @@ export default class NodeDetails extends Component {
customer={customer}
currentUniverse={currentUniverse}
providers={providers}
accessKeys={accessKeys}
/>
)}
</Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {

function mapStateToProps(state) {
return {
accessKeys: state.cloud.accessKeys,
universe: state.universe,
customer: state.customer,
providers: state.cloud.providers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ export default class NodeDetailsTable extends Component {
currentUniverse,
providers,
isDedicatedNodes,
isKubernetesCluster
isKubernetesCluster,
accessKeys
} = this.props;
const successIcon = <i className="fa fa-check-circle yb-success-color" />;
const warningIcon = <i className="fa fa-warning yb-fail-color" />;
Expand Down Expand Up @@ -307,6 +308,8 @@ export default class NodeDetailsTable extends Component {
clusterType={clusterType}
isKubernetes={isKubernetes}
isOnPremManuallyProvisioned={isOnPremManuallyProvisioned}
cluster={cluster}
accessKeys={accessKeys}
/>
);
};
Expand Down
16 changes: 14 additions & 2 deletions managed/ui/src/redesign/features/universe/universe-form/EditRR.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
UniverseFormData
} from './utils/dto';
import { PROVIDER_FIELD, TOAST_AUTO_DISMISS_INTERVAL } from './utils/constants';
import { providerQueryKey, api as helperApi } from '../../../helpers/api';

interface EditReadReplicaProps {
uuid: string;
Expand Down Expand Up @@ -80,6 +81,15 @@ export const EditReadReplica: FC<EditReadReplicaProps> = ({ uuid, isViewMode })
}
);

const asyncProviderUuid = universe?.universeDetails
? getAsyncCluster(universe?.universeDetails)?.userIntent?.provider ?? ''
: '';
const providerConfigQuery = useQuery(
providerQueryKey.detail(asyncProviderUuid),
() => helperApi.fetchProvider(asyncProviderUuid),
{ enabled: !!asyncProviderUuid }
);

const onCancel = () => browserHistory.push(`/universes/${uuid}`);

const onSubmit = async (formData: UniverseFormData) => {
Expand Down Expand Up @@ -127,12 +137,14 @@ export const EditReadReplica: FC<EditReadReplicaProps> = ({ uuid, isViewMode })
} else setK8Modal(true);
};

if (isLoading || contextState.isLoading) return <YBLoading />;
if (isLoading || contextState.isLoading || providerConfigQuery.isLoading) {
return <YBLoading />;
}

if (!universe?.universeDetails) return null;

//get async form data and intitalize the form
const initialFormData = getAsyncFormData(universe.universeDetails);
const initialFormData = getAsyncFormData(universe.universeDetails, providerConfigQuery.data);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { api, QUERY_KEY } from './utils/api';
import { getPlacements } from './form/fields/PlacementsField/PlacementsFieldHelper';
import {
createErrorMessage,
getPrimaryCluster,
getPrimaryFormData,
transformTagsArrayToObject,
transitToUniverse
Expand Down Expand Up @@ -48,6 +49,7 @@ import {
USER_TAGS_FIELD,
SPOT_INSTANCE_FIELD
} from './utils/constants';
import { providerQueryKey, api as helperApi } from '../../../helpers/api';

interface EditUniverseProps {
uuid: string;
Expand Down Expand Up @@ -101,6 +103,15 @@ export const EditUniverse: FC<EditUniverseProps> = ({ uuid, isViewMode }) => {
}
);

const primaryProviderUuid = originalData?.universeDetails
? getPrimaryCluster(originalData?.universeDetails)?.userIntent?.provider ?? ''
: '';
const providerConfigQuery = useQuery(
providerQueryKey.detail(primaryProviderUuid),
() => helperApi.fetchProvider(primaryProviderUuid),
{ enabled: !!primaryProviderUuid }
);

const onCancel = () => browserHistory.push(`/universes/${uuid}`);

const submitEditUniverse = async (finalPayload: UniverseConfigure) => {
Expand All @@ -113,9 +124,18 @@ export const EditUniverse: FC<EditUniverseProps> = ({ uuid, isViewMode }) => {
}
};

if (isUniverseLoading || isLoading || !originalData?.universeDetails) return <YBLoading />;
if (
isUniverseLoading ||
isLoading ||
!originalData?.universeDetails ||
providerConfigQuery.isLoading
)
return <YBLoading />;

const initialFormData = getPrimaryFormData(originalData.universeDetails);
const initialFormData = getPrimaryFormData(
originalData.universeDetails,
providerConfigQuery.data
);

const onSubmit = async (formData: UniverseFormData) => {
if (!_.isEqual(formData, initialFormData)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { YBLabel, YBSelectField } from '../../../../../../components';
import { AccessKey, UniverseFormData } from '../../../utils/dto';
import { ACCESS_KEY_FIELD, PROVIDER_FIELD } from '../../../utils/constants';
import { useFormFieldStyles } from '../../../universeMainStyle';
import { YBProvider } from '../../../../../../../components/configRedesign/providerRedesign/types';
import { ProviderCode } from '../../../../../../../components/configRedesign/providerRedesign/constants';

const useStyles = makeStyles((theme) => ({
overrideMuiSelectMenu: {
Expand Down Expand Up @@ -43,14 +45,20 @@ export const AccessKeysField = ({ disabled }: AccessKeysFieldProps): ReactElemen
const accessKeys = allAccessKeys.data.filter(
(item: AccessKey) => item?.idKey?.providerUUID === provider?.uuid
);
if (accessKeys?.length)
if (accessKeys?.length) {
setValue(ACCESS_KEY_FIELD, accessKeys[0]?.idKey.keyCode, { shouldValidate: true });
} else {
setValue(ACCESS_KEY_FIELD, null, { shouldValidate: true });
}
}, [provider]);

//only first time
useEffectOnce(() => {
if (accessKeysList?.length && provider?.uuid)
if (accessKeysList?.length && provider?.uuid) {
setValue(ACCESS_KEY_FIELD, accessKeysList[0]?.idKey.keyCode, { shouldValidate: true });
} else {
setValue(ACCESS_KEY_FIELD, null, { shouldValidate: true });
}
});

return (
Expand All @@ -62,11 +70,12 @@ export const AccessKeysField = ({ disabled }: AccessKeysFieldProps): ReactElemen
<YBSelectField
className={`${classes.defaultTextBox} ${helperClasses.overrideMuiSelectMenu}`}
rules={{
required: !disabled
? (t('universeForm.validation.required', {
field: t('universeForm.advancedConfig.accessKey')
}) as string)
: ''
required:
!disabled && !provider.isOnPremManuallyProvisioned
? (t('universeForm.validation.required', {
field: t('universeForm.advancedConfig.accessKey')
}) as string)
: ''
}}
inputProps={{
'data-testid': 'AccessKeysField-Select'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@ import { UniverseFormData, Provider, DEFAULT_CLOUD_CONFIG } from '../../../utils
import { PROVIDER_FIELD } from '../../../utils/constants';
import { useFormFieldStyles } from '../../../universeMainStyle';
import { YBProvider } from '../../../../../../../components/configRedesign/providerRedesign/types';
import { ProviderStatus } from '../../../../../../../components/configRedesign/providerRedesign/constants';
import {
ProviderCode,
ProviderStatus
} from '../../../../../../../components/configRedesign/providerRedesign/constants';

interface ProvidersFieldProps {
disabled?: boolean;
filterByProvider: string | null; //pass only if there is a need to filter the providers list by code
}

// simplified provider object with minimum fields needed in UI
export type ProviderMin = Pick<Provider, 'uuid' | 'code'>;
export type ProviderMin = Pick<Provider, 'uuid' | 'code'> & {
isOnPremManuallyProvisioned: boolean;
};
const getOptionLabel = (option: Record<string, string>): string => option.name;

export const ProvidersField = ({
Expand All @@ -32,9 +37,12 @@ export const ProvidersField = ({
onSuccess: (providers) => {
// Pre-select provider by default
if (_.isEmpty(getValues(PROVIDER_FIELD)) && providers.length >= 1) {
const provider = providers[0];
const isOnPremManuallyProvisioned =
provider?.code === ProviderCode.ON_PREM && provider?.details?.skipProvisioning;
setValue(
PROVIDER_FIELD,
{ code: providers[0]?.code, uuid: providers[0]?.uuid },
{ code: provider?.code, uuid: provider?.uuid, isOnPremManuallyProvisioned },
{ shouldValidate: true }
);
}
Expand All @@ -54,7 +62,13 @@ export const ProvidersField = ({
const handleChange = (e: ChangeEvent<{}>, option: any) => {
if (option) {
const { code, uuid } = option;
setValue(PROVIDER_FIELD, { code, uuid }, { shouldValidate: true });
const isOnPremManuallyProvisioned =
option?.code === ProviderCode.ON_PREM && option?.details?.skipProvisioning;
setValue(
PROVIDER_FIELD,
{ code, uuid, isOnPremManuallyProvisioned },
{ shouldValidate: true }
);
} else {
setValue(PROVIDER_FIELD, DEFAULT_CLOUD_CONFIG.provider, { shouldValidate: true });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const CloudConfiguration = ({ runtimeConfigs }: UniverseFormConfiguration
const { t } = useTranslation();
const isLargeDevice = useMediaQuery('(min-width:1400px)');

const provider: YBProvider = useWatch({ name: PROVIDER_FIELD });
const provider = useWatch({ name: PROVIDER_FIELD });

const providerRuntimeConfigQuery = useQuery(
runtimeConfigQueryKey.providerScope(provider?.uuid),
Expand Down Expand Up @@ -111,11 +111,7 @@ export const CloudConfiguration = ({ runtimeConfigs }: UniverseFormConfiguration
<TotalNodesField disabled={isViewMode} />
</Box>
<Box mt={2}>
<ReplicationFactor
disabled={isViewMode}
isPrimary={isPrimary}
isEditMode={isEditMode}
/>
<ReplicationFactor disabled={isViewMode} isPrimary={isPrimary} isEditMode={isEditMode} />
</Box>
{isPrimary && isGeoPartitionEnabled && (
<Box mt={2} display="flex" flexDirection="column">
Expand Down
Loading

0 comments on commit 6614afb

Please sign in to comment.