Skip to content

Commit

Permalink
Merge pull request #14601 from lyft/provide-vpc-exists-support
Browse files Browse the repository at this point in the history
Provide vpc exists support
  • Loading branch information
thatch45 committed Aug 4, 2014
2 parents 970f1f1 + 3bcfd4b commit 77d709a
Show file tree
Hide file tree
Showing 2 changed files with 269 additions and 0 deletions.
169 changes: 169 additions & 0 deletions salt/modules/boto_vpc.py
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
100 changes: 100 additions & 0 deletions tests/unit/modules/boto_vpc.py
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)

0 comments on commit 77d709a

Please sign in to comment.