This is an automated email from the ASF dual-hosted git repository.

weizhou pushed a commit to branch 4.20
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.20 by this push:
     new 70af55e8489 UI support for extraconfig in deploy and update instance  
(#11719)
70af55e8489 is described below

commit 70af55e84897ab23b8f37f7ade04a520d99e1d8a
Author: Abhisar Sinha <[email protected]>
AuthorDate: Tue Sep 30 12:50:44 2025 +0530

    UI support for extraconfig in deploy and update instance  (#11719)
---
 .../org/apache/cloudstack/api/ApiConstants.java    |  1 +
 .../command/user/config/ListCapabilitiesCmd.java   |  1 +
 .../api/response/CapabilitiesResponse.java         |  8 +++++++
 .../com/cloud/server/ManagementServerImpl.java     |  2 ++
 .../src/main/java/com/cloud/vm/UserVmManager.java  |  9 ++++++++
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  |  9 +++-----
 ui/public/locales/en.json                          |  2 ++
 ui/src/views/compute/DeployVM.vue                  | 12 +++++++++++
 ui/src/views/compute/EditVM.vue                    | 25 +++++++++++++++++++++-
 9 files changed, 62 insertions(+), 7 deletions(-)

diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java 
b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
index 89c9a194e3f..4abc0d13d74 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -26,6 +26,7 @@ public class ApiConstants {
     public static final String ACTIVATION_RULE = "activationrule";
     public static final String ACTIVITY = "activity";
     public static final String ADAPTER_TYPE = "adaptertype";
+    public static final String ADDITONAL_CONFIG_ENABLED = 
"additionalconfigenabled";
     public static final String ADDRESS = "address";
     public static final String ALGORITHM = "algorithm";
     public static final String ALIAS = "alias";
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java
index bd3f39a09aa..7553ccffa7d 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java
@@ -73,6 +73,7 @@ public class ListCapabilitiesCmd extends BaseCmd {
         
response.setSharedFsVmMinCpuCount((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT));
         
response.setSharedFsVmMinRamSize((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE));
         response.setDynamicScalingEnabled((Boolean) 
capabilities.get(ApiConstants.DYNAMIC_SCALING_ENABLED));
+        response.setAdditionalConfigEnabled((Boolean) 
capabilities.get(ApiConstants.ADDITONAL_CONFIG_ENABLED));
         response.setObjectName("capability");
         response.setResponseName(getCommandName());
         this.setResponseObject(response);
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java
 
b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java
index ff2e33b1389..affa130d4b0 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java
@@ -140,6 +140,10 @@ public class CapabilitiesResponse extends BaseResponse {
     @Param(description = "true if dynamically scaling for instances is 
enabled", since = "4.21.0")
     private Boolean dynamicScalingEnabled;
 
+    @SerializedName(ApiConstants.ADDITONAL_CONFIG_ENABLED)
+    @Param(description = "true if additional configurations or extraconfig can 
be passed to Instances", since = "4.20.2")
+    private Boolean additionalConfigEnabled;
+
     public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) {
         this.securityGroupsEnabled = securityGroupsEnabled;
     }
@@ -255,4 +259,8 @@ public class CapabilitiesResponse extends BaseResponse {
     public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) {
         this.dynamicScalingEnabled = dynamicScalingEnabled;
     }
+
+    public void setAdditionalConfigEnabled(Boolean additionalConfigEnabled) {
+        this.additionalConfigEnabled = additionalConfigEnabled;
+    }
 }
diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java 
b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
index 271372bf656..56e8a56f2e2 100644
--- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java
@@ -4535,6 +4535,8 @@ public class ManagementServerImpl extends ManagerBase 
implements ManagementServe
         }
         capabilities.put(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT, fsVmMinCpu);
         capabilities.put(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE, fsVmMinRam);
+        capabilities.put(ApiConstants.ADDITONAL_CONFIG_ENABLED, 
UserVmManager.EnableAdditionalVmConfig.valueIn(caller.getId()));
+
 
         return capabilities;
     }
diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java 
b/server/src/main/java/com/cloud/vm/UserVmManager.java
index f2a8a672d42..21ac6e3eb36 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManager.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManager.java
@@ -83,6 +83,15 @@ public interface UserVmManager extends UserVmService {
             "If set to true, tags specified in `resource.limit.host.tags` are 
also included in vm.strict.host.tags.",
             true);
 
+    ConfigKey<Boolean> EnableAdditionalVmConfig = new ConfigKey<>(
+            "Advanced",
+            Boolean.class,
+            "enable.additional.vm.configuration",
+            "false",
+            "allow additional arbitrary configuration to vm",
+            true,
+            ConfigKey.Scope.Account);
+
     static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
 
     public  static  final String CKS_NODE = "cksnode";
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java 
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 5b3284c2c1e..a67484b6dd6 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -670,9 +670,6 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
     private static final ConfigKey<Boolean> AllowDeployVmIfGivenHostFails = 
new ConfigKey<Boolean>("Advanced", Boolean.class, 
"allow.deploy.vm.if.deploy.on.given.host.fails", "false",
             "allow vm to deploy on different host if vm fails to deploy on the 
given host ", true);
 
-    private static final ConfigKey<Boolean> EnableAdditionalVmConfig = new 
ConfigKey<>("Advanced", Boolean.class,
-            "enable.additional.vm.configuration", "false", "allow additional 
arbitrary configuration to vm", true, ConfigKey.Scope.Account);
-
     private static final ConfigKey<String> KvmAdditionalConfigAllowList = new 
ConfigKey<>(String.class,
     "allow.additional.vm.configuration.list.kvm", "Advanced", "", "Comma 
separated list of allowed additional configuration options.", true, 
ConfigKey.Scope.Account, null, null, EnableAdditionalVmConfig.key(), null, 
null, ConfigKey.Kind.CSV, null);
 
@@ -6280,7 +6277,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
     protected void persistExtraConfigVmware(String decodedUrl, UserVm vm) {
         boolean isValidConfig = isValidKeyValuePair(decodedUrl);
         if (isValidConfig) {
-            String[] extraConfigs = decodedUrl.split("\\r?\\n");
+            String[] extraConfigs = decodedUrl.split("\\r?\\n+");
             for (String cfg : extraConfigs) {
                 // Validate cfg against unsupported operations set by admin 
here
                 String[] allowedKeyList = 
VmwareAdditionalConfigAllowList.value().split(",");
@@ -6308,7 +6305,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
     protected void persistExtraConfigXenServer(String decodedUrl, UserVm vm) {
         boolean isValidConfig = isValidKeyValuePair(decodedUrl);
         if (isValidConfig) {
-            String[] extraConfigs = decodedUrl.split("\\r?\\n");
+            String[] extraConfigs = decodedUrl.split("\\r?\\n+");
             int i = 1;
             String extraConfigKey = ApiConstants.EXTRA_CONFIG + "-";
             for (String cfg : extraConfigs) {
@@ -6388,8 +6385,8 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         // validate config against denied cfg commands
         validateKvmExtraConfig(decodedUrl, vm.getAccountId());
         String[] extraConfigs = decodedUrl.split("\n\n");
+        int i = 1;
         for (String cfg : extraConfigs) {
-            int i = 1;
             String[] cfgParts = cfg.split("\n");
             String extraConfigKey = ApiConstants.EXTRA_CONFIG;
             String extraConfigValue;
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 79ea9bb9d8f..d18c0945d3b 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -975,6 +975,8 @@
 "label.externalid": "External Id",
 "label.externalloadbalanceripaddress": "External load balancer IP address.",
 "label.extra": "Extra arguments",
+"label.extraconfig": "Additional Configuration",
+"label.extraconfig.tooltip": "Additional configuration parameters 
(extraconfig) to pass to the instance in plain text",
 "label.f5": "F5",
 "label.f5.ip.loadbalancer": "F5 BIG-IP load balancer.",
 "label.failed": "Failed",
diff --git a/ui/src/views/compute/DeployVM.vue 
b/ui/src/views/compute/DeployVM.vue
index a604fe68fe4..82a54ed8314 100644
--- a/ui/src/views/compute/DeployVM.vue
+++ b/ui/src/views/compute/DeployVM.vue
@@ -724,6 +724,12 @@
                         </div>
                       </a-card>
                     </a-form-item>
+                    <a-form-item v-if="extraConfigEnabled" name="extraconfig" 
ref="extraconfig">
+                      <template #label>
+                        <tooltip-label :title="$t('label.extraconfig')" 
:tooltip="$t('label.extraconfig.tooltip')"/>
+                      </template>
+                      <a-textarea v-model:value="form.extraconfig"/>
+                    </a-form-item>
                     <a-form-item :label="$t('label.affinity.groups')">
                       <affinity-group-selection
                         :items="options.affinityGroups"
@@ -1418,6 +1424,9 @@ export default {
     dynamicScalingVmConfigValue () {
       return this.$store.getters.features.dynamicscalingenabled
     },
+    extraConfigEnabled () {
+      return this.$store.getters.features.additionalconfigenabled
+    },
     isCustomizedDiskIOPS () {
       return this.diskSelected?.iscustomizediops || false
     },
@@ -2054,6 +2063,9 @@ export default {
         if (isUserdataAllowed && values.userdata && values.userdata.length > 
0) {
           deployVmData.userdata = this.$toBase64AndURIEncoded(values.userdata)
         }
+        if (values.extraconfig && values.extraconfig.length > 0) {
+          deployVmData.extraconfig = encodeURIComponent(values.extraconfig)
+        }
         // step 2: select template/iso
         if (this.tabKey === 'templateid') {
           deployVmData.templateid = values.templateid
diff --git a/ui/src/views/compute/EditVM.vue b/ui/src/views/compute/EditVM.vue
index d5e75fcc658..9e60175f2a9 100644
--- a/ui/src/views/compute/EditVM.vue
+++ b/ui/src/views/compute/EditVM.vue
@@ -91,6 +91,12 @@
         <a-textarea v-model:value="form.userdata">
         </a-textarea>
       </a-form-item>
+      <a-form-item v-if="extraConfigEnabled">
+        <template #label>
+          <tooltip-label :title="$t('label.extraconfig')" 
:tooltip="$t('label.extraconfig.tooltip')"/>
+        </template>
+        <a-textarea v-model:value="form.extraconfig"/>
+      </a-form-item>
       <a-form-item ref="securitygroupids" name="securitygroupids" 
:label="$t('label.security.groups')" v-if="securityGroupsEnabled">
         <a-select
           mode="multiple"
@@ -167,6 +173,19 @@ export default {
       }
     }
   },
+  computed: {
+    extraConfigEnabled () {
+      return this.$store.getters.features.additionalconfigenabled
+    },
+    combinedExtraConfig () {
+      if (!this.extraConfigEnabled || !this.resource.details) return ''
+      const configs = Object.keys(this.resource.details)
+        .filter(key => key.startsWith('extraconfig-'))
+        .map(key => this.resource.details[key] || '')
+        .filter(val => val.trim())
+      return configs.join('\n\n')
+    }
+  },
   beforeCreate () {
     this.apiParams = this.$getApiParams('updateVirtualMachine')
   },
@@ -185,7 +204,8 @@ export default {
         deleteprotection: this.resource.deleteprotection,
         group: this.resource.group,
         userdata: '',
-        haenable: this.resource.haenable
+        haenable: this.resource.haenable,
+        extraconfig: this.combinedExtraConfig
       })
       this.rules = reactive({})
     },
@@ -342,6 +362,9 @@ export default {
         if (values.userdata && values.userdata.length > 0) {
           params.userdata = this.$toBase64AndURIEncoded(values.userdata)
         }
+        if (values.extraconfig && values.extraconfig.length > 0) {
+          params.extraconfig = encodeURIComponent(values.extraconfig)
+        }
         this.loading = true
 
         api('updateVirtualMachine', {}, 'POST', params).then(json => {

Reply via email to