gcp-platforms-auto 0.7.0__py3-none-any.whl → 0.7.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gcp_platforms_auto/__init__.py +15 -1
- gcp_platforms_auto/db.py +277 -0
- gcp_platforms_auto/iam.py +212 -0
- {gcp_platforms_auto-0.7.0.dist-info → gcp_platforms_auto-0.7.5.dist-info}/METADATA +5 -1
- gcp_platforms_auto-0.7.5.dist-info/RECORD +8 -0
- {gcp_platforms_auto-0.7.0.dist-info → gcp_platforms_auto-0.7.5.dist-info}/WHEEL +1 -1
- gcp_platforms_auto-0.7.0.dist-info/RECORD +0 -6
- {gcp_platforms_auto-0.7.0.dist-info → gcp_platforms_auto-0.7.5.dist-info}/top_level.txt +0 -0
gcp_platforms_auto/__init__.py
CHANGED
|
@@ -1 +1,15 @@
|
|
|
1
|
-
from .git import get_github_app_token, generate_github_path, get_repo, git_push, create_repo
|
|
1
|
+
from .git import get_github_app_token, generate_github_path, get_repo, git_push, create_repo
|
|
2
|
+
from .db import (
|
|
3
|
+
get_sql_engine,
|
|
4
|
+
execute_query,
|
|
5
|
+
cleanup_connector,
|
|
6
|
+
create_tables,
|
|
7
|
+
save_model,
|
|
8
|
+
get_all,
|
|
9
|
+
get_by_id,
|
|
10
|
+
get_by_filter,
|
|
11
|
+
get_one,
|
|
12
|
+
update_model,
|
|
13
|
+
delete_model,
|
|
14
|
+
Base
|
|
15
|
+
)
|
gcp_platforms_auto/db.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
import sqlalchemy
|
|
4
|
+
from sqlalchemy.orm import sessionmaker, scoped_session, declarative_base
|
|
5
|
+
from sqlalchemy import text
|
|
6
|
+
|
|
7
|
+
# Configure the logger
|
|
8
|
+
logger = logging.getLogger("uvicorn")
|
|
9
|
+
logger.setLevel(logging.INFO)
|
|
10
|
+
|
|
11
|
+
Base = declarative_base()
|
|
12
|
+
|
|
13
|
+
def get_sql_engine(db_host, db_user, db_pass, db_name, db_port=5432) -> sqlalchemy.engine.base.Engine:
|
|
14
|
+
"""Initializes a TCP connection pool for a Cloud SQL instance of Postgres.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
db_host: Database host address (e.g., '127.0.0.1')
|
|
18
|
+
db_user: Database username
|
|
19
|
+
db_pass: Database password
|
|
20
|
+
db_name: Database name
|
|
21
|
+
db_port: Database port (default: 5432)
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
SQLAlchemy Engine instance
|
|
25
|
+
"""
|
|
26
|
+
logger.info(f"Connecting to database '{db_name}' at {db_host}:{db_port}")
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
pool = sqlalchemy.create_engine(
|
|
30
|
+
# Equivalent URL:
|
|
31
|
+
# postgresql+pg8000://<db_user>:<db_pass>@<db_host>:<db_port>/<db_name>
|
|
32
|
+
sqlalchemy.engine.url.URL.create(
|
|
33
|
+
drivername="postgresql+pg8000",
|
|
34
|
+
username=db_user,
|
|
35
|
+
password=db_pass,
|
|
36
|
+
host=db_host,
|
|
37
|
+
port=db_port,
|
|
38
|
+
database=db_name,
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
logger.info("Successfully created database connection pool.")
|
|
42
|
+
return pool
|
|
43
|
+
except Exception as e:
|
|
44
|
+
logger.exception(f"Error connecting to database: {str(e)}")
|
|
45
|
+
raise e
|
|
46
|
+
|
|
47
|
+
def get_session(engine):
|
|
48
|
+
"""Creates and returns a new session for the provided engine.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
engine: SQLAlchemy Engine instance
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
SQLAlchemy Session instance
|
|
55
|
+
"""
|
|
56
|
+
Session = sessionmaker(bind=engine)
|
|
57
|
+
return Session()
|
|
58
|
+
|
|
59
|
+
def execute_query(engine, query_str: str, params: dict = None):
|
|
60
|
+
"""Executes a raw SQL query.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
engine: SQLAlchemy Engine instance
|
|
64
|
+
query_str: SQL query string
|
|
65
|
+
params: Dictionary of query parameters (optional)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Query result
|
|
69
|
+
"""
|
|
70
|
+
session = get_session(engine)
|
|
71
|
+
try:
|
|
72
|
+
logger.info(f"Executing query: {query_str[:100]}...")
|
|
73
|
+
result = session.execute(text(query_str), params or {})
|
|
74
|
+
session.commit()
|
|
75
|
+
logger.info("Query executed successfully.")
|
|
76
|
+
return result
|
|
77
|
+
except Exception as e:
|
|
78
|
+
session.rollback()
|
|
79
|
+
logger.exception(f"Error executing query: {str(e)}")
|
|
80
|
+
raise e
|
|
81
|
+
finally:
|
|
82
|
+
session.close()
|
|
83
|
+
|
|
84
|
+
def create_tables(engine, base_class=Base):
|
|
85
|
+
"""Creates all tables defined in the SQLAlchemy Base metadata.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
engine: SQLAlchemy Engine instance
|
|
89
|
+
base_class: SQLAlchemy declarative base class (default: Base)
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
logger.info("Creating database tables...")
|
|
93
|
+
base_class.metadata.create_all(engine)
|
|
94
|
+
logger.info("Successfully created database tables.")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.exception(f"Error creating tables: {str(e)}")
|
|
97
|
+
raise e
|
|
98
|
+
|
|
99
|
+
def save_model(engine, model_instance):
|
|
100
|
+
"""Saves a single model instance to the database.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
engine: SQLAlchemy Engine instance
|
|
104
|
+
model_instance: SQLAlchemy model instance to save
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The saved model instance with refreshed data
|
|
108
|
+
"""
|
|
109
|
+
session = get_session(engine)
|
|
110
|
+
try:
|
|
111
|
+
logger.info(f"Saving model instance: {type(model_instance).__name__}")
|
|
112
|
+
session.add(model_instance)
|
|
113
|
+
session.commit()
|
|
114
|
+
session.refresh(model_instance)
|
|
115
|
+
logger.info("Successfully saved model instance.")
|
|
116
|
+
return model_instance
|
|
117
|
+
except Exception as e:
|
|
118
|
+
session.rollback()
|
|
119
|
+
logger.exception(f"Error saving model: {str(e)}")
|
|
120
|
+
raise e
|
|
121
|
+
finally:
|
|
122
|
+
session.close()
|
|
123
|
+
|
|
124
|
+
def get_all(engine, model_class):
|
|
125
|
+
"""Retrieves all records for a given model class.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
engine: SQLAlchemy Engine instance
|
|
129
|
+
model_class: SQLAlchemy model class
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
List of all records for the model
|
|
133
|
+
"""
|
|
134
|
+
session = get_session(engine)
|
|
135
|
+
try:
|
|
136
|
+
logger.info(f"Retrieving all records for {model_class.__name__}")
|
|
137
|
+
results = session.query(model_class).all()
|
|
138
|
+
logger.info(f"Retrieved {len(results)} records.")
|
|
139
|
+
return results
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.exception(f"Error retrieving records: {str(e)}")
|
|
142
|
+
raise e
|
|
143
|
+
finally:
|
|
144
|
+
session.close()
|
|
145
|
+
|
|
146
|
+
def get_by_id(engine, model_class, record_id):
|
|
147
|
+
"""Retrieves a single record by its primary key ID.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
engine: SQLAlchemy Engine instance
|
|
151
|
+
model_class: SQLAlchemy model class
|
|
152
|
+
record_id: Primary key value to search for
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Model instance if found, None otherwise
|
|
156
|
+
"""
|
|
157
|
+
session = get_session(engine)
|
|
158
|
+
try:
|
|
159
|
+
logger.info(f"Retrieving {model_class.__name__} with ID: {record_id}")
|
|
160
|
+
result = session.query(model_class).get(record_id)
|
|
161
|
+
if result:
|
|
162
|
+
logger.info(f"Found record with ID: {record_id}")
|
|
163
|
+
else:
|
|
164
|
+
logger.info(f"No record found with ID: {record_id}")
|
|
165
|
+
return result
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.exception(f"Error retrieving record by ID: {str(e)}")
|
|
168
|
+
raise e
|
|
169
|
+
finally:
|
|
170
|
+
session.close()
|
|
171
|
+
|
|
172
|
+
def get_by_filter(engine, model_class, **filters):
|
|
173
|
+
"""Retrieves records matching the provided filter criteria.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
engine: SQLAlchemy Engine instance
|
|
177
|
+
model_class: SQLAlchemy model class
|
|
178
|
+
**filters: Keyword arguments for filtering (e.g., name="John", age=30)
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
List of matching records
|
|
182
|
+
"""
|
|
183
|
+
session = get_session(engine)
|
|
184
|
+
try:
|
|
185
|
+
logger.info(f"Filtering {model_class.__name__} with criteria: {filters}")
|
|
186
|
+
results = session.query(model_class).filter_by(**filters).all()
|
|
187
|
+
logger.info(f"Found {len(results)} matching records.")
|
|
188
|
+
return results
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.exception(f"Error filtering records: {str(e)}")
|
|
191
|
+
raise e
|
|
192
|
+
finally:
|
|
193
|
+
session.close()
|
|
194
|
+
|
|
195
|
+
def get_one(engine, model_class, **filters):
|
|
196
|
+
"""Retrieves a single record matching the provided filter criteria.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
engine: SQLAlchemy Engine instance
|
|
200
|
+
model_class: SQLAlchemy model class
|
|
201
|
+
**filters: Keyword arguments for filtering (e.g., email="user@example.com")
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
First matching model instance if found, None otherwise
|
|
205
|
+
"""
|
|
206
|
+
session = get_session(engine)
|
|
207
|
+
try:
|
|
208
|
+
logger.info(f"Retrieving single {model_class.__name__} with criteria: {filters}")
|
|
209
|
+
result = session.query(model_class).filter_by(**filters).first()
|
|
210
|
+
if result:
|
|
211
|
+
logger.info(f"Found matching record.")
|
|
212
|
+
else:
|
|
213
|
+
logger.info(f"No matching record found.")
|
|
214
|
+
return result
|
|
215
|
+
except Exception as e:
|
|
216
|
+
logger.exception(f"Error retrieving single record: {str(e)}")
|
|
217
|
+
raise e
|
|
218
|
+
finally:
|
|
219
|
+
session.close()
|
|
220
|
+
|
|
221
|
+
def update_model(engine, model_instance):
|
|
222
|
+
"""Updates an existing model instance in the database.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
engine: SQLAlchemy Engine instance
|
|
226
|
+
model_instance: SQLAlchemy model instance with updated values
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
The updated model instance with refreshed data
|
|
230
|
+
"""
|
|
231
|
+
session = get_session(engine)
|
|
232
|
+
try:
|
|
233
|
+
logger.info(f"Updating model instance: {type(model_instance).__name__}")
|
|
234
|
+
session.merge(model_instance)
|
|
235
|
+
session.commit()
|
|
236
|
+
logger.info("Successfully updated model instance.")
|
|
237
|
+
return model_instance
|
|
238
|
+
except Exception as e:
|
|
239
|
+
session.rollback()
|
|
240
|
+
logger.exception(f"Error updating model: {str(e)}")
|
|
241
|
+
raise e
|
|
242
|
+
finally:
|
|
243
|
+
session.close()
|
|
244
|
+
|
|
245
|
+
def delete_model(engine, model_instance):
|
|
246
|
+
"""Deletes a model instance from the database.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
engine: SQLAlchemy Engine instance
|
|
250
|
+
model_instance: SQLAlchemy model instance to delete
|
|
251
|
+
"""
|
|
252
|
+
session = get_session(engine)
|
|
253
|
+
try:
|
|
254
|
+
logger.info(f"Deleting model instance: {type(model_instance).__name__}")
|
|
255
|
+
session.delete(model_instance)
|
|
256
|
+
session.commit()
|
|
257
|
+
logger.info("Successfully deleted model instance.")
|
|
258
|
+
except Exception as e:
|
|
259
|
+
session.rollback()
|
|
260
|
+
logger.exception(f"Error deleting model: {str(e)}")
|
|
261
|
+
raise e
|
|
262
|
+
finally:
|
|
263
|
+
session.close()
|
|
264
|
+
|
|
265
|
+
def cleanup_connector(engine):
|
|
266
|
+
"""Disposes the connection pool for the provided engine.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
engine: SQLAlchemy Engine instance to dispose
|
|
270
|
+
"""
|
|
271
|
+
try:
|
|
272
|
+
logger.info("Disposing database connection pool...")
|
|
273
|
+
engine.dispose()
|
|
274
|
+
logger.info("Successfully disposed connection pool.")
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logger.exception(f"Error disposing connection pool: {str(e)}")
|
|
277
|
+
raise e
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""IAM access management utilities for GCP."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import google.cloud.logging
|
|
5
|
+
from google.cloud import asset_v1, resourcemanager_v3
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
# Initialize Google Cloud Logging
|
|
9
|
+
client = google.cloud.logging.Client()
|
|
10
|
+
client.setup_logging()
|
|
11
|
+
|
|
12
|
+
# Configure the logger
|
|
13
|
+
logger = logging.getLogger("uvicorn")
|
|
14
|
+
logger.setLevel(logging.INFO)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get_identity_string(email: str) -> str:
|
|
18
|
+
"""
|
|
19
|
+
Helper function to construct the proper identity string.
|
|
20
|
+
Automatically detects if the email is a service account or user.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
email: Email address (user or service account)
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
str: Properly formatted identity string
|
|
27
|
+
"""
|
|
28
|
+
if ".gserviceaccount.com" in email.lower():
|
|
29
|
+
return f"serviceAccount:{email}"
|
|
30
|
+
else:
|
|
31
|
+
return f"user:{email}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def check_user_has_role(
|
|
35
|
+
project_id: str,
|
|
36
|
+
user_email: str,
|
|
37
|
+
organization_id: str,
|
|
38
|
+
role: str = "roles/owner",
|
|
39
|
+
expand_groups: bool = True
|
|
40
|
+
) -> bool:
|
|
41
|
+
"""
|
|
42
|
+
Check if a user or service account has a specific role in a GCP project.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
user_email: Email of the user or service account to check
|
|
46
|
+
(e.g., 'oshasha10@dev.sky320.com' or 'my-sa@project.iam.gserviceaccount.com')
|
|
47
|
+
role: Role to check (e.g., 'roles/owner', 'roles/editor')
|
|
48
|
+
project_id: GCP project ID (e.g., 'sky-starfi-mam-res-gcpro-1')
|
|
49
|
+
organization_id: GCP organization ID (e.g., '111111111111')
|
|
50
|
+
expand_groups: Whether to expand group memberships (default: True)
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
bool: True if the user/service account has the role in the project, False otherwise
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
>>> has_access = check_user_has_role(
|
|
57
|
+
... user_email='oshasha10@dev.sky320.com',
|
|
58
|
+
... role='roles/owner',
|
|
59
|
+
... project_id='sky-starfi-mam-res-gcpro-1'
|
|
60
|
+
... )
|
|
61
|
+
"""
|
|
62
|
+
client = asset_v1.AssetServiceClient()
|
|
63
|
+
|
|
64
|
+
# Construct the full resource name
|
|
65
|
+
scope = f"organizations/{organization_id}"
|
|
66
|
+
full_resource_name = f"//cloudresourcemanager.googleapis.com/projects/{project_id}"
|
|
67
|
+
identity = _get_identity_string(user_email)
|
|
68
|
+
|
|
69
|
+
# Build the request
|
|
70
|
+
request = asset_v1.AnalyzeIamPolicyRequest(
|
|
71
|
+
analysis_query=asset_v1.IamPolicyAnalysisQuery(
|
|
72
|
+
scope=scope,
|
|
73
|
+
resource_selector=asset_v1.IamPolicyAnalysisQuery.ResourceSelector(
|
|
74
|
+
full_resource_name=full_resource_name
|
|
75
|
+
),
|
|
76
|
+
identity_selector=asset_v1.IamPolicyAnalysisQuery.IdentitySelector(
|
|
77
|
+
identity=identity
|
|
78
|
+
),
|
|
79
|
+
access_selector=asset_v1.IamPolicyAnalysisQuery.AccessSelector(
|
|
80
|
+
roles=[role]
|
|
81
|
+
),
|
|
82
|
+
options=asset_v1.IamPolicyAnalysisQuery.Options(
|
|
83
|
+
expand_groups=expand_groups,
|
|
84
|
+
expand_roles=True,
|
|
85
|
+
# expand_resources=True
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
# Execute the analysis
|
|
92
|
+
logger.info(f"Checking if {user_email} has role {role} in project {project_id}")
|
|
93
|
+
response = client.analyze_iam_policy(request=request)
|
|
94
|
+
|
|
95
|
+
# Check if any results were returned
|
|
96
|
+
# If the user has the role, there will be analysis results
|
|
97
|
+
if response.main_analysis and response.main_analysis.analysis_results:
|
|
98
|
+
logger.info(f"{user_email} has role {role} in project {project_id}")
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
logger.info(f"{user_email} does not have role {role} in project {project_id}")
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.exception(f"Error analyzing IAM policy: {e}")
|
|
106
|
+
raise
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _get_all_folders_recursive(parent: str, folders_client) -> list:
|
|
110
|
+
"""
|
|
111
|
+
Recursively get all folders under a parent (organization or folder).
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
parent: Parent resource (e.g., 'organizations/123' or 'folders/456')
|
|
115
|
+
folders_client: FoldersClient instance
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
list: List of all folder resource names
|
|
119
|
+
"""
|
|
120
|
+
all_folders = []
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
request = resourcemanager_v3.ListFoldersRequest(parent=parent)
|
|
124
|
+
folders = folders_client.list_folders(request=request)
|
|
125
|
+
|
|
126
|
+
for folder in folders:
|
|
127
|
+
folder_name = folder.name # Format: folders/123
|
|
128
|
+
all_folders.append(folder_name)
|
|
129
|
+
logger.info(f"Found folder: {folder_name}")
|
|
130
|
+
|
|
131
|
+
# Recursively get subfolders
|
|
132
|
+
subfolders = _get_all_folders_recursive(folder_name, folders_client)
|
|
133
|
+
all_folders.extend(subfolders)
|
|
134
|
+
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.warning(f"Error listing folders under {parent}: {e}")
|
|
137
|
+
|
|
138
|
+
return all_folders
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def get_projects_with_role(
|
|
142
|
+
user_email: str,
|
|
143
|
+
organization_id: str,
|
|
144
|
+
role: str = "roles/owner",
|
|
145
|
+
expand_groups: bool = True
|
|
146
|
+
) -> list:
|
|
147
|
+
"""
|
|
148
|
+
Get all projects where a user or service account has a specific role.
|
|
149
|
+
Searches recursively through all folders in the organization.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
user_email: Email of the user or service account to check
|
|
153
|
+
(e.g., 'oshasha10@dev.sky320.com' or 'my-sa@project.iam.gserviceaccount.com')
|
|
154
|
+
organization_id: GCP organization ID (e.g., '111111111111')
|
|
155
|
+
role: Role to check (e.g., 'roles/owner', 'roles/editor')
|
|
156
|
+
expand_groups: Whether to expand group memberships (default: True)
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
list: List of project IDs where the user/service account has the specified role
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
>>> projects = get_projects_with_role(
|
|
163
|
+
... user_email='oshasha10@dev.sky320.com',
|
|
164
|
+
... organization_id='111111111111',
|
|
165
|
+
... role='roles/owner'
|
|
166
|
+
... )
|
|
167
|
+
"""
|
|
168
|
+
logger.info(f"Fetching all projects in organization {organization_id} (including folders)")
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
# Initialize clients
|
|
172
|
+
projects_client = resourcemanager_v3.ProjectsClient()
|
|
173
|
+
folders_client = resourcemanager_v3.FoldersClient()
|
|
174
|
+
|
|
175
|
+
# Get all folders recursively
|
|
176
|
+
org_parent = f"organizations/{organization_id}"
|
|
177
|
+
all_folders = _get_all_folders_recursive(org_parent, folders_client)
|
|
178
|
+
logger.info(f"Found {len(all_folders)} folder(s) in organization")
|
|
179
|
+
|
|
180
|
+
# Create list of all parents to search (organization + all folders)
|
|
181
|
+
parents_to_search = [org_parent] + all_folders
|
|
182
|
+
|
|
183
|
+
# Collect all projects from all parents
|
|
184
|
+
all_projects = []
|
|
185
|
+
for parent in parents_to_search:
|
|
186
|
+
request = resourcemanager_v3.ListProjectsRequest(parent=parent)
|
|
187
|
+
projects = projects_client.list_projects(request=request)
|
|
188
|
+
for project in projects:
|
|
189
|
+
all_projects.append(project.project_id)
|
|
190
|
+
|
|
191
|
+
logger.info(f"Found {len(all_projects)} total project(s) across organization and folders")
|
|
192
|
+
|
|
193
|
+
# Filter projects where user has the specified role
|
|
194
|
+
matching_projects = []
|
|
195
|
+
|
|
196
|
+
for project_id in all_projects:
|
|
197
|
+
# Check if user has the role in this project
|
|
198
|
+
if check_user_has_role(
|
|
199
|
+
project_id=project_id,
|
|
200
|
+
user_email=user_email,
|
|
201
|
+
organization_id=organization_id,
|
|
202
|
+
role=role,
|
|
203
|
+
expand_groups=expand_groups
|
|
204
|
+
):
|
|
205
|
+
matching_projects.append(project_id)
|
|
206
|
+
|
|
207
|
+
logger.info(f"Checked {len(all_projects)} projects, found {len(matching_projects)} where {user_email} has role {role}")
|
|
208
|
+
return sorted(matching_projects)
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.exception(f"Error fetching projects: {e}")
|
|
212
|
+
raise
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gcp_platforms_auto
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.5
|
|
4
4
|
Summary: A brief description of your package
|
|
5
5
|
Author-email: ofir4858 <ofirshasha10@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -12,6 +12,10 @@ Description-Content-Type: text/markdown
|
|
|
12
12
|
Requires-Dist: requests
|
|
13
13
|
Requires-Dist: pyjwt
|
|
14
14
|
Requires-Dist: google-cloud-logging
|
|
15
|
+
Requires-Dist: google-cloud-asset
|
|
16
|
+
Requires-Dist: google-cloud-resource-manager
|
|
15
17
|
Requires-Dist: gitpython
|
|
18
|
+
Requires-Dist: sqlalchemy
|
|
19
|
+
Requires-Dist: pg8000
|
|
16
20
|
|
|
17
21
|
# gcp_sdk
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
gcp_platforms_auto/__init__.py,sha256=jpdcmFArf_rwuQ0Cn26GUq5_DxSbGF6oMwxSKYu7ELY,322
|
|
2
|
+
gcp_platforms_auto/db.py,sha256=jE5nwmqVHcxT4m6-meUgUz4V4DM8M_sMmeTpvKr2Z2Y,8768
|
|
3
|
+
gcp_platforms_auto/git.py,sha256=NnLDfRzzrzbm9yekepc-qgu8ejYmjNxQ4VDlW46gG2o,5508
|
|
4
|
+
gcp_platforms_auto/iam.py,sha256=h3NytTLB04eO6TvxVwBN2vWJOElGQaEkWeoNkRD6BVI,7653
|
|
5
|
+
gcp_platforms_auto-0.7.5.dist-info/METADATA,sha256=IJDfoM9XSyYJlKAk7dEGE1o7noOIDBpF6p0j5rAoFfo,621
|
|
6
|
+
gcp_platforms_auto-0.7.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
7
|
+
gcp_platforms_auto-0.7.5.dist-info/top_level.txt,sha256=4q-ofPMmvBaTnIbAzs-Wp_OwheAVxxmJ1fW9vl3-kyE,19
|
|
8
|
+
gcp_platforms_auto-0.7.5.dist-info/RECORD,,
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
gcp_platforms_auto/__init__.py,sha256=7CmPCQxUqAvZTHKCREhL2b4MKD8Pn7FnKZUcMxYC09g,92
|
|
2
|
-
gcp_platforms_auto/git.py,sha256=NnLDfRzzrzbm9yekepc-qgu8ejYmjNxQ4VDlW46gG2o,5508
|
|
3
|
-
gcp_platforms_auto-0.7.0.dist-info/METADATA,sha256=QaX8CCdAbVNESyK1jCJU-ykpKXYtXQUcdFQYh9La0R0,494
|
|
4
|
-
gcp_platforms_auto-0.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
5
|
-
gcp_platforms_auto-0.7.0.dist-info/top_level.txt,sha256=4q-ofPMmvBaTnIbAzs-Wp_OwheAVxxmJ1fW9vl3-kyE,19
|
|
6
|
-
gcp_platforms_auto-0.7.0.dist-info/RECORD,,
|
|
File without changes
|