def test_populate_desired_state_cases(self): ri = ResourceInventory() for resource_type in ( "Deployment", "Service", "ConfigMap", ): ri.initialize_resource_type("stage-1", "yolo-stage", resource_type) ri.initialize_resource_type("prod-1", "yolo", resource_type) self.saasherder.populate_desired_state(ri) cnt = 0 for (cluster, namespace, resource_type, data) in ri: for _, d_item in data['desired'].items(): expected = yaml.safe_load( self.fxts.get( f"expected_{cluster}_{namespace}_{resource_type}.json", )) self.assertEqual(expected, d_item.body) cnt += 1 self.assertEqual(5, cnt, "expected 5 resources, found less")
class TestConfigHashPromotionsValidation(TestCase): """ TestCase to test SaasHerder promotions validation. SaasHerder is initialized with ResourceInventory population. Like is done in openshift-saas-deploy""" cluster: str namespace: str fxt: Any template: Any @classmethod def setUpClass(cls): cls.fxt = Fixtures('saasherder') cls.cluster = "test-cluster" cls.template = cls.fxt.get_anymarkup('template_1.yml') def setUp(self) -> None: self.all_saas_files = \ [self.fxt.get_anymarkup('saas.gql.yml')] self.state_patcher = \ patch("reconcile.utils.saasherder.State", autospec=True) self.state_mock = self.state_patcher.start().return_value self.ig_patcher = \ patch.object(SaasHerder, "_initiate_github", autospec=True) self.ig_patcher.start() self.image_auth_patcher = \ patch.object(SaasHerder, "_initiate_image_auth") self.image_auth_patcher.start() self.gfc_patcher = \ patch.object(SaasHerder, "_get_file_contents", autospec=True) gfc_mock = self.gfc_patcher.start() self.saas_file = \ self.fxt.get_anymarkup('saas.gql.yml') # ApiVersion is set in the saas gql query method in queries module self.saas_file["apiVersion"] = "v2" gfc_mock.return_value = (self.template, "url", "ahash") self.deploy_current_state_fxt = \ self.fxt.get_anymarkup('saas_deploy.state.json') self.post_deploy_current_state_fxt = \ self.fxt.get_anymarkup('saas_post_deploy.state.json') self.saasherder = SaasHerder( [self.saas_file], thread_pool_size=1, gitlab=None, integration='', integration_version='', accounts={"name": "test-account"}, # Initiates State in SaasHerder settings={"hashLength": 24}) # IMPORTANT: Populating desired state modify self.saas_files within # saasherder object. self.ri = ResourceInventory() for ns in ["test-ns-publisher", "test-ns-subscriber"]: for kind in ["Service", "Deployment"]: self.ri.initialize_resource_type(self.cluster, ns, kind) self.saasherder.populate_desired_state(self.ri) if self.ri.has_error_registered(): raise Exception("Errors registered in Resourceinventory") def tearDown(self): self.state_patcher.stop() self.ig_patcher.stop() self.gfc_patcher.stop() def test_config_hash_is_filled(self): """ Ensures the get_config_diff_saas_file fills the promotion data on the publisher target. This data is used in publish_promotions method to add the hash to subscribed targets. IMPORTANT: This is not the promotion_data within promotion. This fields are set by _process_template method in saasherder """ job_spec = \ self.saasherder.get_configs_diff_saas_file(self.saas_file)[0] promotion = job_spec["target_config"]["promotion"] self.assertIsNotNone(promotion[TARGET_CONFIG_HASH]) def test_promotion_state_config_hash_match_validates(self): """ A promotion is valid if the pusblisher state got from the state is equal to the one set in the subscriber target promotion data. This is the happy path, publisher job state target config hash is the same set in the subscriber job """ configs = \ self.saasherder.get_saas_targets_config(self.saas_file) tcs = list(configs.values()) publisher_config_hash = tcs[0]['promotion'][TARGET_CONFIG_HASH] publisher_state = { "success": True, "saas_file": self.saas_file["name"], TARGET_CONFIG_HASH: publisher_config_hash } self.state_mock.get.return_value = publisher_state result = self.saasherder.validate_promotions(self.all_saas_files) self.assertTrue(result) def test_promotion_state_config_hash_not_match_no_validates(self): """ Promotion is not valid if the parent target config hash set in promotion data is not the same set in the publisher job state. This could happen if a new publisher job has before the subscriber job """ publisher_state = { "success": True, "saas_file": self.saas_file["name"], TARGET_CONFIG_HASH: "will_not_match" } self.state_mock.get.return_value = publisher_state result = self.saasherder.validate_promotions(self.all_saas_files) self.assertFalse(result) def test_promotion_without_state_config_hash_validates(self): """ Existent states won't have promotion data. If there is an ongoing promotion, this ensures it will happen. """ promotion_result = { "success": True, } self.state_mock.get.return_value = promotion_result result = self.saasherder.validate_promotions(self.all_saas_files) self.assertTrue(result)
def init_specs_to_fetch( ri: ResourceInventory, oc_map: OC_Map, namespaces: Optional[Iterable[Mapping]] = None, clusters: Optional[Iterable[Mapping]] = None, override_managed_types: Optional[Iterable[str]] = None, managed_types_key: str = 'managedResourceTypes') -> list[StateSpec]: state_specs = [] if clusters and namespaces: raise KeyError('expected only one of clusters or namespaces.') elif namespaces: for namespace_info in namespaces: if override_managed_types is None: managed_types = set( namespace_info.get(managed_types_key) or []) else: managed_types = set(override_managed_types) if not managed_types: continue cluster = namespace_info['cluster']['name'] privileged = namespace_info.get("clusterAdmin", False) is True oc = oc_map.get(cluster, privileged) if not oc: if oc.log_level >= logging.ERROR: ri.register_error() logging.log(level=oc.log_level, msg=oc.message) continue namespace = namespace_info['name'] # These may exit but have a value of None managed_resource_names = \ namespace_info.get('managedResourceNames') or [] managed_resource_type_overrides = \ namespace_info.get('managedResourceTypeOverrides') or [] # Initialize current state specs for resource_type in managed_types: ri.initialize_resource_type(cluster, namespace, resource_type) resource_names = {} resource_type_overrides = {} for mrn in managed_resource_names: # Current implementation guarantees only one # managed_resource_name of each managed type if mrn['resource'] in managed_types: resource_names[mrn['resource']] = mrn['resourceNames'] elif override_managed_types: logging.debug( f"Skipping resource {mrn['resource']} in {cluster}/" f"{namespace} because the integration explicitly " "dismisses it") else: raise KeyError( f"Non-managed resource name {mrn} listed on " f"{cluster}/{namespace} (valid kinds: {managed_types})" ) for o in managed_resource_type_overrides: # Current implementation guarantees only one # override of each managed type if o['resource'] in managed_types: resource_type_overrides[o['resource']] = o['override'] elif override_managed_types: logging.debug( f"Skipping resource type override {o} listed on" f"{cluster}/{namespace} because the integration " "dismisses it explicitly") else: raise KeyError( f"Non-managed override {o} listed on " f"{cluster}/{namespace} (valid kinds: {managed_types})" ) for kind, names in resource_names.items(): c_spec = StateSpec( "current", oc, cluster, namespace, kind, resource_type_override=resource_type_overrides.get(kind), resource_names=names) state_specs.append(c_spec) managed_types.remove(kind) # Produce "empty" StateSpec's for any resource type that # doesn't have an explicit managedResourceName listed in # the namespace state_specs.extend( StateSpec("current", oc, cluster, namespace, t, resource_type_override=resource_type_overrides.get( t), resource_names=None) for t in managed_types) # Initialize desired state specs openshift_resources = namespace_info.get('openshiftResources') for openshift_resource in openshift_resources or []: d_spec = StateSpec("desired", oc, cluster, namespace, openshift_resource, namespace_info, privileged=privileged) state_specs.append(d_spec) elif clusters: # set namespace to something indicative namespace = 'cluster' for cluster_info in clusters: cluster = cluster_info['name'] oc = oc_map.get(cluster) if not oc: if oc.log_level >= logging.ERROR: ri.register_error() logging.log(level=oc.log_level, msg=oc.message) continue # we currently only use override_managed_types, # and not allow a `managedResourcesTypes` field in a cluster file for resource_type in override_managed_types or []: ri.initialize_resource_type(cluster, namespace, resource_type) # Initialize current state specs c_spec = StateSpec("current", oc, cluster, namespace, resource_type) state_specs.append(c_spec) # Initialize desired state specs d_spec = StateSpec("desired", oc, cluster, namespace, resource_type) state_specs.append(d_spec) else: raise KeyError('expected one of clusters or namespaces.') return state_specs