feat: support non-root users to log in
Support vm password login for non-root users when creating vm/ironic Change-Id: Iaf692e333686d2563013c0ea41777da8c772ce35
This commit is contained in:
parent
d02497a15d
commit
80e1d1275d
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Support non-root users can log in to VM:
|
||||
|
||||
* When creating a vm/ironic, log in with a password. The username is required. The username comes from the image configuration or user input. Non-root is supported.
|
@ -39,11 +39,28 @@ export class SystemStep extends Base {
|
||||
}));
|
||||
}
|
||||
|
||||
get imageInfo() {
|
||||
const { context = {} } = this.props;
|
||||
const { image = {} } = context || {};
|
||||
const { selectedRows = [] } = image;
|
||||
return selectedRows.length && selectedRows[0];
|
||||
}
|
||||
|
||||
get loginUserName() {
|
||||
return this.imageInfo?.os_admin_user;
|
||||
}
|
||||
|
||||
get loginUserNameInContext() {
|
||||
const { username = '' } = this.props.context || {};
|
||||
return username || '';
|
||||
}
|
||||
|
||||
get defaultValue() {
|
||||
const { context = {} } = this.props;
|
||||
const data = {
|
||||
loginType: context.loginType || this.loginTypes[0],
|
||||
more: false,
|
||||
username: this.loginUserName || this.loginUserNameInContext,
|
||||
};
|
||||
return data;
|
||||
}
|
||||
@ -71,10 +88,32 @@ export class SystemStep extends Base {
|
||||
return ['loginType', 'password', 'confirmPassword'];
|
||||
}
|
||||
|
||||
get formItems() {
|
||||
get isPassword() {
|
||||
const { loginType } = this.state;
|
||||
const isPassword = loginType === this.loginTypes[1].value;
|
||||
return loginType === this.loginTypes[1].value;
|
||||
}
|
||||
|
||||
get usernameFormItem() {
|
||||
const item = {
|
||||
name: 'username',
|
||||
label: t('Login Name'),
|
||||
type: 'input',
|
||||
extra: this.loginUserName
|
||||
? ''
|
||||
: t(
|
||||
"The feasible configuration of cloud-init or cloudbase-init service in the image is not synced to image's properties, so the Login Name is unknown."
|
||||
),
|
||||
tip: t(
|
||||
'Whether the Login Name can be used is up to the feasible configuration of cloud-init or cloudbase-init service in the image.'
|
||||
),
|
||||
required: this.isPassword,
|
||||
hidden: !this.isPassword,
|
||||
};
|
||||
item.disabled = !!this.loginUserName;
|
||||
return item;
|
||||
}
|
||||
|
||||
get formItems() {
|
||||
return [
|
||||
{
|
||||
name: 'name',
|
||||
@ -91,6 +130,7 @@ export class SystemStep extends Base {
|
||||
options: this.loginTypes,
|
||||
isWrappedValue: true,
|
||||
},
|
||||
this.usernameFormItem,
|
||||
{
|
||||
name: 'keypair',
|
||||
label: t('Keypair'),
|
||||
@ -98,8 +138,8 @@ export class SystemStep extends Base {
|
||||
data: this.keypairs,
|
||||
isLoading: this.keyPairStore.list.isLoading,
|
||||
isMulti: false,
|
||||
required: !isPassword,
|
||||
hidden: isPassword,
|
||||
required: !this.isPassword,
|
||||
hidden: this.isPassword,
|
||||
tip: t(
|
||||
'The SSH key is a way to remotely log in to the instance. The cloud platform only helps to keep the public key. Please keep your private key properly.'
|
||||
),
|
||||
@ -125,16 +165,16 @@ export class SystemStep extends Base {
|
||||
name: 'password',
|
||||
label: t('Password'),
|
||||
type: 'input-password',
|
||||
required: isPassword,
|
||||
hidden: !isPassword,
|
||||
required: this.isPassword,
|
||||
hidden: !this.isPassword,
|
||||
otherRule: getPasswordOtherRule('password', 'instance'),
|
||||
},
|
||||
{
|
||||
name: 'confirmPassword',
|
||||
label: t('Confirm Password'),
|
||||
type: 'input-password',
|
||||
required: isPassword,
|
||||
hidden: !isPassword,
|
||||
required: this.isPassword,
|
||||
hidden: !this.isPassword,
|
||||
otherRule: getPasswordOtherRule('confirmPassword', 'instance'),
|
||||
},
|
||||
];
|
||||
|
@ -327,7 +327,10 @@ export class CreateIronic extends StepAction {
|
||||
server.return_reservation_id = true;
|
||||
}
|
||||
if (server.adminPass || userData) {
|
||||
server.user_data = btoa(getUserData(server.adminPass, userData));
|
||||
const { username } = values;
|
||||
server.user_data = btoa(
|
||||
getUserData(server.adminPass, userData, username || 'root')
|
||||
);
|
||||
}
|
||||
return {
|
||||
server,
|
||||
|
@ -147,6 +147,7 @@ export class SystemStep extends Base {
|
||||
more: false,
|
||||
physicalNodeType: physicalNodeTypes[0],
|
||||
userData: '',
|
||||
username: this.loginUserName || this.loginUserNameInContext,
|
||||
};
|
||||
if (servergroup) {
|
||||
data.serverGroup = {
|
||||
@ -210,6 +211,11 @@ export class SystemStep extends Base {
|
||||
return this.sourceInfo && this.sourceInfo.os_admin_user;
|
||||
}
|
||||
|
||||
get loginUserNameInContext() {
|
||||
const { username = '' } = this.props.context || {};
|
||||
return username || '';
|
||||
}
|
||||
|
||||
onValuesChange = (changedFields) => {
|
||||
if (has(changedFields, 'serverGroup')) {
|
||||
this.onServerGroupChange(changedFields.serverGroup);
|
||||
@ -223,9 +229,33 @@ export class SystemStep extends Base {
|
||||
});
|
||||
};
|
||||
|
||||
get isPassword() {
|
||||
const { loginType } = this.state;
|
||||
return loginType === this.loginTypes[1].value;
|
||||
}
|
||||
|
||||
get usernameFormItem() {
|
||||
const item = {
|
||||
name: 'username',
|
||||
label: t('Login Name'),
|
||||
type: 'input',
|
||||
extra: this.loginUserName
|
||||
? ''
|
||||
: t(
|
||||
"The feasible configuration of cloud-init or cloudbase-init service in the image is not synced to image's properties, so the Login Name is unknown."
|
||||
),
|
||||
tip: t(
|
||||
'Whether the Login Name can be used is up to the feasible configuration of cloud-init or cloudbase-init service in the image.'
|
||||
),
|
||||
required: this.isPassword,
|
||||
hidden: !this.isPassword,
|
||||
};
|
||||
item.disabled = !!this.loginUserName;
|
||||
return item;
|
||||
}
|
||||
|
||||
get formItems() {
|
||||
const { loginType, more = false, physicalNodeType } = this.state;
|
||||
const isPassword = loginType === this.loginTypes[1].value;
|
||||
const { more = false, physicalNodeType } = this.state;
|
||||
const isManually = physicalNodeType === physicalNodeTypes[1].value;
|
||||
|
||||
const { initKeyPair } = this.state;
|
||||
@ -245,27 +275,15 @@ export class SystemStep extends Base {
|
||||
options: this.loginTypes,
|
||||
isWrappedValue: true,
|
||||
},
|
||||
{
|
||||
name: 'username',
|
||||
label: t('Login Name'),
|
||||
content: this.loginUserName || '-',
|
||||
extra: this.loginUserName
|
||||
? ''
|
||||
: t(
|
||||
"The feasible configuration of cloud-init or cloudbase-init service in the image is not synced to image's properties, so the Login Name is unknown."
|
||||
),
|
||||
tip: t(
|
||||
'Whether the Login Name can be used is up to the feasible configuration of cloud-init or cloudbase-init service in the image.'
|
||||
),
|
||||
},
|
||||
this.usernameFormItem,
|
||||
{
|
||||
name: 'keypair',
|
||||
label: t('Keypair'),
|
||||
type: 'select-table',
|
||||
data: this.keypairs,
|
||||
isLoading: this.keyPairStore.list.isLoading,
|
||||
required: !isPassword,
|
||||
hidden: isPassword,
|
||||
required: !this.isPassword,
|
||||
hidden: this.isPassword,
|
||||
header: getKeyPairHeader(this),
|
||||
initValue: initKeyPair,
|
||||
tip: t(
|
||||
@ -293,16 +311,16 @@ export class SystemStep extends Base {
|
||||
name: 'password',
|
||||
label: t('Login Password'),
|
||||
type: 'input-password',
|
||||
required: isPassword,
|
||||
hidden: !isPassword,
|
||||
required: this.isPassword,
|
||||
hidden: !this.isPassword,
|
||||
otherRule: getPasswordOtherRule('password', 'instance'),
|
||||
},
|
||||
{
|
||||
name: 'confirmPassword',
|
||||
label: t('Confirm Password'),
|
||||
type: 'input-password',
|
||||
required: isPassword,
|
||||
hidden: !isPassword,
|
||||
required: this.isPassword,
|
||||
hidden: !this.isPassword,
|
||||
otherRule: getPasswordOtherRule('confirmPassword', 'instance'),
|
||||
},
|
||||
{
|
||||
|
@ -743,7 +743,10 @@ export class StepCreate extends StepAction {
|
||||
physicalNode.selectedRows[0].hypervisor_hostname;
|
||||
}
|
||||
if (server.adminPass || userData) {
|
||||
server.user_data = btoa(getUserData(server.adminPass, userData));
|
||||
const { username } = values;
|
||||
server.user_data = btoa(
|
||||
getUserData(server.adminPass, userData, username || 'root')
|
||||
);
|
||||
}
|
||||
const body = {
|
||||
server,
|
||||
|
@ -218,7 +218,7 @@ const passwordAndUserData =
|
||||
'Content-Disposition: attachment; filename="passwd-script.txt" \n' +
|
||||
'\n' +
|
||||
'#!/bin/sh\n' +
|
||||
"echo 'root:USER_PASSWORD' | chpasswd\n" +
|
||||
"echo 'USER_NAME:USER_PASSWORD' | chpasswd\n" +
|
||||
'\n' +
|
||||
'--===============2309984059743762475==\n' +
|
||||
'Content-Type: text/x-shellscript; charset="us-ascii" \n' +
|
||||
@ -252,7 +252,7 @@ const onlyPassword =
|
||||
'Content-Disposition: attachment; filename="passwd-script.txt" \n' +
|
||||
'\n' +
|
||||
'#!/bin/sh\n' +
|
||||
"echo 'root:USER_PASSWORD' | chpasswd\n" +
|
||||
"echo 'USER_NAME:USER_PASSWORD' | chpasswd\n" +
|
||||
'\n' +
|
||||
'--===============2309984059743762475==--';
|
||||
|
||||
@ -270,13 +270,15 @@ const onlyUserData =
|
||||
'\n' +
|
||||
'--===============2309984059743762475==--';
|
||||
|
||||
export const getUserData = (password, userData) => {
|
||||
export const getUserData = (password, userData, username = 'root') => {
|
||||
if (password && userData) {
|
||||
const str = passwordAndUserData.replace(/USER_PASSWORD/g, password);
|
||||
let str = passwordAndUserData.replace(/USER_PASSWORD/g, password);
|
||||
str = str.replace(/USER_NAME/g, username);
|
||||
return str.replace(/USER_DATA/g, userData);
|
||||
}
|
||||
if (password) {
|
||||
return onlyPassword.replace(/USER_PASSWORD/g, password);
|
||||
const str = onlyPassword.replace(/USER_PASSWORD/g, password);
|
||||
return str.replace(/USER_NAME/g, username);
|
||||
}
|
||||
return onlyUserData.replace(/USER_DATA/g, userData);
|
||||
};
|
||||
|
@ -58,6 +58,7 @@ describe('The Instance Page', () => {
|
||||
.clickStepActionNextButton()
|
||||
.formInput('name', name)
|
||||
.formRadioChoose('loginType', 1)
|
||||
.formInput('username', 'root')
|
||||
.formInput('password', password)
|
||||
.formInput('confirmPassword', password)
|
||||
.wait(2000)
|
||||
|
@ -66,6 +66,7 @@ onlyOn(ironicServiceEnabled, () => {
|
||||
.clickStepActionNextButton()
|
||||
.formInput('name', name)
|
||||
.formRadioChoose('loginType', 1)
|
||||
.formInput('username', 'root')
|
||||
.formInput('password', password)
|
||||
.formInput('confirmPassword', password)
|
||||
.wait(2000)
|
||||
|
@ -62,6 +62,7 @@ describe('The Server Group Page', () => {
|
||||
.clickStepActionNextButton()
|
||||
.formInput('name', instanceName)
|
||||
.formRadioChoose('loginType', 1)
|
||||
.formInput('username', 'root')
|
||||
.formInput('password', password)
|
||||
.formInput('confirmPassword', password)
|
||||
.clickStepActionNextButton()
|
||||
|
@ -48,6 +48,7 @@ Cypress.Commands.add('createInstance', ({ name, networkName }) => {
|
||||
.clickStepActionNextButton()
|
||||
.formInput('name', name)
|
||||
.formRadioChoose('loginType', 1)
|
||||
.formInput('username', 'root')
|
||||
.formInput('password', password)
|
||||
.formInput('confirmPassword', password)
|
||||
.wait(2000)
|
||||
@ -159,6 +160,7 @@ Cypress.Commands.add(
|
||||
.clickStepActionNextButton()
|
||||
.formInput('name', name)
|
||||
.formRadioChoose('loginType', 1)
|
||||
.formInput('username', 'root')
|
||||
.formInput('password', password)
|
||||
.formInput('confirmPassword', password)
|
||||
.clickStepActionNextButton()
|
||||
|
Loading…
Reference in New Issue
Block a user