├── .github └── workflows │ ├── python-package.yml │ ├── python-publish.yml │ └── python-validate.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── requirements.txt ├── setup.py ├── skew ├── __init__.py ├── _version ├── arn │ └── __init__.py ├── awsclient.py ├── config.py ├── exception.py └── resources │ ├── __init__.py │ ├── aws │ ├── __init__.py │ ├── acm.py │ ├── apigateway.py │ ├── autoscaling.py │ ├── cloudformation.py │ ├── cloudfront.py │ ├── cloudtrail.py │ ├── cloudwatch.py │ ├── dynamodb.py │ ├── ec2.py │ ├── elasticache.py │ ├── elasticbeanstalk.py │ ├── elb.py │ ├── es.py │ ├── firehose.py │ ├── iam.py │ ├── kinesis.py │ ├── lambda.py │ ├── rds.py │ ├── redshift.py │ ├── route53.py │ ├── s3.py │ ├── sns.py │ └── sqs.py │ └── resource.py ├── tests ├── __init__.py └── unit │ ├── __init__.py │ ├── cfg │ ├── aws_credentials │ └── skew.yml │ ├── responses │ ├── addresses │ │ └── ec2.DescribeAddresses_1.json │ ├── alarms │ │ └── monitoring.DescribeAlarms_1.json │ ├── buckets │ │ ├── s3.GetBucketLocation_1.json │ │ ├── s3.GetBucketLocation_2.json │ │ ├── s3.GetBucketLocation_3.json │ │ ├── s3.GetBucketLocation_4.json │ │ ├── s3.GetBucketLocation_5.json │ │ └── s3.ListBuckets_1.json │ ├── certificates │ │ ├── acm.DescribeCertificate_1.json │ │ ├── acm.DescribeCertificate_2.json │ │ ├── acm.ListCertificates_1.json │ │ ├── acm.ListTagsForCertificate_1.json │ │ └── acm.ListTagsForCertificate_2.json │ ├── customergateways │ │ └── ec2.DescribeCustomerGateways_1.json │ ├── elbs │ │ ├── elasticloadbalancing.DescribeLoadBalancerAttributes_1.json │ │ ├── elasticloadbalancing.DescribeLoadBalancerPolicies_1.json │ │ ├── elasticloadbalancing.DescribeLoadBalancers_1.json │ │ └── elasticloadbalancing.DescribeTags_1.json │ ├── environments │ │ └── elasticbeanstalk.DescribeEnvironments_1.json │ ├── flowlogs │ │ └── ec2.DescribeFlowLogs_1.json │ ├── groups │ │ └── iam.ListGroups_1.json │ ├── instances_1 │ │ └── ec2.DescribeInstances_1.json │ ├── instances_2 │ │ └── ec2.DescribeInstances_1.json │ ├── instances_3 │ │ └── ec2.DescribeInstances_1.json │ ├── keypairs │ │ └── ec2.DescribeKeyPairs_1.json │ ├── launchtemplates │ │ └── ec2.DescribeLaunchTemplates_1.json │ ├── loggroups │ │ ├── logs.DescribeLogGroups_1.json │ │ ├── logs.DescribeLogStreams_1.json │ │ ├── logs.DescribeMetricFilters_1.json │ │ ├── logs.DescribeQueries_1.json │ │ ├── logs.DescribeSubscriptionFilters_1.json │ │ └── logs.ListTagsLogGroup_1.json │ ├── natgateways │ │ └── ec2.DescribeNatGateways_1.json │ ├── networkacls │ │ └── ec2.DescribeNetworkAcls_1.json │ ├── peeringconnections │ │ └── ec2.DescribeVpcPeeringConnections_1.json │ ├── routetables │ │ └── ec2.DescribeRouteTables_1.json │ ├── secgrp │ │ └── ec2.DescribeSecurityGroups_1.json │ ├── stacks │ │ ├── cloudformation.DescribeStackResources_1.json │ │ └── cloudformation.DescribeStacks_1.json │ ├── trail │ │ ├── cloudtrail.DescribeTrails_1.json │ │ └── cloudtrail.ListTags_1.json │ ├── users │ │ ├── iam.GetUserPolicy_1.json │ │ ├── iam.GetUser_1.json │ │ ├── iam.ListAccessKeys_1.json │ │ ├── iam.ListAttachedUserPolicies_1.json │ │ ├── iam.ListGroupsForUser_1.json │ │ ├── iam.ListSSHPublicKeys_1.json │ │ ├── iam.ListUserPolicies_1.json │ │ ├── iam.ListUserTags_1.json │ │ └── iam.ListUsers_1.json │ ├── volumes │ │ └── ec2.DescribeVolumes_1.json │ └── vpcs │ │ ├── ec2.DescribeVpcPeeringConnections_1.json │ │ └── ec2.DescribeVpcs_1.json │ ├── test_arn.py │ ├── test_arn_component.py │ └── test_resource.py └── tox.ini /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ develop ] 9 | pull_request: 10 | branches: [ develop ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: [3.5, 3.6, 3.7, 3.8] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install pytest 30 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 31 | - name: Test with pytest 32 | run: | 33 | pytest 34 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /.github/workflows/python-validate.yml: -------------------------------------------------------------------------------- 1 | name: Python validate releasability 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | branches: [ develop ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: [3.6, 3.7, 3.8] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install pytest 27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 28 | - name: Validate packaging / publishability 29 | run: | 30 | pip install setuptools wheel twine readme_renderer[md] 31 | python setup.py sdist bdist_wheel 32 | twine check dist/* 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # Emacs backup files 56 | *~ 57 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | dist: xenial 3 | python: 4 | - "2.7" 5 | - "3.4" 6 | - "3.5" 7 | - "3.6" 8 | - "3.7" 9 | install: 10 | - pip install -r requirements.txt 11 | - pip install coverage python-coveralls 12 | script: nosetests tests/unit --cover-erase --with-coverage --cover-package skew 13 | after_success: coveralls 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include requirements.txt 4 | include skew/_version 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # skew 2 | 3 | ![PyPI](https://img.shields.io/pypi/v/skew) https://pypi.org/project/skew/ 4 | 5 | `Skew` is a package for identifying and enumerating cloud resources. 6 | The name is a homonym for SKU (Stock Keeping Unit). Skew allows you to 7 | define different SKU `schemes` which are a particular encoding of a 8 | SKU. Skew then allows you to use this scheme pattern and regular expressions 9 | based on the scheme pattern to identify and enumerate a resource or set 10 | of resources. 11 | 12 | At the moment, the the only available `scheme` is the `ARN` scheme. 13 | The `ARN` scheme uses the basic structure of 14 | [Amazon Resource Names](http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) (ARNs) to assign a unique identifier to every AWS 15 | resource. 16 | 17 | An example ARN pattern would be: 18 | 19 | ``` 20 | arn:aws:ec2:us-west-2:123456789012:instance/i-12345678 21 | ``` 22 | 23 | This pattern identifies a specific EC2 instance running in the `us-west-2` 24 | region under the account ID `123456789012`. The account ID is the 12-digit 25 | unique identifier for a specific AWS account as described 26 | [here](http://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html). 27 | To allow `skew` to find your account number, you need to create a `skew` 28 | YAML config file. By default, `skew` will look for your config file in 29 | `~/.skew` but you can use the `SKEW_CONFIG` environment variable to tell `skew` 30 | where to find your config file if you choose to put it somewhere else. The 31 | basic format of the `skew` config file is: 32 | 33 | ```yaml 34 | --- 35 | accounts: 36 | "123456789012": 37 | profile: dev 38 | "234567890123": 39 | profile: prod 40 | ``` 41 | 42 | Within the `accounts` section, you create keys named after your 12-digit 43 | account ID (as a string). Within that, you must have an entry called *profile* 44 | that lists the profile name this account maps to within your AWS credential 45 | file. 46 | 47 | The main purpose of skew is to identify resources or sets of resources 48 | across services, regions, and accounts and to quickly and easily return the 49 | data associated with those resources. For example, if you wanted to return 50 | the data associated with the example ARN above: 51 | 52 | ```python 53 | from skew import scan 54 | 55 | arn = scan('arn:aws:ec2:us-west-2:123456789012:instance/i-12345678') 56 | for resource in arn: 57 | print(resource.data) 58 | ``` 59 | 60 | The call to `scan` returns an ARN object which implements the 61 | [iterator pattern](https://docs.python.org/2/library/stdtypes.html#iterator-types) 62 | and returns a `Resource` object for each AWS resource that matches the 63 | ARN pattern provided. The `Resource` object contains all of the data 64 | associated with the AWS resource in dictionary under the `data` attribute. 65 | 66 | Any of the elements of the ARN can be replaced with a regular expression. 67 | The simplest regular expression is `*` which means all available choices. 68 | So, for example: 69 | 70 | ```python 71 | arn = scan('arn:aws:ec2:us-east-1:*:instance/*') 72 | ``` 73 | 74 | would return an iterator for all EC2 instances in the `us-east-1` region 75 | found in all accounts defined in the config file. 76 | 77 | To find all DynamoDB tables in all US regions for the account ID 234567890123 78 | you would use: 79 | 80 | ```python 81 | arn = scan('arn:aws:dynamodb:us-.*:234567890123:table/*') 82 | ``` 83 | 84 | CloudWatch Metrics 85 | ------------------ 86 | 87 | In addition to making the metadata about a particular AWS resource available 88 | to you, `skew` also tries to make it easy to access the available CloudWatch 89 | metrics for a given resource. 90 | 91 | For example, assume that you had did a `scan` on the original ARN above 92 | and had the resource associated with that instance available as the variable 93 | `instance`. You could do the following: 94 | 95 | ```python 96 | >>> instance.metric_names 97 | ['CPUUtilization', 98 | 'NetworkOut', 99 | 'StatusCheckFailed', 100 | 'StatusCheckFailed_System', 101 | 'NetworkIn', 102 | 'DiskWriteOps', 103 | 'DiskReadBytes', 104 | 'DiskReadOps', 105 | 'StatusCheckFailed_Instance', 106 | 'DiskWriteBytes'] 107 | >>> 108 | ``` 109 | 110 | The `metric_names` attribute returns the list of available CloudWatch metrics 111 | for this resource. The retrieve the metric data for one of these: 112 | 113 | ```python 114 | >>> instance.get_metric_data('CPUUtilization') 115 | [{'Average': 0.134, 'Timestamp': '2014-09-29T14:04:00Z', 'Unit': 'Percent'}, 116 | {'Average': 0.066, 'Timestamp': '2014-09-29T13:54:00Z', 'Unit': 'Percent'}, 117 | {'Average': 0.066, 'Timestamp': '2014-09-29T14:09:00Z', 'Unit': 'Percent'}, 118 | {'Average': 0.134, 'Timestamp': '2014-09-29T13:34:00Z', 'Unit': 'Percent'}, 119 | {'Average': 0.066, 'Timestamp': '2014-09-29T14:19:00Z', 'Unit': 'Percent'}, 120 | {'Average': 0.068, 'Timestamp': '2014-09-29T13:44:00Z', 'Unit': 'Percent'}, 121 | {'Average': 0.134, 'Timestamp': '2014-09-29T14:14:00Z', 'Unit': 'Percent'}, 122 | {'Average': 0.066, 'Timestamp': '2014-09-29T13:29:00Z', 'Unit': 'Percent'}, 123 | {'Average': 0.132, 'Timestamp': '2014-09-29T13:59:00Z', 'Unit': 'Percent'}, 124 | {'Average': 0.134, 'Timestamp': '2014-09-29T13:49:00Z', 'Unit': 'Percent'}, 125 | {'Average': 0.134, 'Timestamp': '2014-09-29T13:39:00Z', 'Unit': 'Percent'}] 126 | >>> 127 | ``` 128 | 129 | You can also customize the data returned rather than using the default settings: 130 | 131 | ```python 132 | >>> instance.get_metric_data('CPUUtilization', hours=8, statistics=['Average', 'Minimum', 'Maximum']) 133 | [{'Average': 0.132, 134 | 'Maximum': 0.33, 135 | 'Minimum': 0.0, 136 | 'Timestamp': '2014-09-29T10:54:00Z', 137 | 'Unit': 'Percent'}, 138 | {'Average': 0.134, 139 | 'Maximum': 0.34, 140 | 'Minimum': 0.0, 141 | 'Timestamp': '2014-09-29T14:04:00Z', 142 | 'Unit': 'Percent'}, 143 | ..., 144 | {'Average': 0.066, 145 | 'Maximum': 0.33, 146 | 'Minimum': 0.0, 147 | 'Timestamp': '2014-09-29T08:34:00Z', 148 | 'Unit': 'Percent'}, 149 | {'Average': 0.134, 150 | 'Maximum': 0.34, 151 | 'Minimum': 0.0, 152 | 'Timestamp': '2014-09-29T08:04:00Z', 153 | 'Unit': 'Percent'}] 154 | >>> 155 | ``` 156 | 157 | Filtering Data 158 | -------------- 159 | 160 | Each resource that is retrieved is a Python dictionary. Some of these (e.g. 161 | an EC2 Instance) can be quite large and complex. Skew allows you to filter 162 | the data returned by applying a [jmespath](http://jmespath.org) query to 163 | the resulting data. If you aren't familiar with jmespath, check it out. 164 | Its a very powerful query language for JSON data and has full support in 165 | Python as well as a number of other languages such as Ruby, PHP, and 166 | Javascript. It is also the query language used in the 167 | [AWSCLI](https://aws.amazon.com/cli/) so if you are familiar with the 168 | `--query` option there, you can use the same thing with skew. 169 | 170 | To specify a query to be applied to results of a scan, simply append 171 | the query to the end of the ARN, separated by a `|` (pipe) character. 172 | For example: 173 | 174 | ``` 175 | arn:aws:ec2:us-west-2:123456789012:instance/i-12345678|InstanceType 176 | ``` 177 | 178 | Would retrieve the data for this particular EC2 instance and would then 179 | filter the returned data through the (very) simple jmespath query to which 180 | retrieves the value of the attribute `InstanceType` within the data. The 181 | filtered data is available as the `filtered_data` attribute of the 182 | Resource object. The full, unfiltered data is still available as the 183 | `data` attribute. 184 | 185 | Multithreaded Usage 186 | ------------------- 187 | 188 | Skew is single-threaded by default, like most Python libraries. In order to 189 | speed up the enumeration of matching resources, you can use multiple threads: 190 | 191 | ```python 192 | import skew 193 | 194 | class Worker(Thread): 195 | def __init__(self, arn): 196 | Thread.__init__(self) 197 | self.arn = arn 198 | self.name = arn 199 | 200 | def run(self): 201 | for i in skew.scan(self.arn): 202 | # now do something with the stuff 203 | 204 | arn = skew.ARN() 205 | 206 | for service in arn.service.choices(): 207 | uri = 'arn:aws:' + service + ':*:*:*/*' 208 | worker = Worker(uri); 209 | worker.start() 210 | ``` 211 | 212 | (thanks to @alFReD-NSH for the snippet) 213 | 214 | More Examples 215 | ------------- 216 | 217 | [Find Unattached Volumes](https://gist.github.com/garnaat/73804a6b0bd506ee6075) 218 | 219 | [Audit Security Groups](https://gist.github.com/garnaat/4123f1aefe7d65df9b48) 220 | 221 | [Find Untagged Instances](https://gist.github.com/garnaat/11004f5661b4798d27c7) 222 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | boto3>=1.2.3 2 | placebo==0.4.3 3 | six>=1.9.0 4 | PyYAML==3.13 5 | python-dateutil>=2.1,<3.0.0 6 | mock==1.0.1 7 | nose==1.3.4 8 | tox==1.8.1 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | import os 6 | 7 | requires = [ 8 | 'boto3>=1.2.3', 9 | 'six>=1.9.0', 10 | 'python-dateutil>=2.1,<3.0.0', 11 | 'PyYAML>=3.11'] 12 | 13 | 14 | here = os.path.dirname(os.path.realpath(__file__)) 15 | with open(os.path.join(here, 'README.md'), encoding='utf-8') as readme: 16 | long_description = readme.read() 17 | 18 | 19 | setup( 20 | name='skew', 21 | version=open(os.path.join('skew', '_version')).read().strip(), 22 | description='A flat address space for all your AWS resources.', 23 | long_description=long_description, 24 | long_description_content_type='text/markdown', 25 | author='Mitch Garnaat', 26 | author_email='mitch@garnaat.com', 27 | url='https://github.com/scopely-devops/skew', 28 | packages=find_packages(exclude=['tests*']), 29 | package_data={'skew': ['_version']}, 30 | package_dir={'skew': 'skew'}, 31 | install_requires=requires, 32 | license='Apache License 2.0', 33 | classifiers=[ 34 | 'Development Status :: 4 - Beta', 35 | 'Intended Audience :: Developers', 36 | 'Intended Audience :: System Administrators', 37 | 'Natural Language :: English', 38 | 'License :: OSI Approved :: Apache Software License', 39 | 'Programming Language :: Python :: 2', 40 | 'Programming Language :: Python :: 3' 41 | ], 42 | ) 43 | -------------------------------------------------------------------------------- /skew/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You 4 | # may not use this file except in compliance with the License. A copy of 5 | # the License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is 10 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 | # ANY KIND, either express or implied. See the License for the specific 12 | # language governing permissions and limitations under the License. 13 | 14 | import os 15 | 16 | from skew.arn import ARN 17 | 18 | __version__ = open(os.path.join(os.path.dirname(__file__), '_version')).read() 19 | 20 | 21 | def scan(sku, **kwargs): 22 | """ 23 | Scan (i.e. look up) a SKU. 24 | 25 | The main interface into the skew library. Pass in a SKU and we try 26 | to look it up and return the appropriate data. 27 | 28 | Each SKU scheme should be implemented as a class. That class should 29 | have an attribute called ``RegEx``. The lookup method simply tries 30 | to match the SKU with the ``RegEx`` of the scheme. If it matches, 31 | an instance of the class is created by passing in the ``groupdict`` 32 | of the regular expression to the constructor. The scheme class 33 | should also implement the ``iterator`` protocol because people 34 | will want to iterate over the scheme object to get their results. 35 | 36 | We could use some sort of dynamic loading of scheme classes 37 | but since there is currently only one (ARN) let's not over-complicate 38 | things. 39 | """ 40 | return ARN(sku, **kwargs) 41 | -------------------------------------------------------------------------------- /skew/_version: -------------------------------------------------------------------------------- 1 | 0.19.0 2 | -------------------------------------------------------------------------------- /skew/arn/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import logging 17 | import re 18 | 19 | from six.moves import zip_longest 20 | import jmespath 21 | 22 | import skew.resources 23 | from skew.config import get_config 24 | 25 | LOG = logging.getLogger(__name__) 26 | DebugFmtString = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 27 | 28 | 29 | class ARNComponent(object): 30 | 31 | def __init__(self, pattern, arn): 32 | self.pattern = pattern 33 | self._arn = arn 34 | 35 | def __repr__(self): 36 | return self.pattern 37 | 38 | def choices(self, context=None): 39 | """ 40 | This method is responsible for returning all of the possible 41 | choices for the value of this component. 42 | 43 | The ``context`` can be a list of values of the components 44 | that precede this component. The value of one or more of 45 | those previous components could affect the possible 46 | choices for this component. 47 | 48 | If no ``context`` is provided this method should return 49 | all possible choices. 50 | """ 51 | return [] 52 | 53 | def match(self, pattern, context=None): 54 | """ 55 | This method returns a (possibly empty) list of strings that 56 | match the regular expression ``pattern`` provided. You can 57 | also provide a ``context`` as described above. 58 | 59 | This method calls ``choices`` to get a list of all possible 60 | choices and then filters the list by performing a regular 61 | expression search on each choice using the supplied ``pattern``. 62 | """ 63 | matches = [] 64 | regex = pattern 65 | if regex == '*': 66 | regex = '.*' 67 | regex = re.compile(regex) 68 | for choice in self.choices(context): 69 | if regex.search(choice): 70 | matches.append(choice) 71 | return matches 72 | 73 | def matches(self, context=None): 74 | """ 75 | This is a convenience method to return all possible matches 76 | filtered by the current value of the ``pattern`` attribute. 77 | """ 78 | return self.match(self.pattern, context) 79 | 80 | def complete(self, prefix='', context=None): 81 | return [c for c in self.choices(context) if c.startswith(prefix)] 82 | 83 | 84 | class Resource(ARNComponent): 85 | 86 | def _split_resource(self, resource): 87 | LOG.debug('split_resource: %s', resource) 88 | if '/' in resource: 89 | resource_type, resource_id = resource.split('/', 1) 90 | elif ':' in resource: 91 | resource_type, resource_id = resource.split(':', 1) 92 | else: 93 | # TODO: Some services use ARN's that include only a resource 94 | # identifier (i.e. no resource type). SNS is one example but 95 | # there are others. We need to refactor this code to allow 96 | # the splitting of the resource part of the ARN to be handled 97 | # by the individual resource classes rather than here. 98 | resource_type = None 99 | resource_id = resource 100 | return (resource_type, resource_id) 101 | 102 | def match(self, pattern, context=None): 103 | resource_type, _ = self._split_resource(pattern) 104 | return super(Resource, self).match(resource_type, context) 105 | 106 | def choices(self, context=None): 107 | if context: 108 | provider, service = context[1:3] 109 | else: 110 | service = self._arn.service.pattern 111 | provider = self._arn.provider.pattern 112 | all_resources = skew.resources.all_types(provider, service) 113 | if not all_resources: 114 | all_resources = ['*'] 115 | return all_resources 116 | 117 | def enumerate(self, context, **kwargs): 118 | LOG.debug('Resource.enumerate %s', context) 119 | _, provider, service_name, region, account = context 120 | resource_type, resource_id = self._split_resource(self.pattern) 121 | LOG.debug('resource_type=%s, resource_id=%s', 122 | resource_type, resource_id) 123 | resources = [] 124 | for resource_type in self.matches(context): 125 | resource_path = '.'.join([provider, service_name, resource_type]) 126 | resource_cls = skew.resources.find_resource_class(resource_path) 127 | resources.extend(resource_cls.enumerate( 128 | self._arn, region, account, resource_id, **kwargs)) 129 | return resources 130 | 131 | 132 | class Account(ARNComponent): 133 | 134 | def __init__(self, pattern, arn): 135 | self._accounts = get_config()['accounts'] 136 | super(Account, self).__init__(pattern, arn) 137 | 138 | def choices(self, context=None): 139 | return list(self._accounts.keys()) 140 | 141 | def enumerate(self, context, **kwargs): 142 | LOG.debug('Account.enumerate %s', context) 143 | for match in self.matches(context): 144 | context.append(match) 145 | for resource in self._arn.resource.enumerate( 146 | context, **kwargs): 147 | yield resource 148 | context.pop() 149 | 150 | 151 | class Region(ARNComponent): 152 | _all_region_names = ['us-east-1', 153 | 'us-east-2', 154 | 'us-west-1', 155 | 'us-west-2', 156 | 'eu-west-1', 157 | 'eu-west-2', 158 | 'eu-west-3', 159 | 'eu-central-1', 160 | 'eu-north-1', 161 | 'eu-south-1', 162 | 'ap-southeast-1', 163 | 'ap-southeast-2', 164 | 'ap-northeast-1', 165 | 'ap-northeast-2', 166 | 'ap-south-1', 167 | 'ap-east-1', 168 | 'af-south-1' 169 | 'ca-central-1', 170 | 'sa-east-1', 171 | 'me-south-1', 172 | 'cn-north-1', 173 | 'cn-northwest-1'] 174 | 175 | _no_region_required = [''] 176 | 177 | _service_region_map = { 178 | 'redshift': _all_region_names, 179 | 'glacier': ['ap-northeast-1', 'ap-northeast-2', 'ap-south-1', 'ap-southeast-2', 'ca-central-1', 'eu-central-1', 180 | 'eu-west-1', 'eu-west-2', 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2'], 181 | 'cloudfront': _no_region_required, 182 | 'iam': _no_region_required, 183 | 'route53': _no_region_required 184 | } 185 | 186 | def choices(self, context=None): 187 | if context: 188 | service = context[2] 189 | else: 190 | service = self._arn.service 191 | return self._service_region_map.get( 192 | service, self._all_region_names) 193 | 194 | def enumerate(self, context, **kwargs): 195 | LOG.debug('Region.enumerate %s', context) 196 | for match in self.matches(context): 197 | context.append(match) 198 | for account in self._arn.account.enumerate( 199 | context, **kwargs): 200 | yield account 201 | context.pop() 202 | 203 | 204 | class Service(ARNComponent): 205 | 206 | def choices(self, context=None): 207 | if context: 208 | provider = context[1] 209 | else: 210 | provider = self._arn.provider.pattern 211 | return skew.resources.all_services(provider) 212 | 213 | def enumerate(self, context, **kwargs): 214 | LOG.debug('Service.enumerate %s', context) 215 | for match in self.matches(context): 216 | context.append(match) 217 | for region in self._arn.region.enumerate( 218 | context, **kwargs): 219 | yield region 220 | context.pop() 221 | 222 | 223 | class Provider(ARNComponent): 224 | 225 | def choices(self, context=None): 226 | return ['aws'] 227 | 228 | def enumerate(self, context, **kwargs): 229 | LOG.debug('Provider.enumerate %s', context) 230 | for match in self.matches(context): 231 | context.append(match) 232 | for service in self._arn.service.enumerate( 233 | context, **kwargs): 234 | yield service 235 | context.pop() 236 | 237 | 238 | class Scheme(ARNComponent): 239 | 240 | def choices(self, context=None): 241 | return ['arn'] 242 | 243 | def enumerate(self, context, **kwargs): 244 | LOG.debug('Scheme.enumerate %s', context) 245 | for match in self.matches(context): 246 | context.append(match) 247 | for provider in self._arn.provider.enumerate( 248 | context, **kwargs): 249 | yield provider 250 | context.pop() 251 | 252 | 253 | class ARN(object): 254 | 255 | ComponentClasses = [Scheme, Provider, Service, Region, Account, Resource] 256 | 257 | def __init__(self, arn_string='arn:aws:*:*:*:*', **kwargs): 258 | self.query = None 259 | self._components = None 260 | self._build_components_from_string(arn_string) 261 | self.kwargs = kwargs 262 | 263 | def __repr__(self): 264 | return ':'.join([str(c) for c in self._components]) 265 | 266 | def debug(self): 267 | self.set_logger('skew', logging.DEBUG) 268 | 269 | def set_logger(self, logger_name, level=logging.DEBUG): 270 | """ 271 | Convenience function to quickly configure full debug output 272 | to go to the console. 273 | """ 274 | log = logging.getLogger(logger_name) 275 | log.setLevel(level) 276 | 277 | ch = logging.StreamHandler(None) 278 | ch.setLevel(level) 279 | 280 | # create formatter 281 | formatter = logging.Formatter(DebugFmtString) 282 | 283 | # add formatter to ch 284 | ch.setFormatter(formatter) 285 | 286 | # add ch to logger 287 | log.addHandler(ch) 288 | 289 | def _build_components_from_string(self, arn_string): 290 | if '|' in arn_string: 291 | arn_string, query = arn_string.split('|') 292 | self.query = jmespath.compile(query) 293 | pairs = zip_longest( 294 | self.ComponentClasses, arn_string.split(':', 5), fillvalue='*') 295 | self._components = [c(n, self) for c, n in pairs] 296 | 297 | @property 298 | def scheme(self): 299 | return self._components[0] 300 | 301 | @property 302 | def provider(self): 303 | return self._components[1] 304 | 305 | @property 306 | def service(self): 307 | return self._components[2] 308 | 309 | @property 310 | def region(self): 311 | return self._components[3] 312 | 313 | @property 314 | def account(self): 315 | return self._components[4] 316 | 317 | @property 318 | def resource(self): 319 | return self._components[5] 320 | 321 | def __iter__(self): 322 | context = [] 323 | for scheme in self.scheme.enumerate(context, **self.kwargs): 324 | yield scheme 325 | -------------------------------------------------------------------------------- /skew/awsclient.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Mitch Garnaat 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import logging 16 | import time 17 | 18 | import datetime 19 | import jmespath 20 | import boto3 21 | from botocore.exceptions import ClientError 22 | 23 | from skew.config import get_config 24 | 25 | LOG = logging.getLogger(__name__) 26 | 27 | 28 | def json_encoder(obj): 29 | """JSON encoder that formats datetimes as ISO8601 format.""" 30 | if isinstance(obj, datetime.datetime): 31 | return obj.isoformat() 32 | else: 33 | return obj 34 | 35 | 36 | class AWSClient(object): 37 | 38 | def __init__(self, service_name, region_name, account_id, **kwargs): 39 | self._config = get_config() 40 | self._service_name = service_name 41 | self._region_name = region_name 42 | self._account_id = account_id 43 | self._has_credentials = False 44 | self.aws_creds = kwargs.get('aws_creds') 45 | if self.aws_creds is None: 46 | self.aws_creds = self._config['accounts'][account_id].get( 47 | 'credentials') 48 | if self.aws_creds is None: 49 | # no aws_creds, need profile to get creds from ~/.aws/credentials 50 | self._profile = self._config['accounts'][account_id]['profile'] 51 | self.placebo = kwargs.get('placebo') 52 | self.placebo_dir = kwargs.get('placebo_dir') 53 | self.placebo_mode = kwargs.get('placebo_mode', 'record') 54 | self._client = self._create_client() 55 | 56 | @property 57 | def service_name(self): 58 | return self._service_name 59 | 60 | @property 61 | def region_name(self): 62 | return self._region_name 63 | 64 | @property 65 | def account_id(self): 66 | return self._account_id 67 | 68 | @property 69 | def profile(self): 70 | return self._profile 71 | 72 | def _create_client(self): 73 | if self.aws_creds: 74 | session = boto3.Session(**self.aws_creds) 75 | else: 76 | session = boto3.Session( 77 | profile_name=self.profile) 78 | if self.placebo and self.placebo_dir: 79 | pill = self.placebo.attach(session, self.placebo_dir) 80 | if self.placebo_mode == 'record': 81 | pill.record() 82 | elif self.placebo_mode == 'playback': 83 | pill.playback() 84 | return session.client(self.service_name, 85 | region_name=self.region_name if self.region_name else None) 86 | 87 | def call(self, op_name, query=None, **kwargs): 88 | """ 89 | Make a request to a method in this client. The response data is 90 | returned from this call as native Python data structures. 91 | 92 | This method differs from just calling the client method directly 93 | in the following ways: 94 | 95 | * It automatically handles the pagination rather than 96 | relying on a separate pagination method call. 97 | * You can pass an optional jmespath query and this query 98 | will be applied to the data returned from the low-level 99 | call. This allows you to tailor the returned data to be 100 | exactly what you want. 101 | 102 | :type op_name: str 103 | :param op_name: The name of the request you wish to make. 104 | 105 | :type query: str 106 | :param query: A jmespath query that will be applied to the 107 | data returned by the operation prior to returning 108 | it to the user. 109 | 110 | :type kwargs: keyword arguments 111 | :param kwargs: Additional keyword arguments you want to pass 112 | to the method when making the request. 113 | """ 114 | LOG.debug(kwargs) 115 | if query: 116 | query = jmespath.compile(query) 117 | if self._client.can_paginate(op_name): 118 | paginator = self._client.get_paginator(op_name) 119 | results = paginator.paginate(**kwargs) 120 | data = results.build_full_result() 121 | else: 122 | op = getattr(self._client, op_name) 123 | done = False 124 | data = {} 125 | while not done: 126 | try: 127 | data = op(**kwargs) 128 | done = True 129 | except ClientError as e: 130 | LOG.debug(e, kwargs) 131 | if 'Throttling' in str(e): 132 | time.sleep(1) 133 | elif 'AccessDenied' in str(e): 134 | done = True 135 | elif 'NoSuchTagSet' in str(e): 136 | done = True 137 | except Exception: 138 | done = True 139 | if query: 140 | data = query.search(data) 141 | return data 142 | 143 | 144 | def get_awsclient(service_name, region_name, account_id, **kwargs): 145 | if region_name == '': 146 | region_name = None 147 | return AWSClient(service_name, region_name, account_id, **kwargs) 148 | -------------------------------------------------------------------------------- /skew/config.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Mitch Garnaat 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | import logging 17 | 18 | import yaml 19 | 20 | from skew.exception import ConfigNotFoundError 21 | 22 | LOG = logging.getLogger(__name__) 23 | 24 | 25 | _config = None 26 | 27 | 28 | def get_config(): 29 | global _config 30 | if _config is None: 31 | path = os.environ.get('SKEW_CONFIG', os.path.join('~', '.skew')) 32 | path = os.path.expanduser(path) 33 | path = os.path.expandvars(path) 34 | if not os.path.exists(path): 35 | raise ConfigNotFoundError('Unable to find skew config file') 36 | with open(path) as config_file: 37 | _config = yaml.safe_load(config_file) 38 | return _config 39 | -------------------------------------------------------------------------------- /skew/exception.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | class ConfigNotFoundError(Exception): 18 | 19 | pass 20 | 21 | 22 | class BaseOperationError(Exception): 23 | 24 | def __init__(self, error_code, error_body, operation_name): 25 | msg = 'Error(%d) when calling (%s): %s' % (error_code, 26 | operation_name, 27 | error_body) 28 | super(BaseOperationError, self).__init__(msg) 29 | self.error_code = error_code 30 | self.error_body = error_body 31 | self.operation_name = operation_name 32 | 33 | 34 | class ClientError(BaseOperationError): 35 | pass 36 | 37 | 38 | class ServerError(BaseOperationError): 39 | pass 40 | -------------------------------------------------------------------------------- /skew/resources/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | import importlib 16 | 17 | # Maps resources names as they appear in ARN's to the path name 18 | # of the Python class representing that resource. 19 | ResourceTypes = { 20 | 'aws.acm.certificate': 'aws.acm.Certificate', 21 | 'aws.apigateway.restapis': 'aws.apigateway.RestAPI', 22 | 'aws.autoscaling.autoScalingGroup': 'aws.autoscaling.AutoScalingGroup', 23 | 'aws.autoscaling.launchConfigurationName': 'aws.autoscaling.LaunchConfiguration', 24 | 'aws.cloudfront.distribution': 'aws.cloudfront.Distribution', 25 | 'aws.cloudformation.stack': 'aws.cloudformation.Stack', 26 | 'aws.cloudwatch.alarm': 'aws.cloudwatch.Alarm', 27 | 'aws.logs.log-group': 'aws.cloudwatch.LogGroup', 28 | 'aws.cloudtrail.trail': 'aws.cloudtrail.CloudTrail', 29 | 'aws.dynamodb.table': 'aws.dynamodb.Table', 30 | 'aws.ec2.address': 'aws.ec2.Address', 31 | 'aws.ec2.customer-gateway': 'aws.ec2.CustomerGateway', 32 | 'aws.ec2.key-pair': 'aws.ec2.KeyPair', 33 | 'aws.ec2.image': 'aws.ec2.Image', 34 | 'aws.ec2.instance': 'aws.ec2.Instance', 35 | 'aws.ec2.natgateway': 'aws.ec2.NatGateway', 36 | 'aws.ec2.network-acl': 'aws.ec2.NetworkAcl', 37 | 'aws.ec2.route-table': 'aws.ec2.RouteTable', 38 | 'aws.ec2.internet-gateway': 'aws.ec2.InternetGateway', 39 | 'aws.ec2.security-group': 'aws.ec2.SecurityGroup', 40 | 'aws.ec2.snapshot': 'aws.ec2.Snapshot', 41 | 'aws.ec2.volume': 'aws.ec2.Volume', 42 | 'aws.ec2.vpc': 'aws.ec2.Vpc', 43 | 'aws.ec2.flow-log': 'aws.ec2.FlowLog', 44 | 'aws.ec2.vpc-peering-connection': 'aws.ec2.VpcPeeringConnection', 45 | 'aws.ec2.subnet': 'aws.ec2.Subnet', 46 | 'aws.ec2.launch-template': 'aws.ec2.LaunchTemplate', 47 | 'aws.elasticache.cluster': 'aws.elasticache.Cluster', 48 | 'aws.elasticache.subnet-group': 'aws.elasticache.SubnetGroup', 49 | 'aws.elasticache.snapshot': 'aws.elasticache.Snapshot', 50 | 'aws.elasticbeanstalk.application': 'aws.elasticbeanstalk.Application', 51 | 'aws.elasticbeanstalk.environment': 'aws.elasticbeanstalk.Environment', 52 | 'aws.elb.loadbalancer': 'aws.elb.LoadBalancer', 53 | 'aws.es.domain': 'aws.es.ElasticsearchDomain', 54 | 'aws.firehose.deliverystream': 'aws.firehose.DeliveryStream', 55 | 'aws.iam.group': 'aws.iam.Group', 56 | 'aws.iam.instance-profile': 'aws.iam.InstanceProfile', 57 | 'aws.iam.role': 'aws.iam.Role', 58 | 'aws.iam.policy': 'aws.iam.Policy', 59 | 'aws.iam.user': 'aws.iam.User', 60 | 'aws.iam.server-certificate': 'aws.iam.ServerCertificate', 61 | 'aws.kinesis.stream': 'aws.kinesis.Stream', 62 | 'aws.lambda.function': 'aws.lambda.Function', 63 | 'aws.rds.db': 'aws.rds.DBInstance', 64 | 'aws.rds.secgrp': 'aws.rds.DBSecurityGroup', 65 | 'aws.redshift.cluster': 'aws.redshift.Cluster', 66 | 'aws.route53.hostedzone': 'aws.route53.HostedZone', 67 | 'aws.route53.healthcheck': 'aws.route53.HealthCheck', 68 | 'aws.s3.bucket': 'aws.s3.Bucket', 69 | 'aws.sqs.queue': 'aws.sqs.Queue', 70 | 'aws.sns.subscription': 'aws.sns.Subscription', 71 | 'aws.sns.topic': 'aws.sns.Topic' 72 | } 73 | 74 | 75 | def all_providers(): 76 | providers = set() 77 | for resource_type in ResourceTypes: 78 | providers.add(resource_type.split('.')[0]) 79 | return list(providers) 80 | 81 | 82 | def all_services(provider_name): 83 | services = set() 84 | for resource_type in ResourceTypes: 85 | t = resource_type.split('.') 86 | if t[0] == provider_name: 87 | services.add(t[1]) 88 | return list(services) 89 | 90 | 91 | def all_types(provider_name, service_name): 92 | types = set() 93 | for resource_type in ResourceTypes: 94 | t = resource_type.split('.') 95 | if t[0] == provider_name and t[1] == service_name: 96 | types.add(t[2]) 97 | return list(types) 98 | 99 | 100 | def find_resource_class(resource_path): 101 | """ 102 | dynamically load a class from a string 103 | """ 104 | class_path = ResourceTypes[resource_path] 105 | # First prepend our __name__ to the resource string passed in. 106 | full_path = '.'.join([__name__, class_path]) 107 | class_data = full_path.split(".") 108 | module_path = ".".join(class_data[:-1]) 109 | class_str = class_data[-1] 110 | module = importlib.import_module(module_path) 111 | # Finally, we retrieve the Class 112 | return getattr(module, class_str) 113 | -------------------------------------------------------------------------------- /skew/resources/aws/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | import logging 16 | import datetime 17 | from collections import namedtuple 18 | 19 | import jmespath 20 | 21 | import skew.awsclient 22 | from skew.resources.resource import Resource 23 | 24 | LOG = logging.getLogger(__name__) 25 | 26 | 27 | class MetricData(object): 28 | """ 29 | This is a simple object that allows us to compose both the returned 30 | data from a call to ``get_metrics_data`` as well as the period that 31 | was used when getting the data from CloudWatch. Since the period 32 | may be calculated by ``get_metrics_data`` rather than passed explicitly 33 | the user would otherwise not how what the period value was. 34 | """ 35 | 36 | def __init__(self, data, period): 37 | self.data = data 38 | self.period = period 39 | 40 | 41 | class AWSResource(Resource): 42 | """ 43 | Each Resource class defines a Config variable at the class level. This 44 | is a dictionary that gives the specifics about which service the resource 45 | belongs to and how to enumerate the resource. 46 | 47 | Each entry in the dictionary we define: 48 | 49 | * service - The AWS service in which this resource is defined. 50 | * enum_spec - The enumeration configuration. This is a tuple consisting 51 | of the name of the operation to call to enumerate the resources, 52 | a jmespath query that will be run against the result of the operation 53 | to retrieve the list of resources, and a dictionary containing any 54 | extra arguments you want to pass in the enumeration call. This 55 | can be None if no additional arguments are required. 56 | * tags_spec - Some AWS resources return the tags for the resource in 57 | the enumeration operation (e.g. DescribeInstances) while others 58 | require a second operation to retrieve the tags. If a second 59 | operation is required the ``tags_spec`` describes how to make the 60 | call. It is a tuple consisting of: 61 | * operation name 62 | * jmespath query to find the tags in the response 63 | * the name of the parameter to send to identify the specific resource 64 | (this is not always the same as filter_name, e.g. RDS) 65 | * the value of the parameter to send to identify the specific resource 66 | (this is not always the same as the id, e.g. RDS) 67 | * [OPTIONAL] the fifth parameter is a dict containing the constants 68 | needed to the call of the operation, in addition to the parameter 69 | used to identify the specific resource (e.g. needed for Route53). 70 | Those constants are expressed in a dict of key, value pairs. 71 | * detail_spec - Some services provide only summary information in the 72 | list or describe method and require you to make another request to get 73 | the detailed info for a specific resource. If that is the case, this 74 | would contain a tuple consisting of the operation to call for the 75 | details, the parameter name to pass in to identify the desired 76 | resource and the jmespath filter to apply to the results to get 77 | the details. 78 | * id - The name of the field within the resource data that uniquely 79 | identifies the resource. 80 | * dimension - The CloudWatch dimension for this resource. A value 81 | of None indicates that this resource is not monitored by CloudWatch. 82 | * filter_name - By default, the enumerator returns all resources of a 83 | given type. But you can also tell it to filter the results by 84 | passing in a list of id's. This parameter tells it the name of the 85 | parameter to use to specify this list of id's. 86 | """ 87 | 88 | class Meta(object): 89 | type = 'awsresource' 90 | 91 | @classmethod 92 | def filter(cls, arn, resource_id, data): 93 | pass 94 | 95 | def __init__(self, client, data, query=None): 96 | self._client = client 97 | self._query = query 98 | if data is None: 99 | data = {} 100 | self.data = data 101 | if self._query: 102 | self.filtered_data = self._query.search(self.data) 103 | else: 104 | self.filtered_data = None 105 | if hasattr(self.Meta, 'id') and isinstance(self.data, dict): 106 | self._id = self.data.get(self.Meta.id, '') 107 | else: 108 | self._id = '' 109 | self._cloudwatch = None 110 | if hasattr(self.Meta, 'dimension') and self.Meta.dimension: 111 | self._cloudwatch = skew.awsclient.get_awsclient( 112 | 'cloudwatch', self._client.region_name, 113 | self._client.account_id) 114 | self._metrics = None 115 | self._name = None 116 | self._date = None 117 | self._tags = None 118 | 119 | def __repr__(self): 120 | return self.arn 121 | 122 | @property 123 | def arn(self): 124 | return 'arn:aws:%s:%s:%s:%s/%s' % ( 125 | self._client.service_name, 126 | self._client.region_name, 127 | self._client.account_id, self.resourcetype, self.id) 128 | 129 | @property 130 | def metrics(self): 131 | if self._metrics is None: 132 | if self._cloudwatch: 133 | data = self._cloudwatch.call( 134 | 'list_metrics', 135 | Dimensions=[{'Name': self.Meta.dimension, 136 | 'Value': self._id}]) 137 | self._metrics = jmespath.search('Metrics', data) 138 | else: 139 | self._metrics = [] 140 | return self._metrics 141 | 142 | @property 143 | def tags(self): 144 | """ 145 | Convert the ugly Tags JSON into a real dictionary and 146 | memorize the result. 147 | """ 148 | if self._tags is None: 149 | LOG.debug('need to build tags') 150 | self._tags = {} 151 | 152 | if hasattr(self.Meta, 'tags_spec') and (self.Meta.tags_spec is not None): 153 | LOG.debug('have a tags_spec') 154 | method, path, param_name, param_value = self.Meta.tags_spec[:4] 155 | kwargs = {} 156 | filter_type = getattr(self.Meta, 'filter_type', None) 157 | if filter_type == 'arn': 158 | kwargs = {param_name: [getattr(self, param_value)]} 159 | elif filter_type == 'list': 160 | kwargs = {param_name: [getattr(self, param_value)]} 161 | else: 162 | kwargs = {param_name: getattr(self, param_value)} 163 | if len(self.Meta.tags_spec) > 4: 164 | kwargs.update(self.Meta.tags_spec[4]) 165 | LOG.debug('fetching tags') 166 | self.data['Tags'] = self._client.call( 167 | method, query=path, **kwargs) 168 | LOG.debug(self.data['Tags']) 169 | 170 | if 'Tags' in self.data: 171 | _tags = self.data['Tags'] 172 | if isinstance(_tags, list): 173 | for kvpair in _tags: 174 | if kvpair['Key'] in self._tags: 175 | if not isinstance(self._tags[kvpair['Key']], list): 176 | self._tags[kvpair['Key']] = [self._tags[kvpair['Key']]] 177 | self._tags[kvpair['Key']].append(kvpair['Value']) 178 | else: 179 | self._tags[kvpair['Key']] = kvpair['Value'] 180 | elif isinstance(_tags, dict): 181 | self._tags = _tags 182 | return self._tags 183 | 184 | def find_metric(self, metric_name): 185 | for m in self.metrics: 186 | if m['MetricName'] == metric_name: 187 | return m 188 | return None 189 | 190 | def _total_seconds(self, delta): 191 | # python2.6 does not have timedelta.total_seconds() so we have 192 | # to calculate this ourselves. This is straight from the 193 | # datetime docs. 194 | return ((delta.microseconds + (delta.seconds + delta.days * 24 * 3600) 195 | * 10 ** 6) / 10 ** 6) 196 | 197 | def get_metric_data(self, metric_name=None, metric=None, 198 | days=None, hours=1, minutes=None, 199 | statistics=None, period=None): 200 | """ 201 | Get metric data for this resource. You can specify the time 202 | frame for the data as either the number of days or number of 203 | hours. The maximum window is 14 days. Based on the time frame 204 | this method will calculate the correct ``period`` to return 205 | the maximum number of data points up to the CloudWatch max 206 | of 1440. 207 | 208 | :type metric_name: str 209 | :param metric_name: The name of the metric this data will 210 | pertain to. 211 | 212 | :type days: int 213 | :param days: The number of days worth of data to return. 214 | You can specify either ``days`` or ``hours``. The default 215 | is one hour. The maximum value is 14 days. 216 | 217 | :type hours: int 218 | :param hours: The number of hours worth of data to return. 219 | You can specify either ``days`` or ``hours``. The default 220 | is one hour. The maximum value is 14 days. 221 | 222 | :type statistics: list of str 223 | :param statistics: The metric statistics to return. The default 224 | value is **Average**. Possible values are: 225 | 226 | * Average 227 | * Sum 228 | * SampleCount 229 | * Maximum 230 | * Minimum 231 | 232 | :returns: A ``MetricData`` object that contains both the CloudWatch 233 | data as well as the ``period`` used since this value may have 234 | been calculated by skew. 235 | """ 236 | if not statistics: 237 | statistics = ['Average'] 238 | if days: 239 | delta = datetime.timedelta(days=days) 240 | elif hours: 241 | delta = datetime.timedelta(hours=hours) 242 | else: 243 | delta = datetime.timedelta(minutes=minutes) 244 | if not period: 245 | period = max(60, self._total_seconds(delta) // 1440) 246 | if not metric: 247 | metric = self.find_metric(metric_name) 248 | if metric and self._cloudwatch: 249 | end = datetime.datetime.utcnow() 250 | start = end - delta 251 | data = self._cloudwatch.call( 252 | 'get_metric_statistics', 253 | Dimensions=metric['Dimensions'], 254 | Namespace=metric['Namespace'], 255 | MetricName=metric['MetricName'], 256 | StartTime=start.isoformat(), EndTime=end.isoformat(), 257 | Statistics=statistics, Period=period) 258 | return MetricData(jmespath.search('Datapoints', data), 259 | period) 260 | else: 261 | raise ValueError('Metric (%s) not available' % metric_name) 262 | 263 | 264 | ArnComponents = namedtuple('ArnComponents', 265 | ['scheme', 'provider', 'service', 'region', 266 | 'account', 'resource']) 267 | -------------------------------------------------------------------------------- /skew/resources/aws/acm.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | import logging 16 | 17 | import jmespath 18 | 19 | from skew.resources.aws import AWSResource 20 | 21 | 22 | LOG = logging.getLogger(__name__) 23 | 24 | 25 | class Certificate(AWSResource): 26 | 27 | class Meta(object): 28 | service = 'acm' 29 | type = 'certificate' 30 | enum_spec = ('list_certificates', 'CertificateSummaryList', None) 31 | detail_spec = ('describe_certificate', 'CertificateArn', 'Certificate') 32 | id = 'CertificateArn' 33 | tags_spec = ('list_tags_for_certificate', 'Tags[]', 34 | 'CertificateArn', 'id') 35 | filter_name = None 36 | name = 'DomainName' 37 | date = 'CreatedAt' 38 | dimension = None 39 | 40 | @classmethod 41 | def filter(cls, arn, resource_id, data): 42 | certificate_id = data.get(cls.Meta.id).split('/')[-1] 43 | LOG.debug('%s == %s', resource_id, certificate_id) 44 | return resource_id == certificate_id 45 | 46 | @property 47 | def arn(self): 48 | return self.data['CertificateArn'] 49 | 50 | def __init__(self, client, data, query=None): 51 | super(Certificate, self).__init__(client, data, query) 52 | 53 | self._id = data['CertificateArn'] 54 | 55 | detail_op, param_name, detail_path = self.Meta.detail_spec 56 | params = {param_name: data['CertificateArn']} 57 | data = client.call(detail_op, **params) 58 | 59 | self.data = jmespath.search(detail_path, data) 60 | -------------------------------------------------------------------------------- /skew/resources/aws/apigateway.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"). You 2 | # may not use this file except in compliance with the License. A copy of 3 | # the License is located at 4 | # 5 | # http://aws.amazon.com/apache2.0/ 6 | # 7 | # or in the "license" file accompanying this file. This file is 8 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 9 | # ANY KIND, either express or implied. See the License for the specific 10 | # language governing permissions and limitations under the License. 11 | 12 | import logging 13 | 14 | from skew.resources.aws import AWSResource 15 | 16 | LOG = logging.getLogger(__name__) 17 | 18 | 19 | class RestAPI(AWSResource): 20 | 21 | class Meta(object): 22 | service = 'apigateway' 23 | type = 'restapis' 24 | enum_spec = ('get_rest_apis', 'items', None) 25 | id = 'id' 26 | filter_name = None 27 | filter_type = None 28 | detail_spec = None 29 | name = 'name' 30 | date = 'createdDate' 31 | dimension = 'GatewayName' 32 | 33 | @classmethod 34 | def filter(cls, arn, resource_id, data): 35 | api_id = data.get(cls.Meta.id) 36 | LOG.debug('%s == %s', resource_id, api_id) 37 | return resource_id == api_id 38 | -------------------------------------------------------------------------------- /skew/resources/aws/autoscaling.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | import jmespath 16 | 17 | from skew.resources.aws import AWSResource 18 | 19 | 20 | class AutoScalingGroup(AWSResource): 21 | 22 | class Meta(object): 23 | service = 'autoscaling' 24 | type = 'autoScalingGroup' 25 | name = 'AutoScalingGroupName' 26 | date = 'CreatedTime' 27 | dimension = 'AutoScalingGroupName' 28 | enum_spec = ('describe_auto_scaling_groups', 'AutoScalingGroups', None) 29 | detail_spec = None 30 | id = 'AutoScalingGroupName' 31 | filter_name = 'AutoScalingGroupNames' 32 | filter_type = 'list' 33 | 34 | def __init__(self, client, data, query=None): 35 | super(AutoScalingGroup, self).__init__(client, data, query) 36 | self._arn_query = jmespath.compile('AutoScalingGroupARN') 37 | 38 | @property 39 | def arn(self): 40 | return self._arn_query.search(self.data) 41 | 42 | 43 | class LaunchConfiguration(AWSResource): 44 | 45 | class Meta(object): 46 | service = 'autoscaling' 47 | type = 'launchConfiguration' 48 | name = 'LaunchConfigurationName' 49 | date = 'CreatedTime' 50 | dimension = 'AutoScalingGroupName' 51 | enum_spec = ( 52 | 'describe_launch_configurations', 'LaunchConfigurations', None) 53 | detail_spec = None 54 | id = 'LaunchConfigurationName' 55 | filter_name = 'LaunchConfigurationNames' 56 | filter_type = 'list' 57 | 58 | def __init__(self, client, data, query=None): 59 | super(LaunchConfiguration, self).__init__(client, data, query) 60 | self._arn_query = jmespath.compile('LaunchConfigurationARN') 61 | 62 | @property 63 | def arn(self): 64 | return self._arn_query.search(self.data) 65 | -------------------------------------------------------------------------------- /skew/resources/aws/cloudformation.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You 4 | # may not use this file except in compliance with the License. A copy of 5 | # the License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is 10 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 | # ANY KIND, either express or implied. See the License for the specific 12 | # language governing permissions and limitations under the License. 13 | import jmespath 14 | 15 | from skew.resources.aws import AWSResource 16 | 17 | 18 | class Stack(AWSResource): 19 | 20 | @classmethod 21 | def enumerate(cls, arn, region, account, resource_id=None, **kwargs): 22 | resources = super(Stack, cls).enumerate(arn, region, account, 23 | resource_id, **kwargs) 24 | for stack in resources: 25 | stack.data['Resources'] = [] 26 | for stack_resource in stack: 27 | resource_id = stack_resource.get('PhysicalResourceId') 28 | if not resource_id: 29 | resource_id = stack_resource.get('LogicalResourceId') 30 | stack.data['Resources'].append( 31 | { 32 | 'id': resource_id, 33 | 'type': stack_resource['ResourceType'] 34 | } 35 | ) 36 | return resources 37 | 38 | class Meta(object): 39 | service = 'cloudformation' 40 | type = 'stack' 41 | enum_spec = ('describe_stacks', 'Stacks[]', None) 42 | detail_spec = ('describe_stack_resources', 'StackName', 43 | 'StackResources[]') 44 | id = 'StackName' 45 | filter_name = 'StackName' 46 | name = 'StackName' 47 | date = 'CreationTime' 48 | dimension = None 49 | 50 | def __init__(self, client, data, query=None): 51 | super(Stack, self).__init__(client, data, query) 52 | self._data = data 53 | self._resources = [] 54 | 55 | def __iter__(self): 56 | detail_op, param_name, detail_path = self.Meta.detail_spec 57 | params = {param_name: self.id} 58 | if not self._resources: 59 | data = self._client.call(detail_op, **params) 60 | self._resources = jmespath.search(detail_path, data) 61 | for resource in self._resources: 62 | yield resource 63 | 64 | @property 65 | def arn(self): 66 | return self._data['StackId'] 67 | -------------------------------------------------------------------------------- /skew/resources/aws/cloudfront.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from skew.resources.aws import AWSResource 4 | 5 | 6 | LOG = logging.getLogger(__name__) 7 | 8 | 9 | class CloudfrontResource(AWSResource): 10 | 11 | @property 12 | def arn(self): 13 | return 'arn:aws:%s::%s:%s/%s' % ( 14 | self._client.service_name, 15 | self._client.account_id, self.resourcetype, self.id) 16 | 17 | 18 | class Distribution(CloudfrontResource): 19 | 20 | class Meta(object): 21 | service = 'cloudfront' 22 | type = 'distribution' 23 | enum_spec = ('list_distributions', 'DistributionList.Items[]', None) 24 | detail_spec = None 25 | id = 'Id' 26 | tags_spec = ('list_tags_for_resource', 'Tags.Items[]', 27 | 'Resource', 'arn') 28 | name = 'DomainName' 29 | filter_name = None 30 | date = 'LastModifiedTime' 31 | dimension = None 32 | 33 | @classmethod 34 | def filter(cls, arn, resource_id, data): 35 | LOG.debug('%s == %s', resource_id, data) 36 | return resource_id == data['Id'] 37 | -------------------------------------------------------------------------------- /skew/resources/aws/cloudtrail.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | import jmespath 15 | import logging 16 | from skew.resources.aws import AWSResource 17 | 18 | LOG = logging.getLogger(__name__) 19 | 20 | 21 | class CloudTrail(AWSResource): 22 | 23 | class Meta(object): 24 | service = 'cloudtrail' 25 | type = 'trail' 26 | enum_spec = ('describe_trails', 'trailList[]', None) 27 | attr_spec = None 28 | detail_spec = None 29 | id = 'Name' 30 | tags_spec = ('list_tags', 'ResourceTagList[].TagsList[]', 31 | 'ResourceIdList', 'name') 32 | filter_name = 'trailNameList' 33 | filter_type = 'arn' 34 | name = 'TrailARN' 35 | date = None 36 | dimension = None 37 | -------------------------------------------------------------------------------- /skew/resources/aws/cloudwatch.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | import jmespath 15 | import logging 16 | from skew.resources.aws import AWSResource 17 | 18 | LOG = logging.getLogger(__name__) 19 | 20 | 21 | class Alarm(AWSResource): 22 | 23 | class Meta(object): 24 | service = 'cloudwatch' 25 | type = 'alarm' 26 | enum_spec = ('describe_alarms', 'MetricAlarms', None) 27 | id = 'AlarmName' 28 | filter_name = 'AlarmNames' 29 | filter_type = None 30 | detail_spec = None 31 | name = 'AlarmName' 32 | date = 'AlarmConfigurationUpdatedTimestamp' 33 | dimension = None 34 | tags_spec = ('list_tags_for_resource', 'Tags[]', 'ResourceARN', 'arn') 35 | 36 | @property 37 | def arn(self): 38 | return 'arn:aws:%s:%s:%s:%s:%s' % ( 39 | self._client.service_name, 40 | self._client.region_name, 41 | self._client.account_id, 42 | self.resourcetype, self.id) 43 | 44 | 45 | class LogGroup(AWSResource): 46 | 47 | class Meta(object): 48 | service = 'logs' 49 | type = 'log-group' 50 | enum_spec = ('describe_log_groups', 'logGroups[]', None) 51 | attr_spec = [ 52 | ('describe_log_streams', 'logGroupName', 53 | 'logStreams', 'logStreams'), 54 | ('describe_metric_filters', 'logGroupName', 55 | 'metricFilters', 'metricFilters'), 56 | ('describe_subscription_filters', 'logGroupName', 57 | 'subscriptionFilters', 'subscriptionFilters'), 58 | ('describe_queries', 'logGroupName', 59 | 'queries', 'queries'), 60 | ] 61 | detail_spec = None 62 | id = 'logGroupName' 63 | tags_spec = ('list_tags_log_group', 'tags', 64 | 'logGroupName', 'id') 65 | filter_name = 'logGroupNamePrefix' 66 | filter_type = 'dict' 67 | name = 'logGroupName' 68 | date = 'creationTime' 69 | dimension = 'logGroupName' 70 | 71 | def __init__(self, client, data, query=None): 72 | super(LogGroup, self).__init__(client, data, query) 73 | self._data = data 74 | self._keys = [] 75 | self._id = data['logGroupName'] 76 | 77 | # add addition attribute data 78 | for attr in self.Meta.attr_spec: 79 | LOG.debug(attr) 80 | detail_op, param_name, detail_path, detail_key = attr 81 | params = {param_name: self._id} 82 | data = self._client.call(detail_op, **params) 83 | if not (detail_path is None): 84 | data = jmespath.search(detail_path, data) 85 | if 'ResponseMetadata' in data: 86 | del data['ResponseMetadata'] 87 | self.data[detail_key] = data 88 | LOG.debug(data) 89 | 90 | @property 91 | def logGroupName(self): 92 | return self.data.get('logGroupName') 93 | 94 | @property 95 | def arn(self): 96 | return 'arn:aws:%s:%s:%s:%s:%s' % ( 97 | self._client.service_name, 98 | self._client.region_name, 99 | self._client.account_id, self.resourcetype, self.id) 100 | -------------------------------------------------------------------------------- /skew/resources/aws/dynamodb.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | import logging 16 | 17 | import jmespath 18 | 19 | from skew.resources.aws import AWSResource 20 | 21 | 22 | LOG = logging.getLogger(__name__) 23 | 24 | 25 | class Table(AWSResource): 26 | 27 | class Meta(object): 28 | service = 'dynamodb' 29 | type = 'table' 30 | enum_spec = ('list_tables', 'TableNames', None) 31 | detail_spec = ('describe_table', 'TableName', 'Table') 32 | id = 'Table' 33 | tags_spec = ('list_tags_of_resource', 'Tags[]', 34 | 'ResourceArn', 'arn') 35 | filter_name = None 36 | name = 'TableName' 37 | date = 'CreationDateTime' 38 | dimension = 'TableName' 39 | 40 | @classmethod 41 | def filter(cls, arn, resource_id, data): 42 | LOG.debug('%s == %s', resource_id, data) 43 | return resource_id == data 44 | 45 | def __init__(self, client, data, query=None): 46 | super(Table, self).__init__(client, data, query) 47 | self._id = data 48 | detail_op, param_name, detail_path = self.Meta.detail_spec 49 | params = {param_name: self.id} 50 | data = client.call(detail_op, **params) 51 | self.data = jmespath.search(detail_path, data) 52 | -------------------------------------------------------------------------------- /skew/resources/aws/ec2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | from skew.resources.aws import AWSResource 16 | 17 | 18 | class Instance(AWSResource): 19 | 20 | class Meta(object): 21 | service = 'ec2' 22 | type = 'instance' 23 | enum_spec = ('describe_instances', 'Reservations[].Instances[]', None) 24 | detail_spec = None 25 | id = 'InstanceId' 26 | filter_name = 'InstanceIds' 27 | filter_type = 'list' 28 | name = 'PublicDnsName' 29 | date = 'LaunchTime' 30 | dimension = 'InstanceId' 31 | 32 | @property 33 | def parent(self): 34 | return self.data['ImageId'] 35 | 36 | 37 | class SecurityGroup(AWSResource): 38 | 39 | class Meta(object): 40 | service = 'ec2' 41 | type = 'security-group' 42 | enum_spec = ('describe_security_groups', 'SecurityGroups', None) 43 | detail_spec = None 44 | id = 'GroupId' 45 | filter_name = 'GroupNames' 46 | filter_type = 'list' 47 | name = 'GroupName' 48 | date = None 49 | dimension = None 50 | 51 | 52 | class KeyPair(AWSResource): 53 | 54 | class Meta(object): 55 | service = 'ec2' 56 | type = 'key-pair' 57 | enum_spec = ('describe_key_pairs', 'KeyPairs', None) 58 | detail_spec = None 59 | id = 'KeyPairId' 60 | filter_name = 'KeyNames' 61 | name = 'KeyName' 62 | date = None 63 | dimension = None 64 | 65 | 66 | class Address(AWSResource): 67 | 68 | class Meta(object): 69 | service = 'ec2' 70 | type = 'address' 71 | enum_spec = ('describe_addresses', 'Addresses', None) 72 | detail_spec = None 73 | id = 'AllocationId' 74 | filter_name = 'PublicIps' 75 | filter_type = 'list' 76 | name = 'PublicIp' 77 | date = None 78 | dimension = None 79 | 80 | 81 | class Volume(AWSResource): 82 | 83 | class Meta(object): 84 | service = 'ec2' 85 | type = 'volume' 86 | enum_spec = ('describe_volumes', 'Volumes', None) 87 | detail_spec = None 88 | id = 'VolumeId' 89 | filter_name = 'VolumeIds' 90 | filter_type = 'list' 91 | name = 'VolumeId' 92 | date = 'createTime' 93 | dimension = 'VolumeId' 94 | 95 | @property 96 | def parent(self): 97 | if len(self.data['Attachments']): 98 | return self.data['Attachments'][0]['InstanceId'] 99 | else: 100 | return None 101 | 102 | 103 | class Snapshot(AWSResource): 104 | 105 | class Meta(object): 106 | service = 'ec2' 107 | type = 'snapshot' 108 | enum_spec = ( 109 | 'describe_snapshots', 'Snapshots', {'OwnerIds': ['self']}) 110 | detail_spec = None 111 | id = 'SnapshotId' 112 | filter_name = 'SnapshotIds' 113 | filter_type = 'list' 114 | name = 'SnapshotId' 115 | date = 'StartTime' 116 | dimension = None 117 | 118 | @property 119 | def parent(self): 120 | if self.data['VolumeId']: 121 | return self.data['VolumeId'] 122 | else: 123 | return None 124 | 125 | 126 | class Image(AWSResource): 127 | 128 | class Meta(object): 129 | service = 'ec2' 130 | type = 'image' 131 | enum_spec = ( 132 | 'describe_images', 'Images', {'Owners': ['self']}) 133 | detail_spec = None 134 | id = 'ImageId' 135 | filter_name = 'ImageIds' 136 | filter_type = 'list' 137 | name = 'ImageId' 138 | date = 'StartTime' 139 | dimension = None 140 | 141 | @property 142 | def parent(self): 143 | if self.data['VolumeId']: 144 | return self.data['VolumeId'] 145 | else: 146 | return None 147 | 148 | 149 | class Vpc(AWSResource): 150 | 151 | class Meta(object): 152 | service = 'ec2' 153 | type = 'vpc' 154 | enum_spec = ('describe_vpcs', 'Vpcs', None) 155 | detail_spec = None 156 | id = 'VpcId' 157 | filter_name = 'VpcIds' 158 | filter_type = 'list' 159 | name = 'VpcId' 160 | date = None 161 | dimension = None 162 | 163 | 164 | class Subnet(AWSResource): 165 | 166 | class Meta(object): 167 | service = 'ec2' 168 | type = 'subnet' 169 | enum_spec = ('describe_subnets', 'Subnets', None) 170 | detail_spec = None 171 | id = 'SubnetId' 172 | filter_name = 'SubnetIds' 173 | filter_type = 'list' 174 | name = 'SubnetId' 175 | date = None 176 | dimension = None 177 | 178 | 179 | class CustomerGateway(AWSResource): 180 | 181 | class Meta(object): 182 | service = 'ec2' 183 | type = 'customer-gateway' 184 | enum_spec = ('describe_customer_gateways', 'CustomerGateways', None) 185 | detail_spec = None 186 | id = 'CustomerGatewayId' 187 | filter_name = 'CustomerGatewayIds' 188 | filter_type = 'list' 189 | name = 'CustomerGatewayId' 190 | date = None 191 | dimension = None 192 | 193 | 194 | class InternetGateway(AWSResource): 195 | 196 | class Meta(object): 197 | service = 'ec2' 198 | type = 'internet-gateway' 199 | enum_spec = ('describe_internet_gateways', 'InternetGateways', None) 200 | detail_spec = None 201 | id = 'InternetGatewayId' 202 | filter_name = 'InternetGatewayIds' 203 | filter_type = 'list' 204 | name = 'InternetGatewayId' 205 | date = None 206 | dimension = None 207 | 208 | 209 | class RouteTable(AWSResource): 210 | 211 | class Meta(object): 212 | service = 'ec2' 213 | type = 'route-table' 214 | enum_spec = ('describe_route_tables', 'RouteTables', None) 215 | detail_spec = None 216 | id = 'RouteTableId' 217 | filter_name = 'RouteTableIds' 218 | filter_type = 'list' 219 | name = 'RouteTableId' 220 | date = None 221 | dimension = None 222 | 223 | 224 | class NatGateway(AWSResource): 225 | 226 | class Meta(object): 227 | service = 'ec2' 228 | type = 'natgateway' 229 | enum_spec = ('describe_nat_gateways', 'NatGateways', None) 230 | detail_spec = None 231 | id = 'NatGatewayId' 232 | filter_name = 'NatGatewayIds' 233 | filter_type = 'list' 234 | name = 'NatGatewayId' 235 | date = 'CreateTime' 236 | dimension = None 237 | 238 | 239 | class NetworkAcl(AWSResource): 240 | 241 | class Meta(object): 242 | service = 'ec2' 243 | type = 'network-acl' 244 | enum_spec = ('describe_network_acls', 'NetworkAcls', None) 245 | detail_spec = None 246 | id = 'NetworkAclId' 247 | filter_name = 'NetworkAclIds' 248 | filter_type = 'list' 249 | name = 'NetworkAclId' 250 | date = None 251 | dimension = None 252 | 253 | 254 | class VpcPeeringConnection(AWSResource): 255 | 256 | class Meta(object): 257 | service = 'ec2' 258 | type = 'vpc-peering-connection' 259 | enum_spec = ('describe_vpc_peering_connections', 260 | 'VpcPeeringConnections', None) 261 | detail_spec = None 262 | id = 'VpcPeeringConnectionId' 263 | filter_name = 'VpcPeeringConnectionIds' 264 | filter_type = 'list' 265 | name = 'VpcPeeringConnectionId' 266 | date = None 267 | dimension = None 268 | 269 | 270 | class LaunchTemplate(AWSResource): 271 | 272 | class Meta(object): 273 | service = 'ec2' 274 | type = 'launch-template' 275 | enum_spec = ('describe_launch_templates', 'LaunchTemplates', None) 276 | detail_spec = None 277 | id = 'LaunchTemplateId' 278 | filter_name = 'LaunchTemplateIds' 279 | filter_type = 'list' 280 | name = 'LaunchTemplateName' 281 | date = 'CreateTime' 282 | dimension = None 283 | 284 | 285 | class FlowLog(AWSResource): 286 | 287 | class Meta(object): 288 | service = 'ec2' 289 | type = 'flow-log' 290 | enum_spec = ('describe_flow_logs', 'FlowLogs', None) 291 | detail_spec = None 292 | id = 'FlowLogId' 293 | filter_name = 'FlowLogIds' 294 | filter_type = 'list' 295 | name = 'LogGroupName' 296 | date = 'CreationTime' 297 | dimension = None 298 | -------------------------------------------------------------------------------- /skew/resources/aws/elasticache.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | from skew.resources.aws import AWSResource 16 | 17 | 18 | class Cluster(AWSResource): 19 | 20 | class Meta(object): 21 | service = 'elasticache' 22 | type = 'cluster' 23 | enum_spec = ('describe_cache_clusters', 24 | 'CacheClusters[]', None) 25 | detail_spec = None 26 | id = 'CacheClusterId' 27 | tags_spec = ('list_tags_for_resource', 'TagList', 28 | 'ResourceName', 'arn') 29 | filter_name = 'CacheClusterId' 30 | filter_type = 'scalar' 31 | name = 'CacheClusterId' 32 | date = 'CacheClusterCreateTime' 33 | dimension = 'CacheClusterId' 34 | 35 | @property 36 | def arn(self): 37 | return 'arn:aws:%s:%s:%s:%s:%s' % ( 38 | self._client.service_name, 39 | self._client.region_name, 40 | self._client.account_id, self.resourcetype, self.id) 41 | 42 | 43 | class SubnetGroup(AWSResource): 44 | 45 | class Meta(object): 46 | service = 'elasticache' 47 | type = 'subnet-group' 48 | enum_spec = ('describe_cache_subnet_groups', 49 | 'CacheSubnetGroups', None) 50 | detail_spec = None 51 | id = 'CacheSubnetGroupName' 52 | filter_name = 'CacheSubnetGroupName' 53 | filter_type = 'scalar' 54 | name = 'CacheSubnetGroupName' 55 | date = None 56 | dimension = None 57 | 58 | 59 | class Snapshot(AWSResource): 60 | 61 | class Meta(object): 62 | service = 'elasticache' 63 | type = 'snapshot' 64 | enum_spec = ('describe_snapshots', 'Snapshots', None) 65 | detail_spec = None 66 | id = 'SnapshotName' 67 | tags_spec = ('list_tags_for_resource', 'TagList', 68 | 'ResourceName', 'arn') 69 | filter_name = 'SnapshotName' 70 | filter_type = 'scalar' 71 | name = 'SnapshotName' 72 | date = 'StartTime' 73 | dimension = None 74 | 75 | @property 76 | def arn(self): 77 | return 'arn:aws:%s:%s:%s:%s:%s' % ( 78 | self._client.service_name, 79 | self._client.region_name, 80 | self._client.account_id, self.resourcetype, self.id) 81 | -------------------------------------------------------------------------------- /skew/resources/aws/elasticbeanstalk.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"). You 2 | # may not use this file except in compliance with the License. A copy of 3 | # the License is located at 4 | # 5 | # http://aws.amazon.com/apache2.0/ 6 | # 7 | # or in the "license" file accompanying this file. This file is 8 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 9 | # ANY KIND, either express or implied. See the License for the specific 10 | # language governing permissions and limitations under the License. 11 | 12 | from skew.resources.aws import AWSResource 13 | 14 | 15 | class Application(AWSResource): 16 | class Meta(object): 17 | service = 'elasticbeanstalk' 18 | type = 'application' 19 | enum_spec = ('describe_applications', 'Applications', None) 20 | detail_spec = None 21 | id = 'ApplicationName' 22 | filter_name = None 23 | filter_type = None 24 | name = 'ApplicationName' 25 | date = None 26 | dimension = None 27 | tags_spec = ('list_tags_for_resource', 'ResourceTags[]', 28 | 'ResourceArn', 'arn') 29 | 30 | 31 | class Environment(AWSResource): 32 | class Meta(object): 33 | service = 'elasticbeanstalk' 34 | type = 'environment' 35 | enum_spec = ('describe_environments', 'Environments', None) 36 | detail_spec = None 37 | id = 'EnvironmentName' 38 | filter_name = None 39 | filter_type = None 40 | name = 'EnvironmentName' 41 | date = None 42 | dimension = None 43 | tags_spec = ('list_tags_for_resource', 'ResourceTags[]', 44 | 'ResourceArn', 'arn') 45 | 46 | @property 47 | def arn(self): 48 | return 'arn:aws:%s:%s:%s:%s/%s/%s' % ( 49 | self._client.service_name, 50 | self._client.region_name, 51 | self._client.account_id, 52 | self.resourcetype, 53 | self.data['ApplicationName'], 54 | self.id 55 | ) 56 | -------------------------------------------------------------------------------- /skew/resources/aws/elb.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | import jmespath 15 | import logging 16 | from skew.resources.aws import AWSResource 17 | 18 | LOG = logging.getLogger(__name__) 19 | 20 | 21 | class LoadBalancer(AWSResource): 22 | 23 | class Meta(object): 24 | service = 'elb' 25 | type = 'loadbalancer' 26 | enum_spec = ('describe_load_balancers', 27 | 'LoadBalancerDescriptions', None) 28 | detail_spec = None 29 | attr_spec = [ 30 | ('describe_load_balancer_attributes', 'LoadBalancerName', 31 | 'LoadBalancerAttributes', 'LoadBalancerAttributes'), 32 | ('describe_load_balancer_policies', 'LoadBalancerName', 33 | 'PolicyDescriptions', 'PolicyDescriptions'), 34 | ] 35 | id = 'LoadBalancerName' 36 | filter_name = 'LoadBalancerNames' 37 | filter_type = 'list' 38 | name = 'DNSName' 39 | date = 'CreatedTime' 40 | dimension = 'LoadBalancerName' 41 | tags_spec = ('describe_tags', 'TagDescriptions[].Tags[]', 42 | 'LoadBalancerNames', 'id') 43 | 44 | def __init__(self, client, data, query=None): 45 | super(LoadBalancer, self).__init__(client, data, query) 46 | self._id = data['LoadBalancerName'] 47 | 48 | # add addition attribute data 49 | for attr in self.Meta.attr_spec: 50 | LOG.debug(attr) 51 | detail_op, param_name, detail_path, detail_key = attr 52 | params = {param_name: self._id} 53 | data = self._client.call(detail_op, **params) 54 | if not (detail_path is None): 55 | data = jmespath.search(detail_path, data) 56 | if 'ResponseMetadata' in data: 57 | del data['ResponseMetadata'] 58 | self.data[detail_key] = data 59 | LOG.debug(data) 60 | -------------------------------------------------------------------------------- /skew/resources/aws/es.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | import jmespath 16 | 17 | from skew.resources.aws import AWSResource 18 | 19 | 20 | class ElasticsearchDomain(AWSResource): 21 | 22 | class Meta(object): 23 | service = 'es' 24 | type = 'domain' 25 | enum_spec = ('list_domain_names', 'DomainNames[].DomainName', None) 26 | tags_spec = ('list_tags', 'TagList', 27 | 'ARN', 'arn') 28 | detail_spec = ('describe_elasticsearch_domain', 'DomainName', 'DomainStatus') 29 | id = 'DomainName' 30 | filter_name = None 31 | name = 'DomainName' 32 | date = None 33 | dimension = 'DomainName' 34 | 35 | def __init__(self, client, data, query=None): 36 | super(ElasticsearchDomain, self).__init__(client, data, query) 37 | self._id = data 38 | detail_op, param_name, detail_path = self.Meta.detail_spec 39 | params = {param_name: self.id} 40 | data = client.call(detail_op, **params) 41 | self.data = jmespath.search(detail_path, data) 42 | -------------------------------------------------------------------------------- /skew/resources/aws/firehose.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"). You 2 | # may not use this file except in compliance with the License. A copy of 3 | # the License is located at 4 | # 5 | # http://aws.amazon.com/apache2.0/ 6 | # 7 | # or in the "license" file accompanying this file. This file is 8 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 9 | # ANY KIND, either express or implied. See the License for the specific 10 | # language governing permissions and limitations under the License. 11 | 12 | from skew.resources.aws import AWSResource 13 | 14 | import jmespath 15 | 16 | class DeliveryStream(AWSResource): 17 | 18 | class Meta(object): 19 | service = 'firehose' 20 | type = 'deliverystream' 21 | enum_spec = ('list_delivery_streams', 'DeliveryStreamNames', None) 22 | detail_spec = ('describe_delivery_stream', 'DeliveryStreamName', 'DeliveryStreamDescription') 23 | id = 'DeliveryStreamName' 24 | filter_name = None 25 | filter_type = None 26 | name = 'DeliveryStreamName' 27 | date = 'CreateTimestamp' 28 | dimension = 'DeliveryStreamName' 29 | tags_spec = ('list_tags_for_delivery_stream', 'Tags[]', 'DeliveryStreamName', 'id') 30 | 31 | def __init__(self, client, data, query=None): 32 | super(DeliveryStream, self).__init__(client, data, query) 33 | self._id = data 34 | detail_op, param_name, detail_path = self.Meta.detail_spec 35 | params = {param_name: self.id} 36 | data = client.call(detail_op, **params) 37 | self.data = jmespath.search(detail_path, data) 38 | -------------------------------------------------------------------------------- /skew/resources/aws/iam.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | import logging 16 | import jmespath 17 | from skew.resources.aws import AWSResource 18 | 19 | LOG = logging.getLogger(__name__) 20 | 21 | 22 | class IAMResource(AWSResource): 23 | 24 | @property 25 | def arn(self): 26 | return 'arn:aws:%s::%s:%s/%s' % ( 27 | self._client.service_name, 28 | self._client.account_id, self.resourcetype, self.name) 29 | 30 | 31 | class Group(IAMResource): 32 | 33 | class Meta(object): 34 | service = 'iam' 35 | type = 'group' 36 | enum_spec = ('list_groups', 'Groups', None) 37 | detail_spec = None 38 | id = 'GroupId' 39 | name = 'GroupName' 40 | filter_name = None 41 | date = 'CreateDate' 42 | dimension = None 43 | 44 | @classmethod 45 | def filter(cls, arn, resource_id, data): 46 | LOG.debug('%s == %s', resource_id, data) 47 | return resource_id == data['GroupName'] 48 | 49 | 50 | class User(IAMResource): 51 | 52 | class Meta(object): 53 | service = 'iam' 54 | type = 'user' 55 | enum_spec = ('list_users', 'Users', None) 56 | detail_spec = ('get_user', 'UserName', 'User') 57 | attr_spec = [ 58 | ('list_access_keys', 'UserName', 59 | 'AccessKeyMetadata', 'AccessKeyMetadata'), 60 | ('list_groups_for_user', 'UserName', 61 | 'Groups', 'Groups'), 62 | ('list_user_policies', 'UserName', 63 | 'PolicyNames', 'PolicyNames'), 64 | ('list_attached_user_policies', 'UserName', 65 | 'AttachedPolicies', 'AttachedPolicies'), 66 | ('list_ssh_public_keys', 'UserName', 67 | 'SSHPublicKeys', 'SSHPublicKeys'), 68 | # ('list_mfa_devices', 'UserName', 'MFADevices', 'MFADevices'), 69 | ] 70 | id = 'UserId' 71 | filter_name = None 72 | name = 'UserName' 73 | date = 'CreateDate' 74 | dimension = None 75 | tags_spec = ('list_user_tags', 'Tags[]', 76 | 'UserName', 'name') 77 | 78 | def __init__(self, client, data, query=None): 79 | super(User, self).__init__(client, data, query) 80 | 81 | LOG.debug(data) 82 | # add details 83 | if self.Meta.detail_spec is not None: 84 | detail_op, param_name, detail_path = self.Meta.detail_spec 85 | params = {param_name: self.data[param_name]} 86 | data = client.call(detail_op, **params) 87 | self.data = jmespath.search(detail_path, data) 88 | 89 | # add attribute data 90 | if self.Meta.attr_spec is not None: 91 | for attr in self.Meta.attr_spec: 92 | LOG.debug(attr) 93 | LOG.debug(data) 94 | detail_op, param_name, detail_path, detail_key = attr 95 | params = {param_name: self.data[param_name]} 96 | tmp_data = self._client.call(detail_op, **params) 97 | if not (detail_path is None): 98 | tmp_data = jmespath.search(detail_path, tmp_data) 99 | if 'ResponseMetadata' in tmp_data: 100 | del tmp_data['ResponseMetadata'] 101 | self.data[detail_key] = tmp_data 102 | LOG.debug(data) 103 | 104 | # retrieve all of the inline IAM policies 105 | if 'PolicyNames' in self.data \ 106 | and self.data['PolicyNames']: 107 | tmp_dict = {} 108 | for policy_name in self.data['PolicyNames']: 109 | params = { 110 | 'UserName': self.data['UserName'], 111 | 'PolicyName': policy_name 112 | } 113 | tmp_data = self._client.call('get_user_policy', **params) 114 | tmp_data = jmespath.search('PolicyDocument', tmp_data) 115 | tmp_dict[policy_name] = tmp_data 116 | self.data['PolicyNames'] = tmp_dict 117 | 118 | @classmethod 119 | def filter(cls, arn, resource_id, data): 120 | LOG.debug('%s == %s', resource_id, data) 121 | return resource_id == data['UserName'] 122 | 123 | 124 | class Role(IAMResource): 125 | 126 | class Meta(object): 127 | service = 'iam' 128 | type = 'role' 129 | enum_spec = ('list_roles', 'Roles', None) 130 | detail_spec = None 131 | id = 'RoleId' 132 | filter_name = None 133 | name = 'RoleName' 134 | date = 'CreateDate' 135 | dimension = None 136 | tags_spec = ('list_role_tags', 'Tags[]', 'RoleName', 'name') 137 | 138 | @classmethod 139 | def filter(cls, arn, resource_id, data): 140 | LOG.debug('%s == %s', resource_id, data) 141 | return resource_id == data['RoleName'] 142 | 143 | 144 | class InstanceProfile(IAMResource): 145 | 146 | class Meta(object): 147 | service = 'iam' 148 | type = 'instance-profile' 149 | enum_spec = ('list_instance_profiles', 'InstanceProfiles', None) 150 | detail_spec = None 151 | id = 'InstanceProfileId' 152 | filter_name = None 153 | name = 'InstanceProfileId' 154 | date = 'CreateDate' 155 | dimension = None 156 | 157 | @classmethod 158 | def filter(cls, arn, resource_id, data): 159 | LOG.debug('%s == %s', resource_id, data) 160 | return resource_id == data['InstanceProfileId'] 161 | 162 | 163 | class Policy(IAMResource): 164 | 165 | class Meta(object): 166 | service = 'iam' 167 | type = 'policy' 168 | enum_spec = ('list_policies', 'Policies', None) 169 | detail_spec = None 170 | id = 'PolicyArn' 171 | filter_name = None 172 | name = 'PolicyName' 173 | date = 'CreateDate' 174 | dimension = None 175 | 176 | @classmethod 177 | def filter(cls, arn, resource_id, data): 178 | LOG.debug('%s == %s', resource_id, data) 179 | return resource_id == data['PolicyName'] 180 | 181 | 182 | class ServerCertificate(IAMResource): 183 | 184 | class Meta(object): 185 | service = 'iam' 186 | type = 'server-certificate' 187 | enum_spec = ('list_server_certificates', 188 | 'ServerCertificateMetadataList', 189 | None) 190 | detail_spec = None 191 | id = 'ServerCertificateId' 192 | filter_name = None 193 | name = 'ServerCertificateName' 194 | date = 'Expiration' 195 | dimension = None 196 | 197 | @classmethod 198 | def filter(cls, arn, resource_id, data): 199 | LOG.debug('%s == %s', resource_id, data) 200 | return resource_id == data['ServerCertificateName'] 201 | -------------------------------------------------------------------------------- /skew/resources/aws/kinesis.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | from skew.resources.aws import AWSResource 16 | 17 | 18 | class Stream(AWSResource): 19 | 20 | class Meta(object): 21 | service = 'kinesis' 22 | type = 'stream' 23 | enum_spec = ('list_streams', 'StreamNames', None) 24 | detail_spec = None 25 | id = 'StreamName' 26 | filter_name = None 27 | filter_type = None 28 | name = 'StreamName' 29 | date = None 30 | dimension = 'StreamName' 31 | tags_spec = ('list_tags_for_stream', 'Tags[]', 32 | 'StreamName', 'id') 33 | 34 | def __init__(self, client, data, query=None): 35 | super(Stream, self).__init__(client, data, query) 36 | self.data = {self.Meta.id: data} 37 | self._id = self.data[self.Meta.id] 38 | -------------------------------------------------------------------------------- /skew/resources/aws/lambda.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | import logging 16 | 17 | from skew.resources.aws import AWSResource 18 | 19 | 20 | LOG = logging.getLogger(__name__) 21 | 22 | 23 | class Function(AWSResource): 24 | 25 | @classmethod 26 | def enumerate(cls, arn, region, account, resource_id=None, **kwargs): 27 | resources = super(Function, cls).enumerate(arn, region, account, 28 | resource_id, **kwargs) 29 | for r in resources: 30 | r.data['EventSources'] = [] 31 | kwargs = {'FunctionName': r.data['FunctionName']} 32 | response = r._client.call('list_event_source_mappings', **kwargs) 33 | for esm in response['EventSourceMappings']: 34 | r.data['EventSources'].append(esm['EventSourceArn']) 35 | return resources 36 | 37 | class Meta(object): 38 | service = 'lambda' 39 | type = 'function' 40 | enum_spec = ('list_functions', 'Functions', None) 41 | detail_spec = None 42 | id = 'FunctionName' 43 | filter_name = None 44 | name = 'FunctionName' 45 | date = 'LastModified' 46 | dimension = 'FunctionName' 47 | tags_spec = ('list_tags', 'Tags', 48 | 'Resource', 'arn') 49 | 50 | @classmethod 51 | def filter(cls, arn, resource_id, data): 52 | function_name = data.get(cls.Meta.id) 53 | LOG.debug('%s == %s', resource_id, function_name) 54 | return resource_id == function_name 55 | 56 | @property 57 | def arn(self): 58 | return self.data.get('FunctionArn') 59 | -------------------------------------------------------------------------------- /skew/resources/aws/rds.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | from skew.resources.aws import AWSResource 16 | 17 | 18 | class DBInstance(AWSResource): 19 | 20 | class Meta(object): 21 | service = 'rds' 22 | type = 'db' 23 | enum_spec = ('describe_db_instances', 'DBInstances', None) 24 | tags_spec = ('list_tags_for_resource', 'TagList', 25 | 'ResourceName', 'arn') 26 | detail_spec = None 27 | id = 'DBInstanceIdentifier' 28 | filter_name = 'DBInstanceIdentifier' 29 | filter_type = 'scalar' 30 | name = 'Endpoint.Address' 31 | date = 'InstanceCreateTime' 32 | dimension = 'DBInstanceIdentifier' 33 | 34 | @property 35 | def arn(self): 36 | return 'arn:aws:%s:%s:%s:%s:%s' % ( 37 | self._client.service_name, 38 | self._client.region_name, 39 | self._client.account_id, self.resourcetype, self.id) 40 | 41 | 42 | class DBSecurityGroup(AWSResource): 43 | 44 | class Meta(object): 45 | service = 'rds' 46 | type = 'secgrp' 47 | enum_spec = ('describe_db_security_groups', 'DBSecurityGroups', None) 48 | detail_spec = None 49 | id = 'DBSecurityGroupName' 50 | filter_name = 'DBSecurityGroupName' 51 | filter_type = 'scalar' 52 | name = 'DBSecurityGroupDescription' 53 | date = None 54 | dimension = None 55 | tags_spec = ('list_tags_for_resource', 'TagList', 'ResourceName', 'arn') 56 | 57 | @property 58 | def arn(self): 59 | return 'arn:aws:%s:%s:%s:%s:%s' % ( 60 | self._client.service_name, 61 | self._client.region_name, 62 | self._client.account_id, self.resourcetype, self.id) 63 | 64 | -------------------------------------------------------------------------------- /skew/resources/aws/redshift.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | from skew.resources.aws import AWSResource 16 | 17 | 18 | class Cluster(AWSResource): 19 | 20 | class Meta(object): 21 | service = 'redshift' 22 | type = 'cluster' 23 | enum_spec = ('describe_clusters', 'Clusters', None) 24 | detail_spec = None 25 | id = 'ClusterIdentifier' 26 | filter_name = 'ClusterIdentifier' 27 | filter_type = 'scalar' 28 | name = 'ClusterIdentifier' 29 | date = 'ClusterCreateTime' 30 | dimension = 'ClusterIdentifier' 31 | -------------------------------------------------------------------------------- /skew/resources/aws/route53.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | from skew.resources.aws import AWSResource 16 | 17 | 18 | class Route53Resource(AWSResource): 19 | 20 | @property 21 | def arn(self): 22 | return 'arn:aws:%s:::%s/%s' % ( 23 | self._client.service_name, self.resourcetype, self.id) 24 | 25 | 26 | class HostedZone(Route53Resource): 27 | 28 | class Meta(object): 29 | service = 'route53' 30 | type = 'hostedzone' 31 | enum_spec = ('list_hosted_zones', 'HostedZones', None) 32 | detail_spec = ('GetHostedZone', 'Id', None) 33 | id = 'Id' 34 | filter_name = None 35 | name = 'Name' 36 | date = None 37 | dimension = None 38 | tags_spec = ('list_tags_for_resource', 'ResourceTagSet.Tags[]', 39 | 'ResourceId', 'id', {'ResourceType': 'hostedzone'}) 40 | 41 | @property 42 | def id(self): 43 | return self._id.split('/')[-1] 44 | 45 | 46 | class HealthCheck(Route53Resource): 47 | 48 | class Meta(object): 49 | service = 'route53' 50 | type = 'healthcheck' 51 | enum_spec = ('list_health_checks', 'HealthChecks', None) 52 | detail_spec = ('GetHealthCheck', 'Id', None) 53 | id = 'Id' 54 | filter_name = None 55 | name = None 56 | date = None 57 | dimension = None 58 | tags_spec = ('list_tags_for_resource', 'ResourceTagSet.Tags[]', 59 | 'ResourceId', 'id', {'ResourceType': 'healthcheck'}) 60 | 61 | 62 | class ResourceRecordSet(Route53Resource): 63 | 64 | class Meta(object): 65 | service = 'route53' 66 | type = 'rrset' 67 | enum_spec = ('list_resource_record_sets', 'ResourceRecordSets', None) 68 | detail_spec = None 69 | id = 'Name' 70 | filter_name = None 71 | name = 'Name' 72 | date = None 73 | dimension = None 74 | -------------------------------------------------------------------------------- /skew/resources/aws/s3.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You 4 | # may not use this file except in compliance with the License. A copy of 5 | # the License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is 10 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 | # ANY KIND, either express or implied. See the License for the specific 12 | # language governing permissions and limitations under the License. 13 | import jmespath 14 | import logging 15 | 16 | from skew.resources.aws import AWSResource 17 | 18 | LOG = logging.getLogger(__name__) 19 | 20 | 21 | class Bucket(AWSResource): 22 | 23 | _location_cache = {} 24 | 25 | @classmethod 26 | def enumerate(cls, arn, region, account, resource_id=None, **kwargs): 27 | resources = super(Bucket, cls).enumerate(arn, region, account, 28 | resource_id, 29 | **kwargs) 30 | region_resources = [] 31 | if region is None: 32 | region = 'us-east-1' 33 | for r in resources: 34 | location = cls._location_cache.get(r.id) 35 | if location is None: 36 | LOG.debug('finding location for %s', r.id) 37 | kwargs = {'Bucket': r.id} 38 | response = r._client.call('get_bucket_location', **kwargs) 39 | location = response.get('LocationConstraint', 'us-east-1') 40 | if location is None: 41 | location = 'us-east-1' 42 | if location is 'EU': 43 | location = 'eu-west-1' 44 | cls._location_cache[r.id] = location 45 | if location == region: 46 | region_resources.append(r) 47 | return region_resources 48 | 49 | class Meta(object): 50 | service = 's3' 51 | type = 'bucket' 52 | enum_spec = ('list_buckets', 'Buckets[]', None) 53 | detail_spec = ('list_objects', 'Bucket', 'Contents[]') 54 | id = 'Name' 55 | filter_name = None 56 | name = 'BucketName' 57 | date = 'CreationDate' 58 | dimension = None 59 | tags_spec = ('get_bucket_tagging', 'TagSet[]', 60 | 'Bucket', 'id') 61 | 62 | def __init__(self, client, data, query=None): 63 | super(Bucket, self).__init__(client, data, query) 64 | self._data = data 65 | self._keys = [] 66 | 67 | def __iter__(self): 68 | detail_op, param_name, detail_path = self.Meta.detail_spec 69 | params = {param_name: self.id} 70 | if not self._keys: 71 | data = self._client.call(detail_op, **params) 72 | self._keys = jmespath.search(detail_path, data) 73 | for key in self._keys: 74 | yield key 75 | -------------------------------------------------------------------------------- /skew/resources/aws/sns.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"). You 2 | # may not use this file except in compliance with the License. A copy of 3 | # the License is located at 4 | # 5 | # http://aws.amazon.com/apache2.0/ 6 | # 7 | # or in the "license" file accompanying this file. This file is 8 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 9 | # ANY KIND, either express or implied. See the License for the specific 10 | # language governing permissions and limitations under the License. 11 | 12 | import logging 13 | 14 | import jmespath 15 | 16 | from skew.resources.aws import AWSResource 17 | 18 | LOG = logging.getLogger(__name__) 19 | 20 | 21 | class Topic(AWSResource): 22 | 23 | class Meta(object): 24 | service = 'sns' 25 | type = 'topic' 26 | enum_spec = ('list_topics', 'Topics', None) 27 | detail_spec = ('get_topic_attributes', 'TopicArn', 'Attributes') 28 | id = 'TopicArn' 29 | filter_name = None 30 | filter_type = None 31 | name = 'DisplayName' 32 | date = None 33 | dimension = 'TopicName' 34 | tags_spec = ('list_tags_for_resource', 'Tags[]', 'ResourceArn', 'arn') 35 | 36 | @classmethod 37 | def filter(cls, arn, resource_id, data): 38 | topic_arn = data.get(cls.Meta.id) 39 | LOG.debug('%s == %s', arn, topic_arn) 40 | return arn == topic_arn 41 | 42 | @property 43 | def arn(self): 44 | return self.data.get('TopicArn') 45 | 46 | def __init__(self, client, data, query=None): 47 | super(Topic, self).__init__(client, data, query) 48 | 49 | self._id = data['TopicArn'].split(':', 5)[5] 50 | 51 | detail_op, param_name, detail_path = self.Meta.detail_spec 52 | params = {param_name: data['TopicArn']} 53 | data = client.call(detail_op, **params) 54 | 55 | self.data = jmespath.search(detail_path, data) 56 | 57 | 58 | class Subscription(AWSResource): 59 | 60 | invalid_arns = ['PendingConfirmation', 'Deleted'] 61 | 62 | class Meta(object): 63 | service = 'sns' 64 | type = 'subscription' 65 | enum_spec = ('list_subscriptions', 'Subscriptions', None) 66 | detail_spec = ('get_subscription_attributes', 'SubscriptionArn', 67 | 'Attributes') 68 | id = 'SubscriptionArn' 69 | filter_name = None 70 | filter_type = None 71 | name = 'SubscriptionArn' 72 | date = None 73 | dimension = None 74 | 75 | @property 76 | def arn(self): 77 | return self.data.get('SubscriptionArn') 78 | 79 | @classmethod 80 | def enumerate(cls, arn, region, account, resource_id=None, **kwargs): 81 | resources = super(Subscription, cls).enumerate( 82 | arn, region, account, resource_id, **kwargs) 83 | 84 | return [r for r in resources if r.id not in cls.invalid_arns] 85 | 86 | def __init__(self, client, data, query=None): 87 | super(Subscription, self).__init__(client, data, query) 88 | 89 | if data['SubscriptionArn'] in self.invalid_arns: 90 | self._id = 'PendingConfirmation' 91 | return 92 | 93 | self._id = data['SubscriptionArn'].split(':', 6)[6] 94 | self._name = "" 95 | 96 | detail_op, param_name, detail_path = self.Meta.detail_spec 97 | params = {param_name: data['SubscriptionArn']} 98 | data = client.call(detail_op, **params) 99 | 100 | self.data = jmespath.search(detail_path, data) 101 | -------------------------------------------------------------------------------- /skew/resources/aws/sqs.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | from skew.resources.aws import AWSResource 16 | 17 | 18 | class Queue(AWSResource): 19 | 20 | class Meta(object): 21 | service = 'sqs' 22 | type = 'queue' 23 | enum_spec = ('list_queues', 'QueueUrls', None) 24 | detail_spec = ('get_queue_attributes', 'QueueUrl', 'QueueUrl') 25 | id = 'QueueUrl' 26 | filter_name = 'QueueNamePrefix' 27 | filter_type = 'scalar' 28 | name = 'QueueUrl' 29 | date = None 30 | dimension = 'QueueName' 31 | tags_spec = ('list_queue_tags', 'Tags', 'QueueUrl', 'name') 32 | 33 | def __init__(self, client, data, query=None): 34 | super(Queue, self).__init__(client, data, query) 35 | self.data = {self.Meta.id: data, 36 | 'QueueName': data.split('/')[-1]} 37 | self._id = self.data['QueueName'] 38 | -------------------------------------------------------------------------------- /skew/resources/resource.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # Copyright (c) 2015 Mitch Garnaat 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). You 5 | # may not use this file except in compliance with the License. A copy of 6 | # the License is located at 7 | # 8 | # http://aws.amazon.com/apache2.0/ 9 | # 10 | # or in the "license" file accompanying this file. This file is 11 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 12 | # ANY KIND, either express or implied. See the License for the specific 13 | # language governing permissions and limitations under the License. 14 | 15 | import logging 16 | import jmespath 17 | 18 | import skew.awsclient 19 | 20 | from botocore.exceptions import ClientError 21 | 22 | LOG = logging.getLogger(__name__) 23 | 24 | 25 | class Resource(object): 26 | 27 | @classmethod 28 | def enumerate(cls, arn, region, account, resource_id=None, **kwargs): 29 | client = skew.awsclient.get_awsclient( 30 | cls.Meta.service, region, account, **kwargs) 31 | kwargs = {} 32 | do_client_side_filtering = False 33 | if resource_id and resource_id != '*': 34 | # If we are looking for a specific resource and the 35 | # API provides a way to filter on a specific resource 36 | # id then let's insert the right parameter to do the filtering. 37 | # If the API does not support that, we will have to filter 38 | # after we get all of the results. 39 | filter_name = cls.Meta.filter_name 40 | if filter_name: 41 | if cls.Meta.filter_type == 'arn': 42 | kwargs[filter_name] = [str(arn)] 43 | elif cls.Meta.filter_type == 'list': 44 | kwargs[filter_name] = [resource_id] 45 | else: 46 | kwargs[filter_name] = resource_id 47 | else: 48 | do_client_side_filtering = True 49 | enum_op, path, extra_args = cls.Meta.enum_spec 50 | if extra_args: 51 | kwargs.update(extra_args) 52 | LOG.debug('enum_spec=%s' % str(cls.Meta.enum_spec)) 53 | try: 54 | data = client.call(enum_op, query=path, **kwargs) 55 | except ClientError as e: 56 | LOG.debug(e) 57 | data = {} 58 | # if the error is because the resource was not found, be quiet 59 | if 'NotFound' not in e.response['Error']['Code']: 60 | raise 61 | LOG.debug(data) 62 | resources = [] 63 | if data: 64 | for d in data: 65 | if do_client_side_filtering: 66 | # If the API does not support filtering, the resource 67 | # class should provide a filter method that will 68 | # return True if the returned data matches the 69 | # resource ID we are looking for. 70 | if not cls.filter(arn, resource_id, d): 71 | continue 72 | resources.append(cls(client, d, arn.query)) 73 | return resources 74 | 75 | class Meta(object): 76 | type = 'resource' 77 | dimension = None 78 | tags_spec = None 79 | id = None 80 | date = None 81 | name = None 82 | 83 | def __init__(self, client, data): 84 | self._client = client 85 | if data is None: 86 | data = {} 87 | self.data = data 88 | if hasattr(self.Meta, 'id') and isinstance(self.data, dict): 89 | self._id = self.data.get(self.Meta.id, '') 90 | else: 91 | self._id = '' 92 | self._metrics = list() 93 | self._name = None 94 | self._date = None 95 | 96 | def __repr__(self): 97 | return self.arn 98 | 99 | @property 100 | def arn(self): 101 | return 'arn:aws:%s:%s:%s:%s/%s' % ( 102 | self._client.service_name, 103 | self._client.region_name, 104 | self._client.account_id, 105 | self.resourcetype, self.id) 106 | 107 | @property 108 | def resourcetype(self): 109 | return self.Meta.type 110 | 111 | @property 112 | def parent(self): 113 | pass 114 | 115 | @property 116 | def name(self): 117 | if not self._name: 118 | self._name = jmespath.search(self.Meta.name, self.data) 119 | return self._name 120 | 121 | @property 122 | def id(self): 123 | return self._id 124 | 125 | @property 126 | def date(self): 127 | if not self._date: 128 | self._date = jmespath.search(self.Meta.date, self.data) 129 | return self._date 130 | 131 | @property 132 | def metrics(self): 133 | if self._metrics is None: 134 | self._metrics = [] 135 | return self._metrics 136 | 137 | @property 138 | def metric_names(self): 139 | return [m['MetricName'] for m in self.metrics] 140 | 141 | def find_metric(self, metric_name): 142 | for m in self.metrics: 143 | if m['MetricName'] == metric_name: 144 | return m 145 | return None 146 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You 4 | # may not use this file except in compliance with the License. A copy of 5 | # the License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is 10 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 | # ANY KIND, either express or implied. See the License for the specific 12 | # language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You 4 | # may not use this file except in compliance with the License. A copy of 5 | # the License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is 10 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 | # ANY KIND, either express or implied. See the License for the specific 12 | # language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /tests/unit/cfg/aws_credentials: -------------------------------------------------------------------------------- 1 | [default] 2 | aws_access_key_id = foo 3 | aws_secret_access_key = bar 4 | 5 | [profile foo] 6 | aws_access_key_id = foo 7 | aws_secret_access_key = foosecret 8 | 9 | [profile bar] 10 | aws_access_key_id = bar 11 | aws_secret_access_key = barsecret 12 | 13 | [profile fie] 14 | aws_access_key_id = fie 15 | aws_secret_access_key = fiesecret 16 | 17 | [profile baz] 18 | aws_access_key_id = baz 19 | aws_secret_access_key = bazsecret 20 | 21 | -------------------------------------------------------------------------------- /tests/unit/cfg/skew.yml: -------------------------------------------------------------------------------- 1 | --- 2 | accounts: 3 | "123456789012": 4 | profile: foo 5 | "234567890123": 6 | profile: bar 7 | "345678901234": 8 | profile: fie 9 | "456789012345": 10 | profile: baz 11 | -------------------------------------------------------------------------------- /tests/unit/responses/addresses/ec2.DescribeAddresses_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "Addresses": [ 5 | { 6 | "InstanceId": "i-05ca6c180a9426c1c", 7 | "PublicIp": "18.137.255.223", 8 | "AllocationId": "eipalloc-091f2b843804f008c", 9 | "AssociationId": "eipassoc-0aeebf9ede25e7090", 10 | "Domain": "vpc", 11 | "NetworkInterfaceId": "eni-0f41b9ac56272e485", 12 | "NetworkInterfaceOwnerId": "123456789012", 13 | "PrivateIpAddress": "172.31.39.169", 14 | "Tags": [ 15 | ], 16 | "PublicIpv4Pool": "amazon", 17 | "NetworkBorderGroup": "us-east-1" 18 | }, 19 | { 20 | "PublicIp": "18.158.53.117", 21 | "AllocationId": "eipalloc-05ad23d4ddd56388a", 22 | "Domain": "vpc", 23 | "PublicIpv4Pool": "amazon", 24 | "NetworkBorderGroup": "us-east-1" 25 | }, 26 | { 27 | "PublicIp": "52.29.40.134", 28 | "AllocationId": "eipalloc-0a2c29e29c85027cc", 29 | "Domain": "vpc", 30 | "Tags": [ 31 | { 32 | "Key": "Name", 33 | "Value": "some-name" 34 | }, 35 | { 36 | "Key": "Env", 37 | "Value": "Prod" 38 | } 39 | ], 40 | "PublicIpv4Pool": "amazon", 41 | "NetworkBorderGroup": "us-east-1" 42 | } 43 | ] 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /tests/unit/responses/alarms/monitoring.DescribeAlarms_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "MetricAlarms": [ 5 | { 6 | "AlarmName": "some-alarm", 7 | "AlarmArn": "arn:aws:cloudwatch:us-east-1:123456789012:alarm:some-alarm", 8 | "AlarmConfigurationUpdatedTimestamp": "2020-06-20T13:09:22.983Z", 9 | "ActionsEnabled": true, 10 | "OKActions": [], 11 | "AlarmActions": [ 12 | "arn:aws:sns:us-east-1:123456789012:Default_CloudWatch_Alarms_Topic" 13 | ], 14 | "InsufficientDataActions": [], 15 | "StateValue": "OK", 16 | "StateReason": "Threshold Crossed: 1 out of the last 1 datapoints [0.043371 (25/06/20 13:04:00)] was not greater than the threshold (10000.0) (minimum 1 datapoint for ALARM -> OK transition).", 17 | "StateReasonData": "{\"version\":\"1.0\",\"queryDate\":\"2020-06-25T13:09:47.694+0000\",\"startDate\":\"2020-06-25T13:04:00.000+0000\",\"statistic\":\"Average\",\"period\":300,\"recentDatapoints\":[0.043371],\"threshold\":10000.0}", 18 | "StateUpdatedTimestamp": "2020-06-25T13:09:47.695Z", 19 | "MetricName": "CPUCreditUsage", 20 | "Namespace": "AWS/EC2", 21 | "Statistic": "Average", 22 | "Dimensions": [ 23 | { 24 | "Name": "InstanceId", 25 | "Value": "i-04ca6c180a3426c1b" 26 | } 27 | ], 28 | "Period": 300, 29 | "EvaluationPeriods": 1, 30 | "DatapointsToAlarm": 1, 31 | "Threshold": 10000.0, 32 | "ComparisonOperator": "GreaterThanThreshold", 33 | "TreatMissingData": "missing" 34 | } 35 | ], 36 | "CompositeAlarms": [] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/unit/responses/buckets/s3.GetBucketLocation_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPStatusCode": 200, 6 | "HostId": "7rVlvvCmaat4i1Km3mIaMlpPTxDL6k1LcYObw8uF3mvN0Rm0JDI3sXo4WpIsSccOMuZdh+jvwng=", 7 | "RequestId": "584D07E69B350436" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests/unit/responses/buckets/s3.GetBucketLocation_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPStatusCode": 200, 6 | "HostId": "VvBrGRsNGK+QlCe+QTw0guPUxdnctZQp/nqPV4FcOozcEV07N5bOP5Kwt0nAaialBoTpl3uboXY=", 7 | "RequestId": "DD19C6AC3683ED86" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests/unit/responses/buckets/s3.GetBucketLocation_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPStatusCode": 200, 6 | "HostId": "djLkAppJqT8HRrtg7UBSLWbK+obvUKwevaK3hFyygiVM4nUCxBVWbWxbi9YdcPFZsyOoXl+r5ug=", 7 | "RequestId": "9498DB828AA519CE" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests/unit/responses/buckets/s3.GetBucketLocation_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPStatusCode": 200, 6 | "HostId": "ceaP7O7T6Ge75ui6ZuZbho+LxkARPKjF4xNcwgSNnz+PqnaKahy/pyLA8Fc9Rgb0YiKzlL8g6SQ=", 7 | "RequestId": "8488AFFCFB74117B" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests/unit/responses/buckets/s3.GetBucketLocation_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPStatusCode": 200, 6 | "HostId": "GsDC+CUZnvRiCr69HYXxtHeCEw7m9DCnGMJz2l1a1tM4jWFjgGyTEggMRlxNS1I3cPa6PCcRJzs=", 7 | "RequestId": "AADC3ABE02345F09" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests/unit/responses/buckets/s3.ListBuckets_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "Owner": { 5 | "DisplayName": "foobar", 6 | "ID": "12345678901234567890" 7 | }, 8 | "Buckets": [ 9 | { 10 | "CreationDate": { 11 | "hour": 5, 12 | "__class__": "datetime", 13 | "month": 10, 14 | "second": 5, 15 | "microsecond": 0, 16 | "year": 2015, 17 | "day": 3, 18 | "minute": 15 19 | }, 20 | "Name": "foobar.1" 21 | }, 22 | { 23 | "CreationDate": { 24 | "hour": 20, 25 | "__class__": "datetime", 26 | "month": 11, 27 | "second": 57, 28 | "microsecond": 0, 29 | "year": 2015, 30 | "day": 25, 31 | "minute": 0 32 | }, 33 | "Name": "foobar.2" 34 | }, 35 | { 36 | "CreationDate": { 37 | "hour": 22, 38 | "__class__": "datetime", 39 | "month": 8, 40 | "second": 17, 41 | "microsecond": 0, 42 | "year": 2015, 43 | "day": 17, 44 | "minute": 25 45 | }, 46 | "Name": "foobar.3" 47 | }, 48 | { 49 | "CreationDate": { 50 | "hour": 5, 51 | "__class__": "datetime", 52 | "month": 5, 53 | "second": 9, 54 | "microsecond": 0, 55 | "year": 2015, 56 | "day": 1, 57 | "minute": 26 58 | }, 59 | "Name": "foobar.4" 60 | }, 61 | { 62 | "CreationDate": { 63 | "hour": 20, 64 | "__class__": "datetime", 65 | "month": 5, 66 | "second": 17, 67 | "microsecond": 0, 68 | "year": 2015, 69 | "day": 12, 70 | "minute": 5 71 | }, 72 | "Name": "foobar.5" 73 | } 74 | ], 75 | "ResponseMetadata": { 76 | "HTTPStatusCode": 200, 77 | "HostId": "G4fIQCAT7FKq6O37x/RN6KUJY6sCKv6qr3/2mPDFceHWwLMLI8xAICRB7NhZRrEd9wOLH1dLbeI=", 78 | "RequestId": "ED1C96C5020AD68D" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/unit/responses/certificates/acm.DescribeCertificate_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "Certificate": { 5 | "CertificateArn": "arn:aws:acm:us-west-2:123456789012:certificate/aaaaaaaa-bbbb-cccc-dddd-000000000001", 6 | "CreatedAt": { 7 | "hour": 18, 8 | "__class__": "datetime", 9 | "month": 12, 10 | "second": 30, 11 | "microsecond": 0, 12 | "year": 2017, 13 | "day": 31, 14 | "minute": 0 15 | }, 16 | "DomainName": "example.com", 17 | "DomainValidationOptions": [ 18 | { 19 | "DomainName": "example.com", 20 | "ValidationDomain": "example.com", 21 | "ValidationEmails": [ 22 | "webmaster@example.com", 23 | "hostmaster@example.com", 24 | "domaine@example.com", 25 | "admin@example.com", 26 | "administrator@example.com", 27 | "postmaster@example.com" 28 | ], 29 | "ValidationMethod": "EMAIL", 30 | "ValidationStatus": "PENDING_VALIDATION" 31 | } 32 | ], 33 | "ExtendedKeyUsages": [], 34 | "InUseBy": [], 35 | "KeyAlgorithm": "RSA-2048", 36 | "KeyUsages": [], 37 | "Options": { 38 | "CertificateTransparencyLoggingPreference": "ENABLED" 39 | }, 40 | "RenewalEligibility": "INELIGIBLE", 41 | "SignatureAlgorithm": "SHA256WITHRSA", 42 | "Status": "VALIDATION_TIMED_OUT", 43 | "Subject": "CN=example.com", 44 | "SubjectAlternativeNames": [ 45 | "example.com" 46 | ], 47 | "Type": "AMAZON_ISSUED" 48 | }, 49 | "ResponseMetadata": { 50 | "HTTPHeaders": { 51 | "content-length": "0", 52 | "content-type": "application/x-amz-json-1.1", 53 | "date": "Fri, 28 Dec 2018 12:59:36 GMT", 54 | "x-amzn-requestid": "6e4966b5-0aa0-11e9-a488-d10d70207042" 55 | }, 56 | "HTTPStatusCode": 200, 57 | "RequestId": "6e4966b5-0aa0-11e9-a488-d10d70207042", 58 | "RetryAttempts": 0 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /tests/unit/responses/certificates/acm.DescribeCertificate_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "Certificate": { 5 | "CertificateArn": "arn:aws:acm:us-west-2:123456789012:certificate/aaaaaaaa-bbbb-cccc-dddd-000000000002", 6 | "CreatedAt": { 7 | "hour": 18, 8 | "__class__": "datetime", 9 | "month": 12, 10 | "second": 30, 11 | "microsecond": 0, 12 | "year": 2017, 13 | "day": 31, 14 | "minute": 0 15 | }, 16 | "DomainName": "example.net", 17 | "DomainValidationOptions": [ 18 | { 19 | "DomainName": "example.net", 20 | "ValidationDomain": "example.net", 21 | "ValidationEmails": [ 22 | "webmaster@example.net", 23 | "hostmaster@example.net", 24 | "domaine@example.net", 25 | "admin@example.net", 26 | "administrator@example.net", 27 | "postmaster@example.net" 28 | ], 29 | "ValidationMethod": "EMAIL", 30 | "ValidationStatus": "PENDING_VALIDATION" 31 | } 32 | ], 33 | "ExtendedKeyUsages": [], 34 | "InUseBy": [], 35 | "KeyAlgorithm": "RSA-2048", 36 | "KeyUsages": [], 37 | "Options": { 38 | "CertificateTransparencyLoggingPreference": "ENABLED" 39 | }, 40 | "RenewalEligibility": "INELIGIBLE", 41 | "SignatureAlgorithm": "SHA256WITHRSA", 42 | "Status": "VALIDATION_TIMED_OUT", 43 | "Subject": "CN=example.net", 44 | "SubjectAlternativeNames": [ 45 | "example.net" 46 | ], 47 | "Type": "AMAZON_ISSUED" 48 | }, 49 | "ResponseMetadata": { 50 | "HTTPHeaders": { 51 | "content-length": "0", 52 | "content-type": "application/x-amz-json-1.1", 53 | "date": "Fri, 28 Dec 2018 12:59:36 GMT", 54 | "x-amzn-requestid": "6e4966b5-0aa0-11e9-a488-d10d70207042" 55 | }, 56 | "HTTPStatusCode": 200, 57 | "RequestId": "6e4966b5-0aa0-11e9-a488-d10d70207042", 58 | "RetryAttempts": 0 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /tests/unit/responses/certificates/acm.ListCertificates_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "CertificateSummaryList": [ 5 | { 6 | "CertificateArn": "arn:aws:acm:us-west-2:123456789012:certificate/aaaaaaaa-bbbb-cccc-dddd-000000000001", 7 | "DomainName": "example.com" 8 | }, 9 | { 10 | "CertificateArn": "arn:aws:acm:us-west-2:123456789012:certificate/aaaaaaaa-bbbb-cccc-dddd-000000000002", 11 | "DomainName": "example.net" 12 | } 13 | ], 14 | "ResponseMetadata": { 15 | "RequestId": "43f4a309-0a9d-11e9-bede-fd29c5b8df0c", 16 | "HTTPStatusCode": 200, 17 | "HTTPHeaders": { 18 | "x-amzn-requestid": "43f4a309-0a9d-11e9-bede-fd29c5b8df0c", 19 | "content-type": "application/x-amz-json-1.1", 20 | "content-length": "0", 21 | "date": "Fri, 28 Dec 2018 12:36:56 GMT" 22 | }, 23 | "RetryAttempts": 0 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /tests/unit/responses/certificates/acm.ListTagsForCertificate_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPHeaders": { 6 | "content-length": "0", 7 | "content-type": "application/x-amz-json-1.1", 8 | "date": "Fri, 28 Dec 2018 13:02:06 GMT", 9 | "x-amzn-requestid": "c74454e8-0aa0-11e9-94cb-1757d0684d96" 10 | }, 11 | "HTTPStatusCode": 200, 12 | "RequestId": "c74454e8-0aa0-11e9-94cb-1757d0684d96", 13 | "RetryAttempts": 0 14 | }, 15 | "Tags": [ 16 | { 17 | "Key": "tld", 18 | "Value": ".com" 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /tests/unit/responses/certificates/acm.ListTagsForCertificate_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPHeaders": { 6 | "content-length": "0", 7 | "content-type": "application/x-amz-json-1.1", 8 | "date": "Fri, 28 Dec 2018 13:02:06 GMT", 9 | "x-amzn-requestid": "c74454e8-0aa0-11e9-94cb-1757d0684d96" 10 | }, 11 | "HTTPStatusCode": 200, 12 | "RequestId": "c74454e8-0aa0-11e9-94cb-1757d0684d96", 13 | "RetryAttempts": 0 14 | }, 15 | "Tags": [ 16 | { 17 | "Key": "tld", 18 | "Value": ".net" 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /tests/unit/responses/customergateways/ec2.DescribeCustomerGateways_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "CustomerGateways": [ 5 | { 6 | "BgpAsn": "65000", 7 | "CustomerGatewayId": "cgw-030d9af8cdbcdc12f", 8 | "IpAddress": "1.1.1.1", 9 | "State": "available", 10 | "Type": "ipsec.1", 11 | "Tags": [ 12 | { 13 | "Key": "Env", 14 | "Value": "Prod" 15 | } 16 | ] 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/unit/responses/elbs/elasticloadbalancing.DescribeLoadBalancerAttributes_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "LoadBalancerAttributes": { 5 | "CrossZoneLoadBalancing": { 6 | "Enabled": false 7 | }, 8 | "AccessLog": { 9 | "Enabled": false 10 | }, 11 | "ConnectionDraining": { 12 | "Enabled": true, 13 | "Timeout": 300 14 | }, 15 | "ConnectionSettings": { 16 | "IdleTimeout": 60 17 | } 18 | }, 19 | "ResponseMetadata": { 20 | "RequestId": "96c45f1d-5be9-11e9-8df5-332fcd8c3731", 21 | "HTTPStatusCode": 200 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/unit/responses/elbs/elasticloadbalancing.DescribeLoadBalancers_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "LoadBalancerDescriptions": [ 5 | { 6 | "LoadBalancerName": "example", 7 | "DNSName": "example-1111111111.us-east-1.elb.amazonaws.com", 8 | "CanonicalHostedZoneName": "example-1111111111.us-east-1.elb.amazonaws.com", 9 | "CanonicalHostedZoneNameID": "ZZZZZZZZZZZZZZ", 10 | "ListenerDescriptions": [ 11 | { 12 | "Listener": { 13 | "Protocol": "HTTP", 14 | "LoadBalancerPort": 80, 15 | "InstanceProtocol": "HTTP", 16 | "InstancePort": 80 17 | }, 18 | "PolicyNames": [] 19 | }, 20 | { 21 | "Listener": { 22 | "Protocol": "HTTPS", 23 | "LoadBalancerPort": 443, 24 | "InstanceProtocol": "HTTP", 25 | "InstancePort": 80, 26 | "SSLCertificateId": "arn:aws:acm:us-east-1:111111111111:certificate/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" 27 | }, 28 | "PolicyNames": [ 29 | "AWSConsole-SSLNegotiationPolicy-example-1111111111111" 30 | ] 31 | } 32 | ], 33 | "Policies": { 34 | "AppCookieStickinessPolicies": [], 35 | "LBCookieStickinessPolicies": [], 36 | "OtherPolicies": [ 37 | "AWSConsole-SSLNegotiationPolicy-example-1111111111111", 38 | "ELBSecurityPolicy-2016-08", 39 | "AWSConsole-SSLNegotiationPolicy-example-1111111111111" 40 | ] 41 | }, 42 | "BackendServerDescriptions": [], 43 | "AvailabilityZones": [ 44 | "us-east-1d" 45 | ], 46 | "Subnets": [ 47 | "subnet-abcd1234" 48 | ], 49 | "VPCId": "vpc-1234abcd", 50 | "Instances": [ 51 | { 52 | "InstanceId": "i-abcdef12345678901" 53 | } 54 | ], 55 | "HealthCheck": { 56 | "Target": "TCP:80", 57 | "Interval": 30, 58 | "Timeout": 5, 59 | "UnhealthyThreshold": 2, 60 | "HealthyThreshold": 4 61 | }, 62 | "SourceSecurityGroup": { 63 | "OwnerAlias": "1234567890AB", 64 | "GroupName": "example" 65 | }, 66 | "SecurityGroups": [ 67 | "sg-1234abcd" 68 | ], 69 | "CreatedTime": { 70 | "__class__": "datetime", 71 | "year": 2016, 72 | "month": 10, 73 | "day": 19, 74 | "hour": 17, 75 | "minute": 52, 76 | "second": 18, 77 | "microsecond": 310000 78 | }, 79 | "Scheme": "internet-facing" 80 | } 81 | ], 82 | "ResponseMetadata": { 83 | "RequestId": "96b2f9e9-5be9-11e9-8df5-332fcd8c3731", 84 | "HTTPStatusCode": 200 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /tests/unit/responses/elbs/elasticloadbalancing.DescribeTags_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "TagDescriptions": [ 5 | { 6 | "LoadBalancerName": "example", 7 | "Tags": [ 8 | { 9 | "Key": "Name", 10 | "Value": "example-web" 11 | } 12 | ] 13 | } 14 | ], 15 | "ResponseMetadata": { 16 | "RequestId": "96dd1759-5be9-11e9-8df5-332fcd8c3731", 17 | "HTTPStatusCode": 200 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /tests/unit/responses/environments/elasticbeanstalk.DescribeEnvironments_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "Environments": [ 5 | { 6 | "EnvironmentName": "Env1", 7 | "EnvironmentId": "e-abc32nm323", 8 | "ApplicationName": "sample-application", 9 | "VersionLabel": "Sample Application", 10 | "SolutionStackName": "64bit Amazon Linux 2 v3.0.3 running Docker", 11 | "PlatformArn": "arn:aws:elasticbeanstalk:us-west-2::platform/Docker running on 64bit Amazon Linux 2/3.0.3", 12 | "EndpointURL": "111.111.111.111", 13 | "CNAME": "Env1.eba-2dmwpyrc.us-west-2.elasticbeanstalk.com", 14 | "DateCreated": "2020-05-18T14:36:31.201Z", 15 | "DateUpdated": "2020-04-24T21:11:56.759Z", 16 | "Status": "Ready", 17 | "AbortableOperationInProgress": false, 18 | "Health": "Green", 19 | "HealthStatus": "Ok", 20 | "Tier": { 21 | "Name": "WebServer", 22 | "Type": "Standard", 23 | "Version": "1.0" 24 | }, 25 | "EnvironmentLinks": [], 26 | "EnvironmentArn": "arn:aws:elasticbeanstalk:us-west-2:123456789012:environment/sample-application/Env1" 27 | } 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/unit/responses/flowlogs/ec2.DescribeFlowLogs_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "FlowLogs": [ 5 | { 6 | "CreationTime": { 7 | "__class__": "datetime", 8 | "year": 2017, 9 | "month": 1, 10 | "day": 23, 11 | "hour": 19, 12 | "minute": 47, 13 | "second": 49, 14 | "microsecond": 0 15 | }, 16 | "DeliverLogsPermissionArn": "arn:aws:iam::123456789123:role/CloudTrail_CloudWatchLogs_Role", 17 | "DeliverLogsStatus": "SUCCESS", 18 | "FlowLogId": "fl-1234abcd", 19 | "FlowLogStatus": "ACTIVE", 20 | "LogGroupName": "CloudTrail/DefaultLogGroup", 21 | "ResourceId": "vpc-1234abcd", 22 | "TrafficType": "ALL", 23 | "LogDestinationType": "cloud-watch-logs" 24 | }, 25 | { 26 | "CreationTime": { 27 | "__class__": "datetime", 28 | "year": 2017, 29 | "month": 1, 30 | "day": 23, 31 | "hour": 21, 32 | "minute": 9, 33 | "second": 33, 34 | "microsecond": 0 35 | }, 36 | "DeliverLogsPermissionArn": "arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role", 37 | "DeliverLogsStatus": "SUCCESS", 38 | "FlowLogId": "fl-abcd1234", 39 | "FlowLogStatus": "ACTIVE", 40 | "LogGroupName": "CloudTrail/DefaultLogGroup", 41 | "ResourceId": "vpc-abcd1234", 42 | "TrafficType": "ALL", 43 | "LogDestinationType": "cloud-watch-logs" 44 | } 45 | ], 46 | "ResponseMetadata": { 47 | "RequestId": "90080755-6d2f-4491-a1ae-3b1d3bc317f0", 48 | "HTTPStatusCode": 200 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /tests/unit/responses/groups/iam.ListGroups_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPStatusCode": 200, 6 | "RequestId": "7b30fc17-ba3c-11e5-84d2-1f83828f2439" 7 | }, 8 | "IsTruncated": false, 9 | "Groups": [ 10 | { 11 | "Path": "/", 12 | "CreateDate": { 13 | "hour": 4, 14 | "__class__": "datetime", 15 | "month": 5, 16 | "second": 13, 17 | "microsecond": 0, 18 | "year": 2015, 19 | "day": 1, 20 | "minute": 55 21 | }, 22 | "GroupId": "AGPAJPEN42SMQRU5UWRGS", 23 | "Arn": "arn:aws:iam::234567890123:group/Administrators", 24 | "GroupName": "Administrators" 25 | }, 26 | { 27 | "Path": "/", 28 | "CreateDate": { 29 | "hour": 21, 30 | "__class__": "datetime", 31 | "month": 5, 32 | "second": 58, 33 | "microsecond": 0, 34 | "year": 2015, 35 | "day": 5, 36 | "minute": 44 37 | }, 38 | "GroupId": "AGPAJG5SZVVECMBTC5ESY", 39 | "Arn": "arn:aws:iam::234567890123:group/FooBarAdmin", 40 | "GroupName": "FooBarAdmin" 41 | }, 42 | { 43 | "Path": "/", 44 | "CreateDate": { 45 | "hour": 19, 46 | "__class__": "datetime", 47 | "month": 11, 48 | "second": 11, 49 | "microsecond": 0, 50 | "year": 2015, 51 | "day": 30, 52 | "minute": 12 53 | }, 54 | "GroupId": "AGPAJR2DDEWJWVUUZ7IRQ", 55 | "Arn": "arn:aws:iam::123456789012:group/TestGroup", 56 | "GroupName": "TestGroup" 57 | } 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/unit/responses/instances_2/ec2.DescribeInstances_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "Reservations": [ 5 | { 6 | "OwnerId": "123456789012", 7 | "ReservationId": "r-86e33840", 8 | "Groups": [], 9 | "RequesterId": "226008221399", 10 | "Instances": [ 11 | { 12 | "Monitoring": { 13 | "State": "enabled" 14 | }, 15 | "PublicDnsName": "ec2-00-000-00-000.us-west-2.compute.amazonaws.com", 16 | "State": { 17 | "Code": 272, 18 | "Name": "running" 19 | }, 20 | "EbsOptimized": false, 21 | "LaunchTime": { 22 | "hour": 0, 23 | "__class__": "datetime", 24 | "month": 12, 25 | "second": 7, 26 | "microsecond": 0, 27 | "year": 2015, 28 | "day": 31, 29 | "minute": 21 30 | }, 31 | "PublicIpAddress": "1.2.3.4", 32 | "PrivateIpAddress": "10.0.11.168", 33 | "ProductCodes": [], 34 | "VpcId": "vpc-7f5e861a", 35 | "StateTransitionReason": "", 36 | "InstanceId": "i-db530902", 37 | "ImageId": "ami-d74357b6", 38 | "PrivateDnsName": "ip-10-0-11-168.us-west-2.compute.internal", 39 | "KeyName": "admin", 40 | "SecurityGroups": [ 41 | { 42 | "GroupName": "FooBar", 43 | "GroupId": "sg-39efad5d" 44 | } 45 | ], 46 | "ClientToken": "98c72e0f-d01b-4a32-b140-e0be2deb33d7_subnet-9312c3e4_1", 47 | "SubnetId": "subnet-9312c3e4", 48 | "InstanceType": "t2.small", 49 | "NetworkInterfaces": [ 50 | { 51 | "Status": "in-use", 52 | "MacAddress": "06:ef:48:f7:c4:ab", 53 | "SourceDestCheck": true, 54 | "VpcId": "vpc-7f5e861a", 55 | "Description": "", 56 | "Association": { 57 | "PublicIp": "1.2.3.4", 58 | "PublicDnsName": "ec2-00-000-00-000.us-west-2.compute.amazonaws.com", 59 | "IpOwnerId": "amazon" 60 | }, 61 | "NetworkInterfaceId": "eni-f58a5ebe", 62 | "PrivateIpAddresses": [ 63 | { 64 | "PrivateDnsName": "ip-10-0-11-168.us-west-2.compute.internal", 65 | "Association": { 66 | "PublicIp": "1.2.3.4", 67 | "PublicDnsName": "ec2-00-000-00-000.us-west-2.compute.amazonaws.com", 68 | "IpOwnerId": "amazon" 69 | }, 70 | "Primary": true, 71 | "PrivateIpAddress": "10.0.11.168" 72 | } 73 | ], 74 | "PrivateDnsName": "ip-10-0-11-168.us-west-2.compute.internal", 75 | "Attachment": { 76 | "Status": "attached", 77 | "DeviceIndex": 0, 78 | "DeleteOnTermination": true, 79 | "AttachmentId": "eni-attach-e3eb1cef", 80 | "AttachTime": { 81 | "hour": 0, 82 | "__class__": "datetime", 83 | "month": 12, 84 | "second": 7, 85 | "microsecond": 0, 86 | "year": 2015, 87 | "day": 31, 88 | "minute": 21 89 | } 90 | }, 91 | "Groups": [ 92 | { 93 | "GroupName": "FooBar", 94 | "GroupId": "sg-39efad5d" 95 | } 96 | ], 97 | "SubnetId": "subnet-9312c3e4", 98 | "OwnerId": "123456789012", 99 | "PrivateIpAddress": "10.0.11.168" 100 | } 101 | ], 102 | "SourceDestCheck": true, 103 | "Placement": { 104 | "Tenancy": "default", 105 | "GroupName": "", 106 | "AvailabilityZone": "us-west-2a" 107 | }, 108 | "Hypervisor": "xen", 109 | "BlockDeviceMappings": [ 110 | { 111 | "DeviceName": "/dev/xvda", 112 | "Ebs": { 113 | "Status": "attached", 114 | "DeleteOnTermination": true, 115 | "VolumeId": "vol-aac7336a", 116 | "AttachTime": { 117 | "hour": 0, 118 | "__class__": "datetime", 119 | "month": 12, 120 | "second": 10, 121 | "microsecond": 0, 122 | "year": 2015, 123 | "day": 31, 124 | "minute": 21 125 | } 126 | } 127 | } 128 | ], 129 | "Architecture": "x86_64", 130 | "RootDeviceType": "ebs", 131 | "IamInstanceProfile": { 132 | "Id": "AIPAIIC7YPOPHECAPLMVS", 133 | "Arn": "arn:aws:iam::123456789012:instance-profile/FooBar" 134 | }, 135 | "RootDeviceName": "/dev/xvda", 136 | "VirtualizationType": "hvm", 137 | "Tags": [ 138 | { 139 | "Value": "FooBar", 140 | "Key": "aws:cloudformation:stack-name" 141 | }, 142 | { 143 | "Value": "ecsAsg", 144 | "Key": "aws:cloudformation:logical-id" 145 | }, 146 | { 147 | "Value": "DevTest", 148 | "Key": "Environment" 149 | } 150 | ], 151 | "AmiLaunchIndex": 0 152 | } 153 | ] 154 | } 155 | ], 156 | "ResponseMetadata": { 157 | "HTTPStatusCode": 200, 158 | "RequestId": "bea049ac-1dfb-4362-b016-193e1525c8b8" 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /tests/unit/responses/instances_3/ec2.DescribeInstances_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 400, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPStatusCode": 400, 6 | "RequestId": "c54d7e0e-ccfc-4a93-a2e5-862de7716e5d" 7 | }, 8 | "Error": { 9 | "Message": "The instance ID 'i-eedf6728' does not exist", 10 | "Code": "InvalidInstanceID.NotFound" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /tests/unit/responses/keypairs/ec2.DescribeKeyPairs_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "KeyPairs": [ 5 | { 6 | "KeyPairId": "key-1274ea12942819e24", 7 | "KeyName": "admin", 8 | "KeyFingerprint": "85:83:08:25:fa:96:45:ea:c9:15:04:12:af:45:3f:c0:ef:e8:b8:ce" 9 | }, 10 | { 11 | "KeyPairId": "key-1274ea12942819e25", 12 | "KeyName": "FooBar", 13 | "KeyFingerprint": "9b:02:68:c2:ff:32:38:1a:49:ec:c4:b5:b3:98:36:cf:cd:1f:73:cc" 14 | } 15 | ], 16 | "ResponseMetadata": { 17 | "HTTPStatusCode": 200, 18 | "RequestId": "2fb7f483-e17b-47d7-b0aa-0f2062770016" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/unit/responses/launchtemplates/ec2.DescribeLaunchTemplates_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPStatusCode": 200, 6 | "RequestId": "5367ca88-d88c-4ff0-b393-1f3399c00bd1" 7 | }, 8 | "LaunchTemplates": [ 9 | { 10 | "LatestVersionNumber": 2, 11 | "LaunchTemplateId": "lt-000005555511111888", 12 | "LaunchTemplateName": "controllershardv2", 13 | "DefaultVersionNumber": 2, 14 | "CreatedBy": "arn:aws:iam::123456789012:user/dev", 15 | "CreateTime": "2018-10-30T13:59:02.000Z" 16 | }, 17 | { 18 | "LatestVersionNumber": 2, 19 | "Tags": [ 20 | { 21 | "Value": "prod", 22 | "Key": "costcenter" 23 | } 24 | ], 25 | "LaunchTemplateId": "lt-000007777744444999", 26 | "LaunchTemplateName": "rabbitmq", 27 | "DefaultVersionNumber": 2, 28 | "CreatedBy": "arn:aws:iam::123456789012:user/prod", 29 | "CreateTime": "2018-10-11T13:25:16.000Z" 30 | }, 31 | { 32 | "LatestVersionNumber": 3, 33 | "Tags": [ 34 | { 35 | "Value": "dev", 36 | "Key": "costcenter" 37 | } 38 | ], 39 | "LaunchTemplateId": "lt-000006666633333888", 40 | "LaunchTemplateName": "kafkazookeeper", 41 | "DefaultVersionNumber": 3, 42 | "CreatedBy": "arn:aws:iam::123456789012:user/dev", 43 | "CreateTime": "2018-10-11T13:52:47.000Z" 44 | }, 45 | { 46 | "LatestVersionNumber": 2, 47 | "LaunchTemplateId": "lt-000777772222211223", 48 | "LaunchTemplateName": "orchestrator", 49 | "DefaultVersionNumber": 1, 50 | "CreatedBy": "arn:aws:iam::123456789012:user/prod", 51 | "CreateTime": "2018-10-30T14:18:51.000Z" 52 | } 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/unit/responses/loggroups/logs.DescribeLogGroups_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "logGroups": [ 5 | { 6 | "logGroupName": "CloudTrail/DefaultLogGroup", 7 | "creationTime": 1485199013984, 8 | "metricFilterCount": 1, 9 | "arn": "arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:*", 10 | "storedBytes": 2745571173 11 | } 12 | ], 13 | "ResponseMetadata": { 14 | "RequestId": "6845270c-5bc8-11e9-b5c6-bbb0be9d8f19", 15 | "HTTPStatusCode": 200 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /tests/unit/responses/loggroups/logs.DescribeLogStreams_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "logStreams": [ 5 | { 6 | "logStreamName": "123456789012_CloudTrail_us-east-1", 7 | "creationTime": 1485199032455, 8 | "firstEventTimestamp": 1485198759000, 9 | "lastEventTimestamp": 1501701075294, 10 | "lastIngestionTime": 1501701075871, 11 | "uploadSequenceToken": "11111111111111111111111111111111111111111111111111111111", 12 | "arn": "arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:log-stream:123456789012_CloudTrail_us-east-1", 13 | "storedBytes": 344477 14 | }, 15 | { 16 | "logStreamName": "123456789012_CloudTrail_us-east-1_2", 17 | "creationTime": 1485203803263, 18 | "firstEventTimestamp": 1485203523000, 19 | "lastEventTimestamp": 1485203584000, 20 | "lastIngestionTime": 1485203803296, 21 | "uploadSequenceToken": "11111111111111111111111111111111111111111111111111111111", 22 | "arn": "arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:log-stream:123456789012_CloudTrail_us-east-1_2", 23 | "storedBytes": 1332 24 | }, 25 | { 26 | "logStreamName": "TestLogStream", 27 | "creationTime": 1554924998073, 28 | "arn": "arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:log-stream:TestLogStream", 29 | "storedBytes": 0 30 | } 31 | ], 32 | "ResponseMetadata": { 33 | "RequestId": "6853cd66-5bc8-11e9-b5c6-bbb0be9d8f19", 34 | "HTTPStatusCode": 200 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /tests/unit/responses/loggroups/logs.DescribeMetricFilters_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "metricFilters": [ 5 | { 6 | "filterName": "EventCount", 7 | "metricTransformations": [ 8 | { 9 | "metricName": "TestEventCount", 10 | "metricNamespace": "TestNameSpace", 11 | "metricValue": "1", 12 | "defaultValue": 0.0 13 | } 14 | ], 15 | "creationTime": 1554924855354, 16 | "logGroupName": "CloudTrail/DefaultLogGroup" 17 | } 18 | ], 19 | "ResponseMetadata": { 20 | "RequestId": "68a5e4fe-5bc8-11e9-b5c6-bbb0be9d8f19", 21 | "HTTPStatusCode": 200 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/unit/responses/loggroups/logs.DescribeQueries_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "queries": [ 5 | { 6 | "queryId": "11111111-cfe3-43db-8eca-8862fee615a3", 7 | "queryString": "SOURCE \"CloudTrail/DefaultLogGroup\" START=-604800s END=now | fields @timestamp, @message\n| sort @timestamp desc\n| limit 20", 8 | "status": "Complete", 9 | "createTime": 1554924920064, 10 | "logGroupName": "CloudTrail/DefaultLogGroup" 11 | }, 12 | { 13 | "queryId": "11111111-05eb-47e2-9a5d-3e2a4a8c357e", 14 | "queryString": "SOURCE \"CloudTrail/DefaultLogGroup\" START=1554922738246 END=1554923698246 | FIELDS @timestamp | LIMIT 1", 15 | "status": "Cancelled", 16 | "createTime": 1554923218276, 17 | "logGroupName": "CloudTrail/DefaultLogGroup" 18 | } 19 | ], 20 | "ResponseMetadata": { 21 | "RequestId": "68b2683d-5bc8-11e9-b5c6-bbb0be9d8f19", 22 | "HTTPStatusCode": 200 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /tests/unit/responses/loggroups/logs.DescribeSubscriptionFilters_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "subscriptionFilters": [ 5 | { 6 | "filterName": "TestLambdaTrigger", 7 | "logGroupName": "CloudTrail/DefaultLogGroup", 8 | "filterPattern": "", 9 | "destinationArn": "arn:aws:lambda:us-east-1:123456789012:function:testCloudwatchLog", 10 | "distribution": "ByLogStream", 11 | "creationTime": 1554925116600 12 | } 13 | ], 14 | "ResponseMetadata": { 15 | "RequestId": "68ad5f28-5bc8-11e9-b5c6-bbb0be9d8f19", 16 | "HTTPStatusCode": 200 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /tests/unit/responses/loggroups/logs.ListTagsLogGroup_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "tags": { 5 | "TestKey": "TestValue" 6 | }, 7 | "ResponseMetadata": { 8 | "RequestId": "a63bfb1c-5bce-11e9-80b7-b3c9c3e385a5", 9 | "HTTPStatusCode": 200 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /tests/unit/responses/natgateways/ec2.DescribeNatGateways_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPStatusCode": 200, 6 | "HTTPHeaders": { 7 | "vary": "Accept-Encoding", 8 | "date": "Tue, 03 Oct 2017 20:24:46 GMT", 9 | "transfer-encoding": "chunked", 10 | "content-type": "text/xml;charset=UTF-8", 11 | "server": "AmazonEC2" 12 | }, 13 | "RequestId": "c79c50e9-4ed3-4492-a063-de1f9cdd8722", 14 | "RetryAttempts": 0 15 | }, 16 | "NatGateways": [ 17 | { 18 | "NatGatewayAddresses": [ 19 | { 20 | "PublicIp": "51.49.128.226", 21 | "AllocationId": "eipalloc-e0f3c63a", 22 | "NetworkInterfaceId": "eni-475b01c8", 23 | "PrivateIp": "10.2.0.55" 24 | } 25 | ], 26 | "CreateTime": "2017-09-17T10:28:05+00:00", 27 | "NatGatewayId": "nat-443d3ea762d00ee83", 28 | "VpcId": "vpc-57c5a560", 29 | "State": "available", 30 | "SubnetId": "subnet-63f1911a" 31 | } 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/unit/responses/peeringconnections/ec2.DescribeVpcPeeringConnections_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "VpcPeeringConnections": [ 5 | { 6 | "AccepterVpcInfo": { 7 | "CidrBlock": "10.0.0.0/24", 8 | "CidrBlockSet": [ 9 | { 10 | "CidrBlock": "10.0.0.0/24" 11 | } 12 | ], 13 | "OwnerId": "123456789012", 14 | "PeeringOptions": { 15 | "AllowDnsResolutionFromRemoteVpc": false, 16 | "AllowEgressFromLocalClassicLinkToRemoteVpc": false, 17 | "AllowEgressFromLocalVpcToRemoteClassicLink": false 18 | }, 19 | "VpcId": "vpc-008f18a8d8ea05b80", 20 | "Region": "eu-central-1" 21 | }, 22 | "RequesterVpcInfo": { 23 | "CidrBlock": "168.31.0.0/16", 24 | "CidrBlockSet": [ 25 | { 26 | "CidrBlock": "168.31.0.0/16" 27 | } 28 | ], 29 | "OwnerId": "123456789012", 30 | "PeeringOptions": { 31 | "AllowDnsResolutionFromRemoteVpc": false, 32 | "AllowEgressFromLocalClassicLinkToRemoteVpc": false, 33 | "AllowEgressFromLocalVpcToRemoteClassicLink": false 34 | }, 35 | "VpcId": "vpc-008ad710ae2d7ad28", 36 | "Region": "eu-central-1" 37 | }, 38 | "Status": { 39 | "Code": "active", 40 | "Message": "Active" 41 | }, 42 | "Tags": [], 43 | "VpcPeeringConnectionId": "pcx-027a582b95db2af78" 44 | } 45 | ] 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /tests/unit/responses/routetables/ec2.DescribeRouteTables_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPStatusCode": 200, 6 | "RequestId": "5367ca88-d88c-4ff0-b393-1f3399c00bd1" 7 | }, 8 | "RouteTables": [ 9 | { 10 | "Associations": [ 11 | { 12 | "RouteTableAssociationId": "rtbassoc-5896533d", 13 | "Main": true, 14 | "RouteTableId": "rtb-48936c2d" 15 | } 16 | ], 17 | "RouteTableId": "rtb-48936c2d", 18 | "VpcId": "vpc-2b86744e", 19 | "PropagatingVgws": [], 20 | "Tags": [], 21 | "Routes": [ 22 | { 23 | "GatewayId": "local", 24 | "DestinationCidrBlock": "172.31.0.0/16", 25 | "State": "active", 26 | "Origin": "CreateRouteTable" 27 | }, 28 | { 29 | "GatewayId": "igw-e200ee87", 30 | "DestinationCidrBlock": "0.0.0.0/0", 31 | "State": "active", 32 | "Origin": "CreateRoute" 33 | } 34 | ] 35 | }, 36 | { 37 | "Associations": [ 38 | { 39 | "SubnetId": "subnet-c5cf399c", 40 | "RouteTableAssociationId": "rtbassoc-7867ce1d", 41 | "Main": false, 42 | "RouteTableId": "rtb-cfab0faa" 43 | }, 44 | { 45 | "SubnetId": "subnet-9112c3e6", 46 | "RouteTableAssociationId": "rtbassoc-4667ce23", 47 | "Main": false, 48 | "RouteTableId": "rtb-cfab0faa" 49 | }, 50 | { 51 | "SubnetId": "subnet-7456e511", 52 | "RouteTableAssociationId": "rtbassoc-4567ce20", 53 | "Main": false, 54 | "RouteTableId": "rtb-cfab0faa" 55 | }, 56 | { 57 | "SubnetId": "subnet-c7cf399e", 58 | "RouteTableAssociationId": "rtbassoc-7a67ce1f", 59 | "Main": false, 60 | "RouteTableId": "rtb-cfab0faa" 61 | }, 62 | { 63 | "SubnetId": "subnet-6b56e50e", 64 | "RouteTableAssociationId": "rtbassoc-7f67ce1a", 65 | "Main": false, 66 | "RouteTableId": "rtb-cfab0faa" 67 | }, 68 | { 69 | "SubnetId": "subnet-9312c3e4", 70 | "RouteTableAssociationId": "rtbassoc-7967ce1c", 71 | "Main": false, 72 | "RouteTableId": "rtb-cfab0faa" 73 | } 74 | ], 75 | "RouteTableId": "rtb-cfab0faa", 76 | "VpcId": "vpc-7f5e861a", 77 | "PropagatingVgws": [], 78 | "Tags": [ 79 | { 80 | "Value": "DevTest", 81 | "Key": "Environment" 82 | }, 83 | { 84 | "Value": "arn:aws:cloudformation:us-west-2:123456789012:stack/DevTest-VPC/f40833d0-8417-11e4-8e98-50fa5e742444", 85 | "Key": "aws:cloudformation:stack-id" 86 | }, 87 | { 88 | "Value": "Public route table", 89 | "Key": "Name" 90 | }, 91 | { 92 | "Value": "publicRouteTable", 93 | "Key": "aws:cloudformation:logical-id" 94 | }, 95 | { 96 | "Value": "DevTest-VPC", 97 | "Key": "aws:cloudformation:stack-name" 98 | } 99 | ], 100 | "Routes": [ 101 | { 102 | "GatewayId": "local", 103 | "DestinationCidrBlock": "10.0.0.0/16", 104 | "State": "active", 105 | "Origin": "CreateRouteTable" 106 | }, 107 | { 108 | "GatewayId": "igw-f8995a9d", 109 | "DestinationCidrBlock": "0.0.0.0/0", 110 | "State": "active", 111 | "Origin": "CreateRoute" 112 | } 113 | ] 114 | }, 115 | { 116 | "Associations": [ 117 | { 118 | "RouteTableAssociationId": "rtbassoc-039df866", 119 | "Main": true, 120 | "RouteTableId": "rtb-dda12bb8" 121 | } 122 | ], 123 | "RouteTableId": "rtb-dda12bb8", 124 | "VpcId": "vpc-06901063", 125 | "PropagatingVgws": [], 126 | "Tags": [], 127 | "Routes": [ 128 | { 129 | "GatewayId": "local", 130 | "DestinationCidrBlock": "10.0.0.0/16", 131 | "State": "active", 132 | "Origin": "CreateRouteTable" 133 | } 134 | ] 135 | }, 136 | { 137 | "Associations": [ 138 | { 139 | "SubnetId": "subnet-d99628ae", 140 | "RouteTableAssociationId": "rtbassoc-169df873", 141 | "Main": false, 142 | "RouteTableId": "rtb-a4a12bc1" 143 | }, 144 | { 145 | "SubnetId": "subnet-4aee762f", 146 | "RouteTableAssociationId": "rtbassoc-119df874", 147 | "Main": false, 148 | "RouteTableId": "rtb-a4a12bc1" 149 | } 150 | ], 151 | "RouteTableId": "rtb-a4a12bc1", 152 | "VpcId": "vpc-06901063", 153 | "PropagatingVgws": [], 154 | "Tags": [ 155 | { 156 | "Value": "CloudNative", 157 | "Key": "Vendor" 158 | }, 159 | { 160 | "Value": "Public route table", 161 | "Key": "Name" 162 | }, 163 | { 164 | "Value": "publicRouteTable", 165 | "Key": "aws:cloudformation:logical-id" 166 | }, 167 | { 168 | "Value": "cyan1-delta-hello-world", 169 | "Key": "aws:cloudformation:stack-name" 170 | }, 171 | { 172 | "Value": "arn:aws:cloudformation:us-west-2:123456789012:stack/cyan1-delta-hello-world/b2fd9dd0-f41a-11e4-8e28-5088484a585d", 173 | "Key": "aws:cloudformation:stack-id" 174 | } 175 | ], 176 | "Routes": [ 177 | { 178 | "GatewayId": "local", 179 | "DestinationCidrBlock": "10.0.0.0/16", 180 | "State": "active", 181 | "Origin": "CreateRouteTable" 182 | }, 183 | { 184 | "GatewayId": "igw-5919b83c", 185 | "DestinationCidrBlock": "0.0.0.0/0", 186 | "State": "active", 187 | "Origin": "CreateRoute" 188 | } 189 | ] 190 | }, 191 | { 192 | "Associations": [ 193 | { 194 | "SubnetId": "subnet-c2cf399b", 195 | "RouteTableAssociationId": "rtbassoc-7c67ce19", 196 | "Main": false, 197 | "RouteTableId": "rtb-cdab0fa8" 198 | }, 199 | { 200 | "SubnetId": "subnet-c6cf399f", 201 | "RouteTableAssociationId": "rtbassoc-4467ce21", 202 | "Main": false, 203 | "RouteTableId": "rtb-cdab0fa8" 204 | }, 205 | { 206 | "RouteTableAssociationId": "rtbassoc-a864cdcd", 207 | "Main": true, 208 | "RouteTableId": "rtb-cdab0fa8" 209 | }, 210 | { 211 | "SubnetId": "subnet-9212c3e5", 212 | "RouteTableAssociationId": "rtbassoc-4767ce22", 213 | "Main": false, 214 | "RouteTableId": "rtb-cdab0fa8" 215 | }, 216 | { 217 | "SubnetId": "subnet-6a56e50f", 218 | "RouteTableAssociationId": "rtbassoc-7e67ce1b", 219 | "Main": false, 220 | "RouteTableId": "rtb-cdab0fa8" 221 | }, 222 | { 223 | "SubnetId": "subnet-9412c3e3", 224 | "RouteTableAssociationId": "rtbassoc-7d67ce18", 225 | "Main": false, 226 | "RouteTableId": "rtb-cdab0fa8" 227 | }, 228 | { 229 | "SubnetId": "subnet-7556e510", 230 | "RouteTableAssociationId": "rtbassoc-7b67ce1e", 231 | "Main": false, 232 | "RouteTableId": "rtb-cdab0fa8" 233 | } 234 | ], 235 | "RouteTableId": "rtb-cdab0fa8", 236 | "VpcId": "vpc-7f5e861a", 237 | "PropagatingVgws": [], 238 | "Tags": [], 239 | "Routes": [ 240 | { 241 | "GatewayId": "local", 242 | "DestinationCidrBlock": "10.0.0.0/16", 243 | "State": "active", 244 | "Origin": "CreateRouteTable" 245 | } 246 | ] 247 | } 248 | ] 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /tests/unit/responses/secgrp/ec2.DescribeSecurityGroups_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "SecurityGroups": [ 5 | { 6 | "IpPermissionsEgress": [], 7 | "Description": "default group", 8 | "IpPermissions": [ 9 | { 10 | "PrefixListIds": [], 11 | "FromPort": 0, 12 | "IpRanges": [], 13 | "ToPort": 65535, 14 | "IpProtocol": "tcp", 15 | "UserIdGroupPairs": [ 16 | { 17 | "GroupName": "default", 18 | "UserId": "123456789012", 19 | "GroupId": "sg-c7286cf7" 20 | } 21 | ] 22 | }, 23 | { 24 | "PrefixListIds": [], 25 | "FromPort": 0, 26 | "IpRanges": [], 27 | "ToPort": 65535, 28 | "IpProtocol": "udp", 29 | "UserIdGroupPairs": [ 30 | { 31 | "GroupName": "default", 32 | "UserId": "123456789012", 33 | "GroupId": "sg-c7286cf7" 34 | } 35 | ] 36 | }, 37 | { 38 | "PrefixListIds": [], 39 | "FromPort": -1, 40 | "IpRanges": [], 41 | "ToPort": -1, 42 | "IpProtocol": "icmp", 43 | "UserIdGroupPairs": [ 44 | { 45 | "GroupName": "default", 46 | "UserId": "123456789012", 47 | "GroupId": "sg-c7286cf7" 48 | } 49 | ] 50 | } 51 | ], 52 | "GroupName": "default", 53 | "OwnerId": "123456789012", 54 | "GroupId": "sg-c7286cf7" 55 | }, 56 | { 57 | "IpPermissionsEgress": [ 58 | { 59 | "IpProtocol": "-1", 60 | "IpRanges": [ 61 | { 62 | "CidrIp": "0.0.0.0/0" 63 | } 64 | ], 65 | "UserIdGroupPairs": [], 66 | "PrefixListIds": [] 67 | } 68 | ], 69 | "Description": "A group for running an iPython notebook server", 70 | "IpPermissions": [ 71 | { 72 | "PrefixListIds": [], 73 | "FromPort": 8888, 74 | "IpRanges": [ 75 | { 76 | "CidrIp": "1.2.3.4/32" 77 | } 78 | ], 79 | "ToPort": 8888, 80 | "IpProtocol": "tcp", 81 | "UserIdGroupPairs": [] 82 | }, 83 | { 84 | "PrefixListIds": [], 85 | "FromPort": 22, 86 | "IpRanges": [ 87 | { 88 | "CidrIp": "0.0.0.0/0" 89 | } 90 | ], 91 | "ToPort": 22, 92 | "IpProtocol": "tcp", 93 | "UserIdGroupPairs": [] 94 | } 95 | ], 96 | "GroupName": "IPythonNotebook", 97 | "VpcId": "vpc-4ff5eb2d", 98 | "OwnerId": "123456789012", 99 | "GroupId": "sg-740eec11" 100 | }, 101 | { 102 | "IpPermissionsEgress": [ 103 | { 104 | "IpProtocol": "-1", 105 | "IpRanges": [ 106 | { 107 | "CidrIp": "0.0.0.0/0" 108 | } 109 | ], 110 | "UserIdGroupPairs": [], 111 | "PrefixListIds": [] 112 | } 113 | ], 114 | "Description": "default VPC security group", 115 | "IpPermissions": [ 116 | { 117 | "IpProtocol": "-1", 118 | "IpRanges": [], 119 | "UserIdGroupPairs": [ 120 | { 121 | "UserId": "123456789012", 122 | "GroupId": "sg-cc09eba9" 123 | } 124 | ], 125 | "PrefixListIds": [] 126 | } 127 | ], 128 | "GroupName": "default", 129 | "VpcId": "vpc-4ff5eb2d", 130 | "OwnerId": "123456789012", 131 | "GroupId": "sg-cc09eba9" 132 | } 133 | ], 134 | "ResponseMetadata": { 135 | "HTTPStatusCode": 200, 136 | "RequestId": "4e4c6b4b-9e97-448b-bf1f-09760c584f2e" 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/unit/responses/stacks/cloudformation.DescribeStackResources_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "StackResources": [ 5 | { 6 | "StackId": "arn:aws:cloudformation:us-west-2:860421987956:stack/foobar-tables/117c58a0-b56e-11e5-a63a-503f2a2ceeae", 7 | "ResourceStatus": "CREATE_COMPLETE", 8 | "ResourceType": "AWS::DynamoDB::Table", 9 | "Timestamp": { 10 | "hour": 18, 11 | "__class__": "datetime", 12 | "month": 1, 13 | "second": 51, 14 | "microsecond": 962000, 15 | "year": 2016, 16 | "day": 7, 17 | "minute": 40 18 | }, 19 | "StackName": "foobar-tables", 20 | "PhysicalResourceId": "foobar-tables", 21 | "LogicalResourceId": "foobar" 22 | }, 23 | { 24 | "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/foobar-tables/117c58a0-b56e-11e5-a63a-503f2a2ceeae", 25 | "ResourceStatus": "CREATE_COMPLETE", 26 | "ResourceType": "AWS::DynamoDB::Table", 27 | "Timestamp": { 28 | "hour": 18, 29 | "__class__": "datetime", 30 | "month": 1, 31 | "second": 50, 32 | "microsecond": 715000, 33 | "year": 2016, 34 | "day": 7, 35 | "minute": 40 36 | }, 37 | "StackName": "foo", 38 | "PhysicalResourceId": "foo", 39 | "LogicalResourceId": "foo" 40 | }, 41 | { 42 | "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/yeobot-tables/117c58a0-b56e-11e5-a63a-503f2a2ceeae", 43 | "ResourceStatus": "CREATE_COMPLETE", 44 | "ResourceType": "AWS::DynamoDB::Table", 45 | "Timestamp": { 46 | "hour": 23, 47 | "__class__": "datetime", 48 | "month": 1, 49 | "second": 29, 50 | "microsecond": 437000, 51 | "year": 2016, 52 | "day": 8, 53 | "minute": 1 54 | }, 55 | "StackName": "bar", 56 | "PhysicalResourceId": "bar", 57 | "LogicalResourceId": "bar" 58 | }, 59 | { 60 | "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/yeobot-tables/117c58a0-b56e-11e5-a63a-503f2a2ceeae", 61 | "ResourceStatus": "CREATE_COMPLETE", 62 | "ResourceType": "AWS::DynamoDB::Table", 63 | "Timestamp": { 64 | "hour": 18, 65 | "__class__": "datetime", 66 | "month": 1, 67 | "second": 50, 68 | "microsecond": 915000, 69 | "year": 2016, 70 | "day": 7, 71 | "minute": 40 72 | }, 73 | "StackName": "fie", 74 | "PhysicalResourceId": "fie", 75 | "LogicalResourceId": "fie" 76 | } 77 | ], 78 | "ResponseMetadata": { 79 | "HTTPStatusCode": 200, 80 | "RequestId": "f7cff340-ba3d-11e5-9c27-a9e2294677da" 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/unit/responses/stacks/cloudformation.DescribeStacks_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "Stacks": [ 5 | { 6 | "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/foobar-tables/117c58a0-b56e-11e5-a63a-503f2a2ceeae", 7 | "Description": "Create all DynamoDB tables needed by FooBar", 8 | "Tags": [], 9 | "Outputs": [ 10 | { 11 | "Description": "The Foo table", 12 | "OutputKey": "foo", 13 | "OutputValue": "foo" 14 | }, 15 | { 16 | "Description": "The Bar table", 17 | "OutputKey": "bar", 18 | "OutputValue": "bar" 19 | }, 20 | { 21 | "Description": "The Fie table", 22 | "OutputKey": "fie", 23 | "OutputValue": "fie" 24 | } 25 | ], 26 | "CreationTime": { 27 | "hour": 18, 28 | "__class__": "datetime", 29 | "month": 1, 30 | "second": 4, 31 | "microsecond": 921000, 32 | "year": 2016, 33 | "day": 7, 34 | "minute": 40 35 | }, 36 | "StackName": "foobar-tables", 37 | "NotificationARNs": [], 38 | "StackStatus": "UPDATE_COMPLETE", 39 | "DisableRollback": false, 40 | "LastUpdatedTime": { 41 | "hour": 23, 42 | "__class__": "datetime", 43 | "month": 1, 44 | "second": 39, 45 | "microsecond": 255000, 46 | "year": 2016, 47 | "day": 8, 48 | "minute": 0 49 | } 50 | } 51 | ], 52 | "ResponseMetadata": { 53 | "HTTPStatusCode": 200, 54 | "RequestId": "f7857c47-ba3d-11e5-bca7-5353f1afe730" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/unit/responses/trail/cloudtrail.DescribeTrails_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "trailList": [ 5 | { 6 | "Name": "awslog", 7 | "S3BucketName": "awslog", 8 | "IncludeGlobalServiceEvents": true, 9 | "IsMultiRegionTrail": true, 10 | "HomeRegion": "us-east-1", 11 | "TrailARN": "arn:aws:cloudtrail:us-east-1:123456789012:trail/awslog", 12 | "LogFileValidationEnabled": false, 13 | "CloudWatchLogsLogGroupArn": "arn:aws:logs:us-east-1:123456789012:log-group:CloudTrail/DefaultLogGroup:*", 14 | "CloudWatchLogsRoleArn": "arn:aws:iam::123456789012:role/CloudTrail_CloudWatchLogs_Role", 15 | "HasCustomEventSelectors": false, 16 | "IsOrganizationTrail": false 17 | } 18 | ], 19 | "ResponseMetadata": { 20 | "RequestId": "fcdf4618-2681-453a-a558-f45699739e20", 21 | "HTTPStatusCode": 200 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /tests/unit/responses/trail/cloudtrail.ListTags_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResourceTagList": [ 5 | { 6 | "ResourceId": "arn:aws:cloudtrail:us-east-1:123456789012:trail/awslog", 7 | "TagsList": [ 8 | { 9 | "Key": "TestKey", 10 | "Value": "TestValue" 11 | } 12 | ] 13 | } 14 | ], 15 | "ResponseMetadata": { 16 | "RequestId": "7757a8e5-438f-4492-a7f1-0274c3f49196", 17 | "HTTPStatusCode": 200 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /tests/unit/responses/users/iam.GetUserPolicy_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "UserName": "testuser", 5 | "PolicyName": "TestInlinePolicy", 6 | "PolicyDocument": "%7B%0A%20%20%20%20%22Version%22%3A%20%222012-10-17%22%2C%0A%20%20%20%20%22Statement%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Effect%22%3A%20%22Allow%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Action%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ec2%3AAttachVolume%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ec2%3ADetachVolume%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Resource%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22arn%3Aaws%3Aec2%3A%2A%3A%2A%3Avolume%2F%2A%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22arn%3Aaws%3Aec2%3A%2A%3A%2A%3Ainstance%2F%2A%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Condition%22%3A%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%22ArnEquals%22%3A%20%7B%22ec2%3ASourceInstanceARN%22%3A%20%22arn%3Aaws%3Aec2%3A%2A%3A%2A%3Ainstance%2F%2A%22%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%5D%0A%7D%0A", 7 | "ResponseMetadata": { 8 | "RequestId": "04e3bdac-e50a-4770-b624-06d0a182ce44", 9 | "HTTPStatusCode": 200 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /tests/unit/responses/users/iam.GetUser_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "User": { 5 | "Path": "/", 6 | "UserName": "testuser", 7 | "UserId": "AIDAAAAAAAAAAAAAAAAAA", 8 | "Arn": "arn:aws:iam::123456789012:user/testuser", 9 | "CreateDate": { 10 | "__class__": "datetime", 11 | "year": 2016, 12 | "month": 3, 13 | "day": 16, 14 | "hour": 20, 15 | "minute": 35, 16 | "second": 19, 17 | "microsecond": 0 18 | }, 19 | "Tags": [ 20 | { 21 | "Key": "TestKey", 22 | "Value": "TestValue" 23 | } 24 | ] 25 | }, 26 | "ResponseMetadata": { 27 | "RequestId": "a86f125a-6067-11e9-9b32-cb63a411b607", 28 | "HTTPStatusCode": 200 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /tests/unit/responses/users/iam.ListAccessKeys_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "AccessKeyMetadata": [ 5 | { 6 | "UserName": "testuser", 7 | "AccessKeyId": "AKIAAAAAAAAAAAAAAAAA", 8 | "Status": "Active", 9 | "CreateDate": { 10 | "__class__": "datetime", 11 | "year": 2016, 12 | "month": 5, 13 | "day": 31, 14 | "hour": 22, 15 | "minute": 13, 16 | "second": 32, 17 | "microsecond": 0 18 | } 19 | } 20 | ], 21 | "IsTruncated": false, 22 | "ResponseMetadata": { 23 | "RequestId": "a8783937-6067-11e9-9b32-cb63a411b607", 24 | "HTTPStatusCode": 200 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /tests/unit/responses/users/iam.ListAttachedUserPolicies_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "AttachedPolicies": [ 5 | { 6 | "PolicyName": "AdministratorAccess", 7 | "PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess" 8 | } 9 | ], 10 | "IsTruncated": false, 11 | "ResponseMetadata": { 12 | "RequestId": "a89621e9-6067-11e9-9b32-cb63a411b607", 13 | "HTTPStatusCode": 200 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /tests/unit/responses/users/iam.ListGroupsForUser_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "Groups": [ 5 | { 6 | "Path": "/", 7 | "GroupName": "TestGroup", 8 | "GroupId": "AGPAAAAAAAAAAAAAAAAAA", 9 | "Arn": "arn:aws:iam::123456789012:group/TestGroup", 10 | "CreateDate": { 11 | "__class__": "datetime", 12 | "year": 2019, 13 | "month": 4, 14 | "day": 16, 15 | "hour": 15, 16 | "minute": 53, 17 | "second": 32, 18 | "microsecond": 0 19 | } 20 | } 21 | ], 22 | "IsTruncated": false, 23 | "ResponseMetadata": { 24 | "RequestId": "a8835cef-6067-11e9-9b32-cb63a411b607", 25 | "HTTPStatusCode": 200 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /tests/unit/responses/users/iam.ListSSHPublicKeys_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "SSHPublicKeys": [ 5 | { 6 | "UserName": "testuser", 7 | "SSHPublicKeyId": "APKAAAAAAAAAAAAAAAAA", 8 | "Status": "Active", 9 | "UploadDate": { 10 | "__class__": "datetime", 11 | "year": 2019, 12 | "month": 4, 13 | "day": 16, 14 | "hour": 16, 15 | "minute": 5, 16 | "second": 34, 17 | "microsecond": 0 18 | } 19 | } 20 | ], 21 | "IsTruncated": false, 22 | "ResponseMetadata": { 23 | "RequestId": "a89f97d9-6067-11e9-9b32-cb63a411b607", 24 | "HTTPStatusCode": 200 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /tests/unit/responses/users/iam.ListUserPolicies_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "PolicyNames": [ 5 | "TestInlinePolicy" 6 | ], 7 | "IsTruncated": false, 8 | "ResponseMetadata": { 9 | "RequestId": "a88cd2fd-6067-11e9-9b32-cb63a411b607", 10 | "HTTPStatusCode": 200 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /tests/unit/responses/users/iam.ListUserTags_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "Tags": [ 5 | { 6 | "Key": "TestKey", 7 | "Value": "TestValue" 8 | } 9 | ], 10 | "IsTruncated": false, 11 | "ResponseMetadata": { 12 | "RequestId": "a9189821-6067-11e9-9b32-cb63a411b607", 13 | "HTTPStatusCode": 200 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /tests/unit/responses/users/iam.ListUsers_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "Users": [ 5 | { 6 | "Path": "/", 7 | "UserName": "testuser", 8 | "UserId": "AIDAAAAAAAAAAAAAAAAAA", 9 | "Arn": "arn:aws:iam::123456789012:user/testuser", 10 | "CreateDate": { 11 | "__class__": "datetime", 12 | "year": 2016, 13 | "month": 3, 14 | "day": 16, 15 | "hour": 20, 16 | "minute": 35, 17 | "second": 19, 18 | "microsecond": 0 19 | } 20 | } 21 | ], 22 | "IsTruncated": false, 23 | "ResponseMetadata": { 24 | "RequestId": "a866fbee-6067-11e9-9b32-cb63a411b607", 25 | "HTTPStatusCode": 200 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /tests/unit/responses/volumes/ec2.DescribeVolumes_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "ResponseMetadata": { 5 | "HTTPStatusCode": 200, 6 | "RequestId": "d856b1fe-1716-462d-bc63-aad062316190" 7 | }, 8 | "Volumes": [ 9 | { 10 | "AvailabilityZone": "us-west-2c", 11 | "Attachments": [], 12 | "Encrypted": false, 13 | "VolumeType": "gp2", 14 | "VolumeId": "vol-b85e475f", 15 | "State": "available", 16 | "Iops": 24, 17 | "SnapshotId": "snap-bfb086e1", 18 | "CreateTime": { 19 | "hour": 6, 20 | "__class__": "datetime", 21 | "month": 9, 22 | "second": 19, 23 | "microsecond": 569000, 24 | "year": 2015, 25 | "day": 23, 26 | "minute": 20 27 | }, 28 | "Size": 8 29 | }, 30 | { 31 | "AvailabilityZone": "us-west-2c", 32 | "Attachments": [ 33 | { 34 | "AttachTime": { 35 | "hour": 0, 36 | "__class__": "datetime", 37 | "month": 12, 38 | "second": 41, 39 | "microsecond": 0, 40 | "year": 2015, 41 | "day": 31, 42 | "minute": 22 43 | }, 44 | "InstanceId": "i-c81fb512", 45 | "VolumeId": "vol-a3510945", 46 | "State": "attached", 47 | "DeleteOnTermination": true, 48 | "Device": "/dev/xvda" 49 | } 50 | ], 51 | "Encrypted": false, 52 | "VolumeType": "gp2", 53 | "VolumeId": "vol-a3510945", 54 | "State": "in-use", 55 | "Iops": 90, 56 | "SnapshotId": "snap-8c9ecb15", 57 | "CreateTime": { 58 | "hour": 0, 59 | "__class__": "datetime", 60 | "month": 12, 61 | "second": 41, 62 | "microsecond": 428000, 63 | "year": 2015, 64 | "day": 31, 65 | "minute": 22 66 | }, 67 | "Size": 30 68 | }, 69 | { 70 | "AvailabilityZone": "us-west-2a", 71 | "Attachments": [ 72 | { 73 | "AttachTime": { 74 | "hour": 21, 75 | "__class__": "datetime", 76 | "month": 11, 77 | "second": 35, 78 | "microsecond": 0, 79 | "year": 2015, 80 | "day": 4, 81 | "minute": 18 82 | }, 83 | "InstanceId": "i-100ec2c9", 84 | "VolumeId": "vol-09f36bc8", 85 | "State": "attached", 86 | "DeleteOnTermination": true, 87 | "Device": "/dev/sda1" 88 | } 89 | ], 90 | "Encrypted": false, 91 | "VolumeType": "gp2", 92 | "VolumeId": "vol-09f36bc8", 93 | "State": "in-use", 94 | "Iops": 24, 95 | "SnapshotId": "snap-6fa83432", 96 | "CreateTime": { 97 | "hour": 21, 98 | "__class__": "datetime", 99 | "month": 11, 100 | "second": 35, 101 | "microsecond": 213000, 102 | "year": 2015, 103 | "day": 4, 104 | "minute": 18 105 | }, 106 | "Size": 8 107 | }, 108 | { 109 | "AvailabilityZone": "us-west-2a", 110 | "Attachments": [ 111 | { 112 | "AttachTime": { 113 | "hour": 0, 114 | "__class__": "datetime", 115 | "month": 12, 116 | "second": 10, 117 | "microsecond": 0, 118 | "year": 2015, 119 | "day": 31, 120 | "minute": 21 121 | }, 122 | "InstanceId": "i-db530902", 123 | "VolumeId": "vol-aac7336a", 124 | "State": "attached", 125 | "DeleteOnTermination": true, 126 | "Device": "/dev/xvda" 127 | } 128 | ], 129 | "Encrypted": false, 130 | "VolumeType": "gp2", 131 | "VolumeId": "vol-aac7336a", 132 | "State": "in-use", 133 | "Iops": 90, 134 | "SnapshotId": "snap-8c9ecb15", 135 | "CreateTime": { 136 | "hour": 0, 137 | "__class__": "datetime", 138 | "month": 12, 139 | "second": 10, 140 | "microsecond": 749000, 141 | "year": 2015, 142 | "day": 31, 143 | "minute": 21 144 | }, 145 | "Size": 30 146 | } 147 | ] 148 | } 149 | } -------------------------------------------------------------------------------- /tests/unit/responses/vpcs/ec2.DescribeVpcPeeringConnections_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "VpcPeeringConnections": [], 5 | "ResponseMetadata": { 6 | "HTTPStatusCode": 200, 7 | "RequestId": "e9826765-8432-4f48-b895-0fc61d448020" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests/unit/responses/vpcs/ec2.DescribeVpcs_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "status_code": 200, 3 | "data": { 4 | "Vpcs": [ 5 | { 6 | "VpcId": "vpc-7f5e861a", 7 | "InstanceTenancy": "default", 8 | "Tags": [ 9 | { 10 | "Value": "arn:aws:cloudformation:us-west-2:123456789012:stack/DevTest-VPC/f40833d0-8417-11e4-8e98-50fa5e742444", 11 | "Key": "aws:cloudformation:stack-id" 12 | }, 13 | { 14 | "Value": "DevTest", 15 | "Key": "Environment" 16 | }, 17 | { 18 | "Value": "DevTest-VPC", 19 | "Key": "aws:cloudformation:stack-name" 20 | }, 21 | { 22 | "Value": "DevTest VPC", 23 | "Key": "Name" 24 | }, 25 | { 26 | "Value": "theVpc", 27 | "Key": "aws:cloudformation:logical-id" 28 | } 29 | ], 30 | "State": "available", 31 | "DhcpOptionsId": "dopt-6fa84c0a", 32 | "CidrBlock": "10.0.0.0/16", 33 | "IsDefault": false 34 | }, 35 | { 36 | "VpcId": "vpc-06901063", 37 | "InstanceTenancy": "default", 38 | "Tags": [ 39 | { 40 | "Value": "theVpc", 41 | "Key": "aws:cloudformation:logical-id" 42 | }, 43 | { 44 | "Value": "Hello World VPC", 45 | "Key": "Name" 46 | }, 47 | { 48 | "Value": "arn:aws:cloudformation:us-west-2:123456789012:stack/cyan1-delta-hello-world/b2fd9dd0-f41a-11e4-8e28-5088484a585d", 49 | "Key": "aws:cloudformation:stack-id" 50 | }, 51 | { 52 | "Value": "cyan1-delta-hello-world", 53 | "Key": "aws:cloudformation:stack-name" 54 | }, 55 | { 56 | "Value": "CloudNative", 57 | "Key": "Vendor" 58 | } 59 | ], 60 | "State": "available", 61 | "DhcpOptionsId": "dopt-195db47c", 62 | "CidrBlock": "10.0.0.0/16", 63 | "IsDefault": false 64 | }, 65 | { 66 | "VpcId": "vpc-2b86744e", 67 | "InstanceTenancy": "default", 68 | "State": "available", 69 | "DhcpOptionsId": "dopt-0cffed6e", 70 | "CidrBlock": "172.31.0.0/16", 71 | "IsDefault": true 72 | } 73 | ], 74 | "ResponseMetadata": { 75 | "HTTPStatusCode": 200, 76 | "RequestId": "0e5a176a-9344-4141-a937-8411c24c8aee" 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/unit/test_arn_component.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Scopely, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You 4 | # may not use this file except in compliance with the License. A copy of 5 | # the License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is 10 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 | # ANY KIND, either express or implied. See the License for the specific 12 | # language governing permissions and limitations under the License. 13 | import unittest 14 | 15 | import mock 16 | 17 | from skew.arn import ARNComponent 18 | 19 | 20 | class FooBarComponent(ARNComponent): 21 | 22 | def choices(self, context=None): 23 | if context: 24 | if 'sorted' in context: 25 | choices = ['bar', 'baz', 'fie', 'foo'] 26 | else: 27 | choices = ['foo', 'bar', 'fie', 'baz'] 28 | return choices 29 | 30 | 31 | class TestARNComponent(unittest.TestCase): 32 | 33 | def setUp(self): 34 | pass 35 | 36 | def tearDown(self): 37 | pass 38 | 39 | def test_choices(self): 40 | foobar = FooBarComponent('*', None) 41 | self.assertEqual(foobar.choices(), ['foo', 'bar', 'fie', 'baz']) 42 | self.assertEqual(foobar.choices( 43 | context=['sorted']), ['bar', 'baz', 'fie', 'foo']) 44 | self.assertEqual(foobar.pattern, '*') 45 | self.assertEqual(foobar.matches(), ['foo', 'bar', 'fie', 'baz']) 46 | foobar.pattern = 'f.*' 47 | self.assertEqual(foobar.pattern, 'f.*') 48 | self.assertEqual(foobar.matches(), ['foo', 'fie']) 49 | self.assertEqual(foobar.complete('b'), ['bar', 'baz']) 50 | -------------------------------------------------------------------------------- /tests/unit/test_resource.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Scopely, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You 4 | # may not use this file except in compliance with the License. A copy of 5 | # the License is located at 6 | # 7 | # http://aws.amazon.com/apache2.0/ 8 | # 9 | # or in the "license" file accompanying this file. This file is 10 | # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF 11 | # ANY KIND, either express or implied. See the License for the specific 12 | # language governing permissions and limitations under the License. 13 | import unittest 14 | import os 15 | 16 | import mock 17 | 18 | import skew.resources 19 | import skew.awsclient 20 | from skew.resources.resource import Resource 21 | 22 | 23 | class FooResource(Resource): 24 | 25 | class Meta(object): 26 | service = 'ec2' 27 | type = 'foo' 28 | id = 'bar' 29 | 30 | 31 | class TestResource(unittest.TestCase): 32 | 33 | def setUp(self): 34 | self.environ = {} 35 | self.environ_patch = mock.patch('os.environ', self.environ) 36 | self.environ_patch.start() 37 | credential_path = os.path.join(os.path.dirname(__file__), 'cfg', 38 | 'aws_credentials') 39 | self.environ['AWS_CONFIG_FILE'] = credential_path 40 | config_path = os.path.join(os.path.dirname(__file__), 'cfg', 41 | 'skew.yml') 42 | self.environ['SKEW_CONFIG'] = config_path 43 | 44 | def tearDown(self): 45 | pass 46 | 47 | def test_resource(self): 48 | client = skew.awsclient.get_awsclient( 49 | 'ec2', 'us-east-1', '123456789012') 50 | resource = FooResource(client, data={'bar': 'bar'}) 51 | self.assertEqual(resource.id, 'bar') 52 | self.assertEqual(resource.__repr__(), 53 | 'arn:aws:ec2:us-east-1:123456789012:foo/bar') 54 | self.assertEqual(resource.metrics, []) 55 | self.assertEqual(resource.find_metric('foobar'), None) 56 | 57 | def test_all_providers(self): 58 | all_providers = skew.resources.all_providers() 59 | self.assertEqual(len(all_providers), 1) 60 | self.assertEqual(all_providers[0], 'aws') 61 | 62 | def test_all_services(self): 63 | all_providers = skew.resources.all_services('aws') 64 | self.assertEqual(len(all_providers), 24) 65 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26,py27,py33,py34 3 | 4 | [testenv] 5 | commands = nosetests tests/unit 6 | deps = 7 | httpretty 8 | nose 9 | mock 10 | placebo 11 | 12 | [testenv:py26] 13 | commands = nosetests tests/unit 14 | deps = 15 | httpretty 16 | importlib 17 | mock 18 | nose 19 | placebo 20 | unittest2 21 | 22 | --------------------------------------------------------------------------------