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

dahn pushed a commit to branch 22.0.1-fixes
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 95816b44e93341e63688c66da7d0673acb59c953
Author: Abhishek Kumar <[email protected]>
AuthorDate: Thu Jan 15 10:22:28 2026 +0530

    extensions: allow reserved resource details
    
    Adds a new request parameter for create/updateExtension API to allow
    operator to provide detail names for the extension resources which will be 
reserved to be used by the extension. The end user won't be able to view or add 
details with these details names for the resource.
    
    Signed-off-by: Abhishek Kumar <[email protected]>
---
 .../org/apache/cloudstack/api/ApiConstants.java    |   1 +
 .../cloudstack/api/response/ExtensionResponse.java |  10 ++
 .../cloudstack/extension/ExtensionHelper.java      |   3 +
 .../META-INF/db/views/cloud.user_vm_view.sql       |   1 +
 .../extensions/api/CreateExtensionCmd.java         |  10 ++
 .../extensions/api/UpdateExtensionCmd.java         |  10 ++
 .../extensions/manager/ExtensionsManagerImpl.java  |  98 ++++++++++--
 .../extensions/api/CreateExtensionCmdTest.java     |  14 ++
 .../extensions/api/UpdateExtensionCmdTest.java     |  15 ++
 .../manager/ExtensionsManagerImplTest.java         | 167 +++++++++++++++++++--
 .../com/cloud/api/query/dao/UserVmJoinDaoImpl.java |  25 ++-
 .../java/com/cloud/api/query/vo/UserVmJoinVO.java  |   7 +
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  |   9 ++
 .../cloud/api/query/dao/UserVmJoinDaoImplTest.java |   4 +
 ui/public/locales/en.json                          |   1 +
 ui/src/config/section/extension.js                 |   2 +-
 ui/src/views/extension/CreateExtension.vue         |  11 ++
 ui/src/views/extension/UpdateExtension.vue         |  17 ++-
 18 files changed, 371 insertions(+), 34 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 8fca652518f..4fa5f595308 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java
