diff --git a/README.md b/README.md index d18e9ab..79fc250 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,14 @@ AWS: - rate limit handling, with exponential backoff. - multi-account sts:assumerole abstraction. - orchestrates all the calls required to fully describe an item. + - control which attributes are returned with flags. GCP: - choosing the best client based on service - client caching - general caching and stats decorators available - basic support for non-specified discovery-API services + - control which attributes are returned with flags. ## Orchestration Supported Technologies @@ -129,7 +131,7 @@ GCP: ### AWS IAM Role - from cloudaux.orchestration.aws.iam.role import get_role + from cloudaux.orchestration.aws.iam.role import get_role, FLAGS # account_number may be extracted from the ARN of the role passed to get_role # if not included in conn. @@ -142,8 +144,15 @@ GCP: role = get_role( dict(arn='arn:aws:iam::000000000000:role/myRole', role_name='myRole'), output='camelized', # optional: {camelized underscored} + flags=FLAGS.ALL, # optional **conn) + # The flags parameter is optional but allows the user to indicate that + # only a subset of the full item description is required. + # IAM Role Flag Options: + # BASE, MANAGED_POLICIES, INLINE_POLICIES, INSTANCE_PROFILES, ALL (default) + # For instance: flags=FLAGS.MANAGED_POLICIES | FLAGS.INSTANCE_PROFILES + # cloudaux makes a number of calls to obtain a full description of the role print(json.dumps(role, indent=4, sort_keys=True)) @@ -162,11 +171,13 @@ GCP: ### GCP IAM Service Account - from cloudaux.orchestration.gcp.iam.serviceaccount import get_serviceaccount_complete + from cloudaux.orchestration.gcp.iam.serviceaccount import get_serviceaccount_complete, FLAGS sa_name = 'projects/my-project-one/serviceAccounts/service-account-key@my-project-one.iam.gserviceaccount.com' - sa = get_serviceaccount_complete(sa_name, **conn_details) + sa = get_serviceaccount_complete(sa_name, flags=FLAGS.ALL, **conn_details) print(json.dumps(sa, indent=4, sort_keys=True)) + # Flag options for Service Accounts are BASE, KEYS, POLICY, ALL (default). + { "DisplayName": "service-account", "Email": "service-account@my-project-one.iam.gserviceaccount.com", diff --git a/cloudaux/__about__.py b/cloudaux/__about__.py index 410750d..9fe510c 100644 --- a/cloudaux/__about__.py +++ b/cloudaux/__about__.py @@ -13,7 +13,7 @@ __summary__ = 'Cloud Auxiliary is a python wrapper and orchestration module for interacting with cloud providers' __uri__ = 'https://github.com/Netflix-Skunkworks/cloudaux' -__version__ = '1.1.9' +__version__ = '1.2.0' __author__ = 'Patrick Kelley' __email__ = 'patrick@netflix.com' diff --git a/cloudaux/aws/README.md b/cloudaux/aws/README.md index b05298e..59aa87c 100644 --- a/cloudaux/aws/README.md +++ b/cloudaux/aws/README.md @@ -15,6 +15,7 @@ Cloud Auxiliary has support for Amazon Web Services. - rate limit handling, with exponential backoff. - multi-account sts:assumerole abstraction. - orchestrates all the calls required to fully describe an item. + - control which attributes are returned flags. ## Orchestration Supported Technologies @@ -72,7 +73,7 @@ Cloud Auxiliary has support for Amazon Web Services. ### Role - from cloudaux.orchestration.aws.iam.role import get_role + from cloudaux.orchestration.aws.iam.role import get_role, FLAGS # account_number may be extracted from the ARN of the role passed to get_role # if not included in conn. @@ -85,7 +86,14 @@ Cloud Auxiliary has support for Amazon Web Services. role = get_role( dict(arn='arn:aws:iam::000000000000:role/myRole', role_name='myRole'), output='camelized', # optional: {camelized underscored} + flags=FLAGS.ALL, # optional **conn) + + # The flags parameter is optional but allows the user to indicate that + # only a subset of the full item description is required. + # IAM Role Flag Options: + # BASE, MANAGED_POLICIES, INLINE_POLICIES, INSTANCE_PROFILES, ALL (default) + # For instance: flags=FLAGS.MANAGED_POLICIES | FLAGS.INSTANCE_PROFILES # cloudaux makes a number of calls to obtain a full description of the role print(json.dumps(role, indent=4, sort_keys=True)) @@ -105,11 +113,19 @@ Cloud Auxiliary has support for Amazon Web Services. ### User - from cloudaux.orchestration.aws.iam.user import get_user + from cloudaux.orchestration.aws.iam.user import get_user, FLAGS user = get_user( dict(arn='arn:aws:iam::000000000000:user/myUser', role_name='myUser'), + flags=FLAGS.ALL, **conn) + + # The flags parameter is optional but allows the user to indicate that + # only a subset of the full item description is required. + # IAM User Flag Options: + # BASE, ACCESS_KEYS, INLINE_POLICIES, MANAGED_POLICIES + # MFA_DEVICES, LOGIN_PROFILE, SIGNING_CERTIFICATES, ALL (default) + # For instance: flags=FLAGS.ACCESS_KEYS | FLAGS.MFA_DEVICES | FLAGS.LOGIN_PROFILE print(json.dumps(user, indent=2, sort_keys=True)) @@ -141,13 +157,21 @@ Cloud Auxiliary has support for Amazon Web Services. ### S3 - from cloudaux.orchestration.aws.s3 import get_bucket + from cloudaux.orchestration.aws.s3 import get_bucket, FLAGS conn = dict( account_number='000000000000', assume_role='SecurityMonkey') - bucket = get_bucket('MyS3Bucket', **conn) + bucket = get_bucket('MyS3Bucket', flags=FLAGS.ALL, **conn) + + # The flags parameter is optional but allows the user to indicate that + # only a subset of the full item description is required. + # S3 Flag Options are: + # BASE, GRANTS, GRANT_REFERENCES, OWNER, LIFECYCLE, LOGGING, POLICY, TAGS + # VERSIONING, WEBSITE, CORS, NOTIFICATIONS, ACCELERATION, REPLICATION + # ANALYTICS, METRICS, INVENTORY, CREATED_DATE, ALL (default) + # For instance: flags=FLAGS.WEBSITE | FLAGS.CORS | FLAGS.POLICY print(json.dumps(bucket, indent=2, sort_keys=True)) diff --git a/cloudaux/gcp/README.md b/cloudaux/gcp/README.md index c36ed0f..4924678 100644 --- a/cloudaux/gcp/README.md +++ b/cloudaux/gcp/README.md @@ -14,6 +14,7 @@ Cloud Auxiliary has support for Google Cloud Platform. - client caching - general caching and stats decorators available - basic support for non-specified discovery-API services + - control which attributes are returned with flags. ## Orchestration Supported Technologies @@ -66,10 +67,12 @@ Cloud Auxiliary has support for Google Cloud Platform. ### IAM Service Account - from cloudaux.orchestration.gcp.iam.serviceaccount import get_serviceaccount_complete + from cloudaux.orchestration.gcp.iam.serviceaccount import get_serviceaccount_complete, FLAGS sa_name = 'projects/my-project-one/serviceAccounts/service-account-key@my-project-one.iam.gserviceaccount.com' - sa = get_serviceaccount_complete(sa_name, **conn_details) + sa = get_serviceaccount_complete(sa_name, flags=FLAGS.ALL, **conn_details) print(json.dumps(sa, indent=4, sort_keys=True)) + + # Flag options for Service Accounts are BASE, KEYS, POLICY, ALL (default). { "DisplayName": "service-account", @@ -103,6 +106,9 @@ Cloud Auxiliary has support for Google Cloud Platform. net_subnet = get_network_and_subnetworks(network=NETWORK, **conn_details) print(json.dumps(net_subnet, indent=4, sort_keys=True)) + # Flag options for Service Accounts are BASE, ALL (default). + # BASE and ALL are equivelant for this service. + { "AutoCreateSubnetworks": true, "CreationTimestamp": "2016-05-09T11:15:47.434-07:00", @@ -132,6 +138,9 @@ Cloud Auxiliary has support for Google Cloud Platform. b = get_bucket(bucket_name=BUCKET, **conn_details) print(json.dumps(b, indent=4, sort_keys=True)) + # Flag options for Service Accounts are BASE, ALL (default). + # BASE and ALL are equivelant for this service. + { "Acl": [ { diff --git a/cloudaux/orchestration/aws/iam/role.py b/cloudaux/orchestration/aws/iam/role.py index 67cea73..9203b41 100644 --- a/cloudaux/orchestration/aws/iam/role.py +++ b/cloudaux/orchestration/aws/iam/role.py @@ -2,8 +2,33 @@ from cloudaux.aws.iam import get_role_managed_policies, get_role_inline_policies, get_role_instance_profiles from cloudaux.orchestration.aws import _get_name_from_structure, _conn_from_args from cloudaux.orchestration import modify +from cloudaux.orchestration.flag_registry import FlagRegistry, Flags +class RoleFlagRegistry(FlagRegistry): + from collections import defaultdict + r = defaultdict(list) + + +FLAGS = Flags('BASE', 'MANAGED_POLICIES', 'INLINE_POLICIES', 'INSTANCE_PROFILES') + + +@RoleFlagRegistry.register(flag=FLAGS.MANAGED_POLICIES, key='managed_policies') +def get_managed_policies(role, **conn): + return get_role_managed_policies(role, **conn) + + +@RoleFlagRegistry.register(flag=FLAGS.INLINE_POLICIES, key='inline_policies') +def get_inline_policies(role, **conn): + return get_role_inline_policies(role, **conn) + + +@RoleFlagRegistry.register(flag=FLAGS.INSTANCE_PROFILES, key='instance_profiles') +def get_instance_profiles(role, **conn): + return get_role_instance_profiles(role, **conn) + + +@RoleFlagRegistry.register(flag=FLAGS.BASE) def _get_base(role, **conn): """ Determine whether the boto get_role call needs to be made or if we already have all that data @@ -27,11 +52,12 @@ def _get_base(role, **conn): # cast CreateDate from a datetime to something JSON serializable. role.update(dict(CreateDate=str(role['CreateDate']))) + role['_version'] = 1 return role -def get_role(role, output='camelized', **conn): +def get_role(role, output='camelized', flags=FLAGS.ALL, **conn): """ Orchestrates all the calls required to fully build out an IAM Role in the following format: @@ -55,13 +81,5 @@ def get_role(role, output='camelized', **conn): """ role = modify(role, 'camelized') _conn_from_args(role, conn) - role = _get_base(role, **conn) - role.update( - { - 'managed_policies': get_role_managed_policies(role, **conn), - 'inline_policies': get_role_inline_policies(role, **conn), - 'instance_profiles': get_role_instance_profiles(role, **conn), - '_version': 1 - } - ) - return modify(role, format=output) \ No newline at end of file + RoleFlagRegistry.build_out(role, flags, role, **conn) + return modify(role, format=output) diff --git a/cloudaux/orchestration/aws/iam/user.py b/cloudaux/orchestration/aws/iam/user.py index 4549f48..74b73a1 100644 --- a/cloudaux/orchestration/aws/iam/user.py +++ b/cloudaux/orchestration/aws/iam/user.py @@ -7,8 +7,48 @@ from cloudaux.aws.iam import get_user_signing_certificates from cloudaux.orchestration.aws import _get_name_from_structure, _conn_from_args from cloudaux.orchestration import modify +from cloudaux.orchestration.flag_registry import FlagRegistry, Flags +class UserFlagRegistry(FlagRegistry): + from collections import defaultdict + r = defaultdict(list) + + +FLAGS = Flags('BASE', 'ACCESS_KEYS', 'INLINE_POLICIES', 'MANAGED_POLICIES', 'MFA_DEVICES', 'LOGIN_PROFILE', 'SIGNING_CERTIFICATES') + + +@UserFlagRegistry.register(flag=FLAGS.ACCESS_KEYS, key='access_keys') +def get_access_keys(user, **conn): + return get_user_access_keys(user, **conn) + + +@UserFlagRegistry.register(flag=FLAGS.INLINE_POLICIES, key='inline_policies') +def get_inline_policies(user, **conn): + return get_user_inline_policies(user, **conn) + + +@UserFlagRegistry.register(flag=FLAGS.MANAGED_POLICIES, key='managed_policies') +def get_managed_policies(user, **conn): + return get_user_managed_policies(user, **conn) + + +@UserFlagRegistry.register(flag=FLAGS.MFA_DEVICES, key='mfa_devices') +def get_mfa_devices(user, **conn): + return get_user_mfa_devices(user, **conn) + + +@UserFlagRegistry.register(flag=FLAGS.LOGIN_PROFILE, key='login_profile') +def get_login_profile(user, **conn): + return get_user_login_profile(user, **conn) + + +@UserFlagRegistry.register(flag=FLAGS.SIGNING_CERTIFICATES, key='signing_certificates') +def get_signing_certificates(user, **conn): + return get_user_signing_certificates(user, **conn) + + +@UserFlagRegistry.register(flag=FLAGS.BASE) def _get_base(user, **conn): base_fields = frozenset(['Arn', 'CreateDate', 'Path', 'UserId', 'UserName']) needs_base = False @@ -27,10 +67,11 @@ def _get_base(user, **conn): if 'PasswordLastUsed' in user: user.update(dict(PasswordLastUsed=str(user['PasswordLastUsed']))) + user['_version'] = 1 return user -def get_user(user, output='camelized', **conn): +def get_user(user, output='camelized', flags=FLAGS.ALL, **conn): """ Orchestrates all the calls required to fully build out an IAM User in the following format: @@ -55,16 +96,5 @@ def get_user(user, output='camelized', **conn): """ user = modify(user, 'camelized') _conn_from_args(user, conn) - user = _get_base(user, **conn) - user.update( - { - 'access_keys': get_user_access_keys(user, **conn), - 'inline_policies': get_user_inline_policies(user, **conn), - 'managed_policies': get_user_managed_policies(user, **conn), - 'mfa_devices': get_user_mfa_devices(user, **conn), - 'login_profile': get_user_login_profile(user, **conn), - 'signing_certificates': get_user_signing_certificates(user, **conn), - '_version': 1 - } - ) + UserFlagRegistry.build_out(user, flags, user, **conn) return modify(user, format=output) diff --git a/cloudaux/orchestration/aws/s3.py b/cloudaux/orchestration/aws/s3.py index 1ef735c..d0cd242 100644 --- a/cloudaux/orchestration/aws/s3.py +++ b/cloudaux/orchestration/aws/s3.py @@ -15,6 +15,7 @@ from cloudaux.aws.s3 import list_bucket_metrics_configurations from cloudaux.aws.s3 import list_bucket_inventory_configurations from cloudaux.orchestration import modify +from cloudaux.orchestration.flag_registry import FlagRegistry, Flags from botocore.exceptions import ClientError import logging @@ -24,7 +25,21 @@ logger = logging.getLogger('cloudaux') -def get_grants(bucket_name, include_owner=False, **conn): +class S3FlagRegistry(FlagRegistry): + from collections import defaultdict + r = defaultdict(list) + + +FLAGS = Flags('BASE', 'GRANTS', 'GRANT_REFERENCES', 'OWNER', 'LIFECYCLE', + 'LOGGING', 'POLICY', 'TAGS', 'VERSIONING', 'WEBSITE', 'CORS', + 'NOTIFICATIONS', 'ACCELERATION', 'REPLICATION', 'ANALYTICS', + 'METRICS', 'INVENTORY', 'CREATED_DATE') + + +@S3FlagRegistry.register( + flag=(FLAGS.GRANTS, FLAGS.GRANT_REFERENCES, FLAGS.OWNER), + key=('grants', 'grant_references', 'owner')) +def get_grants(bucket_name, include_owner=True, **conn): acl = get_bucket_acl(Bucket=bucket_name, **conn) grantees = {} grantee_ref = {} @@ -59,6 +74,7 @@ def get_grants(bucket_name, include_owner=False, **conn): return grantees, grantee_ref +@S3FlagRegistry.register(flag=FLAGS.LIFECYCLE, key='lifecycle_rules') def get_lifecycle(bucket_name, **conn): try: result = get_bucket_lifecycle_configuration(Bucket=bucket_name, **conn) @@ -110,6 +126,7 @@ def get_lifecycle(bucket_name, **conn): return lifecycle_rules +@S3FlagRegistry.register(flag=FLAGS.LOGGING, key='logging') def get_logging(bucket_name, **conn): result = get_bucket_logging(Bucket=bucket_name, **conn) @@ -139,6 +156,7 @@ def get_logging(bucket_name, **conn): return logging_dict +@S3FlagRegistry.register(flag=FLAGS.POLICY, key='policy') def get_policy(bucket_name, **conn): try: result = get_bucket_policy(Bucket=bucket_name, **conn) @@ -149,6 +167,7 @@ def get_policy(bucket_name, **conn): return None +@S3FlagRegistry.register(flag=FLAGS.TAGS, key='tags') def get_tags(bucket_name, **conn): try: result = get_bucket_tagging(Bucket=bucket_name, **conn) @@ -160,6 +179,7 @@ def get_tags(bucket_name, **conn): return {tag['Key']: tag['Value'] for tag in result['TagSet']} +@S3FlagRegistry.register(flag=FLAGS.VERSIONING, key='versioning') def get_versioning(bucket_name, **conn): result = get_bucket_versioning(Bucket=bucket_name, **conn) versioning = {} @@ -171,6 +191,7 @@ def get_versioning(bucket_name, **conn): return versioning +@S3FlagRegistry.register(flag=FLAGS.WEBSITE, key='website') def get_website(bucket_name, **conn): try: result = get_bucket_website(Bucket=bucket_name, **conn) @@ -192,6 +213,7 @@ def get_website(bucket_name, **conn): return website +@S3FlagRegistry.register(flag=FLAGS.CORS, key='cors') def get_cors(bucket_name, **conn): try: result = get_bucket_cors(Bucket=bucket_name, **conn) @@ -219,6 +241,7 @@ def get_cors(bucket_name, **conn): return cors +@S3FlagRegistry.register(flag=FLAGS.NOTIFICATIONS, key='notifications') def get_notifications(bucket_name, **conn): result = get_bucket_notification_configuration(Bucket=bucket_name, **conn) @@ -235,44 +258,54 @@ def get_notifications(bucket_name, **conn): return notifications +@S3FlagRegistry.register(flag=FLAGS.ACCELERATION, key='acceleration') def get_acceleration(bucket_name, **conn): result = get_bucket_accelerate_configuration(Bucket=bucket_name, **conn) - return result.get("Status") +@S3FlagRegistry.register(flag=FLAGS.REPLICATION, key='replication') def get_replication(bucket_name, **conn): try: result = get_bucket_replication(Bucket=bucket_name, **conn) - except ClientError as e: if "ReplicationConfigurationNotFoundError" not in str(e): raise e - return {} - return result["ReplicationConfiguration"] +@S3FlagRegistry.register(flag=FLAGS.CREATED_DATE, key='created') def get_bucket_created(bucket_name, **conn): bucket = get_bucket_resource(bucket_name, **conn) - return str(bucket.creation_date) +@S3FlagRegistry.register(flag=FLAGS.ANALYTICS, key='analytics_configurations') def get_bucket_analytics_configurations(bucket_name, **conn): return list_bucket_analytics_configurations(Bucket=bucket_name, **conn) +@S3FlagRegistry.register(flag=FLAGS.METRICS, key='metrics_configurations') def get_bucket_metrics_configurations(bucket_name, **conn): return list_bucket_metrics_configurations(Bucket=bucket_name, **conn) +@S3FlagRegistry.register(flag=FLAGS.INVENTORY, key='inventory_configurations') def get_bucket_inventory_configurations(bucket_name, **conn): return list_bucket_inventory_configurations(Bucket=bucket_name, **conn) -def get_bucket(bucket_name, output='camelized', include_created=False, **conn): +@S3FlagRegistry.register(flag=FLAGS.BASE) +def get_base(bucket_name, **conn): + return { + 'arn': "arn:aws:s3:::{name}".format(name=bucket_name), + 'region': conn.get('region'), + '_version': 5 + } + + +def get_bucket(bucket_name, output='camelized', include_created=None, flags=FLAGS.ALL ^ FLAGS.CREATED_DATE, **conn): """ Orchestrates all the calls required to fully build out an S3 bucket in the following format: @@ -300,44 +333,26 @@ def get_bucket(bucket_name, output='camelized', include_created=False, **conn): NOTE: "GrantReferences" is an ephemeral field that is not guaranteed to be consistent -- do not base logic off of it - :param include_created: + :param include_created: legacy param moved to FLAGS. :param bucket_name: str bucket name :param output: Determines whether keys should be returned camelized or underscored. + :param flags: By default, set to ALL fields except for FLAGS.CREATED_DATE as obtaining that information is a slow and expensive process. :param conn: dict containing enough information to make a connection to the desired account. Must at least have 'assume_role' key. :return: dict containing a fully built out bucket. """ + if type(include_created) is bool: + # coerce the legacy param "include_created" into the flags param. + if include_created: + flags = flags | FLAGS.CREATED_DATE + else: + flags = flags & ~FLAGS.CREATED_DATE + region = get_bucket_region(Bucket=bucket_name, **conn) if not region: return modify(dict(Error='Unauthorized'), format=output) conn['region'] = region - - grants, grant_refs, owner = get_grants(bucket_name, include_owner=True, **conn) - - result = { - 'arn': "arn:aws:s3:::{name}".format(name=bucket_name), - 'grants': grants, - 'grant_references': grant_refs, - 'owner': owner, - 'lifecycle_rules': get_lifecycle(bucket_name, **conn), - 'logging': get_logging(bucket_name, **conn), - 'policy': get_policy(bucket_name, **conn), - 'region': region, - 'tags': get_tags(bucket_name, **conn), - 'versioning': get_versioning(bucket_name, **conn), - 'website': get_website(bucket_name, **conn), - 'cors': get_cors(bucket_name, **conn), - 'notifications': get_notifications(bucket_name, **conn), - 'acceleration': get_acceleration(bucket_name, **conn), - 'replication': get_replication(bucket_name, **conn), - 'analytics_configurations': get_bucket_analytics_configurations(bucket_name, **conn), - 'metrics_configurations': get_bucket_metrics_configurations(bucket_name, **conn), - 'inventory_configurations': get_bucket_inventory_configurations(bucket_name, **conn), - '_version': 5 - } - - if include_created: - result["Created"] = get_bucket_created(bucket_name, **conn) - + result = dict() + S3FlagRegistry.build_out(result, flags, bucket_name, **conn) return modify(result, format=output) diff --git a/cloudaux/orchestration/flag_registry.py b/cloudaux/orchestration/flag_registry.py new file mode 100644 index 0000000..54e6433 --- /dev/null +++ b/cloudaux/orchestration/flag_registry.py @@ -0,0 +1,98 @@ +from collections import defaultdict + + +class FlagRegistry: + r = defaultdict(list) + + @classmethod + def register(cls, flag, key=None): + """ + optional methods must register their flag with the FlagRegistry. + + The registry: + - stores the flags relevant to each method. + - stores the dictionary key the return value will be saved as. + - for methods with multiple return values, stores the flag/key for each return value. + + Single Return Value Example: + + @FlagRegistry.register(flag=FLAGS.LIFECYCLE, key='lifecycle_rules') + def get_lifecycle(bucket_name, **conn): + pass + + In this example, the `get_lifecycle` method will be called when the `LIFECYCLE` flag is set. + The return value will be appended to the results dictionary with the 'lifecycle_rules' key. + + Multiple Return Value Example: + + @FlagRegistry.register( + flag=(FLAGS.GRANTS, FLAGS.GRANT_REFERENCES, FLAGS.OWNER), + key=('grants', 'grant_references', 'owner')) + def get_grants(bucket_name, include_owner=True, **conn): + pass + + In this example, the `get_grants` method will be called when the `GRANTS`, `GRANT_REFERENCES`, or `OWNER` flags are set. + The return values will be appended to the results dictionary with the corresponding 'grants', 'grant_references', 'owner' key. + """ + def decorator(fn): + flag_list = flag + key_list = key + if type(flag) not in [list, tuple]: + flag_list = [flag] + if type(key) not in [list, tuple]: + key_list = [key] + for idx in xrange(len(flag_list)): + cls.r[fn].append(dict(flag=flag_list[idx], key=key_list[idx], rtv_ix=idx)) + return fn + return decorator + + @classmethod + def build_out(cls, result, flags, *args, **kwargs): + """ + Provided user-supplied flags, `build_out` will find the appropriate methods from the FlagRegistry + and mutate the `result` dictionary. + + :param: result - Dictionary to put results into. + :param: flags - User-supplied combination of FLAGS. (ie. `flags = FLAGS.CORS | FLAGS.WEBSITE`) + :param: *args - Passed on to the method registered in the FlagRegistry + :param: **kwargs - Passed on to the method registered in the FlagRegistry + :return: None. Mutates the results dictionary. + """ + for method, entries in cls.r.items(): + # determine if the method should be called by inspecting the flags. + method_flag = 0 + for entry in entries: + method_flag = method_flag | entry['flag'] + + if not flags & method_flag: + continue + + # At least one of the return values is required. Call the method. + retval = method(*args, **kwargs) + for entry in entries: + if len(entries) > 1: + key_retval = retval[entry['rtv_ix']] + else: + key_retval = retval + if flags & entry['flag']: + if entry['key']: + result.update({entry['key']: key_retval}) + else: + result.update(key_retval) + + +class Flags(object): + def __init__(self, *flags): + from collections import OrderedDict + self.flags = OrderedDict() + self._idx = 0 + for flag in flags: + self.flags[flag] = 2**self._idx + self._idx += 1 + self.flags['ALL'] = 2**self._idx-1 + + def __getattr__(self, k): + return self.flags[k] + + def __repr__(self): + return str(self.flags) \ No newline at end of file diff --git a/cloudaux/orchestration/gcp/gce/network.py b/cloudaux/orchestration/gcp/gce/network.py index 1dca105..fe0b84c 100644 --- a/cloudaux/orchestration/gcp/gce/network.py +++ b/cloudaux/orchestration/gcp/gce/network.py @@ -1,14 +1,28 @@ from cloudaux.gcp.gce.network import get_network, list_subnetworks from cloudaux.orchestration import modify +from cloudaux.orchestration.flag_registry import FlagRegistry, Flags -def get_network_and_subnetworks(network, output='camelized', **conn): - network = get_network(Network=network, **conn) - if network: - result = network - if 'subnetworks' in network: - subnetworks = list_subnetworks(filter='network eq %s' % network['selfLink'], - **conn) - result['subnetworks'] = [modify(s, format=output) for s in subnetworks] - return modify(result, format=output) - else: - return None + +class GCENetworkFlagRegistry(FlagRegistry): + from collections import defaultdict + r = defaultdict(list) + + +FLAGS = Flags('BASE') + + +@GCENetworkFlagRegistry.register(flag=FLAGS.BASE) +def _get_base(network, **conn): + result = get_network(Network=network, **conn) + if 'subnetworks' in result: + subnetworks = list_subnetworks(filter='network eq %s' % result['selfLink'], **conn) + result['subnetworks'] = [modify(s, format=output) for s in subnetworks] + + result['_version'] = 1 + return result + + +def get_network_and_subnetworks(network, output='camelized', flags=FLAGS.ALL, **conn): + result = dict() + GCENetworkFlagRegsitry.build_out(result, flags, network, **conn) + return modify(result, format=output) diff --git a/cloudaux/orchestration/gcp/gcs/bucket.py b/cloudaux/orchestration/gcp/gcs/bucket.py index fce2612..e5ac6de 100644 --- a/cloudaux/orchestration/gcp/gcs/bucket.py +++ b/cloudaux/orchestration/gcp/gcs/bucket.py @@ -8,10 +8,19 @@ from cloudaux.gcp.gcs import get_bucket as fetch_bucket from cloudaux.gcp.utils import strdate from cloudaux.orchestration import modify +from cloudaux.orchestration.flag_registry import FlagRegistry, Flags -def get_bucket(bucket_name, output='camelized', include_created=False, **conn): - result = {} +class GCSFlagRegistry(FlagRegistry): + from collections import defaultdict + r = defaultdict(list) + + +FLAGS = Flags('BASE') + + +@GCSFlagRegistry.register(flag=FLAGS.BASE) +def _get_base(bucket_name, **conn): bucket = fetch_bucket(Bucket=bucket_name, **conn) if not bucket: return modify(dict(Error='Unauthorized'), format=output) @@ -29,7 +38,12 @@ def get_bucket(bucket_name, output='camelized', include_created=False, **conn): result['self_link'] = bucket.self_link result['storage_class'] = bucket.storage_class result['versioning_enabled'] = bucket.versioning_enabled - if include_created: - result['time_created'] = strdate(bucket.time_created) + result['time_created'] = strdate(bucket.time_created) + result['_version'] = 1 + return result + +def get_bucket(bucket_name, output='camelized', flags=FLAGS.ALL, **conn): + result = dict() + GCSFlagRegistry.build_out(result, flags, bucket_name, **conn) return modify(result, format=output) diff --git a/cloudaux/orchestration/gcp/iam/serviceaccount.py b/cloudaux/orchestration/gcp/iam/serviceaccount.py index 09290d8..a2ec99a 100644 --- a/cloudaux/orchestration/gcp/iam/serviceaccount.py +++ b/cloudaux/orchestration/gcp/iam/serviceaccount.py @@ -1,20 +1,40 @@ from cloudaux.gcp.iam import get_iam_policy, get_serviceaccount, get_serviceaccount_keys from cloudaux.orchestration.gcp.utils import list_modify, modify +from cloudaux.orchestration.flag_registry import FlagRegistry, Flags -def get_serviceaccount_complete(service_account, output='camelized', **conn): + +class SAFlagRegistry(FlagRegistry): + from collections import defaultdict + r = defaultdict(list) + + +FLAGS = Flags('BASE', 'KEYS', 'POLICY') + + +@SAFlagRegistry.register(flag=FLAGS.KEYS, key='keys') +def get_keys(service_account, output, **conn): + keys = get_serviceaccount_keys(service_account=service_account, **conn) + if keys: + return list_modify(keys, output) + return None + + +@SAFlagRegistry.register(flag=FLAGS.POLICY, key='policy') +def get_policy(service_account, output, **conn): + policy = get_iam_policy(service_account=service_account, **conn) + if policy: + return list_modify(policy, output) + return None + + +@SAFlagRegistry.register(flag=FLAGS.BASE) +def _get_base(service_account, output, **conn): sa = get_serviceaccount(service_account=service_account, **conn) - if sa: - full_sa = sa - keys = get_serviceaccount_keys(service_account=service_account, **conn) - if keys: - full_sa['keys'] = list_modify(keys, output) - - policy = get_iam_policy(service_account=service_account, **conn) - if policy: - full_sa['policy'] = list_modify(policy, output) - return modify(full_sa, format=output) - else: - return None - - - + sa['_version'] = 1 + return sa + + +def get_serviceaccount_complete(service_account, output='camelized', flags=FLAGS.ALL, **conn): + result = dict() + SAFlagRegistry.build_out(result, flags, service_account, output, **conn) + return modify(result, format=output)