Skip to content

Commit 363a000

Browse files
committed
Add service column to raw_import
ec2 imports now pass region in context, relation mapping takes a context, add support for ec2 security groups ec2 instances now have a relation to security groups Added Key Pairs for AWS EC2. Move transforms into service folders Move keypairs into ec2 folder Fixed KeyPair view. Filter disabled regions. Adding Lamba support. Merge and add db clusters Add async rds support Fix aws organizations mapping, uncomment all resources for debug mode Remove old compose file, we don't need it anymore. Instead, we use sed in build.sh to construct it Fix aws arn function Refactor transform specs partially into dataclasses Image from lightkeeper Added support to serve schema docs. Drop PsaswordPolicy from account in organizations Completed lambda support. ship aws view and transforms from lightkeeper light typing Fix up some typing Added sample query for checking RDS backups disabled. Added VPC generated files. Add relations for instance, add vpcs Make certain correct db session is used Drop prints outside cli
1 parent 241260d commit 363a000

112 files changed

Lines changed: 3589 additions & 2076 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ RUN pip install -r requirements.txt
66
EXPOSE 5000/tcp
77
COPY goldfig.py /app/
88
COPY goldfig /app/goldfig
9+
COPY schema-docs /app/schema-docs
910
LABEL goldfig-cli=0.0.1
1011

1112
ENTRYPOINT ["/app/goldfig.py", "serve"]

Pipfile.lock

Lines changed: 15 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ services:
99
- GOLDFIG_DB_SU_PASSWORD=postgres
1010
depends_on:
1111
- db
12+
ports:
13+
- "5000:5000"
1214
db:
1315
image: postgres
1416
environment:

goldfig/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@ def tail(self) -> str:
3838

3939

4040
class ImportWriter:
41-
def __init__(self, db: Session, import_job_id: int, phase: int):
41+
def __init__(self, db: Session, import_job_id: int, service: str,
42+
phase: int):
4243
self._db = db
4344
self._import_job_id = import_job_id
4445
self._phase = phase
46+
self._service = service
4547

