@@ -67,93 +67,80 @@ def _is_certificate_file_ready(path):
6767 st = os .stat (path )
6868 return stat .S_ISREG (st .st_mode ) and st .st_size > 0
6969 except PermissionError :
70- # Propagate PermissionError to let caller handle it (fail-fast or fallback)
70+ # Propagate PermissionError to let caller handle it (e.g., return early or fallback)
7171 raise
7272 except OSError :
7373 return False
7474
7575
7676def get_agent_identity_certificate_path ():
77- """Gets the certificate path from the certificate config file.
77+ """Gets the agent certificate path from the certificate config file.
7878
7979 The path to the certificate config file is read from the
8080 GOOGLE_API_CERTIFICATE_CONFIG environment variable. This function
81- implements a retry mechanism to handle cases where the environment
81+ can optionally trigger polling to handle cases where the environment
8282 variable is set before the files are available on the filesystem.
8383
8484 Returns:
85- str: The path to the leaf certificate file.
85+ Optional[ str] : The path to the agent's certificate file, or None if unavailable .
8686
8787 Raises:
8888 google.auth.exceptions.RefreshError: If the certificate config file
8989 or the certificate file cannot be found after retries.
9090 """
91- import json
92-
9391 cert_config_path = os .environ .get (environment_vars .GOOGLE_API_CERTIFICATE_CONFIG )
9492
95- # Check if the well-known workload directory is mounted.
93+ if not cert_config_path :
94+ return None
95+
96+ # We trigger polling only if the config path points to the well-known directory.
97+ # Cloud Run dynamically generates these files in this directory, and both the
98+ # config file and the certificate file may experience a brief startup latency.
99+ # For all other paths, we return early to avoid introducing unnecessary startup
100+ # delays.
96101 well_known_dir = os .path .dirname (_WELL_KNOWN_CERT_PATH )
97- has_well_known_dir = os . path . exists (well_known_dir )
102+ should_poll = cert_config_path . startswith (well_known_dir )
98103
99- # If we have neither a config path nor a well-known mount directory, exit immediately.
100- if not cert_config_path and not has_well_known_dir :
101- return None
104+ return _get_cert_path_with_optional_polling (cert_config_path , should_poll )
102105
103- # If ECP config path is specified but does not exist, and we are on a workstation, fail-fast immediately.
104- if (
105- cert_config_path
106- and not has_well_known_dir
107- and not os .path .exists (cert_config_path )
108- ):
109- return None
110106
107+ def _get_cert_path_with_optional_polling (cert_config_path , should_poll ):
108+ """Gets the certificate path, optionally polling until it is ready.
109+
110+ Args:
111+ cert_config_path (str): The path to the certificate configuration file.
112+ should_poll (bool): If True, the function will poll for the file and
113+ certificate to be ready. If False, it will check only once and
114+ return early if they are not immediately available.
115+
116+ Returns:
117+ str: The path to the certificate file, or None if unavailable.
118+
119+ Raises:
120+ google.auth.exceptions.RefreshError: If the certificate config file
121+ or the certificate file cannot be found after retries.
122+ """
111123 has_logged_config_warning = False
112124 has_logged_cert_warning = False
113125
114126 for interval in _POLLING_INTERVALS :
115127 try :
116- # Path A: Config file is explicitly set
117- if cert_config_path :
118- with open (cert_config_path , "r" ) as f :
119- cert_config = json .load (f )
120-
121- cert_configs = (
122- cert_config .get ("cert_configs" )
123- if isinstance (cert_config , dict )
124- else None
125- )
126- workload_config = (
127- cert_configs .get ("workload" )
128- if isinstance (cert_configs , dict )
129- else None
130- )
131-
132- if (
133- not isinstance (workload_config , dict )
134- or "cert_path" not in workload_config
135- ):
136- return None
128+ cert_path = _parse_cert_path_from_config (cert_config_path )
137129
138- cert_path = workload_config ["cert_path" ]
139- if _is_certificate_file_ready (cert_path ):
140- return cert_path
141-
142- # The config was parsed, but the cert file is not ready yet
143- target_path = cert_path
130+ if cert_path is None :
131+ return None
144132
145- # Path B: Config is NOT set, fallback to the well-known path
146- else :
147- if _is_certificate_file_ready (_WELL_KNOWN_CERT_PATH ):
148- return _WELL_KNOWN_CERT_PATH
133+ if _is_certificate_file_ready (cert_path ):
134+ return cert_path
149135
150- # The well-known cert file is not ready yet
151- target_path = _WELL_KNOWN_CERT_PATH
136+ # The config was parsed, but the cert file is not ready yet
137+ if not should_poll :
138+ # If polling is disabled, return early.
139+ return None
152140
153- # Log a warning on the first failed attempt to load the certificate file
154141 if not has_logged_cert_warning :
155142 warnings .warn (
156- f"Certificate file not ready at { target_path } . Retrying until startup timeout (up to { _TOTAL_TIMEOUT } seconds total)..."
143+ f"Certificate file not ready at { cert_path } . Retrying until startup timeout (up to { _TOTAL_TIMEOUT } seconds total)..."
157144 )
158145 has_logged_cert_warning = True
159146
@@ -164,25 +151,24 @@ def get_agent_identity_certificate_path():
164151 )
165152 return None
166153 except (IOError , ValueError , KeyError ) as e :
167- if cert_config_path and os .path .exists (cert_config_path ):
154+ if os .path .exists (cert_config_path ):
168155 # If the file exists but has invalid JSON or is unreadable,
169- # we assume it is in its final format and fail-fast by returning None.
156+ # we assume it is in its final format and return early (returning None).
157+ return None
158+
159+ if not should_poll :
160+ # If polling is disabled, return early if the file doesn't exist.
170161 return None
171162
172- if not has_logged_config_warning and cert_config_path :
163+ if not has_logged_config_warning :
173164 warnings .warn (
174165 f"Certificate config file not found or incomplete: { e } (from "
175166 f"{ environment_vars .GOOGLE_API_CERTIFICATE_CONFIG } environment variable). "
176167 f"Retrying until startup timeout (up to { _TOTAL_TIMEOUT } seconds total)..."
177168 )
178169 has_logged_config_warning = True
179- pass
180170
181- # A sleep is required in two cases:
182- # 1. The config file is not found (the except block).
183- # 2. The config file/well-known path is found, but the certificate is not yet available.
184- # In both cases, we need to poll, so we sleep on every iteration
185- # that doesn't return a certificate.
171+ # Sleep before the next polling attempt.
186172 time .sleep (interval )
187173
188174 raise exceptions .RefreshError (
@@ -193,6 +179,40 @@ def get_agent_identity_certificate_path():
193179 )
194180
195181
182+ def _parse_cert_path_from_config (cert_config_path ):
183+ """Reads the cert config file and returns the cert_path.
184+
185+ Args:
186+ cert_config_path (str): The path to the certificate configuration file.
187+
188+ Returns:
189+ Optional[str]: The path to the certificate file, or None if not found
190+ in the config.
191+
192+ Raises:
193+ IOError: If the certificate config file cannot be read.
194+ ValueError: If the certificate config file contains invalid JSON.
195+ KeyError: If the certificate config file does not contain the
196+ expected structure.
197+ """
198+ import json
199+
200+ with open (cert_config_path , "r" ) as f :
201+ cert_config = json .load (f )
202+
203+ cert_configs = (
204+ cert_config .get ("cert_configs" ) if isinstance (cert_config , dict ) else None
205+ )
206+ workload_config = (
207+ cert_configs .get ("workload" ) if isinstance (cert_configs , dict ) else None
208+ )
209+
210+ if not isinstance (workload_config , dict ) or "cert_path" not in workload_config :
211+ return None
212+
213+ return workload_config ["cert_path" ]
214+
215+
196216def get_and_parse_agent_identity_certificate ():
197217 """Gets and parses the agent identity certificate if not opted out.
198218
0 commit comments