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

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


The following commit(s) were added to refs/heads/main by this push:
     new ac2242ece23 api,server,ui: support tags for domains (#11964)
ac2242ece23 is described below

commit ac2242ece23517b7359056779438b32131529f3c
Author: Abhishek Kumar <[email protected]>
AuthorDate: Tue Jan 27 14:37:51 2026 +0530

    api,server,ui: support tags for domains (#11964)
    
    * api,server,ui: support tags for domains
    
    Fixes #11608
    
    Signed-off-by: Abhishek Kumar <[email protected]>
    
    * address copilot comment
    
    Signed-off-by: Abhishek Kumar <[email protected]>
    
    * fix import
    
    Signed-off-by: Abhishek Kumar <[email protected]>
    
    * Added tags support to listDomains API
    
    ---------
    
    Signed-off-by: Abhishek Kumar <[email protected]>
    Co-authored-by: Harikrishna Patnala <[email protected]>
---
 .../main/java/com/cloud/server/ResourceTag.java    | 10 +++---
 .../api/command/admin/domain/ListDomainsCmd.java   |  4 +--
 .../cloudstack/api/response/DomainResponse.java    | 18 ++++++----
 .../main/java/com/cloud/api/ApiResponseHelper.java | 41 +++++++++++++++-------
 .../java/com/cloud/api/query/QueryManagerImpl.java | 29 +++++++++++++++
 .../com/cloud/api/query/dao/DomainJoinDaoImpl.java | 10 +++---
 ui/src/components/view/InfoCard.vue                |  2 +-
 7 files changed, 81 insertions(+), 33 deletions(-)

diff --git a/api/src/main/java/com/cloud/server/ResourceTag.java 
b/api/src/main/java/com/cloud/server/ResourceTag.java
index b3026deceff..32305753f1a 100644
--- a/api/src/main/java/com/cloud/server/ResourceTag.java
+++ b/api/src/main/java/com/cloud/server/ResourceTag.java
@@ -16,14 +16,14 @@
 // under the License.
 package com.cloud.server;
 
-import org.apache.cloudstack.acl.ControlledEntity;
-import org.apache.cloudstack.api.Identity;
-import org.apache.cloudstack.api.InternalIdentity;
-
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
 
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.api.Identity;
+import org.apache.cloudstack.api.InternalIdentity;
+
 public interface ResourceTag extends ControlledEntity, Identity, 
InternalIdentity {
 
     // FIXME - extract enum to another interface as its used both by 
resourceTags and resourceMetaData code
@@ -70,7 +70,7 @@ public interface ResourceTag extends ControlledEntity, 
Identity, InternalIdentit
         GuestOs(false, true),
         NetworkOffering(false, true),
         VpcOffering(true, false),
-        Domain(false, false, true),
+        Domain(true, false, true),
         ObjectStore(false, false, true);
 
 
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java
index 5c5a92c45ca..aa197804226 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java
@@ -23,7 +23,7 @@ import java.util.List;
 import org.apache.cloudstack.api.APICommand;
 import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.ApiConstants.DomainDetails;
-import org.apache.cloudstack.api.BaseListCmd;
+import org.apache.cloudstack.api.BaseListTaggedResourcesCmd;
 import org.apache.cloudstack.api.Parameter;
 import org.apache.cloudstack.api.ResponseObject.ResponseView;
 import org.apache.cloudstack.api.command.user.UserCmd;
@@ -39,7 +39,7 @@ import com.cloud.server.ResourceTag;
 
 @APICommand(name = "listDomains", description = "Lists domains and provides 
detailed information for listed domains", responseObject = 
DomainResponse.class, responseView = ResponseView.Restricted, entityType = 
{Domain.class},
         requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
-public class ListDomainsCmd extends BaseListCmd implements UserCmd {
+public class ListDomainsCmd extends BaseListTaggedResourcesCmd implements 
UserCmd {
 
     private static final String s_name = "listdomainsresponse";
 
diff --git 
a/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java 
b/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java
index e018b1a0f72..453c6b229e9 100644
--- a/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java
@@ -16,21 +16,21 @@
 // under the License.
 package org.apache.cloudstack.api.response;
 
-import com.google.gson.annotations.SerializedName;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import org.apache.cloudstack.api.ApiConstants;
-import org.apache.cloudstack.api.BaseResponseWithAnnotations;
+import org.apache.cloudstack.api.BaseResponseWithTagInformation;
 import org.apache.cloudstack.api.EntityReference;
 
 import com.cloud.domain.Domain;
 import com.cloud.serializer.Param;
-
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import com.google.gson.annotations.SerializedName;
 
 @EntityReference(value = Domain.class)
-public class DomainResponse extends BaseResponseWithAnnotations implements 
ResourceLimitAndCountResponse, SetResourceIconResponse {
+public class DomainResponse extends BaseResponseWithTagInformation implements 
ResourceLimitAndCountResponse, SetResourceIconResponse {
     @SerializedName(ApiConstants.ID)
     @Param(description = "The ID of the domain")
     private String id;
@@ -589,4 +589,8 @@ public class DomainResponse extends 
BaseResponseWithAnnotations implements Resou
     public void 
setTaggedResourceLimitsAndCounts(List<TaggedResourceLimitAndCountResponse> 
taggedResourceLimitsAndCounts) {
         this.taggedResources = taggedResourceLimitsAndCounts;
     }
+
+    public void setTags(Set<ResourceTagResponse> tags) {
+        this.tags = tags;
+    }
 }
diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java 
b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
index ce794cf5388..48696118a34 100644
--- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java
+++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java
@@ -39,19 +39,6 @@ import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 
-import com.cloud.bgp.ASNumber;
-import com.cloud.bgp.ASNumberRange;
-import com.cloud.configuration.ConfigurationService;
-import com.cloud.dc.ASNumberRangeVO;
-import com.cloud.dc.ASNumberVO;
-import com.cloud.dc.VlanDetailsVO;
-import com.cloud.dc.dao.ASNumberDao;
-import com.cloud.dc.dao.ASNumberRangeDao;
-import com.cloud.dc.dao.VlanDetailsDao;
-import com.cloud.hypervisor.Hypervisor;
-import com.cloud.network.vpc.VpcGateway;
-import com.cloud.network.vpn.Site2SiteVpnManager;
-import com.cloud.storage.BucketVO;
 import org.apache.cloudstack.acl.ControlledEntity;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.affinity.AffinityGroup;
@@ -277,14 +264,19 @@ import com.cloud.api.query.vo.UserVmJoinVO;
 import com.cloud.api.query.vo.VolumeJoinVO;
 import com.cloud.api.query.vo.VpcOfferingJoinVO;
 import com.cloud.api.response.ApiResponseSerializer;
+import com.cloud.bgp.ASNumber;
+import com.cloud.bgp.ASNumberRange;
 import com.cloud.capacity.Capacity;
 import com.cloud.capacity.CapacityVO;
 import com.cloud.capacity.dao.CapacityDaoImpl.SummedCapacity;
 import com.cloud.configuration.ConfigurationManager;
+import com.cloud.configuration.ConfigurationService;
 import com.cloud.configuration.Resource.ResourceOwnerType;
 import com.cloud.configuration.Resource.ResourceType;
 import com.cloud.configuration.ResourceCount;
 import com.cloud.configuration.ResourceLimit;
+import com.cloud.dc.ASNumberRangeVO;
+import com.cloud.dc.ASNumberVO;
 import com.cloud.dc.ClusterDetailsDao;
 import com.cloud.dc.ClusterVO;
 import com.cloud.dc.DataCenter;
@@ -295,7 +287,11 @@ import com.cloud.dc.Pod;
 import com.cloud.dc.StorageNetworkIpRange;
 import com.cloud.dc.Vlan;
 import com.cloud.dc.Vlan.VlanType;
+import com.cloud.dc.VlanDetailsVO;
 import com.cloud.dc.VlanVO;
+import com.cloud.dc.dao.ASNumberDao;
+import com.cloud.dc.dao.ASNumberRangeDao;
+import com.cloud.dc.dao.VlanDetailsDao;
 import com.cloud.domain.Domain;
 import com.cloud.domain.DomainVO;
 import com.cloud.event.Event;
@@ -304,6 +300,7 @@ import com.cloud.exception.PermissionDeniedException;
 import com.cloud.host.ControlState;
 import com.cloud.host.Host;
 import com.cloud.host.HostVO;
+import com.cloud.hypervisor.Hypervisor;
 import com.cloud.hypervisor.HypervisorCapabilities;
 import com.cloud.network.GuestVlan;
 import com.cloud.network.GuestVlanRange;
@@ -367,9 +364,11 @@ import com.cloud.network.vpc.NetworkACLItem;
 import com.cloud.network.vpc.PrivateGateway;
 import com.cloud.network.vpc.StaticRoute;
 import com.cloud.network.vpc.Vpc;
+import com.cloud.network.vpc.VpcGateway;
 import com.cloud.network.vpc.VpcOffering;
 import com.cloud.network.vpc.VpcVO;
 import com.cloud.network.vpc.dao.VpcOfferingDao;
+import com.cloud.network.vpn.Site2SiteVpnManager;
 import com.cloud.offering.DiskOffering;
 import com.cloud.offering.NetworkOffering;
 import com.cloud.offering.NetworkOffering.Detail;
@@ -388,6 +387,7 @@ import com.cloud.server.ResourceIconManager;
 import com.cloud.server.ResourceTag;
 import com.cloud.server.ResourceTag.ResourceObjectType;
 import com.cloud.service.ServiceOfferingVO;
+import com.cloud.storage.BucketVO;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.DiskOfferingVO;
 import com.cloud.storage.GuestOS;
@@ -586,6 +586,7 @@ public class ApiResponseHelper implements ResponseGenerator 
{
         if (domain.getChildCount() > 0) {
             domainResponse.setHasChild(true);
         }
+        populateDomainTags(domain.getUuid(), domainResponse);
         domainResponse.setObjectName("domain");
         return domainResponse;
     }
@@ -3048,6 +3049,20 @@ public class ApiResponseHelper implements 
ResponseGenerator {
         response.setDomainPath(getPrettyDomainPath(object.getDomainPath()));
     }
 
+    public static void populateDomainTags(String domainUuid, DomainResponse 
domainResponse) {
+        List<ResourceTagJoinVO> tags = 
ApiDBUtils.listResourceTagViewByResourceUUID(domainUuid,
+                ResourceTag.ResourceObjectType.Domain);
+        if (CollectionUtils.isEmpty(tags)) {
+            return;
+        }
+        Set<ResourceTagResponse> tagResponses = new HashSet<>();
+        for (ResourceTagJoinVO tag : tags) {
+            ResourceTagResponse tagResponse = 
ApiDBUtils.newResourceTagResponse(tag, true);
+            tagResponses.add(tagResponse);
+        }
+        domainResponse.setTags(tagResponses);
+    }
+
     private void populateAccount(ControlledEntityResponse response, long 
accountId) {
         Account account = ApiDBUtils.findAccountById(accountId);
         if (account == null) {
diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java 
b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
index 472be9a12b7..a25fcdf22ef 100644
--- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
+++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java
@@ -2860,6 +2860,7 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
         boolean listAll = cmd.listAll();
         boolean isRecursive = false;
         Domain domain = null;
+        Map<String, String> tags = cmd.getTags();
 
         if (domainId != null) {
             domain = _domainDao.findById(domainId);
@@ -2889,6 +2890,24 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
         domainSearchBuilder.and("path", 
domainSearchBuilder.entity().getPath(), SearchCriteria.Op.LIKE);
         domainSearchBuilder.and("state", 
domainSearchBuilder.entity().getState(), SearchCriteria.Op.EQ);
 
+        if (MapUtils.isNotEmpty(tags)) {
+            SearchBuilder<ResourceTagVO> resourceTagSearch = 
resourceTagDao.createSearchBuilder();
+            resourceTagSearch.and("resourceType", 
resourceTagSearch.entity().getResourceType(), Op.EQ);
+            resourceTagSearch.and().op();
+            for (int count = 0; count < tags.size(); count++) {
+                if (count == 0) {
+                    resourceTagSearch.op("tagKey" + count, 
resourceTagSearch.entity().getKey(), Op.EQ);
+                } else {
+                    resourceTagSearch.or().op("tagKey" + count, 
resourceTagSearch.entity().getKey(), Op.EQ);
+                }
+                resourceTagSearch.and("tagValue" + count, 
resourceTagSearch.entity().getValue(), Op.EQ);
+                resourceTagSearch.cp();
+            }
+            resourceTagSearch.cp();
+
+            domainSearchBuilder.join("tags", resourceTagSearch, 
resourceTagSearch.entity().getResourceId(), 
domainSearchBuilder.entity().getId(), JoinBuilder.JoinType.INNER);
+        }
+
         if (keyword != null) {
             domainSearchBuilder.and("keywordName", 
domainSearchBuilder.entity().getName(), SearchCriteria.Op.LIKE);
         }
@@ -2918,6 +2937,16 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
             }
         }
 
+        if (MapUtils.isNotEmpty(tags)) {
+            int count = 0;
+            sc.setJoinParameters("tags", "resourceType", 
ResourceObjectType.Domain);
+            for (Map.Entry<String, String> entry  : tags.entrySet()) {
+                sc.setJoinParameters("tags", "tagKey" + count, entry.getKey());
+                sc.setJoinParameters("tags", "tagValue" + count, 
entry.getValue());
+                count++;
+            }
+        }
+
         // return only Active domains to the API
         sc.setParameters("state", Domain.State.Active);
 
diff --git 
a/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java 
b/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
index d4865c5550e..b6a3370e6bc 100644
--- a/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/DomainJoinDaoImpl.java
@@ -20,10 +20,8 @@ import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
+import javax.inject.Inject;
 
-import com.cloud.api.ApiResponseHelper;
-import com.cloud.configuration.Resource;
-import com.cloud.user.AccountManager;
 import org.apache.cloudstack.annotation.AnnotationService;
 import org.apache.cloudstack.annotation.dao.AnnotationDao;
 import org.apache.cloudstack.api.ApiConstants.DomainDetails;
@@ -35,15 +33,16 @@ import 
org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.springframework.stereotype.Component;
 
 import com.cloud.api.ApiDBUtils;
+import com.cloud.api.ApiResponseHelper;
 import com.cloud.api.query.vo.DomainJoinVO;
+import com.cloud.configuration.Resource;
 import com.cloud.configuration.Resource.ResourceType;
 import com.cloud.domain.Domain;
+import com.cloud.user.AccountManager;
 import com.cloud.utils.db.GenericDaoBase;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
 
-import javax.inject.Inject;
-
 @Component
 public class DomainJoinDaoImpl extends GenericDaoBase<DomainJoinVO, Long> 
implements DomainJoinDao {
 
@@ -110,6 +109,7 @@ public class DomainJoinDaoImpl extends 
GenericDaoBase<DomainJoinVO, Long> implem
         }
 
         domainResponse.setDetails(ApiDBUtils.getDomainDetails(domain.getId()));
+        ApiResponseHelper.populateDomainTags(domain.getUuid(), domainResponse);
         domainResponse.setObjectName("domain");
 
         return domainResponse;
diff --git a/ui/src/components/view/InfoCard.vue 
b/ui/src/components/view/InfoCard.vue
index 0031d730f56..996e30ead3b 100644
--- a/ui/src/components/view/InfoCard.vue
+++ b/ui/src/components/view/InfoCard.vue
@@ -1094,7 +1094,7 @@ export default {
       return ['UserVm', 'Template', 'ISO', 'Volume', 'Snapshot', 'Backup', 
'Network',
         'LoadBalancer', 'PortForwardingRule', 'FirewallRule', 'SecurityGroup', 
'SecurityGroupRule',
         'PublicIpAddress', 'Project', 'Account', 'Vpc', 'NetworkACL', 
'StaticRoute', 'VMSnapshot',
-        'RemoteAccessVpn', 'User', 'SnapshotPolicy', 'VpcOffering']
+        'RemoteAccessVpn', 'User', 'SnapshotPolicy', 'VpcOffering', 'Domain']
     },
     name () {
       return this.resource.displayname || this.resource.name || 
this.resource.displaytext || this.resource.username ||

Reply via email to