4648
def __call__(self,
4749
path: Union[PathStack, str],
@@ -53,16 +55,17 @@ def __call__(self,
5355
_log.info(f'writing {path} - {resource_name}')
5456
model = RawImport(import_job_id=self._import_job_id,
5557
path=path,
58+
service=self._service,
5659
resource_name=resource_name,
5760
raw=raw,
5861
context=context,
5962
phase=self._phase)
6063
self._db.add(model)
6164

6265

63-
def db_import_writer(db: Session, import_job_id: int,
66+
def db_import_writer(db: Session, import_job_id: int, service: str,
6467
phase: int) -> ImportWriter:
65-
return ImportWriter(db, import_job_id, phase)
68+
return ImportWriter(db, import_job_id, service, phase)
6669

6770

6871
def collect_exceptions(results: List[f.Future]) -> List[str]:

goldfig/aws/__init__.py

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import concurrent.futures as f
22
import logging
33
import os
4-
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple
4+
from typing import Any, Callable, Dict, Generator, Iterator, List, Optional, Tuple
55

66
from botocore.loaders import create_loader
77
from botocore.regions import EndpointResolver
8+
from botocore.exceptions import ClientError
89
import botocore.session as boto
910
from sqlalchemy.orm import Session
1011

1112
from goldfig import collect_exceptions, PathStack
1213
from goldfig.aws.fetch import Proxy
14+
from goldfig.error import GFInternal
1315
from goldfig.models import ImportJob, ProviderAccount, ProviderCredential
1416

1517
ProxyBuilder = Callable[[boto.Session], Proxy]
@@ -99,21 +101,29 @@ def build_aws_import_job(db: Session, proxy_builder: ProxyBuilder,
99101
}
100102

101103

104+
def _require_resp(tup: Optional[Tuple[str, Dict[str, Any]]]) -> Dict[str, Any]:
105+
if tup is None:
106+
raise GFInternal(f'Missing response')
107+
else:
108+
return tup[1]
109+
110+
102111
def _build_org_graph(proxy: Proxy):
103112
org = proxy.service('organizations')
104113
org_resp = org.get('describe_organization')['Organization']
105-
_, roots_resp = org.list('list_roots')
114+
roots_resp = _require_resp(org.list('list_roots'))
106115
roots = roots_resp['Roots']
107116
accounts = {}
108117
organizational_units = {}
109118

110119
def build_graph(parent_id: str, path: List[str]):
111-
_, accounts_resp = org.list('list_accounts_for_parent', ParentId=parent_id)
120+
accounts_resp = _require_resp(
121+
org.list('list_accounts_for_parent', ParentId=parent_id))
112122
next_path = [*path, parent_id]
113123
path_str = '/'.join(next_path)
114124
accounts[path_str] = accounts_resp['Accounts']
115-
_, organizational_units_resp = org.list(
116-
'list_organizational_units_for_parent', ParentId=parent_id)
125+
organizational_units_resp = _require_resp(
126+
org.list('list_organizational_units_for_parent', ParentId=parent_id))
117127
ous = organizational_units_resp['OrganizationalUnits']
118128
if len(ous) > 0:
119129
organizational_units[path_str] = ous
@@ -146,16 +156,29 @@ def load_boto_session(provider_credential: ProviderCredential) -> boto.Session:
146156
return load_boto_session_from_config(config)
147157

148158

149-
def get_region_fn() -> Callable[[str], List[str]]:
159+
def region_is_available(region):
160+
# https://www.cloudar.be/awsblog/checking-if-a-region-is-enabled-using-the-aws-api/
161+
# https://stackoverflow.com/a/56184952
162+
regional_sts = get_boto_session().create_client('sts', region_name=region)
163+
try:
164+
regional_sts.get_caller_identity()
165+
return True
166+
except ClientError:
167+
return False
168+
169+
170+
def get_region_fn() -> Callable[[str], Iterator[str]]:
150171
data_dir = os.path.join(os.path.dirname(boto.__file__), 'data')
151172
loader = create_loader(data_dir)
152173
endpoint_data = loader.load_data('endpoints')
153174
endpoints = EndpointResolver(endpoint_data)
154175

155-
def get_regions_for_service(service: str) -> List[str]:
176+
def get_regions_for_service(service: str) -> Iterator[str]:
156177
sm = loader.load_service_model(service, type_name='service-2')
157178
prefix = sm['metadata'].get('endpointPrefix', service)
158-
return endpoints.get_available_endpoints(prefix, partition_name='aws')
179+
return filter(
180+
lambda region: region_is_available(region),
181+
endpoints.get_available_endpoints(prefix, partition_name='aws'))
159182

160183
return get_regions_for_service
161184

@@ -166,8 +189,15 @@ def run_parallel_session(accounts: List[Tuple[str, ProviderCredential]],
166189
from goldfig.aws.iam import import_account_iam_with_pool
167190
from goldfig.aws.ec2 import import_account_ec2_region_with_pool
168191
from goldfig.aws.elb import import_account_elb_region_with_pool
192+
from goldfig.aws.rds import import_account_rds_region_with_pool
169193
from goldfig.aws.s3 import import_account_s3_with_pool
170-
workers = max(1, os.cpu_count() - 1)
194+
from goldfig.aws.lambdax import import_account_lambda_region_with_pool
195+
196+
cpu_count = os.cpu_count()
197+
if cpu_count is not None:
198+
workers = max(1, cpu_count - 1)
199+
else:
200+
workers = 1
171201
ps = PathStack.from_import_job(import_job)
172202
get_region_for_service = get_region_fn()
173203
with f.ProcessPoolExecutor(max_workers=workers) as pool:
@@ -183,6 +213,13 @@ def run_parallel_session(accounts: List[Tuple[str, ProviderCredential]],
183213
accounts)
184214
results += import_account_s3_with_pool(pool, proxy_builder_args,
185215
import_job.id, ps, accounts)
216+
for region in get_region_for_service('rds'):
217+
results += import_account_rds_region_with_pool(pool, proxy_builder_args,
218+
import_job.id, region, ps,
219+
accounts)
220+
for region in get_region_for_service('lambda'):
221+
results += import_account_lambda_region_with_pool(
222+
pool, proxy_builder_args, import_job.id, region, ps, accounts)
186223
f.wait(results)
187224
# raise any exceptions
188225
return collect_exceptions(results)
@@ -194,6 +231,9 @@ def run_single_session(db: Session, import_job_id: int,
194231
from goldfig.aws.ec2 import import_account_ec2_region_to_db
195232
from goldfig.aws.elb import import_account_elb_region_to_db
196233
from goldfig.aws.s3 import import_account_s3_to_db
234+
from goldfig.aws.lambdax import import_account_lambda_region_to_db
235+
from goldfig.aws.rds import import_account_rds_region_to_db
236+
197237
import_account_iam_to_db(db, import_job_id, proxy_builder)
198238
db.flush()
199239
get_region_for_service = get_region_fn()
@@ -203,6 +243,14 @@ def run_single_session(db: Session, import_job_id: int,
203243
for region in get_region_for_service('elb'):
204244
import_account_elb_region_to_db(db, import_job_id, region, proxy_builder)
205245
db.flush()
246+
for region in get_region_for_service('lambda'):
247+
import_account_lambda_region_to_db(db, import_job_id, region,
248+
proxy_builder)
249+
db.flush()
250+
for region in get_region_for_service('rds'):
251+
import_account_rds_region_to_db(db, import_job_id, region, proxy_builder)
252+
db.flush()
253+
206254
import_account_s3_to_db(db, import_job_id, proxy_builder)
207255

208256

@@ -222,15 +270,15 @@ def account_paths_for_import(
222270
def find_credential(account_id: str) -> ProviderCredential:
223271
return next(cred for cred in creds if cred.scope == account_id)
224272

225-
account_paths = import_job.configuration['aws_graph']['accounts']
273+
account_paths = import_job.aws_config.graph.accounts
226274
results = []
227275
for path, accounts in account_paths.items():
228276
for account in accounts:
229277
try:
230-
cred = find_credential(account['Id'])
278+
cred = find_credential(account.id)
231279
results.append((f'{path}/{cred.scope}', cred))
232280
except StopIteration:
233281
# If we don't have credentials, don't import it
234-
_log.info(f'Skipping account id {account["Id"]}, no credentials')
282+
_log.info(f'Skipping account id {account.id}, no credentials')
235283
continue
236284
return results

goldfig/aws/ec2.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
from goldfig import ImportWriter, db_import_writer, PathStack
88
from goldfig.aws import (account_paths_for_import, load_boto_session,
9-
ProxyBuilder, make_proxy_builder,
10-
load_boto_session_from_config)
9+
ProxyBuilder, make_proxy_builder,
10+
load_boto_session_from_config)
1111
from goldfig.aws.fetch import Proxy, ServiceProxy
1212
from goldfig.bootstrap_db import import_session
1313
from goldfig.models import ImportJob, ProviderCredential
@@ -28,7 +28,7 @@ def _import_ec2_region(
2828
def import_account_ec2_region_to_db(db: Session, import_job_id: int,
2929
region: str, proxy_builder: ProxyBuilder):
3030
job: ImportJob = db.query(ImportJob).get(import_job_id)
31-
writer = db_import_writer(db, job.id, phase=0)
31+
writer = db_import_writer(db, job.id, 'ec2', phase=0)
3232
for path, account in account_paths_for_import(db, job):
3333
boto = load_boto_session(account)
3434
proxy = proxy_builder(boto)
@@ -41,7 +41,7 @@ def _import_ec2_region_to_db(proxy: Proxy, writer: ImportWriter, ps: PathStack,
4141
service_proxy = proxy.service('ec2', region)
4242
ps = ps.scope(region)
4343
for resource_name, raw_resources in _import_ec2_region(service_proxy):
44-
writer(ps, resource_name, raw_resources)
44+
writer(ps, resource_name, raw_resources, {'region': region})
4545

4646

4747
def _async_proxy(ps: PathStack, proxy_builder_args, import_job_id: int,
@@ -50,7 +50,7 @@ def _async_proxy(ps: PathStack, proxy_builder_args, import_job_id: int,
5050
proxy_builder = make_proxy_builder(*proxy_builder_args)
5151
boto = load_boto_session_from_config(config)
5252
proxy = proxy_builder(boto)
53-
writer = db_import_writer(db, import_job_id, phase=0)
53+
writer = db_import_writer(db, import_job_id, 'ec2', phase=0)
5454
f(proxy, writer, ps, region)
5555
db.commit()
5656

goldfig/aws/elb.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
from goldfig import ImportWriter, db_import_writer, PathStack
77
from goldfig.aws import (load_boto_session, account_paths_for_import,
8-
ProxyBuilder, make_proxy_builder,
9-
load_boto_session_from_config)
8+
ProxyBuilder, make_proxy_builder,
9+
load_boto_session_from_config)
1010
from goldfig.aws.fetch import Proxy, ServiceProxy
1111
from goldfig.bootstrap_db import import_session
1212
from goldfig.models import ImportJob, ProviderCredential
@@ -54,7 +54,7 @@ def _async_proxy(ps: PathStack, proxy_builder_args, import_job_id: int,
5454
proxy_builder = make_proxy_builder(*proxy_builder_args)
5555
boto = load_boto_session_from_config(config)
5656
proxy = proxy_builder(boto)
57-
writer = db_import_writer(db, import_job_id, phase=0)
57+
writer = db_import_writer(db, import_job_id, 'elb', phase=0)
5858
f(proxy, writer, ps, region)
5959
db.commit()
6060

@@ -82,7 +82,7 @@ def queue_job(fn, path: str, account: ProviderCredential):
8282
def import_account_elb_region_to_db(db: Session, import_job_id: int,
8383
region: str, proxy_builder: ProxyBuilder):
8484
job: ImportJob = db.query(ImportJob).get(import_job_id)
85-
writer = db_import_writer(db, job.id, phase=0)
85+
writer = db_import_writer(db, job.id, 'elb', phase=0)
8686
for path, account in account_paths_for_import(db, job):
8787
boto = load_boto_session(account)
8888
proxy = proxy_builder(boto)

0 commit comments

Comments
 (0)