-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14601 from lyft/provide-vpc-exists-support
Provide vpc exists support
- Loading branch information
Showing
2 changed files
with
269 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
# -*- coding: utf-8 -*- | ||
''' | ||
Connection module for Amazon VPC | ||
.. versionadded:: Helium | ||
:configuration: This module accepts explicit autoscale credentials but can also | ||
utilize IAM roles assigned to the instance trough Instance Profiles. | ||
Dynamic credentials are then automatically obtained from AWS API and no | ||
further configuration is necessary. More Information available at:: | ||
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html | ||
If IAM roles are not used you need to specify them either in a pillar or | ||
in the minion's config file:: | ||
asg.keyid: GKTADJGHEIQSXMKKRBJ08H | ||
asg.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs | ||
A region may also be specified in the configuration:: | ||
asg.region: us-east-1 | ||
If a region is not specified, the default is us-east-1. | ||
It's also possible to specify key, keyid and region via a profile, either | ||
as a passed in dict, or as a string to pull from pillars or minion config: | ||
myprofile: | ||
keyid: GKTADJGHEIQSXMKKRBJ08H | ||
key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs | ||
region: us-east-1 | ||
:depends: boto | ||
''' | ||
|
||
# Import Python libs | ||
import logging | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
# Import third party libs | ||
try: | ||
import boto | ||
import boto.vpc | ||
logging.getLogger('boto').setLevel(logging.CRITICAL) | ||
HAS_BOTO = True | ||
except ImportError: | ||
HAS_BOTO = False | ||
|
||
from salt._compat import string_types | ||
|
||
|
||
def __virtual__(): | ||
''' | ||
Only load if boto libraries exist. | ||
''' | ||
if not HAS_BOTO: | ||
return False | ||
return True | ||
|
||
|
||
def get_subnet_association(subnets, region=None, key=None, keyid=None, | ||
profile=None): | ||
''' | ||
Given a subnet (aka: a vpc zone identifier) or list of subnets, returns | ||
vpc association. | ||
Returns a VPC ID if the given subnets are associated with the same VPC ID. | ||
Returns False on an error or if the given subnets are associated with | ||
different VPC IDs. | ||
CLI Examples:: | ||
.. code-block:: bash | ||
salt myminion boto_vpc.get_subnet_association subnet-61b47516 | ||
.. code-block:: bash | ||
salt myminion boto_vpc.get_subnet_association ['subnet-61b47516','subnet-2cb9785b'] | ||
''' | ||
conn = _get_conn(region, key, keyid, profile) | ||
if not conn: | ||
return False | ||
try: | ||
# subnet_ids=subnets can accept either a string or a list | ||
subnets = conn.get_all_subnets(subnet_ids=subnets) | ||
except boto.exception.BotoServerError as e: | ||
log.debug(e) | ||
return False | ||
# using a set to store vpc_ids - the use of set prevents duplicate | ||
# vpc_id values | ||
vpc_ids = set() | ||
for subnet in subnets: | ||
log.debug('examining subnet id: {0} for vpc_id'.format(subnet.id)) | ||
if subnet in subnets: | ||
log.debug('subnet id: {0} is associated with vpc id: {1}' | ||
.format(subnet.id, subnet.vpc_id)) | ||
vpc_ids.add(subnet.vpc_id) | ||
if len(vpc_ids) == 1: | ||
vpc_id = vpc_ids.pop() | ||
log.debug('all subnets are associated with vpc id: {0}'.format(vpc_id)) | ||
return vpc_id | ||
else: | ||
log.debug('given subnets are associated with fewer than 1 or greater' | ||
' than 1 subnets') | ||
return False | ||
|
||
|
||
def exists(vpc_id, region=None, key=None, keyid=None, profile=None): | ||
''' | ||
Given a VPC ID, check to see if the given VPC ID exists. | ||
Returns True if the given VPC ID exists and returns False if the given | ||
VPC ID does not exist. | ||
CLI example:: | ||
.. code-block:: bash | ||
salt myminion boto_vpc.exists myvpc | ||
''' | ||
conn = _get_conn(region, key, keyid, profile) | ||
if not conn: | ||
return False | ||
try: | ||
conn.get_all_vpcs(vpc_ids=[vpc_id]) | ||
return True | ||
except boto.exception.BotoServerError as e: | ||
log.debug(e) | ||
return False | ||
|
||
|
||
def _get_conn(region, key, keyid, profile): | ||
''' | ||
Get a boto connection to vpc. | ||
''' | ||
if profile: | ||
if isinstance(profile, string_types): | ||
_profile = __salt__['config.option'](profile) | ||
elif isinstance(profile, dict): | ||
_profile = profile | ||
key = _profile.get('key', None) | ||
keyid = _profile.get('keyid', None) | ||
region = _profile.get('region', None) | ||
|
||
if not region and __salt__['config.option']('vpc.region'): | ||
region = __salt__['config.option']('vpc.region') | ||
|
||
if not region: | ||
region = 'us-east-1' | ||
|
||
if not key and __salt__['config.option']('vpc.key'): | ||
key = __salt__['config.option']('vpc.key') | ||
if not keyid and __salt__['config.option']('vpc.keyid'): | ||
keyid = __salt__['config.option']('vpc.keyid') | ||
|
||
try: | ||
conn = boto.vpc.connect_to_region(region, aws_access_key_id=keyid, | ||
aws_secret_access_key=key) | ||
except boto.exception.NoAuthHandlerFound: | ||
log.error('No authentication credentials found when attempting to' | ||
' make boto autoscale connection.') | ||
return None | ||
return conn |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# import Python Third Party Libs | ||
try: | ||
import boto | ||
from moto import mock_ec2 | ||
missing_requirements = False | ||
missing_requirements_msg = '' | ||
except ImportError: | ||
missing_requirements = True | ||
missing_requirements_msg = 'boto and moto modules required for test.' | ||
|
||
def mock_ec2(self): | ||
''' | ||
if the mock_ec2 function is not available due to import failure | ||
this replaces the decorated function with stub_function. | ||
Allows boto_vpc unit tests to use the @mock_ec2 decorator | ||
without a "NameError: name 'mock_ec2' is not defined" error. | ||
''' | ||
def stub_function(self): | ||
pass | ||
return stub_function | ||
|
||
# Import Salt Libs | ||
from salt.modules import boto_vpc | ||
|
||
# Import Salt Testing Libs | ||
from salttesting import skipIf, TestCase | ||
|
||
region = 'us-east-1' | ||
access_key = 'GKTADJGHEIQSXMKKRBJ08H' | ||
secret_key = 'askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs' | ||
conn_parameters = {'region': region, 'key': access_key, 'keyid': secret_key, 'profile': {}} | ||
|
||
|
||
class Boto_VpcTestCase(TestCase): | ||
''' | ||
TestCase for salt.modules.boto_vpc module | ||
''' | ||
@skipIf(missing_requirements, missing_requirements_msg) | ||
@mock_ec2 | ||
def test_get_subnet_association_single_subnet(self): | ||
''' | ||
tests that given multiple subnet ids in the same VPC that the VPC ID is | ||
returned. The test is valuable because it uses a string as an argument | ||
to subnets as opposed to a list. | ||
''' | ||
conn = boto.vpc.connect_to_region(region) | ||
vpc = conn.create_vpc('10.0.0.0/24') | ||
subnet = conn.create_subnet(vpc.id, '10.0.0.0/25') | ||
subnet_assocation = boto_vpc.get_subnet_association(subnets=subnet.id, | ||
**conn_parameters) | ||
self.assertEqual(vpc.id, subnet_assocation) | ||
|
||
@skipIf(missing_requirements, missing_requirements_msg) | ||
@mock_ec2 | ||
def test_get_subnet_association_multiple_subnets_same_vpc(self): | ||
''' | ||
tests that given multiple subnet ids in the same VPC that the VPC ID is | ||
returned. | ||
''' | ||
conn = boto.vpc.connect_to_region(region) | ||
vpc = conn.create_vpc('10.0.0.0/24') | ||
subnet_a = conn.create_subnet(vpc.id, '10.0.0.0/25') | ||
subnet_b = conn.create_subnet(vpc.id, '10.0.0.128/25') | ||
subnet_assocation = boto_vpc.get_subnet_association([subnet_a.id, subnet_b.id], | ||
**conn_parameters) | ||
self.assertEqual(vpc.id, subnet_assocation) | ||
|
||
@skipIf(missing_requirements, missing_requirements_msg) | ||
@mock_ec2 | ||
def test_get_subnet_association_multiple_subnets_different_vpc(self): | ||
''' | ||
tests that given multiple subnet ids in different VPCs that False is | ||
returned. | ||
''' | ||
conn = boto.vpc.connect_to_region(region) | ||
vpc_a = conn.create_vpc('10.0.0.0/24') | ||
vpc_b = conn.create_vpc('10.0.0.0/24') | ||
subnet_a = conn.create_subnet(vpc_a.id, '10.0.0.0/24') | ||
subnet_b = conn.create_subnet(vpc_b.id, '10.0.0.0/24') | ||
subnet_assocation = boto_vpc.get_subnet_association([subnet_a.id, subnet_b.id], | ||
**conn_parameters) | ||
self.assertFalse(subnet_assocation) | ||
|
||
@skipIf(missing_requirements, missing_requirements_msg) | ||
@mock_ec2 | ||
def test_exists_true(self): | ||
''' | ||
tests True existence of a VPC. | ||
''' | ||
conn = boto.vpc.connect_to_region(region) | ||
vpc = conn.create_vpc('10.0.0.0/24') | ||
vpc_exists = boto_vpc.exists(vpc.id, **conn_parameters) | ||
self.assertTrue(vpc_exists) | ||
|
||
|
||
if __name__ == '__main__': | ||
from integration import run_tests | ||
run_tests(Boto_VpcTestCase) |