@@ -502,6 +502,7 @@ public class ApiConstants {
     public static final String RECOVER = "recover";
     public static final String REPAIR = "repair";
     public static final String REQUIRES_HVM = "requireshvm";
+    public static final String RESERVED_RESOURCE_DETAILS = 
"reservedresourcedetails";
     public static final String RESOURCES = "resources";
     public static final String RESOURCE_COUNT = "resourcecount";
     public static final String RESOURCE_NAME = "resourcename";
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResponse.java 
b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResponse.java
index fdf1e87df50..911839da405 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResponse.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResponse.java
@@ -85,6 +85,12 @@ public class ExtensionResponse extends BaseResponse {
     @Param(description = "Removal timestamp of the extension, if applicable")
     private Date removed;
 
+    @SerializedName(ApiConstants.RESERVED_RESOURCE_DETAILS)
+    @Param(description = "Resource detail names as comma separated string that 
should be reserved and not visible " +
+                    "to end users",
+            since = "4.22.1")
+    protected String reservedResourceDetails;
+
     public ExtensionResponse(String id, String name, String description, 
String type) {
         this.id = id;
         this.name = name;
@@ -179,4 +185,8 @@ public class ExtensionResponse extends BaseResponse {
     public void setRemoved(Date removed) {
         this.removed = removed;
     }
+
+    public void setReservedResourceDetails(String reservedResourceDetails) {
+        this.reservedResourceDetails = reservedResourceDetails;
+    }
 }
diff --git 
a/api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java 
b/api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java
index f50f841ed74..a01131278a7 100644
--- a/api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java
+++ b/api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java
@@ -17,8 +17,11 @@
 
 package org.apache.cloudstack.extension;
 
+import java.util.List;
+
 public interface ExtensionHelper {
     Long getExtensionIdForCluster(long clusterId);
     Extension getExtension(long id);
     Extension getExtensionForCluster(long clusterId);
+    List<String> getExtensionReservedResourceDetails(long extensionId);
 }
diff --git 
a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql 
b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql
index 94bc8640fd5..34aa59075ac 100644
--- a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql
+++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql
@@ -79,6 +79,7 @@ SELECT
     `vm_template`.`format` AS `template_format`,
     `vm_template`.`display_text` AS `template_display_text`,
     `vm_template`.`enable_password` AS `password_enabled`,
+    `vm_template`.`extension_id` AS `template_extension_id`,
     `iso`.`id` AS `iso_id`,
     `iso`.`uuid` AS `iso_uuid`,
     `iso`.`name` AS `iso_name`,
diff --git 
a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java
 
b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java
index 5ab54149645..9d76a7e6ec2 100644
--- 
a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java
+++ 
b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java
@@ -83,6 +83,12 @@ public class CreateExtensionCmd extends BaseCmd {
             description = "Details in key/value pairs using format 
details[i].keyname=keyvalue. Example: details[0].endpoint.url=urlvalue")
     protected Map details;
 
+    @Parameter(name = ApiConstants.RESERVED_RESOURCE_DETAILS, type = 
CommandType.STRING,
+            description = "Resource detail names as comma separated string 
that should be reserved and not visible " +
+                    "to end users",
+            since = "4.22.1")
+    protected String reservedResourceDetails;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -115,6 +121,10 @@ public class CreateExtensionCmd extends BaseCmd {
         return convertDetailsToMap(details);
     }
 
+    public String getReservedResourceDetails() {
+        return reservedResourceDetails;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
diff --git 
a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java
 
b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java
index ded07d2dd32..5baaea1709d 100644
--- 
a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java
+++ 
b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java
@@ -78,6 +78,12 @@ public class UpdateExtensionCmd extends BaseCmd {
                     "if false or not set, no action)")
     private Boolean cleanupDetails;
 
+    @Parameter(name = ApiConstants.RESERVED_RESOURCE_DETAILS, type = 
CommandType.STRING,
+            description = "Resource detail names as comma separated string 
that should be reserved and not visible " +
+                    "to end users",
+            since = "4.22.1")
+    protected String reservedResourceDetails;
+
     /////////////////////////////////////////////////////
     /////////////////// Accessors ///////////////////////
     /////////////////////////////////////////////////////
@@ -106,6 +112,10 @@ public class UpdateExtensionCmd extends BaseCmd {
         return cleanupDetails;
     }
 
+    public String getReservedResourceDetails() {
+        return reservedResourceDetails;
+    }
+
     /////////////////////////////////////////////////////
     /////////////// API Implementation///////////////////
     /////////////////////////////////////////////////////
diff --git 
a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java
 
b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java
index 4171b9615fe..1422338ddc9 100644
--- 
a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java
+++ 
b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java
@@ -216,6 +216,11 @@ public class ExtensionsManagerImpl extends ManagerBase 
implements ExtensionsMana
     @Inject
     AccountService accountService;
 
+    // Map of in-built extension names and their reserved resource details 
that shouldn't be accessible to end-users
+    protected static final Map<String, List<String>> 
INBUILT_RESERVED_RESOURCE_DETAILS = Map.of(
+            "proxmox", List.of("proxmox_vmid")
+    );
+
     private ScheduledExecutorService extensionPathStateCheckExecutor;
 
     protected String getDefaultExtensionRelativePath(String name) {
@@ -563,6 +568,25 @@ public class ExtensionsManagerImpl extends ManagerBase 
implements ExtensionsMana
         updateExtensionPathReady(extension, true);
     }
 
+    protected void addInbuiltExtensionReservedResourceDetails(long 
extensionId, List<String> reservedResourceDetails) {
+        ExtensionVO vo = extensionDao.findById(extensionId);
+        if (vo == null || vo.isUserDefined()) {
+            return;
+        }
+        String lowerName = 
StringUtils.defaultString(vo.getName()).toLowerCase();
+        Optional<Map.Entry<String, List<String>>> match = 
INBUILT_RESERVED_RESOURCE_DETAILS.entrySet().stream()
+                .filter(e -> lowerName.contains(e.getKey().toLowerCase()))
+                .findFirst();
+        if (match.isPresent()) {
+            Set<String> existing = new HashSet<>(reservedResourceDetails);
+            for (String detailKey : match.get().getValue()) {
+                if (existing.add(detailKey)) {
+                    reservedResourceDetails.add(detailKey);
+                }
+            }
+        }
+    }
+
     @Override
     public String getExtensionsPath() {
         return externalProvisioner.getExtensionsPath();
@@ -577,6 +601,7 @@ public class ExtensionsManagerImpl extends ManagerBase 
implements ExtensionsMana
         String relativePath = cmd.getPath();
         final Boolean orchestratorRequiresPrepareVm = 
cmd.isOrchestratorRequiresPrepareVm();
         final String stateStr = cmd.getState();
+        final String reservedResourceDetails = 
cmd.getReservedResourceDetails();
         ExtensionVO extensionByName = extensionDao.findByName(name);
         if (extensionByName != null) {
             throw new CloudRuntimeException("Extension by name already 
exists");
@@ -624,6 +649,10 @@ public class ExtensionsManagerImpl extends ManagerBase 
implements ExtensionsMana
                         ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM, 
String.valueOf(orchestratorRequiresPrepareVm),
                         false));
             }
+            if (StringUtils.isNotBlank(reservedResourceDetails)) {
+                detailsVOList.add(new ExtensionDetailsVO(extension.getId(),
+                        ApiConstants.RESERVED_RESOURCE_DETAILS, 
reservedResourceDetails, false));
+            }
             if (CollectionUtils.isNotEmpty(detailsVOList)) {
                 extensionDetailsDao.saveDetails(detailsVOList);
             }
@@ -704,6 +733,7 @@ public class ExtensionsManagerImpl extends ManagerBase 
implements ExtensionsMana
         final String stateStr = cmd.getState();
         final Map<String, String> details = cmd.getDetails();
         final Boolean cleanupDetails = cmd.isCleanupDetails();
+        final String reservedResourceDetails = 
cmd.getReservedResourceDetails();
         final ExtensionVO extensionVO = extensionDao.findById(id);
         if (extensionVO == null) {
             throw new InvalidParameterValueException("Failed to find the 
extension");
@@ -732,7 +762,8 @@ public class ExtensionsManagerImpl extends ManagerBase 
implements ExtensionsMana
                 throw new CloudRuntimeException(String.format("Failed to 
updated the extension: %s",
                         extensionVO.getName()));
             }
-            updateExtensionsDetails(cleanupDetails, details, 
orchestratorRequiresPrepareVm, id);
+            updateExtensionsDetails(cleanupDetails, details, 
orchestratorRequiresPrepareVm, reservedResourceDetails,
+                    id);
             return extensionVO;
         });
         if (StringUtils.isNotBlank(stateStr)) {
@@ -748,9 +779,11 @@ public class ExtensionsManagerImpl extends ManagerBase 
implements ExtensionsMana
         return result;
     }
 
-    protected void updateExtensionsDetails(Boolean cleanupDetails, Map<String, 
String> details, Boolean orchestratorRequiresPrepareVm, long id) {
+    protected void updateExtensionsDetails(Boolean cleanupDetails, Map<String, 
String> details,
+               Boolean orchestratorRequiresPrepareVm, String 
reservedResourceDetails, long id) {
         final boolean needToUpdateAllDetails = 
Boolean.TRUE.equals(cleanupDetails) || MapUtils.isNotEmpty(details);
-        if (!needToUpdateAllDetails && orchestratorRequiresPrepareVm == null) {
+        if (!needToUpdateAllDetails && orchestratorRequiresPrepareVm == null &&
+                StringUtils.isBlank(reservedResourceDetails)) {
             return;
         }
         if (needToUpdateAllDetails) {
@@ -761,6 +794,9 @@ public class ExtensionsManagerImpl extends ManagerBase 
implements ExtensionsMana
                 
hiddenDetails.put(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM,
                         String.valueOf(orchestratorRequiresPrepareVm));
             }
+            if (StringUtils.isNotBlank(reservedResourceDetails)) {
+                hiddenDetails.put(ApiConstants.RESERVED_RESOURCE_DETAILS, 
reservedResourceDetails);
+            }
             if (MapUtils.isNotEmpty(hiddenDetails)) {
                 hiddenDetails.forEach((key, value) -> detailsVOList.add(
                         new ExtensionDetailsVO(id, key, value, false)));
@@ -775,15 +811,29 @@ public class ExtensionsManagerImpl extends ManagerBase 
implements ExtensionsMana
                 extensionDetailsDao.removeDetails(id);
             }
         } else {
-            ExtensionDetailsVO detailsVO = extensionDetailsDao.findDetail(id,
-                    ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM);
-            if (detailsVO == null) {
-                extensionDetailsDao.persist(new ExtensionDetailsVO(id,
-                        ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM,
-                        String.valueOf(orchestratorRequiresPrepareVm), false));
-            } else if (Boolean.parseBoolean(detailsVO.getValue()) != 
orchestratorRequiresPrepareVm) {
-                
detailsVO.setValue(String.valueOf(orchestratorRequiresPrepareVm));
-                extensionDetailsDao.update(detailsVO.getId(), detailsVO);
+            if (orchestratorRequiresPrepareVm != null) {
+                ExtensionDetailsVO detailsVO = 
extensionDetailsDao.findDetail(id,
+                        ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM);
+                if (detailsVO == null) {
+                    extensionDetailsDao.persist(new ExtensionDetailsVO(id,
+                            ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM,
+                            String.valueOf(orchestratorRequiresPrepareVm), 
false));
+                } else if (Boolean.parseBoolean(detailsVO.getValue()) != 
orchestratorRequiresPrepareVm) {
+                    
detailsVO.setValue(String.valueOf(orchestratorRequiresPrepareVm));
+                    extensionDetailsDao.update(detailsVO.getId(), detailsVO);
+                }
+            }
+            if (StringUtils.isNotBlank(reservedResourceDetails)) {
+                ExtensionDetailsVO detailsVO = 
extensionDetailsDao.findDetail(id,
+                        ApiConstants.RESERVED_RESOURCE_DETAILS);
+                if (detailsVO == null) {
+                    extensionDetailsDao.persist(new ExtensionDetailsVO(id,
+                            ApiConstants.RESERVED_RESOURCE_DETAILS,
+                            reservedResourceDetails, false));
+                } else if 
(!reservedResourceDetails.equals(detailsVO.getValue())) {
+                    detailsVO.setValue(reservedResourceDetails);
+                    extensionDetailsDao.update(detailsVO.getId(), detailsVO);
+                }
             }
         }
     }
@@ -961,12 +1011,16 @@ public class ExtensionsManagerImpl extends ManagerBase 
implements ExtensionsMana
             hiddenDetails = extensionDetails.second();
         } else {
             hiddenDetails = 
extensionDetailsDao.listDetailsKeyPairs(extension.getId(),
-                    List.of(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM));
+                    List.of(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM,
+                            ApiConstants.RESERVED_RESOURCE_DETAILS));
         }
         if 
(hiddenDetails.containsKey(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM)) {
             response.setOrchestratorRequiresPrepareVm(Boolean.parseBoolean(
                     
hiddenDetails.get(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM)));
         }
+        if (hiddenDetails.containsKey(ApiConstants.RESERVED_RESOURCE_DETAILS)) 
{
+            
response.setReservedResourceDetails(hiddenDetails.get(ApiConstants.RESERVED_RESOURCE_DETAILS));
+        }
         response.setObjectName(Extension.class.getSimpleName().toLowerCase());
         return response;
     }
@@ -1605,6 +1659,24 @@ public class ExtensionsManagerImpl extends ManagerBase 
implements ExtensionsMana
         return extensionDao.findById(extensionId);
     }
 
+    @Override
+    public List<String> getExtensionReservedResourceDetails(long extensionId) {
+        ExtensionDetailsVO detailsVO = 
extensionDetailsDao.findDetail(extensionId,
+                ApiConstants.RESERVED_RESOURCE_DETAILS);
+        if (detailsVO == null || 
!StringUtils.isNotBlank(detailsVO.getValue())) {
+            return Collections.emptyList();
+        }
+        List<String> reservedDetails = new ArrayList<>();
+        String[] parts = detailsVO.getValue().split(",");
+        for (String part : parts) {
+            if (StringUtils.isNotBlank(part)) {
+                reservedDetails.add(part.trim());
+            }
+        }
+        addInbuiltExtensionReservedResourceDetails(extensionId, 
reservedDetails);
+        return reservedDetails;
+    }
+
     @Override
     public boolean start() {
         long pathStateCheckInterval = PathStateCheckInterval.value();
diff --git 
a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmdTest.java
 
b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmdTest.java
index 2edb6ea48e3..2f630966056 100644
--- 
a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmdTest.java
+++ 
b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmdTest.java
@@ -94,4 +94,18 @@ public class CreateExtensionCmdTest {
         setField(cmd, "details", details);
         assertTrue(MapUtils.isNotEmpty(cmd.getDetails()));
     }
+
+    @Test
+    public void getReservedResourceDetailsReturnsValueWhenSet() {
+        setField(cmd, "reservedResourceDetails", "detail1,detail2,detail3");
+        String result = cmd.getReservedResourceDetails();
+        assertEquals("detail1,detail2,detail3", result);
+    }
+
+    @Test
+    public void getReservedResourceDetailsReturnsNullWhenNotSet() {
+        setField(cmd, "reservedResourceDetails", null);
+        String result = cmd.getReservedResourceDetails();
+        assertNull(result);
+    }
 }
diff --git 
a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmdTest.java
 
b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmdTest.java
index f0a3a6fcf21..5c5c2014a52 100644
--- 
a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmdTest.java
+++ 
b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmdTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.springframework.test.util.ReflectionTestUtils.setField;
 
 import java.util.EnumSet;
 import java.util.HashMap;
@@ -134,6 +135,20 @@ public class UpdateExtensionCmdTest {
         assertTrue(cmd.isCleanupDetails());
     }
 
+    @Test
+    public void getReservedResourceDetailsReturnsValueWhenSet() {
+        setField(cmd, "reservedResourceDetails", "detail1,detail2,detail3");
+        String result = cmd.getReservedResourceDetails();
+        assertEquals("detail1,detail2,detail3", result);
+    }
+
+    @Test
+    public void getReservedResourceDetailsReturnsNullWhenNotSet() {
+        setField(cmd, "reservedResourceDetails", null);
+        String result = cmd.getReservedResourceDetails();
+        assertNull(result);
+    }
+
     @Test
     public void executeSetsExtensionResponseWhenManagerSucceeds() {
         Extension extension = mock(Extension.class);
diff --git 
a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImplTest.java
 
b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImplTest.java
index 085ae212b28..ff3fce06b00 100644
--- 
a/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImplTest.java
+++ 
b/framework/extensions/src/test/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImplTest.java
@@ -23,11 +23,13 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
@@ -40,6 +42,7 @@ import static org.mockito.Mockito.when;
 
 import java.io.File;
 import java.security.InvalidParameterException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
@@ -49,8 +52,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-import com.cloud.exception.PermissionDeniedException;
-import com.cloud.user.AccountService;
 import org.apache.cloudstack.acl.Role;
 import org.apache.cloudstack.acl.RoleService;
 import org.apache.cloudstack.acl.RoleType;
@@ -85,9 +86,11 @@ import 
org.apache.cloudstack.framework.extensions.dao.ExtensionResourceMapDao;
 import 
org.apache.cloudstack.framework.extensions.dao.ExtensionResourceMapDetailsDao;
 import 
org.apache.cloudstack.framework.extensions.vo.ExtensionCustomActionDetailsVO;
 import org.apache.cloudstack.framework.extensions.vo.ExtensionCustomActionVO;
+import org.apache.cloudstack.framework.extensions.vo.ExtensionDetailsVO;
 import org.apache.cloudstack.framework.extensions.vo.ExtensionResourceMapVO;
 import org.apache.cloudstack.framework.extensions.vo.ExtensionVO;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
+import org.apache.commons.collections.CollectionUtils;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -113,6 +116,7 @@ import com.cloud.dc.dao.ClusterDao;
 import com.cloud.exception.AgentUnavailableException;
 import com.cloud.exception.InvalidParameterValueException;
 import com.cloud.exception.OperationTimedoutException;
+import com.cloud.exception.PermissionDeniedException;
 import com.cloud.host.Host;
 import com.cloud.host.dao.HostDao;
 import com.cloud.host.dao.HostDetailsDao;
@@ -122,6 +126,7 @@ import com.cloud.org.Cluster;
 import com.cloud.serializer.GsonHelper;
 import com.cloud.storage.dao.VMTemplateDao;
 import com.cloud.user.Account;
+import com.cloud.user.AccountService;
 import com.cloud.utils.Pair;
 import com.cloud.utils.UuidUtils;
 import com.cloud.utils.db.EntityManager;
@@ -664,6 +669,8 @@ public class ExtensionsManagerImplTest {
         when(cmd.getPath()).thenReturn(null);
         when(cmd.isOrchestratorRequiresPrepareVm()).thenReturn(null);
         when(cmd.getState()).thenReturn(null);
+        String reservedResourceDetails = "abc,xyz";
+        
when(cmd.getReservedResourceDetails()).thenReturn(reservedResourceDetails);
         when(extensionDao.findByName("ext1")).thenReturn(null);
         when(extensionDao.persist(any())).thenAnswer(inv -> {
             ExtensionVO extensionVO = inv.getArgument(0);
@@ -671,11 +678,20 @@ public class ExtensionsManagerImplTest {
             return extensionVO;
         });
         
when(managementServerHostDao.listBy(any())).thenReturn(Collections.emptyList());
-
+        List<ExtensionDetailsVO> detailsList = new ArrayList<>();
+        doAnswer(inv -> {
+            List<ExtensionDetailsVO> detailsVO = inv.getArgument(0);
+            detailsList.addAll(detailsVO);
+            return null;
+        }).when(extensionDetailsDao).saveDetails(anyList());
         Extension ext = extensionsManager.createExtension(cmd);
 
         assertEquals("ext1", ext.getName());
         verify(extensionDao).persist(any());
+        assertTrue(CollectionUtils.isNotEmpty(detailsList));
+        assertTrue(detailsList.stream()
+                .anyMatch(detail -> 
ApiConstants.RESERVED_RESOURCE_DETAILS.equals(detail.getName())
+                    && reservedResourceDetails.equals(detail.getValue())));
     }
 
     @Test
@@ -938,14 +954,32 @@ public class ExtensionsManagerImplTest {
     public void updateExtensionsDetails_SavesDetails_WhenDetailsProvided() {
         long extensionId = 10L;
         Map<String, String> details = Map.of("foo", "bar", "baz", "qux");
-        extensionsManager.updateExtensionsDetails(false, details, null, 
extensionId);
+        extensionsManager.updateExtensionsDetails(false, details, null, null, 
extensionId);
         verify(extensionDetailsDao).saveDetails(any());
     }
 
+    @Test
+    public void updateExtensionsDetails_PersistReservedDetail_WhenProvided() {
+        long extensionId = 10L;
+        
when(extensionDetailsDao.persist(any())).thenReturn(mock(ExtensionDetailsVO.class));
+        extensionsManager.updateExtensionsDetails(false, null, null, 
"abc,xyz", extensionId);
+        verify(extensionDetailsDao).persist(any());
+    }
+
+    @Test
+    public void updateExtensionsDetails_UpdateReservedDetail_WhenProvided() {
+        long extensionId = 10L;
+        when(extensionDetailsDao.findDetail(anyLong(), 
eq(ApiConstants.RESERVED_RESOURCE_DETAILS)))
+                .thenReturn(mock(ExtensionDetailsVO.class));
+        when(extensionDetailsDao.update(anyLong(), any())).thenReturn(true);
+        extensionsManager.updateExtensionsDetails(false, null, null, 
"abc,xyz", extensionId);
+        verify(extensionDetailsDao).update(anyLong(), any());
+    }
+
     @Test
     public void 
updateExtensionsDetails_DoesNothing_WhenDetailsAndCleanupAreNull() {
         long extensionId = 11L;
-        extensionsManager.updateExtensionsDetails(null, null, null, 
extensionId);
+        extensionsManager.updateExtensionsDetails(null, null, null, null, 
extensionId);
         verify(extensionDetailsDao, never()).removeDetails(anyLong());
         verify(extensionDetailsDao, never()).saveDetails(any());
     }
@@ -953,7 +987,7 @@ public class ExtensionsManagerImplTest {
     @Test
     public void updateExtensionsDetails_RemovesDetailsOnly_WhenCleanupIsTrue() 
{
         long extensionId = 12L;
-        extensionsManager.updateExtensionsDetails(true, null, null, 
extensionId);
+        extensionsManager.updateExtensionsDetails(true, null, null, null, 
extensionId);
         verify(extensionDetailsDao).removeDetails(extensionId);
         verify(extensionDetailsDao, never()).saveDetails(any());
     }
@@ -961,7 +995,7 @@ public class ExtensionsManagerImplTest {
     @Test
     public void 
updateExtensionsDetails_PersistsOrchestratorFlag_WhenFlagIsNotNull() {
         long extensionId = 13L;
-        extensionsManager.updateExtensionsDetails(false, null, true, 
extensionId);
+        extensionsManager.updateExtensionsDetails(false, null, true, null, 
extensionId);
         verify(extensionDetailsDao).persist(any());
     }
 
@@ -970,7 +1004,7 @@ public class ExtensionsManagerImplTest {
         long extensionId = 14L;
         Map<String, String> details = Map.of("foo", "bar");
         
doThrow(CloudRuntimeException.class).when(extensionDetailsDao).saveDetails(any());
-        extensionsManager.updateExtensionsDetails(false, details, null, 
extensionId);
+        extensionsManager.updateExtensionsDetails(false, details, null, null, 
extensionId);
     }
 
     @Test
@@ -1161,7 +1195,8 @@ public class ExtensionsManagerImplTest {
         
when(externalProvisioner.getExtensionPath("entry2.sh")).thenReturn("/some/path/entry2.sh");
 
         Map<String, String> hiddenDetails = 
Map.of(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM, "false");
-        when(extensionDetailsDao.listDetailsKeyPairs(2L, 
List.of(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM)))
+        when(extensionDetailsDao.listDetailsKeyPairs(2L, List.of(
+                ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM, 
ApiConstants.RESERVED_RESOURCE_DETAILS)))
                 .thenReturn(hiddenDetails);
 
         EnumSet<ApiConstants.ExtensionDetails> viewDetails = 
EnumSet.noneOf(ApiConstants.ExtensionDetails.class);
@@ -2069,4 +2104,118 @@ public class ExtensionsManagerImplTest {
         }
     }
 
+    @Test
+    public void 
getExtensionReservedResourceDetailsReturnsEmptyListWhenDetailsNotFound() {
+        long extensionId = 1L;
+        when(extensionDetailsDao.findDetail(extensionId, 
ApiConstants.RESERVED_RESOURCE_DETAILS)).thenReturn(null);
+
+        List<String> result = 
extensionsManager.getExtensionReservedResourceDetails(extensionId);
+
+        assertNotNull(result);
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void 
getExtensionReservedResourceDetailsReturnsEmptyListWhenValueIsBlank() {
+        long extensionId = 2L;
+        ExtensionDetailsVO detailsVO = mock(ExtensionDetailsVO.class);
+        when(detailsVO.getValue()).thenReturn("   ");
+        when(extensionDetailsDao.findDetail(extensionId, 
ApiConstants.RESERVED_RESOURCE_DETAILS)).thenReturn(detailsVO);
+
+        List<String> result = 
extensionsManager.getExtensionReservedResourceDetails(extensionId);
+
+        assertNotNull(result);
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void 
getExtensionReservedResourceDetailsReturnsListOfTrimmedDetails() {
+        long extensionId = 3L;
+        ExtensionDetailsVO detailsVO = mock(ExtensionDetailsVO.class);
+        when(detailsVO.getValue()).thenReturn(" detail1 , detail2,detail3 ");
+        when(extensionDetailsDao.findDetail(extensionId, 
ApiConstants.RESERVED_RESOURCE_DETAILS)).thenReturn(detailsVO);
+
+        List<String> result = 
extensionsManager.getExtensionReservedResourceDetails(extensionId);
+
+        assertNotNull(result);
+        assertEquals(3, result.size());
+        assertEquals("detail1", result.get(0));
+        assertEquals("detail2", result.get(1));
+        assertEquals("detail3", result.get(2));
+    }
+
+    @Test
+    public void 
getExtensionReservedResourceDetailsHandlesEmptyPartsGracefully() {
+        long extensionId = 4L;
+        ExtensionDetailsVO detailsVO = mock(ExtensionDetailsVO.class);
+        when(detailsVO.getValue()).thenReturn("detail1,,detail2, ,detail3");
+        when(extensionDetailsDao.findDetail(extensionId, 
ApiConstants.RESERVED_RESOURCE_DETAILS)).thenReturn(detailsVO);
+
+        List<String> result = 
extensionsManager.getExtensionReservedResourceDetails(extensionId);
+
+        assertNotNull(result);
+        assertEquals(3, result.size());
+        assertEquals("detail1", result.get(0));
+        assertEquals("detail2", result.get(1));
+        assertEquals("detail3", result.get(2));
+    }
+
+    @Test
+    public void 
getExtensionReservedResourceDetailsReturnsEmptyListWhenSplitResultsInNoParts() {
+        long extensionId = 5L;
+        ExtensionDetailsVO detailsVO = mock(ExtensionDetailsVO.class);
+        when(detailsVO.getValue()).thenReturn(",");
+        when(extensionDetailsDao.findDetail(extensionId, 
ApiConstants.RESERVED_RESOURCE_DETAILS)).thenReturn(detailsVO);
+
+        List<String> result = 
extensionsManager.getExtensionReservedResourceDetails(extensionId);
+
+        assertNotNull(result);
+        assertTrue(result.isEmpty());
+    }
+
+    @Test
+    public void 
addInbuiltExtensionReservedResourceDetailsDoesNothingWhenExtensionNotFound() {
+        when(extensionDao.findById(1L)).thenReturn(null);
+        List<String> reservedResourceDetails = new ArrayList<>();
+        extensionsManager.addInbuiltExtensionReservedResourceDetails(1L, 
reservedResourceDetails);
+        assertTrue(reservedResourceDetails.isEmpty());
+    }
+
+    @Test
+    public void 
addInbuiltExtensionReservedResourceDetailsDoesNothingForUserDefinedExtension() {
+        ExtensionVO extension = mock(ExtensionVO.class);
+        when(extension.isUserDefined()).thenReturn(true);
+        when(extensionDao.findById(2L)).thenReturn(extension);
+        List<String> reservedResourceDetails = new ArrayList<>();
+        reservedResourceDetails.add("existing-detail");
+        extensionsManager.addInbuiltExtensionReservedResourceDetails(2L, 
reservedResourceDetails);
+        assertEquals(1, reservedResourceDetails.size());
+        assertTrue(reservedResourceDetails.contains("existing-detail"));
+    }
+
+    @Test
+    public void 
addInbuiltExtensionReservedResourceDetailsDoesNothingWhenNoMatchFound() {
+        ExtensionVO extension = mock(ExtensionVO.class);
+        when(extension.isUserDefined()).thenReturn(false);
+        when(extension.getName()).thenReturn("no-such-inbuilt-key-expected");
+        when(extensionDao.findById(3L)).thenReturn(extension);
+        List<String> reservedResourceDetails = new ArrayList<>();
+        extensionsManager.addInbuiltExtensionReservedResourceDetails(3L, 
reservedResourceDetails);
+        assertTrue(reservedResourceDetails.isEmpty());
+    }
+
+    @Test
+    public void addInbuiltExtensionReservedResourceDetailsAddedDetails() {
+        ExtensionVO extension = mock(ExtensionVO.class);
+        when(extension.isUserDefined()).thenReturn(false);
+        Map.Entry<String, List<String>> entry =
+                
ExtensionsManagerImpl.INBUILT_RESERVED_RESOURCE_DETAILS.entrySet().iterator().next();
+        when(extension.getName()).thenReturn(entry.getKey());
+        when(extensionDao.findById(3L)).thenReturn(extension);
+        List<String> reservedResourceDetails = new ArrayList<>();
+        extensionsManager.addInbuiltExtensionReservedResourceDetails(3L, 
reservedResourceDetails);
+        assertFalse(reservedResourceDetails.isEmpty());
+        assertEquals(reservedResourceDetails.size(), entry.getValue().size());
+        assertTrue(reservedResourceDetails.containsAll(entry.getValue()));
+    }
 }
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
index a2f9544de39..84b259a89a0 100644
--- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java
@@ -17,14 +17,13 @@
 package com.cloud.api.query.dao;
 
 import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.time.LocalDate;
 import java.time.ZoneId;
 import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Collections;
 import java.util.Date;
-
 import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
@@ -34,8 +33,6 @@ import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
-import com.cloud.gpu.dao.VgpuProfileDao;
-import com.cloud.service.dao.ServiceOfferingDao;
 import org.apache.cloudstack.affinity.AffinityGroupResponse;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
@@ -49,6 +46,7 @@ import 
org.apache.cloudstack.api.response.SecurityGroupResponse;
 import org.apache.cloudstack.api.response.UserVmResponse;
 import org.apache.cloudstack.api.response.VnfNicResponse;
 import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.extension.ExtensionHelper;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.query.QueryService;
 import org.apache.cloudstack.vm.lease.VMLeaseManager;
@@ -61,11 +59,13 @@ import com.cloud.api.ApiDBUtils;
 import com.cloud.api.ApiResponseHelper;
 import com.cloud.api.query.vo.UserVmJoinVO;
 import com.cloud.gpu.GPU;
+import com.cloud.gpu.dao.VgpuProfileDao;
 import com.cloud.host.ControlState;
 import com.cloud.network.IpAddress;
 import com.cloud.network.vpc.VpcVO;
 import com.cloud.network.vpc.dao.VpcDao;
 import com.cloud.service.ServiceOfferingDetailsVO;
+import com.cloud.service.dao.ServiceOfferingDao;
 import com.cloud.storage.DiskOfferingVO;
 import com.cloud.storage.GuestOS;
 import com.cloud.storage.Storage.TemplateType;
@@ -92,7 +92,6 @@ import com.cloud.vm.VirtualMachine.State;
 import com.cloud.vm.VmStats;
 import com.cloud.vm.dao.NicExtraDhcpOptionDao;
 import com.cloud.vm.dao.NicSecondaryIpVO;
-
 import com.cloud.vm.dao.VMInstanceDetailsDao;
 
 @Component
@@ -124,6 +123,8 @@ public class UserVmJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<UserVmJo
     private ServiceOfferingDao serviceOfferingDao;
     @Inject
     private VgpuProfileDao vgpuProfileDao;
+    @Inject
+    ExtensionHelper extensionHelper;
 
     private final SearchBuilder<UserVmJoinVO> VmDetailSearch;
     private final SearchBuilder<UserVmJoinVO> activeVmByIsoSearch;
@@ -456,7 +457,16 @@ public class UserVmJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<UserVmJo
 
             // Remove deny listed settings if user is not admin
             if (caller.getType() != Account.Type.ADMIN) {
-                String[] userVmSettingsToHide = 
QueryService.UserVMDeniedDetails.value().split(",");
+                List<String> userVmSettingsToHide = new ArrayList<>();
+                String[] parts = 
QueryService.UserVMDeniedDetails.value().split(",");
+                if (parts.length > 0) {
+                    Collections.addAll(userVmSettingsToHide, parts);
+                }
+                if (userVm.getTemplateExtensionId() != null) {
+                    
userVmSettingsToHide.addAll(extensionHelper.getExtensionReservedResourceDetails(
+                            userVm.getTemplateExtensionId()));
+                }
+
                 for (String key : userVmSettingsToHide) {
                     resourceDetails.remove(key.trim());
                 }
@@ -512,7 +522,6 @@ public class UserVmJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<UserVmJo
         return userVmResponse;
     }
 
-
     private long computeLeaseDurationFromExpiryDate(Date created, Date 
leaseExpiryDate) {
         LocalDate createdDate = 
created.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
         LocalDate expiryDate = 
leaseExpiryDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java 
b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java
index eab34081d51..d5677ef032d 100644
--- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java
+++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java
@@ -207,6 +207,9 @@ public class UserVmJoinVO extends 
BaseViewWithTagInformationVO implements Contro
     @Column(name = "template_format")
     private Storage.ImageFormat templateFormat;
 
+    @Column(name = "template_extension_id")
+    private Long templateExtensionId;
+
     @Column(name = "password_enabled")
     private boolean passwordEnabled;
 
@@ -709,6 +712,10 @@ public class UserVmJoinVO extends 
BaseViewWithTagInformationVO implements Contro
         return templateFormat;
     }
 
+    public Long getTemplateExtensionId() {
+        return templateExtensionId;
+    }
+
     public boolean isPasswordEnabled() {
         return passwordEnabled;
     }
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java 
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 24baf538394..01f8558658e 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -126,6 +126,7 @@ import 
org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService;
 import 
org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult;
+import org.apache.cloudstack.extension.ExtensionHelper;
 import org.apache.cloudstack.framework.async.AsyncCallFuture;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
@@ -632,6 +633,9 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
     @Inject
     SnapshotDataFactory snapshotDataFactory;
 
+    @Inject
+    ExtensionHelper extensionHelper;
+
     private ScheduledExecutorService _executor = null;
     private ScheduledExecutorService _vmIpFetchExecutor = null;
     private int _expungeInterval;
@@ -2907,6 +2911,11 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                 .map(item -> (item).trim())
                 .collect(Collectors.toList());
         userDenyListedSettings.addAll(QueryService.RootAdminOnlyVmSettings);
+        if (template != null && template.getExtensionId() != null) {
+            
userDenyListedSettings.addAll(extensionHelper.getExtensionReservedResourceDetails(
+                    template.getExtensionId()));
+        }
+
         final List<String> userReadOnlySettings = 
Stream.of(QueryService.UserVMReadOnlyDetails.value().split(","))
                 .map(item -> (item).trim())
                 .collect(Collectors.toList());
diff --git 
a/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java 
b/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java
index 14074add021..7dc3a486779 100755
--- a/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java
+++ b/server/src/test/java/com/cloud/api/query/dao/UserVmJoinDaoImplTest.java
@@ -26,6 +26,7 @@ import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ResponseObject;
 import org.apache.cloudstack.api.response.UserVmResponse;
+import org.apache.cloudstack.extension.ExtensionHelper;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -78,6 +79,9 @@ public class UserVmJoinDaoImplTest extends 
GenericDaoBaseWithTagInformationBaseT
     @Mock
     private VnfTemplateDetailsDao vnfTemplateDetailsDao;
 
+    @Mock
+    ExtensionHelper extensionHelper;
+
     private UserVmJoinVO userVm = new UserVmJoinVO();
     private UserVmResponse userVmResponse = new UserVmResponse();
 
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 4f450e940fc..00078c0a2b5 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -2087,6 +2087,7 @@
 "label.requireshvm": "HVM",
 "label.requiresupgrade": "Requires upgrade",
 "label.reserved": "Reserved",
+"label.reservedresourcedetails": "Reserved resource details",
 "label.reserved.system.gateway": "Reserved system gateway",
 "label.reserved.system.ip": "Reserved system IP",
 "label.reserved.system.netmask": "Reserved system netmask",
diff --git a/ui/src/config/section/extension.js 
b/ui/src/config/section/extension.js
index 4c6d9ebf076..5904abae30b 100644
--- a/ui/src/config/section/extension.js
+++ b/ui/src/config/section/extension.js
@@ -44,7 +44,7 @@ export default {
       }, 'created']
     return fields
   },
-  details: ['name', 'description', 'id', 'type', 'details', 'path', 
'pathready', 'isuserdefined', 'orchestratorrequirespreparevm', 'created'],
+  details: ['name', 'description', 'id', 'type', 'details', 'path', 
'pathready', 'isuserdefined', 'orchestratorrequirespreparevm', 
'reservedresourcedetails', 'created'],
   filters: ['orchestrator'],
   tabs: [{
     name: 'details',
diff --git a/ui/src/views/extension/CreateExtension.vue 
b/ui/src/views/extension/CreateExtension.vue
index 9a7e0c8ae76..6cbd934a1f2 100644
--- a/ui/src/views/extension/CreateExtension.vue
+++ b/ui/src/views/extension/CreateExtension.vue
@@ -89,6 +89,14 @@
         <details-input
           v-model:value="form.details" />
       </a-form-item>
+      <a-form-item name="reservedresourcedetails" 
ref="reservedresourcedetails">
+        <template #label>
+          <tooltip-label :title="$t('label.reservedresourcedetails')" 
:tooltip="apiParams.reservedresourcedetails.description"/>
+        </template>
+        <a-input
+          v-model:value="form.reservedresourcedetails"
+          :placeholder="apiParams.reservedresourcedetails.description" />
+      </a-form-item>
       <a-form-item name="state" ref="state">
         <template #label>
           <tooltip-label :title="$t('label.enabled')" 
:tooltip="apiParams.state.description"/>
@@ -201,6 +209,9 @@ export default {
             params['details[0].' + key] = value
           })
         }
+        if (values.reservedresourcedetails) {
+          params.reservedresourcedetails = values.reservedresourcedetails
+        }
         postAPI('createExtension', params).then(response => {
           this.$emit('refresh-data')
           this.$notification.success({
diff --git a/ui/src/views/extension/UpdateExtension.vue 
b/ui/src/views/extension/UpdateExtension.vue
index 192a339b43a..0926c6268b6 100644
--- a/ui/src/views/extension/UpdateExtension.vue
+++ b/ui/src/views/extension/UpdateExtension.vue
@@ -46,6 +46,14 @@
         <details-input
           v-model:value="form.details" />
       </a-form-item>
+      <a-form-item name="reservedresourcedetails" 
ref="reservedresourcedetails">
+        <template #label>
+          <tooltip-label :title="$t('label.reservedresourcedetails')" 
:tooltip="apiParams.reservedresourcedetails.description"/>
+        </template>
+        <a-input
+          v-model:value="form.reservedresourcedetails"
+          :placeholder="apiParams.reservedresourcedetails.description" />
+      </a-form-item>
       <div :span="24" class="action-button">
         <a-button @click="closeAction">{{ $t('label.cancel') }}</a-button>
         <a-button :loading="loading" ref="submit" type="primary" 
@click="handleSubmit">{{ $t('label.ok') }}</a-button>
@@ -90,13 +98,16 @@ export default {
       this.form = reactive({
         description: this.resource.description,
         details: this.resource.details,
-        orchestratorrequirespreparevm: 
this.resource.orchestratorrequirespreparevm
+        orchestratorrequirespreparevm: 
this.resource.orchestratorrequirespreparevm,
+        reservedresourcedetails: this.resource.reservedresourcedetails
       })
     },
     fetchData () {
       this.loading = true
       getAPI('listExtensions', { id: this.resource.id }).then(json => {
-        this.form.details = 
json?.listextensionsresponse?.extension?.[0]?.details
+        const ext = json?.listextensionsresponse?.extension?.[0] || {}
+        this.form.details = ext.details
+        this.form.reservedresourcedetails = ext.reservedresourcedetails
       }).finally(() => {
         this.loading = false
       })
@@ -110,7 +121,7 @@ export default {
         const params = {
           id: this.resource.id
         }
-        const keys = ['description', 'orchestratorrequirespreparevm']
+        const keys = ['description', 'orchestratorrequirespreparevm', 
'reservedresourcedetails']
         for (const key of keys) {
           if (values[key] !== undefined || values[key] !== null) {
             params[key] = values[key]

Reply via email to