mirror of https://github.com/rancher/ui.git
HARVESTER: harvester rke1 support control guest agent
This commit is contained in:
parent
371809ca3e
commit
b5e4ce09c4
|
|
@ -5,9 +5,11 @@ import {
|
|||
import Component from '@ember/component';
|
||||
import NodeDriver from 'shared/mixins/node-driver';
|
||||
import layout from './template';
|
||||
import jsyaml from 'js-yaml'
|
||||
import { inject as service } from '@ember/service';
|
||||
import { throttledObserver } from 'ui/utils/debounce';
|
||||
import { hash } from 'rsvp';
|
||||
import YAML from 'yaml';
|
||||
|
||||
const DRIVER = 'harvester';
|
||||
const CONFIG = 'harvesterConfig';
|
||||
|
|
@ -39,6 +41,22 @@ const PRIORITY = {
|
|||
|
||||
const STORAGE_NETWORK = 'storage-network.settings.harvesterhci.io'
|
||||
|
||||
// init qemu guest agent
|
||||
export const QGA_JSON = {
|
||||
package_update: true,
|
||||
packages: ['qemu-guest-agent'],
|
||||
runcmd: [
|
||||
[
|
||||
'systemctl',
|
||||
'enable',
|
||||
'--now',
|
||||
'qemu-guest-agent.service'
|
||||
]
|
||||
]
|
||||
};
|
||||
// Different operating systems may have different guest agents
|
||||
export const QGA_MAP = { default: 'qemu-guest-agent.service' };
|
||||
|
||||
export default Component.extend(NodeDriver, {
|
||||
growl: service(),
|
||||
settings: service(),
|
||||
|
|
@ -68,6 +86,8 @@ export default Component.extend(NodeDriver, {
|
|||
loading: false,
|
||||
disks: [],
|
||||
interfaces: [],
|
||||
installAgent: false,
|
||||
userDataTemplate: '',
|
||||
|
||||
config: alias(`model.${ CONFIG }`),
|
||||
|
||||
|
|
@ -83,6 +103,8 @@ export default Component.extend(NodeDriver, {
|
|||
this.initSchedulings();
|
||||
}
|
||||
|
||||
this.initUserData();
|
||||
|
||||
this.initDisks()
|
||||
|
||||
this.initInterfaces()
|
||||
|
|
@ -170,6 +192,54 @@ export default Component.extend(NodeDriver, {
|
|||
updatePodScheduling() {
|
||||
this.parsePodScheduling();
|
||||
},
|
||||
|
||||
updateAgent() {
|
||||
const isInstall = !get(this, 'installAgent');
|
||||
|
||||
set(this, 'installAgent', isInstall)
|
||||
const userData = get(this, 'config.userData')
|
||||
|
||||
const userDataDoc = isInstall ? this.addGuestAgent(userData) : this.deleteGuestAgent(userData);
|
||||
let userDataYaml = userDataDoc.toString();
|
||||
|
||||
if (userDataYaml === '{}\n') {
|
||||
// When the YAML parsed value is '{}\n', it means that the userData is empty.
|
||||
userDataYaml = '';
|
||||
}
|
||||
|
||||
const hasCloudComment = this.hasCloudConfigComment(userDataYaml);
|
||||
|
||||
if (!hasCloudComment) {
|
||||
userDataYaml = `#cloud-config\n${ userDataYaml }`;
|
||||
}
|
||||
|
||||
set(this, 'config.userData', userDataYaml);
|
||||
},
|
||||
|
||||
chooseUserDataTemplate() {
|
||||
const templateValue = get(this, 'userDataTemplate');
|
||||
const isInstallAgent = get(this, 'installAgent');
|
||||
|
||||
try {
|
||||
const templateJsonData = this.convertToJson(templateValue);
|
||||
|
||||
let userDataYaml;
|
||||
|
||||
if (isInstallAgent) {
|
||||
const mergedObj = Object.assign(templateJsonData, { ...QGA_JSON });
|
||||
|
||||
userDataYaml = this.addCloudConfigComment(mergedObj);
|
||||
} else {
|
||||
userDataYaml = templateValue;
|
||||
}
|
||||
|
||||
set(this, 'config.userData', userDataYaml)
|
||||
} catch (e) {
|
||||
const message = this.intl.t('nodeDriver.harvester.templateError')
|
||||
|
||||
get(this, 'growl').fromError(undefined, message);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
clearData: observer('currentCredential.id', function() {
|
||||
|
|
@ -183,8 +253,8 @@ export default Component.extend(NodeDriver, {
|
|||
set(this, 'config.diskInfo', '');
|
||||
set(this, 'config.networkInfo', '');
|
||||
|
||||
this.initUserData();
|
||||
this.initDisks()
|
||||
|
||||
this.initInterfaces()
|
||||
}),
|
||||
|
||||
|
|
@ -215,6 +285,14 @@ export default Component.extend(NodeDriver, {
|
|||
this.parsePodScheduling();
|
||||
}),
|
||||
|
||||
userDataChanged: observer('config.userData', function() {
|
||||
const userData = get(this, 'config.userData');
|
||||
const installAgent = get(this, 'installAgent')
|
||||
const hasInstall = this.hasInstallAgent(userData, installAgent);
|
||||
|
||||
set(this, 'installAgent', hasInstall)
|
||||
}),
|
||||
|
||||
fetchResource: throttledObserver('currentCredential.id', 'currentCredential.harvestercredentialConfig.clusterId', async function() {
|
||||
const clusterId = get(this, 'currentCredential') && get(this, 'currentCredential').harvestercredentialConfig && get(this, 'currentCredential').harvestercredentialConfig.clusterId;
|
||||
|
||||
|
|
@ -423,6 +501,124 @@ export default Component.extend(NodeDriver, {
|
|||
set(this, `model.${ CONFIG }`, config);
|
||||
},
|
||||
|
||||
addGuestAgent(userData) {
|
||||
const userDataDoc = userData ? YAML.parseDocument(userData) : YAML.parseDocument({});
|
||||
const userDataYAML = userDataDoc.toString();
|
||||
const userDataJSON = YAML.parse(userDataYAML);
|
||||
let packages = userDataJSON?.packages || [];
|
||||
let runcmd = userDataJSON?.runcmd || [];
|
||||
|
||||
userDataDoc.setIn(['package_update'], true);
|
||||
if (Array.isArray(packages)) {
|
||||
if (!packages.includes('qemu-guest-agent')) {
|
||||
packages.push('qemu-guest-agent');
|
||||
}
|
||||
} else {
|
||||
packages = QGA_JSON.packages;
|
||||
}
|
||||
if (Array.isArray(runcmd)) {
|
||||
const hasSameRuncmd = runcmd.find( (S) => Array.isArray(S) && S.join('-') === QGA_JSON.runcmd[0].join('-'));
|
||||
|
||||
if (!hasSameRuncmd) {
|
||||
runcmd.push(QGA_JSON.runcmd[0]);
|
||||
}
|
||||
} else {
|
||||
runcmd = QGA_JSON.runcmd;
|
||||
}
|
||||
if (packages.length > 0) {
|
||||
userDataDoc.setIn(['packages'], packages);
|
||||
} else {
|
||||
userDataDoc.setIn(['packages'], []); // It needs to be set empty first, as it is possible that cloud-init comments are mounted on this node
|
||||
this.deleteYamlDocProp(userDataDoc, ['packages']);
|
||||
this.deleteYamlDocProp(userDataDoc, ['package_update']);
|
||||
}
|
||||
if (runcmd.length > 0) {
|
||||
userDataDoc.setIn(['runcmd'], runcmd);
|
||||
} else {
|
||||
this.deleteYamlDocProp(userDataDoc, ['runcmd']);
|
||||
}
|
||||
|
||||
return userDataDoc;
|
||||
},
|
||||
|
||||
deleteGuestAgent(userData) {
|
||||
const userDataDoc = userData ? YAML.parseDocument(userData) : YAML.parseDocument({});
|
||||
const userDataYAML = userDataDoc.toString();
|
||||
const userDataJSON = YAML.parse(userDataYAML);
|
||||
const packages = userDataJSON?.packages || [];
|
||||
const runcmd = userDataJSON?.runcmd || [];
|
||||
|
||||
if (Array.isArray(packages)) {
|
||||
for (let i = 0; i < packages.length; i++) {
|
||||
if (packages[i] === 'qemu-guest-agent') {
|
||||
packages.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(runcmd)) {
|
||||
for (let i = 0; i < runcmd.length; i++) {
|
||||
if (Array.isArray(runcmd[i]) && runcmd[i].join('-') === QGA_JSON.runcmd[0].join('-')) {
|
||||
runcmd.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (packages.length > 0) {
|
||||
userDataDoc.setIn(['packages'], packages);
|
||||
} else {
|
||||
userDataDoc.setIn(['packages'], []);
|
||||
this.deleteYamlDocProp(userDataDoc, ['packages']);
|
||||
this.deleteYamlDocProp(userDataDoc, ['package_update']);
|
||||
}
|
||||
if (runcmd.length > 0) {
|
||||
userDataDoc.setIn(['runcmd'], runcmd);
|
||||
} else {
|
||||
this.deleteYamlDocProp(userDataDoc, ['runcmd']);
|
||||
}
|
||||
|
||||
return userDataDoc;
|
||||
},
|
||||
|
||||
hasCloudConfigComment(userScript) {
|
||||
// Check that userData contains: #cloud-config
|
||||
const userDataDoc = userScript ? YAML.parseDocument(userScript) : YAML.parseDocument({});
|
||||
|
||||
const items = userDataDoc?.contents?.items || [];
|
||||
let exist = false;
|
||||
|
||||
if (userDataDoc?.comment === 'cloud-config' || userDataDoc?.comment?.includes('cloud-config\n')) {
|
||||
exist = true;
|
||||
}
|
||||
|
||||
if (userDataDoc?.commentBefore === 'cloud-config' || userDataDoc?.commentBefore?.includes('cloud-config\n')) {
|
||||
exist = true;
|
||||
}
|
||||
|
||||
items.map((item) => {
|
||||
const key = item.key;
|
||||
|
||||
if (key?.commentBefore?.trim() === 'cloud-config' || key?.commentBefore?.includes('cloud-config\n') || /\ncloud-config$/.test(key?.commentBefore)) {
|
||||
exist = true;
|
||||
}
|
||||
});
|
||||
|
||||
return exist;
|
||||
},
|
||||
|
||||
deleteYamlDocProp(doc, paths) {
|
||||
try {
|
||||
const item = doc.getIn([])?.items[0];
|
||||
const key = item?.key;
|
||||
const hasCloudConfigComment = !!key?.commentBefore?.includes('cloud-config');
|
||||
const isMatchProp = key.source === paths[paths.length - 1];
|
||||
|
||||
if (key && hasCloudConfigComment && isMatchProp) {
|
||||
// Comments are mounted on the next node and we should not delete the node containing cloud-config
|
||||
} else {
|
||||
doc.deleteIn(paths);
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
validate() {
|
||||
this._super();
|
||||
let errors = get(this, 'errors') || [];
|
||||
|
|
@ -568,6 +764,19 @@ export default Component.extend(NodeDriver, {
|
|||
set(this, 'podSchedulings', podSchedulings);
|
||||
},
|
||||
|
||||
initUserData() {
|
||||
if (!get(this, 'config.userData')) {
|
||||
let userData = this.addCloudConfigComment(QGA_JSON);
|
||||
|
||||
set(this, 'config.userData', userData)
|
||||
}
|
||||
|
||||
const userData = get(this, 'config.userData')
|
||||
const hasInstall = this.hasInstallAgent(userData, true);
|
||||
|
||||
set(this, 'installAgent', hasInstall)
|
||||
},
|
||||
|
||||
initDisks() {
|
||||
let disks = [];
|
||||
|
||||
|
|
@ -595,6 +804,42 @@ export default Component.extend(NodeDriver, {
|
|||
set(this, 'disks', disks);
|
||||
},
|
||||
|
||||
convertToJson(script = '') {
|
||||
let out = {};
|
||||
|
||||
try {
|
||||
out = jsyaml.load(script);
|
||||
} catch (e) {
|
||||
throw new Error('Function(convertToJson) error');
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
hasInstallAgent(userScript, installAgent) {
|
||||
let dataFormat = {};
|
||||
|
||||
try {
|
||||
dataFormat = this.convertToJson(userScript);
|
||||
} catch {
|
||||
// When the yaml cannot be converted to json, the previous installAgent value should be returned
|
||||
return installAgent;
|
||||
}
|
||||
const hasInstall = dataFormat?.packages?.includes('qemu-guest-agent') && !!dataFormat?.runcmd?.find( (S) => Array.isArray(S) && S.join('-') === QGA_JSON.runcmd[0].join('-'));
|
||||
|
||||
return !!hasInstall;
|
||||
},
|
||||
|
||||
addCloudConfigComment(value) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
return `#cloud-config\n${ jsyaml.safeDump(value) }`;
|
||||
} else if (typeof value === 'string' && !value.startsWith('#cloud-config')) {
|
||||
return `#cloud-config\n${ value }`;
|
||||
} else if (typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
|
||||
initInterfaces() {
|
||||
let _interfaces = [];
|
||||
|
||||
|
|
@ -796,5 +1041,5 @@ export default Component.extend(NodeDriver, {
|
|||
errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.network.macFormat') }));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -326,7 +326,8 @@
|
|||
<SearchableSelect
|
||||
@class="form-control"
|
||||
@content={{userDataContent}}
|
||||
@value={{config.userData}}
|
||||
@value={{userDataTemplate}}
|
||||
@change={{action "chooseUserDataTemplate" userDataTemplate}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -348,6 +349,21 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col span-12 mt-0">
|
||||
<label class="acc-label">
|
||||
{{input
|
||||
type="checkbox"
|
||||
classNames="form-control"
|
||||
checked=installAgent
|
||||
click=(action "updateAgent")
|
||||
}}
|
||||
|
||||
{{t "nodeDriver.harvester.installAgent.label"}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col span-6">
|
||||
<label class="acc-label">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@
|
|||
"ember-auto-import": "*",
|
||||
"ember-intl": "*",
|
||||
"ember-cli-htmlbars": "*",
|
||||
"ember-cli-babel": "*"
|
||||
"ember-cli-babel": "*",
|
||||
"yaml": "*",
|
||||
"js-yaml": "*"
|
||||
},
|
||||
"ember-addon": {
|
||||
"paths": [
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@
|
|||
"webpack": "^4.44.1",
|
||||
"xterm": "^4.9.0",
|
||||
"xterm-addon-fit": "^0.4.0",
|
||||
"yaml": "^2.2.1",
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -9891,6 +9891,9 @@ nodeDriver:
|
|||
label: User Data Template
|
||||
nodeTemplate:
|
||||
tooltip: Harvester requires virtual machine under the same namespace
|
||||
installAgent:
|
||||
label: Install guest agent
|
||||
templateError: Incorrect template format
|
||||
pnap:
|
||||
access:
|
||||
title: Account Access
|
||||
|
|
|
|||
|
|
@ -4735,6 +4735,9 @@ nodeDriver:
|
|||
label: User Data YAML
|
||||
template:
|
||||
label: User Data Template
|
||||
installAgent:
|
||||
label: Install guest agent
|
||||
templateError: Incorrect template format
|
||||
packet:
|
||||
accountSection: Acceso a la Cuenta
|
||||
projectId:
|
||||
|
|
|
|||
|
|
@ -9237,6 +9237,9 @@ nodeDriver:
|
|||
label: 用户数据
|
||||
template:
|
||||
label: 用户配置模板
|
||||
installAgent:
|
||||
label: 安装访客代理
|
||||
templateError: 模版格式错误
|
||||
oci:
|
||||
placement:
|
||||
title: Placement
|
||||
|
|
|
|||
|
|
@ -15184,6 +15184,11 @@ yam@^1.0.0:
|
|||
fs-extra "^4.0.2"
|
||||
lodash.merge "^4.6.0"
|
||||
|
||||
yaml@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npmjs.org/yaml/-/yaml-2.2.1.tgz#3014bf0482dcd15147aa8e56109ce8632cd60ce4"
|
||||
integrity sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==
|
||||
|
||||
yamljs@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/yamljs/-/yamljs-0.3.0.tgz#dc060bf267447b39f7304e9b2bfbe8b5a7ddb03b"
|
||||
|
|
|
|||
Loading…
Reference in New Issue