Add service agent outputs to folder and organization (#3436)
* Add service agent outputs to folder and organization * Fix tests
This commit is contained in:
@@ -58,6 +58,13 @@ IGNORED_AGENTS = [
|
||||
'c-PROJECT_NUMBER-IDENTIFIER@gcp-sa-alloydb.iam.gserviceaccount.com'
|
||||
]
|
||||
|
||||
AGENT_NAME_OVERRIDE = {
|
||||
# special case for Cloud Build that has two service agents:
|
||||
# - %s@cloudbuild.gserviceaccount.com
|
||||
# - service-%s@gcp-sa-cloudbuild.iam.gserviceaccount.com
|
||||
'PROJECT_NUMBER@cloudbuild.gserviceaccount.com': 'cloudbuild-sa',
|
||||
}
|
||||
|
||||
E2E_SERVICES = [
|
||||
"alloydb.googleapis.com",
|
||||
"analyticshub.googleapis.com",
|
||||
@@ -112,7 +119,13 @@ class Agent:
|
||||
|
||||
@click.command()
|
||||
@click.option('--e2e', is_flag=True, default=False)
|
||||
def main(e2e=False):
|
||||
@click.option('--organization', 'mode', flag_value='organization',
|
||||
default=False, help='Extract organization-level service agents')
|
||||
@click.option('--folder', 'mode', flag_value='folder', default=False,
|
||||
help='Extract folder-level service agents')
|
||||
@click.option('--project', 'mode', flag_value='project', default=False,
|
||||
help='Extract project-level service agents')
|
||||
def main(mode, e2e=False):
|
||||
page = requests.get(SERVICE_AGENTS_URL).content
|
||||
soup = BeautifulSoup(page, 'html.parser')
|
||||
agents = []
|
||||
@@ -120,32 +133,58 @@ def main(e2e=False):
|
||||
agent_text = content.get_text()
|
||||
col1, col2 = content.find_all('td')
|
||||
|
||||
# skip agents with more than one identity
|
||||
# Extract all identities from col1 (could be in a single <p> or multiple in a <ul>)
|
||||
identities = []
|
||||
if col1.find('ul'):
|
||||
# Multiple identities in a list
|
||||
for li in col1.find_all('li'):
|
||||
identities.append(li.get_text().strip())
|
||||
elif col1.find('p'):
|
||||
# Single identity
|
||||
identities.append(col1.p.get_text().strip())
|
||||
|
||||
# Filter identities based on mode and find the matching one
|
||||
identity = None
|
||||
for id_candidate in identities:
|
||||
if mode == 'project' and 'PROJECT_NUMBER' in id_candidate:
|
||||
identity = id_candidate
|
||||
break
|
||||
elif mode == 'organization' and 'ORGANIZATION_NUMBER' in id_candidate:
|
||||
identity = id_candidate
|
||||
break
|
||||
elif mode == 'folder' and 'FOLDER_NUMBER' in id_candidate:
|
||||
identity = id_candidate
|
||||
break
|
||||
# Skip if no matching identity found for this mode
|
||||
if not identity:
|
||||
continue
|
||||
|
||||
identity = col1.p.get_text()
|
||||
if identity in IGNORED_AGENTS:
|
||||
if identity in IGNORED_AGENTS or '-IDENTIFIER' in identity:
|
||||
continue
|
||||
|
||||
# skip agents that are not contained in a project
|
||||
if 'PROJECT_NUMBER' not in identity:
|
||||
continue
|
||||
|
||||
# special case for Cloud Build that has two service agents:
|
||||
# - %s@cloudbuild.gserviceaccount.com
|
||||
# - service-%s@gcp-sa-cloudbuild.iam.gserviceaccount.com
|
||||
if identity == 'PROJECT_NUMBER@cloudbuild.gserviceaccount.com':
|
||||
name = "cloudbuild-sa" # Cloud Build Service Account
|
||||
if identity in AGENT_NAME_OVERRIDE:
|
||||
name = AGENT_NAME_OVERRIDE[identity]
|
||||
else:
|
||||
# most service agents have the format
|
||||
# service-PROJECT_NUMBER@gcp-sa-SERVICE_NAME.iam.gserviceaccount.com.
|
||||
# service-PROJECT_NUMBER@gcp-sa-SERVICE_NAME.iam.gserviceaccount.com
|
||||
# or service-ORGANIZATION_NUMBER@gcp-sa-SERVICE_NAME.iam.gserviceaccount.com
|
||||
# or service-FOLDER_NUMBER@gcp-sa-SERVICE_NAME.iam.gserviceaccount.com
|
||||
# We keep the SERVICE_NAME part as the agent's name
|
||||
name = identity.split('@')[1].split('.')[0]
|
||||
name = name.removeprefix('gcp-sa-')
|
||||
identity = identity.replace('PROJECT_NUMBER', '${project_number}')
|
||||
identity = identity.replace('.iam.gserviceaccount.',
|
||||
'.${universe_domain}iam.gserviceaccount.')
|
||||
|
||||
# Replace identifiers based on mode
|
||||
if mode == 'project':
|
||||
identity = identity.replace('PROJECT_NUMBER', '${project_number}')
|
||||
identity = identity.replace('.iam.gserviceaccount.',
|
||||
'.${universe_domain}iam.gserviceaccount.')
|
||||
elif mode == 'organization':
|
||||
identity = identity.replace('ORGANIZATION_NUMBER',
|
||||
'${organization_number}')
|
||||
# Skip universe domain replacement for organization agents
|
||||
elif mode == 'folder':
|
||||
identity = identity.replace('FOLDER_NUMBER', '${folder_number}')
|
||||
# Skip universe domain replacement for folder agents
|
||||
|
||||
if name == 'monitoring':
|
||||
# monitoring is deprecated in favor of monitoring-notification.
|
||||
@@ -163,7 +202,7 @@ def main(e2e=False):
|
||||
aliases=ALIASES.get(name, []),
|
||||
)
|
||||
|
||||
if agent.name == 'cloudservices':
|
||||
if mode == 'project' and agent.name == 'cloudservices':
|
||||
# cloudservices role is granted automatically, we don't want to manage it
|
||||
agent.role = None
|
||||
|
||||
@@ -175,6 +214,10 @@ def main(e2e=False):
|
||||
aliases = set(chain.from_iterable(agent.aliases for agent in agents))
|
||||
assert aliases.isdisjoint(names)
|
||||
|
||||
# ensure there are no aliases for folders or organization service agents
|
||||
# mode \in [O, F] => empty(aliases)
|
||||
assert mode not in ['organization', 'folder'] or len(aliases) == 0
|
||||
|
||||
if not e2e:
|
||||
# take the header from the first lines of this file
|
||||
header = open(__file__).readlines()[2:15]
|
||||
|
||||
Reference in New Issue
Block a user