From dd212f2554d702522810edcd184ebb8cfc2292ed Mon Sep 17 00:00:00 2001 From: sstent Date: Mon, 15 Dec 2025 08:29:22 -0800 Subject: [PATCH] feat(consul): Update consul config on fitbit token refresh When fitbit tokens are refreshed, the new tokens are now written back to Consul if it is being used as the configuration source. This creates a closed loop, ensuring the tokens in Consul do not become stale. --- fitbitsync.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/fitbitsync.py b/fitbitsync.py index 7475e31..33b7d36 100644 --- a/fitbitsync.py +++ b/fitbitsync.py @@ -155,20 +155,12 @@ class ConfigManager: try: # Attempt 1: Assume the value is the direct UTF-8 bytes of the JSON string. + # The python-consul client automatically base64-decodes the value. decoded_json_str = raw_value_from_consul.decode('utf-8') logger.info("Successfully decoded Consul value directly as UTF-8.") - except UnicodeDecodeError: - logger.warning("Direct UTF-8 decoding failed. Falling back to base64 decoding.") - # Attempt 2: Assume the value is base64 encoded. - encoded_value = raw_value_from_consul - - # Add padding if necessary for base64 decoding - padding_needed = len(encoded_value) % 4 - if padding_needed != 0: - encoded_value += b'=' * (4 - padding_needed) - - decoded_json_str = base64.b64decode(encoded_value).decode('utf-8') - logger.info("Successfully decoded Consul value using base64 fallback.") + except Exception as e: + logger.error(f"Failed to decode consul value: {e}") + return # Can't proceed if we can't decode logger.debug(f"Decoded JSON string: {decoded_json_str}") consul_conf = json.loads(decoded_json_str) # Parse the JSON @@ -485,6 +477,22 @@ class ConsulStateManager: return status_info + def update_config(self, config_dict: Dict) -> bool: + """Update the full configuration in Consul.""" + full_config_key = f"{self.prefix}/config" + try: + # Serialize the dictionary to a JSON string + config_json = json.dumps(config_dict, indent=2) + + # Base64 encode the JSON string. Note: Consul client handles the bytes conversion. + # The client library expects a string, which it will encode to bytes. + self.client.kv.put(full_config_key, config_json) + logger.info("Successfully updated configuration in Consul.") + return True + except Exception as e: + logger.error(f"Failed to update configuration in Consul: {e}") + return False + class FitbitClient: """Client for Fitbit API using python-fitbit""" @@ -683,6 +691,13 @@ class FitbitClient: access_token=token['access_token'], refresh_token=token['refresh_token'] ) + + # If using Consul, update the configuration there as well. + if os.getenv('CONFIG_SOURCE') == 'consul': + logger.info("Updating Fitbit tokens in Consul...") + # We need a state manager instance to talk to consul + consul_manager = ConsulStateManager(self.config) + consul_manager.update_config(self.config.config) async def get_weight_data(self, start_date: datetime, end_date: datetime) -> List[WeightRecord]: """Fetch weight data from Fitbit API"""