From 92f0fa7148543a512b825c53a00365510a0d6883 Mon Sep 17 00:00:00 2001 From: Pragya Chaudhary Date: Wed, 11 Mar 2026 14:23:16 +0530 Subject: [PATCH 1/6] Initial code fix --- .../aws/tag_cluster/tag_cluster_resouces.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py b/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py index 42caae55..bce17af9 100644 --- a/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py +++ b/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py @@ -15,6 +15,8 @@ class TagClusterResources(TagClusterOperations): SHORT_ID = 5 NA_VALUE = 'NA' + # Tag key prefixes we never propagate from one resource to others + TAGS_DO_NOT_PROPAGATE_PREFIXES = ('kubernetes.io/', 'sigs.k8s.io/') def __init__(self, cluster_name: str = None, cluster_prefix: list = None, input_tags: dict = None, region: str = 'us-east-2', dry_run: str = 'yes', cluster_only: bool = False): @@ -109,8 +111,12 @@ def __get_cluster_tags_by_instance_cluster(self, cluster_name: str): if any(prefix in tag.get('Key', '') for prefix in self.cluster_prefix): if cluster_name in tag.get('Key'): i_tags = [instance_tag for instance_tag in item.get('Tags') if - instance_tag.get('Key') != 'Name'] - return [i_tag for i_tag in i_tags if i_tag.get('Key') != cluster_name] + instance_tag.get('Key') != 'Name' and + not any((instance_tag.get('Key') or '').startswith(prefix) + for prefix in self.cluster_prefix) and + not (instance_tag.get('Key') or '').startswith( + self.TAGS_DO_NOT_PROPAGATE_PREFIXES)] + return i_tags return [] def get_date_from_date(self, date_time: datetime): From 75de9831a6f740989b77edd00d9e08c7dff007cf Mon Sep 17 00:00:00 2001 From: Pragya Chaudhary Date: Thu, 12 Mar 2026 19:50:49 +0530 Subject: [PATCH 2/6] Move constant to environment_variables.py --- cloud_governance/main/environment_variables.py | 2 ++ .../aws/tag_cluster/tag_cluster_resouces.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cloud_governance/main/environment_variables.py b/cloud_governance/main/environment_variables.py index 43f07871..cc49b34f 100644 --- a/cloud_governance/main/environment_variables.py +++ b/cloud_governance/main/environment_variables.py @@ -69,6 +69,8 @@ def __init__(self): # parameters for running policies self._environment_variables_dict['account'] = EnvironmentVariables.get_env('account', '').upper().strip() self._environment_variables_dict['AWS_DEFAULT_REGION'] = EnvironmentVariables.get_env('AWS_DEFAULT_REGION', '') + self._environment_variables_dict['TAGS_DO_NOT_PROPAGATE_PREFIXES'] = literal_eval( + EnvironmentVariables.get_env('TAGS_DO_NOT_PROPAGATE_PREFIXES', "('kubernetes.io/', 'sigs.k8s.io/')")) self._environment_variables_dict['log_level'] = EnvironmentVariables.get_env('log_level', 'INFO') self._environment_variables_dict['GLOBAL_TAGS'] = literal_eval(EnvironmentVariables.get_env('GLOBAL_TAGS', "{}")) self._environment_variables_dict['DAYS_TO_TAKE_ACTION'] = int( diff --git a/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py b/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py index bce17af9..5e80aa4e 100644 --- a/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py +++ b/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py @@ -2,6 +2,7 @@ from cloud_governance.common.clouds.aws.utils.common_methods import get_tag_value_from_tags from cloud_governance.common.logger.init_logger import logger +from cloud_governance.main.environment_variables import environment_variables from cloud_governance.policy.policy_operations.aws.tag_cluster.tag_cluster_operations import TagClusterOperations from cloud_governance.policy.policy_operations.aws.tag_non_cluster.tag_non_cluster_resources import \ @@ -15,8 +16,6 @@ class TagClusterResources(TagClusterOperations): SHORT_ID = 5 NA_VALUE = 'NA' - # Tag key prefixes we never propagate from one resource to others - TAGS_DO_NOT_PROPAGATE_PREFIXES = ('kubernetes.io/', 'sigs.k8s.io/') def __init__(self, cluster_name: str = None, cluster_prefix: list = None, input_tags: dict = None, region: str = 'us-east-2', dry_run: str = 'yes', cluster_only: bool = False): @@ -115,7 +114,8 @@ def __get_cluster_tags_by_instance_cluster(self, cluster_name: str): not any((instance_tag.get('Key') or '').startswith(prefix) for prefix in self.cluster_prefix) and not (instance_tag.get('Key') or '').startswith( - self.TAGS_DO_NOT_PROPAGATE_PREFIXES)] + environment_variables.environment_variables_dict.get( + 'TAGS_DO_NOT_PROPAGATE_PREFIXES', ('kubernetes.io/', 'sigs.k8s.io/')))] return i_tags return [] From 17d79ffbfb0527b61e6c2afd6ca3e9ff3852b55b Mon Sep 17 00:00:00 2001 From: Pragya Chaudhary Date: Mon, 16 Mar 2026 15:15:02 +0530 Subject: [PATCH 3/6] Reuse existing env variable --- cloud_governance/main/environment_variables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud_governance/main/environment_variables.py b/cloud_governance/main/environment_variables.py index cc49b34f..be9e9f12 100644 --- a/cloud_governance/main/environment_variables.py +++ b/cloud_governance/main/environment_variables.py @@ -69,8 +69,6 @@ def __init__(self): # parameters for running policies self._environment_variables_dict['account'] = EnvironmentVariables.get_env('account', '').upper().strip() self._environment_variables_dict['AWS_DEFAULT_REGION'] = EnvironmentVariables.get_env('AWS_DEFAULT_REGION', '') - self._environment_variables_dict['TAGS_DO_NOT_PROPAGATE_PREFIXES'] = literal_eval( - EnvironmentVariables.get_env('TAGS_DO_NOT_PROPAGATE_PREFIXES', "('kubernetes.io/', 'sigs.k8s.io/')")) self._environment_variables_dict['log_level'] = EnvironmentVariables.get_env('log_level', 'INFO') self._environment_variables_dict['GLOBAL_TAGS'] = literal_eval(EnvironmentVariables.get_env('GLOBAL_TAGS', "{}")) self._environment_variables_dict['DAYS_TO_TAKE_ACTION'] = int( @@ -142,6 +140,8 @@ def __init__(self): "kubernetes.io/cluster", "sigs.k8s.io/cluster-api-provider-aws/cluster" ] + prefixes = tuple(p.split('/', 1)[0] + '/' for p in self._environment_variables_dict['CLUSTER_PREFIX']) + self._environment_variables_dict['TAGS_DO_NOT_PROPAGATE_PREFIXES'] = prefixes # AWS Cost Explorer tags self._environment_variables_dict['cost_metric'] = EnvironmentVariables.get_env('cost_metric', 'UnblendedCost') self._environment_variables_dict['start_date'] = EnvironmentVariables.get_env('start_date', '') From 7b7a9ff0833b0f7da79cef75dbeedacdc5239ca3 Mon Sep 17 00:00:00 2001 From: Pragya Chaudhary Date: Mon, 16 Mar 2026 16:15:21 +0530 Subject: [PATCH 4/6] Review comments --- cloud_governance/main/environment_variables.py | 2 -- .../aws/tag_cluster/tag_cluster_resouces.py | 8 +++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cloud_governance/main/environment_variables.py b/cloud_governance/main/environment_variables.py index be9e9f12..43f07871 100644 --- a/cloud_governance/main/environment_variables.py +++ b/cloud_governance/main/environment_variables.py @@ -140,8 +140,6 @@ def __init__(self): "kubernetes.io/cluster", "sigs.k8s.io/cluster-api-provider-aws/cluster" ] - prefixes = tuple(p.split('/', 1)[0] + '/' for p in self._environment_variables_dict['CLUSTER_PREFIX']) - self._environment_variables_dict['TAGS_DO_NOT_PROPAGATE_PREFIXES'] = prefixes # AWS Cost Explorer tags self._environment_variables_dict['cost_metric'] = EnvironmentVariables.get_env('cost_metric', 'UnblendedCost') self._environment_variables_dict['start_date'] = EnvironmentVariables.get_env('start_date', '') diff --git a/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py b/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py index 5e80aa4e..4a582204 100644 --- a/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py +++ b/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py @@ -109,13 +109,15 @@ def __get_cluster_tags_by_instance_cluster(self, cluster_name: str): for tag in item.get('Tags'): if any(prefix in tag.get('Key', '') for prefix in self.cluster_prefix): if cluster_name in tag.get('Key'): + # Propagation-blocked prefixes derived from CLUSTER_PREFIX (e.g. kubernetes.io/, sigs.k8s.io/) + cluster_prefix = environment_variables.environment_variables_dict.get( + 'CLUSTER_PREFIX', ['kubernetes.io/cluster', 'sigs.k8s.io/cluster-api-provider-aws/cluster']) + no_propagate_prefixes = tuple(p.split('/', 1)[0] + '/' for p in cluster_prefix) i_tags = [instance_tag for instance_tag in item.get('Tags') if instance_tag.get('Key') != 'Name' and not any((instance_tag.get('Key') or '').startswith(prefix) for prefix in self.cluster_prefix) and - not (instance_tag.get('Key') or '').startswith( - environment_variables.environment_variables_dict.get( - 'TAGS_DO_NOT_PROPAGATE_PREFIXES', ('kubernetes.io/', 'sigs.k8s.io/')))] + not (instance_tag.get('Key') or '').startswith(no_propagate_prefixes)] return i_tags return [] From 85d2eebe376c8e0792dac99d966e35107e91d3e2 Mon Sep 17 00:00:00 2001 From: Pragya Chaudhary Date: Mon, 16 Mar 2026 18:09:37 +0530 Subject: [PATCH 5/6] Review comments --- .../policy_operations/aws/tag_cluster/tag_cluster_resouces.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py b/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py index 4a582204..234bf331 100644 --- a/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py +++ b/cloud_governance/policy/policy_operations/aws/tag_cluster/tag_cluster_resouces.py @@ -110,8 +110,7 @@ def __get_cluster_tags_by_instance_cluster(self, cluster_name: str): if any(prefix in tag.get('Key', '') for prefix in self.cluster_prefix): if cluster_name in tag.get('Key'): # Propagation-blocked prefixes derived from CLUSTER_PREFIX (e.g. kubernetes.io/, sigs.k8s.io/) - cluster_prefix = environment_variables.environment_variables_dict.get( - 'CLUSTER_PREFIX', ['kubernetes.io/cluster', 'sigs.k8s.io/cluster-api-provider-aws/cluster']) + cluster_prefix = environment_variables.environment_variables_dict['CLUSTER_PREFIX'] no_propagate_prefixes = tuple(p.split('/', 1)[0] + '/' for p in cluster_prefix) i_tags = [instance_tag for instance_tag in item.get('Tags') if instance_tag.get('Key') != 'Name' and From e1cf82f2cc8edb28ce96b30928b6a17d7e38a142 Mon Sep 17 00:00:00 2001 From: Pragya Chaudhary Date: Tue, 24 Mar 2026 19:33:01 +0530 Subject: [PATCH 6/6] Add Unittests --- .../tag_cluster/test_tag_cluster_resources.py | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tests/unittest/cloud_governance/aws/tag_cluster/test_tag_cluster_resources.py b/tests/unittest/cloud_governance/aws/tag_cluster/test_tag_cluster_resources.py index a4bed840..ed7e4070 100644 --- a/tests/unittest/cloud_governance/aws/tag_cluster/test_tag_cluster_resources.py +++ b/tests/unittest/cloud_governance/aws/tag_cluster/test_tag_cluster_resources.py @@ -1,6 +1,7 @@ # TEST DRY RUN: mandatory_tags = None import json import os +from unittest.mock import patch import pytest from moto import mock_ec2, mock_cloudtrail, mock_iam, mock_s3, mock_elb, mock_elbv2 @@ -236,3 +237,108 @@ def test_cluster_ec2(): tag_resources = TagClusterResources(cluster_prefix=cluster_prefix, cluster_name=cluster_name, input_tags=mandatory_tags, region='us-east-2') assert len(tag_resources.cluster_instance()) == 3 + + +# --- Hypershift / tag_cluster propagation (PR #976): do not propagate kubernetes.io/ or sigs.k8s.io/ tags --- + +CLUSTER_STAMP_KEY = 'kubernetes.io/cluster/hyper2-unittest' + + +def test_get_cluster_tags_for_propagation_excludes_cluster_and_k8s_sigs_tags(): + """ + Tags returned for propagation must not include kubernetes.io/cluster, other kubernetes.io/*, + sigs.k8s.io/*, or Name — only governance-style tags (e.g. User) should propagate. + """ + with mock_ec2(), mock_iam(), mock_cloudtrail(), mock_s3(), mock_elb(), mock_elbv2(): + tcr = TagClusterResources( + cluster_prefix=cluster_prefix, + cluster_name=cluster_name, + input_tags=mandatory_tags, + region='us-east-2', + ) + fake_instance = [{ + 'InstanceId': 'i-hyper2test', + 'Tags': [ + {'Key': CLUSTER_STAMP_KEY, 'Value': 'owned'}, + { + 'Key': 'sigs.k8s.io/cluster-api-provider-aws/cluster/hyper2-unittest', + 'Value': 'owned', + }, + {'Key': 'kubernetes.io/role/worker', 'Value': 'true'}, + {'Key': 'User', 'Value': 'alice'}, + {'Key': 'Manager', 'Value': 'bob'}, + {'Key': 'Name', 'Value': 'hypershift-node-1'}, + ], + }] + with patch.object(tcr, '_get_instances_data', return_value=[fake_instance]): + result = tcr._TagClusterResources__get_cluster_tags_by_instance_cluster(CLUSTER_STAMP_KEY) + + assert result == [ + {'Key': 'User', 'Value': 'alice'}, + {'Key': 'Manager', 'Value': 'bob'}, + ] + assert not any(t['Key'].startswith('kubernetes.io/') for t in result) + assert not any(t['Key'].startswith('sigs.k8s.io/') for t in result) + assert not any(t['Key'] == 'Name' for t in result) + + +def test_get_cluster_tags_for_propagation_empty_when_only_cluster_system_tags(): + """If the instance has only cluster stamp and k8s/sigs system tags (plus Name), nothing should propagate.""" + with mock_ec2(), mock_iam(), mock_cloudtrail(), mock_s3(), mock_elb(), mock_elbv2(): + tcr = TagClusterResources( + cluster_prefix=cluster_prefix, + cluster_name=cluster_name, + input_tags=mandatory_tags, + region='us-east-2', + ) + fake_instance = [{ + 'InstanceId': 'i-onlysys', + 'Tags': [ + {'Key': CLUSTER_STAMP_KEY, 'Value': 'owned'}, + { + 'Key': 'sigs.k8s.io/cluster-api-provider-aws/cluster/hyper2-unittest', + 'Value': 'owned', + }, + {'Key': 'kubernetes.io/role/master', 'Value': 'true'}, + {'Key': 'Name', 'Value': 'cp-0'}, + ], + }] + with patch.object(tcr, '_get_instances_data', return_value=[fake_instance]): + result = tcr._TagClusterResources__get_cluster_tags_by_instance_cluster(CLUSTER_STAMP_KEY) + + assert result == [] + + +def test_get_cluster_tags_for_propagation_uses_cluster_prefix_from_environment_variables(): + """ + no_propagate_prefixes must follow CLUSTER_PREFIX in environment_variables (same derivation as production). + """ + from cloud_governance.main.environment_variables import environment_variables + + custom_prefixes = ['api.openshift.com/cluster', 'kubernetes.io/cluster'] + original = environment_variables.environment_variables_dict['CLUSTER_PREFIX'] + try: + environment_variables.environment_variables_dict['CLUSTER_PREFIX'] = custom_prefixes + with mock_ec2(), mock_iam(), mock_cloudtrail(), mock_s3(), mock_elb(), mock_elbv2(): + tcr = TagClusterResources( + cluster_prefix=custom_prefixes, + cluster_name=cluster_name, + input_tags=mandatory_tags, + region='us-east-2', + ) + stamp = 'api.openshift.com/cluster/hyper2-unittest' + fake_instance = [{ + 'InstanceId': 'i-custom', + 'Tags': [ + {'Key': stamp, 'Value': 'owned'}, + {'Key': 'api.openshift.com/something-else', 'Value': 'x'}, + {'Key': 'User', 'Value': 'carol'}, + ], + }] + with patch.object(tcr, '_get_instances_data', return_value=[fake_instance]): + result = tcr._TagClusterResources__get_cluster_tags_by_instance_cluster(stamp) + # api.openshift.com/ and kubernetes.io/ blocked; User kept + assert result == [{'Key': 'User', 'Value': 'carol'}] + assert not any(t['Key'].startswith('api.openshift.com/') for t in result) + finally: + environment_variables.environment_variables_dict['CLUSTER_PREFIX'] = original