gcpdiag.queries.apis
Build and cache GCP APIs + handle authentication.
AUTH_SCOPES =
['openid', 'https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/userinfo.email']
def
set_credentials(cred_json):
def
get_credentials():
90def get_credentials(): 91 if _auth_method() == 'adc': 92 return _get_credentials_adc() 93 elif _auth_method() == 'key': 94 return _get_credentials_key() 95 else: 96 raise AssertionError( 97 'BUG: AUTH_METHOD method should be one of `adc` or `key`, ' 98 f'but got `{_auth_method()}` instead.' 99 ' Please report at https://gcpdiag.dev/issues/' 100 )
def
login():
110def login(): 111 """Force GCP login (this otherwise happens on the first get_api call).""" 112 get_credentials()
Force GCP login (this otherwise happens on the first get_api call).
def
get_user_email() -> str:
115def get_user_email() -> str: 116 if config.get('universe_domain') != 'googleapis.com': 117 return 'TPC user' 118 credentials = get_credentials().with_quota_project(None) 119 120 http = google_auth_httplib2.AuthorizedHttp(credentials, http=httplib2.Http()) 121 resp, content = http.request('https://www.googleapis.com/userinfo/v2/me') 122 if resp['status'] != '200': 123 raise RuntimeError(f"can't determine user email. status={resp['status']}") 124 data = json.loads(content) 125 logging.debug('determined my email address: %s', data['email']) 126 return data['email']
@caching.cached_api_call(in_memory=True)
def
get_api( service_name: str, version: str, project_id: Optional[str] = None, region: Optional[str] = None):
129@caching.cached_api_call(in_memory=True) 130def get_api( 131 service_name: str, version: str, project_id: Optional[str] = None, region: Optional[str] = None 132): 133 """Get an API object, as returned by googleapiclient.discovery.build. 134 135 If project_id is specified, this will be used as the billed project, and usually 136 you should put there the project id of the project that you are inspecting.""" 137 credentials = get_credentials() 138 139 def _request_builder(http, *args, **kwargs): 140 del http 141 142 if 'headers' in kwargs: 143 # thread safety: make sure that original dictionary isn't modified 144 kwargs['headers'] = kwargs['headers'].copy() 145 146 headers = kwargs.get('headers', {}) 147 headers['user-agent'] = f'gcpdiag/{config.VERSION} (gzip)' 148 if project_id: 149 headers['x-goog-user-project'] = _get_project_or_billing_id(project_id) 150 151 hooks.request_builder_hook(*args, **kwargs) 152 153 # thread safety: create a new AuthorizedHttp object for every request 154 # https://github.com/googleapis/google-api-python-client/blob/master/docs/thread_safety.md 155 new_http = google_auth_httplib2.AuthorizedHttp(credentials, http=httplib2.Http()) 156 return googleapiclient.http.HttpRequest(new_http, *args, **kwargs) 157 158 universe_domain = config.get('universe_domain') 159 cred_universe = getattr(credentials, 'universe_domain', 'googleapis.com') 160 if cred_universe != universe_domain: 161 raise ValueError(f'credential universe_domain mismatch {cred_universe} != {universe_domain}') 162 client_options = ClientOptions() 163 if universe_domain != 'googleapis.com': 164 client_options.universe_domain = universe_domain 165 if region: 166 client_options.api_endpoint = f'https://{region}-{service_name}.{universe_domain}' 167 else: 168 client_options.api_endpoint = f'https://{service_name}.{universe_domain}' 169 if service_name in ['compute', 'bigquery', 'storage', 'dns']: 170 client_options.api_endpoint += f'/{service_name}/{version}' 171 api = discovery.build( 172 service_name, 173 version, 174 cache_discovery=False, 175 credentials=credentials, 176 requestBuilder=_request_builder, 177 client_options=client_options, 178 ) 179 return api
Get an API object, as returned by googleapiclient.discovery.build.
If project_id is specified, this will be used as the billed project, and usually you should put there the project id of the project that you are inspecting.
def
is_enabled(project_id: str, service_name: str) -> bool:
def
is_all_enabled(project_id: str, services: list) -> Dict[str, str]:
@caching.cached_api_call(in_memory=True)
def
list_services_with_state(project_id: str) -> Dict[str, str]:
223@caching.cached_api_call(in_memory=True) 224def list_services_with_state(project_id: str) -> Dict[str, str]: 225 logging.debug('listing all APIs with their state') 226 serviceusage = get_api('serviceusage', 'v1', project_id) 227 request = serviceusage.services().list(parent=f'projects/{project_id}') 228 apis_state: Dict[str, str] = {} 229 try: 230 while request is not None: 231 response = request.execute(num_retries=config.API_RETRIES) 232 for service in response['services']: 233 apis_state.setdefault(service['config']['name'], service['state']) 234 request = serviceusage.services().list_next(request, response) 235 except googleapiclient.errors.HttpError as err: 236 raise utils.GcpApiError(err) from err 237 return apis_state
def
verify_access(project_id: str):
240def verify_access(project_id: str): 241 """Verify that the user has access to the project, exit with an error otherwise.""" 242 243 try: 244 if not is_enabled(project_id, 'cloudresourcemanager'): 245 service = f'cloudresourcemanager.{config.get("universe_domain")}' 246 error_msg = ( 247 'Cloud Resource Manager API must be enabled. To enable, execute:\n' 248 f'gcloud services enable {service} --project={project_id}' 249 ) 250 raise utils.GcpApiError(response=error_msg, service=service, reason='SERVICE_DISABLED') 251 if not is_enabled(project_id, 'iam'): 252 service = f'iam.{config.get("universe_domain")}' 253 error_msg = ( 254 'Identity and Access Management (IAM) API must be enabled. To enable, execute:\n' 255 f'gcloud services enable iam.{config.get("universe_domain")} --project={project_id}' 256 ) 257 raise utils.GcpApiError(response=error_msg, service=service, reason='SERVICE_DISABLED') 258 if not is_enabled(project_id, 'logging'): 259 logging.warning( 260 'Cloud Logging API is not enabled (related rules will be skipped).' 261 ' To enable, execute:\ngcloud services enable' 262 ' logging.%s --project=%s', 263 config.get('universe_domain'), 264 project_id, 265 ) 266 except utils.GcpApiError as err: 267 if 'SERVICE_DISABLED' == err.reason: 268 if f'serviceusage.{config.get("universe_domain")}' == err.service: 269 err.response += ( 270 '\nService Usage API must be enabled. To enable, execute:\n' 271 f'gcloud services enable serviceusage.{config.get("universe_domain")} ' 272 f'--project={project_id}' 273 ) 274 else: 275 logging.error("can't access project %s: %s", project_id, err.message) 276 raise err 277 except exceptions.GoogleAuthError as err: 278 logging.error(err) 279 if _auth_method() == 'adc': 280 logging.error( 281 'Error using application default credentials. Try running: gcloud auth login --update-adc' 282 ) 283 raise err 284 # Plug-in additional authorization verifications 285 hooks.verify_access_hook(project_id)
Verify that the user has access to the project, exit with an error otherwise.