Exemple #1
0
    async def report_update_results(self, update: DataUpdate,
                                    reports: List[DataEntryReport],
                                    data_fetcher: DataFetcher):
        try:
            whole_report = DataUpdateReport(update_id=update.id,
                                            reports=reports)

            callbacks = update.callback.callbacks or opal_client_config.DEFAULT_UPDATE_CALLBACKS.callbacks
            urls = []
            for callback in callbacks:
                if isinstance(callback, str):
                    url = callback
                    callback_config = opal_client_config.DEFAULT_UPDATE_CALLBACK_CONFIG.copy(
                    )
                else:
                    url, callback_config = callback
                callback_config.data = whole_report.json()
                urls.append((url, callback_config))

            logger.info("Reporting the update to requested callbacks",
                        urls=repr(urls))
            report_results = await data_fetcher.handle_urls(urls)
            # log reports which we failed to send
            for (url, config), result in zip(urls, report_results):
                if isinstance(result, Exception):
                    logger.error(
                        "Failed to send report to {url} with config {config}",
                        url=url,
                        config=config,
                        exc_info=result)
        except:
            logger.exception("Failed to excute report_update_results")
Exemple #2
0
 async def get_policy_data_config(self,
                                  url: str = None) -> DataSourceConfig:
     """
     Get the configuration for
     Args:
         url: the URL to query for the config, Defaults to self._data_sources_config_url
     Returns:
         DataSourceConfig: the data sources config
     """
     if url is None:
         url = self._data_sources_config_url
     logger.info("Getting data-sources configuration from '{source}'",
                 source=url)
     try:
         async with ClientSession(headers=self._extra_headers) as session:
             response = await session.get(url)
             if response.status == 200:
                 return DataSourceConfig.parse_obj(await response.json())
             else:
                 error_details = await response.json()
                 raise ClientError(
                     f"Fetch data sources failed with status code {response.status}, error: {error_details}"
                 )
     except:
         logger.exception(f"Failed to load data sources config")
         raise
Exemple #3
0
 def calc_hash(data):
     """
     Calculate an hash (sah256) on the given data, if data isn't a string, it will be converted to JSON.
     String are encoded as 'utf-8' prior to hash calculation.
     Returns: 
         the hash of the given data (as a a hexdigit string) or '' on failure to process. 
     """
     try:
         if not isinstance(data, str):
             data = json.dumps(data)
         return hashlib.sha256(data.encode('utf-8')).hexdigest()
     except:
         logger.exception("Failed to calculate hash for data {data}", data=data)
         return ""
Exemple #4
0
 async def get_policy_data_config(self, url: str = None) -> DataSourceConfig:
     """
     Get the configuration for
     Args:
         url: the URL to query for the config, Defaults to self._data_sources_config_url
     Returns:
         DataSourceConfig: the data sources config
     """
     if url is None:
         url = self._data_sources_config_url
     logger.info("Getting data-sources configuration from '{source}'", source=url)
     try:
         async with ClientSession(headers=self._extra_headers) as session:
             res = await session.get(url)
         return DataSourceConfig.parse_obj(await res.json())
     except:
         logger.exception(f"Failed to load data sources config")
         raise
Exemple #5
0
    async def update_policy_data(self, update: DataUpdate = None, policy_store: BasePolicyStoreClient = None, data_fetcher=None):
        """
        fetches policy data (policy configuration) from backend and updates it into policy-store (i.e. OPA)
        """
        policy_store = policy_store or DEFAULT_POLICY_STORE_GETTER()
        if data_fetcher is None:
            data_fetcher = DataFetcher()
        # types / defaults
        urls: List[Tuple[str, FetcherConfig]] = None
        entries: List[DataSourceEntry] = []
        # track the result of each url in order to report back
        reports: List[DataEntryReport] = []
        # if we have an actual specification for the update
        if update is not None:
            entries = update.entries
            urls = [(entry.url, entry.config) for entry in entries]

        # get the data for the update
        logger.info("Fetching policy data", urls=urls)
        # Urls may be None - handle_urls has a default for None
        policy_data_with_urls = await data_fetcher.handle_urls(urls)
        # Save the data from the update
        # We wrap our interaction with the policy store with a transaction  
        async with policy_store.transaction_context(update.id) as store_transaction:
            # for intelisense treat store_transaction as a PolicyStoreClient (which it proxies)
            store_transaction: BasePolicyStoreClient
            for (url, fetch_config, result), entry in itertools.zip_longest(policy_data_with_urls, entries):
                if not isinstance(result, Exception):
                    # get path to store the URL data (default mode (None) is as "" - i.e. as all the data at root)
                    policy_store_path = "" if entry is None else entry.dst_path
                    # None is not valid - use "" (protect from missconfig)
                    if policy_store_path is None:
                        policy_store_path = ""
                    # fix opa_path (if not empty must start with "/" to be nested under data)
                    if policy_store_path != "" and not policy_store_path.startswith("/"):
                        policy_store_path = f"/{policy_store_path}"
                    policy_data = result
                    # Create a report on the data-fetching
                    report = DataEntryReport(entry=entry, hash=self.calc_hash(policy_data), fetched=True)
                    logger.info(
                        "Saving fetched data to policy-store: source url='{url}', destination path='{path}'",
                        url=url,
                        path=policy_store_path or '/'
                    )
                    try:
                        await store_transaction.set_policy_data(policy_data, path=policy_store_path)
                        # No exception we we're able to save to the policy-store
                        report.saved = True
                        # save the report for the entry
                        reports.append(report)
                    except:
                        logger.exception("Failed to save data update to policy-store")
                        # we failed to save to policy-store
                        report.saved = False
                        # save the report for the entry
                        reports.append(report)
                        # re-raise so the context manager will be aware of the failure
                        raise
                else:
                    report = DataEntryReport(entry=entry, fetched=False, saved=False)
                    # save the report for the entry
                    reports.append(report)
        # should we send a report to defined callbackers?
        if self._should_send_reports:
            # spin off reporting (no need to wait on it)
            asyncio.create_task(self.report_update_results(update, reports, data_fetcher))