mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 17:09:03 +00:00
Add boto_sns module and state.
Include integration tests.
This commit is contained in:
parent
2b97da4015
commit
4542072c4e
120
salt/modules/boto_sns.py
Normal file
120
salt/modules/boto_sns.py
Normal file
@ -0,0 +1,120 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Import third party libs
|
||||
try:
|
||||
import boto
|
||||
import boto.sns
|
||||
logging.getLogger('boto').setLevel(logging.CRITICAL)
|
||||
HAS_BOTO = True
|
||||
except ImportError:
|
||||
HAS_BOTO = False
|
||||
|
||||
from salt.ext.six import string_types
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only load if boto libraries exist.
|
||||
'''
|
||||
if not HAS_BOTO:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_all_topics(region=None, key=None, keyid=None, profile=None):
|
||||
cache_key = 'boto_sns.topics_cache'
|
||||
try:
|
||||
return __context__[cache_key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
conn = _get_conn(region, key, keyid, profile)
|
||||
__context__[cache_key] = {}
|
||||
# TODO: support >100 SNS topics (via NextToken)
|
||||
for t in conn.get_all_topics()['ListTopicsResponse']\
|
||||
['ListTopicsResult']['Topics']:
|
||||
short_name = t['TopicArn'].split(':')[-1]
|
||||
__context__[cache_key][short_name] = t['TopicArn']
|
||||
return __context__[cache_key]
|
||||
|
||||
def exists(name, region=None, key=None, keyid=None, profile=None):
|
||||
'''
|
||||
Check to see if an SNS topic exists.
|
||||
|
||||
CLI example::
|
||||
|
||||
salt myminion boto_sns.exists mytopic region=us-east-1
|
||||
'''
|
||||
topics = get_all_topics(region=region, key=key, keyid=keyid, profile=profile)
|
||||
if name.startswith('arn:aws:sns:'):
|
||||
return name in topics.values()
|
||||
else:
|
||||
return name in topics.keys()
|
||||
|
||||
|
||||
def create(name, region=None, key=None, keyid=None, profile=None):
|
||||
'''
|
||||
Create an SNS topic.
|
||||
|
||||
CLI example to create a topic::
|
||||
|
||||
salt myminion boto_sns.create mytopic region=us-east-1
|
||||
'''
|
||||
conn = _get_conn(region, key, keyid, profile)
|
||||
ret = conn.create_topic(name)
|
||||
log.info('Created SNS topic {0}'.format(name))
|
||||
return True
|
||||
|
||||
def delete(name, region=None, key=None, keyid=None, profile=None):
|
||||
'''
|
||||
Delete an SNS topic.
|
||||
|
||||
CLI example to delete a topic::
|
||||
|
||||
salt myminion boto_sns.delete mytopic region=us-east-1
|
||||
'''
|
||||
conn = _get_conn(region, key, keyid, profile)
|
||||
conn.delete_topic(get_arn(name, region, key, keyid, profile))
|
||||
log.info('Deleted SNS topic {0}'.format(name))
|
||||
return True
|
||||
|
||||
def get_arn(name, region=None, key=None, keyid=None, profile=None):
|
||||
if name.startswith('arn:aws:sns:'):
|
||||
return name
|
||||
account_id = __salt__['boto_iam.get_account_id']()
|
||||
return 'arn:aws:sns:{}:{}:{}'.format(_get_region(region), account_id, name)
|
||||
|
||||
def _get_region(region=None):
|
||||
if not region and __salt__['config.option']('sns.region'):
|
||||
region = __salt__['config.option']('sns.region')
|
||||
if not region:
|
||||
region = 'us-east-1'
|
||||
return region
|
||||
|
||||
def _get_conn(region, key, keyid, profile):
|
||||
'''
|
||||
Get a boto connection to SNS.
|
||||
'''
|
||||
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)
|
||||
|
||||
region = _get_region(region)
|
||||
|
||||
if not key and __salt__['config.option']('sns.key'):
|
||||
key = __salt__['config.option']('sns.key')
|
||||
if not keyid and __salt__['config.option']('sns.keyid'):
|
||||
keyid = __salt__['config.option']('sns.keyid')
|
||||
|
||||
conn = boto.sns.connect_to_region(region,
|
||||
aws_access_key_id=keyid,
|
||||
aws_secret_access_key=key)
|
||||
return conn
|
165
salt/states/boto_sns.py
Normal file
165
salt/states/boto_sns.py
Normal file
@ -0,0 +1,165 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Manage SNS Topics
|
||||
|
||||
|
||||
Create and destroy SNS topics. Be aware that this interacts with Amazon's
|
||||
services, and so may incur charges.
|
||||
|
||||
This module uses ``boto``, which can be installed via package, or pip.
|
||||
|
||||
This module accepts explicit SQS credentials but can also utilize
|
||||
IAM roles assigned to the instance through Instance Profiles. Dynamic
|
||||
credentials are then automatically obtained from AWS API and no further
|
||||
configuration is necessary. More information available `here
|
||||
<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 file or
|
||||
in the minion's config file:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
sns.keyid: GKTADJGHEIQSXMKKRBJ08H
|
||||
sns.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
|
||||
|
||||
It's also possible to specify ``key``, ``keyid`` and ``region`` via a profile, either
|
||||
passed in as a dict, or as a string to pull from pillars or minion config:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
myprofile:
|
||||
keyid: GKTADJGHEIQSXMKKRBJ08H
|
||||
key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
|
||||
region: us-east-1
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
mytopic:
|
||||
boto_sns.present:
|
||||
- region: us-east-1
|
||||
- keyid: GKTADJGHEIQSXMKKRBJ08H
|
||||
- key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
|
||||
|
||||
# Using a profile from pillars
|
||||
mytopic:
|
||||
boto_sns.present:
|
||||
- region: us-east-1
|
||||
- profile: mysnsprofile
|
||||
|
||||
# Passing in a profile
|
||||
mytopic:
|
||||
boto_sns.present:
|
||||
- region: us-east-1
|
||||
- profile:
|
||||
keyid: GKTADJGHEIQSXMKKRBJ08H
|
||||
key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
|
||||
'''
|
||||
from __future__ import absolute_import
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only load if boto is available.
|
||||
'''
|
||||
return 'boto_sns' if 'boto_sns.exists' in __salt__ else False
|
||||
|
||||
def present(
|
||||
name,
|
||||
region=None,
|
||||
key=None,
|
||||
keyid=None,
|
||||
profile=None):
|
||||
'''
|
||||
Ensure the SNS topic exists.
|
||||
|
||||
name
|
||||
Name of the SNS topic.
|
||||
|
||||
region
|
||||
Region to connect to.
|
||||
|
||||
key
|
||||
Secret key to be used.
|
||||
|
||||
keyid
|
||||
Access key to be used.
|
||||
|
||||
profile
|
||||
A dict with region, key and keyid, or a pillar key (string)
|
||||
that contains a dict with region, key and keyid.
|
||||
'''
|
||||
ret = {'name': name, 'result': True, 'comment': '', 'changes': {}}
|
||||
|
||||
|
||||
is_present = __salt__['boto_sns.exists'](name, region, key, keyid, profile)
|
||||
if is_present:
|
||||
ret['comment'] = 'AWS SNS topic {0} present.'.format(name)
|
||||
else:
|
||||
if __opts__['test']:
|
||||
msg = 'AWS SNS topic {0} is set to be created.'.format(name)
|
||||
ret['comment'] = msg
|
||||
ret['result'] = None
|
||||
return ret
|
||||
|
||||
created = __salt__['boto_sns.create'](name, region, key, keyid,
|
||||
profile)
|
||||
if created:
|
||||
msg = 'AWS SNS topic {0} created.'.format(name)
|
||||
ret['comment'] = msg
|
||||
ret['changes']['old'] = None
|
||||
ret['changes']['new'] = {'topic': name}
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed to create {0} AWS SNS topic'.format(name)
|
||||
return ret
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def absent(
|
||||
name,
|
||||
region=None,
|
||||
key=None,
|
||||
keyid=None,
|
||||
profile=None):
|
||||
'''
|
||||
Ensure the named sns topic is deleted.
|
||||
|
||||
name
|
||||
Name of the SNS topic.
|
||||
|
||||
region
|
||||
Region to connect to.
|
||||
|
||||
key
|
||||
Secret key to be used.
|
||||
|
||||
keyid
|
||||
Access key to be used.
|
||||
|
||||
profile
|
||||
A dict with region, key and keyid, or a pillar key (string)
|
||||
that contains a dict with region, key and keyid.
|
||||
'''
|
||||
ret = {'name': name, 'result': True, 'comment': '', 'changes': {}}
|
||||
|
||||
is_present = __salt__['boto_sns.exists'](name, region, key, keyid, profile)
|
||||
|
||||
if is_present:
|
||||
if __opts__['test']:
|
||||
ret['comment'] = 'AWS SNS topic {0} is set to be removed.'.format(
|
||||
name)
|
||||
ret['result'] = None
|
||||
return ret
|
||||
deleted = __salt__['boto_sns.delete'](name, region, key, keyid,
|
||||
profile)
|
||||
if deleted:
|
||||
ret['comment'] = 'AWS SNS topic {0} does not exist.'.format(name)
|
||||
ret['changes']['old'] = {'topic': name}
|
||||
ret['changes']['new'] = None
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Failed to delete {0} AWS SNS topic.'.format(name)
|
||||
else:
|
||||
ret['comment'] = 'AWS SNS topic {0} does not exist.'.format(name)
|
||||
|
||||
return ret
|
67
tests/integration/modules/boto_sns.py
Normal file
67
tests/integration/modules/boto_sns.py
Normal file
@ -0,0 +1,67 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Validate the boto_sns module
|
||||
'''
|
||||
from salttesting import skipIf
|
||||
from salttesting.helpers import ensure_in_syspath
|
||||
ensure_in_syspath('../../')
|
||||
|
||||
import integration
|
||||
|
||||
NO_BOTO_MODULE = True
|
||||
BOTO_NOT_CONFIGURED = True
|
||||
try:
|
||||
import boto
|
||||
NO_BOTO_MODULE = False
|
||||
try:
|
||||
boto.connect_iam()
|
||||
BOTO_NOT_CONFIGURED = False
|
||||
except boto.exception.NoAuthHandlerFound:
|
||||
pass
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
@skipIf(
|
||||
NO_BOTO_MODULE,
|
||||
'Please install the boto library before running boto integration tests.'
|
||||
)
|
||||
@skipIf(
|
||||
BOTO_NOT_CONFIGURED,
|
||||
'Please setup boto AWS credentials before running boto integration tests.'
|
||||
)
|
||||
class BotoSNSTest(integration.ModuleCase):
|
||||
|
||||
def test_exists(self):
|
||||
ret = self.run_function('boto_sns.exists', ['nonexistent'])
|
||||
self.assertFalse(ret)
|
||||
|
||||
def test_create(self):
|
||||
ret = self.run_function('boto_sns.create', ['my-test-topic'])
|
||||
self.assertTrue(ret)
|
||||
|
||||
def test_delete(self):
|
||||
ret = self.run_function('boto_sns.delete', ['my-test-topic'])
|
||||
self.assertTrue(ret)
|
||||
|
||||
def test_get_all_topics(self):
|
||||
self.run_function('boto_sns.create', ['my-test-topic'])
|
||||
self.run_function('boto_sns.create', ['my-second-test-topic'])
|
||||
|
||||
ret = self.run_function('boto_sns.get_all_topics')
|
||||
|
||||
self.assertIn('my-test-topic', ret.keys())
|
||||
self.assertIn(self._get_arn('my-test-topic'), ret.values())
|
||||
self.assertIn('my-second-test-topic', ret.keys())
|
||||
self.assertIn(self._get_arn('my-second-test-topic'), ret.values())
|
||||
|
||||
|
||||
def _get_arn(self, name):
|
||||
return 'arn:aws:sns:us-east-1:{}:{}'.format(self.account_id, name)
|
||||
|
||||
@property
|
||||
def account_id(self):
|
||||
if not hasattr(self, '_account_id'):
|
||||
account_id = self.run_function('boto_iam.get_account_id')
|
||||
setattr(self, '_account_id', account_id)
|
||||
return self._account_id
|
105
tests/integration/states/boto_sns.py
Normal file
105
tests/integration/states/boto_sns.py
Normal file
@ -0,0 +1,105 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for the boto_sns state
|
||||
"""
|
||||
from salttesting import skipIf
|
||||
from salttesting.helpers import ensure_in_syspath
|
||||
ensure_in_syspath('../../')
|
||||
|
||||
import integration
|
||||
|
||||
NO_BOTO_MODULE = True
|
||||
BOTO_NOT_CONFIGURED = True
|
||||
try:
|
||||
import boto
|
||||
NO_BOTO_MODULE = False
|
||||
try:
|
||||
boto.connect_iam()
|
||||
BOTO_NOT_CONFIGURED = False
|
||||
except boto.exception.NoAuthHandlerFound:
|
||||
pass
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
@skipIf(
|
||||
NO_BOTO_MODULE,
|
||||
'Please install the boto library before running boto integration tests.'
|
||||
)
|
||||
@skipIf(
|
||||
BOTO_NOT_CONFIGURED,
|
||||
'Please setup boto AWS credentials before running boto integration tests.'
|
||||
)
|
||||
class BotoSNSTest(integration.ModuleCase,
|
||||
integration.SaltReturnAssertsMixIn):
|
||||
|
||||
def setUp(self):
|
||||
self.run_function('boto_sns.delete', name='my-state-test-topic')
|
||||
|
||||
def tearDown(self):
|
||||
self.run_function('boto_sns.delete', name='my-state-test-topic')
|
||||
|
||||
def test_present_not_exist(self):
|
||||
ret = self.run_state('boto_sns.present',
|
||||
name='my-state-test-topic')
|
||||
self.assertSaltTrueReturn(ret)
|
||||
self.assertInSaltReturn('my-state-test-topic', ret, 'name')
|
||||
self.assertInSaltComment('AWS SNS topic my-state-test-topic created.', ret)
|
||||
self.assertSaltStateChangesEqual(ret, {'old': None, 'new': {'topic': 'my-state-test-topic'}})
|
||||
|
||||
def test_present_already_exist(self):
|
||||
self.run_state('boto_sns.present',
|
||||
name='my-state-test-topic')
|
||||
ret = self.run_state('boto_sns.present',
|
||||
name='my-state-test-topic')
|
||||
self.assertSaltTrueReturn(ret)
|
||||
self.assertInSaltReturn('my-state-test-topic', ret, 'name')
|
||||
self.assertInSaltComment('AWS SNS topic my-state-test-topic present.', ret)
|
||||
self.assertSaltStateChangesEqual(ret, {})
|
||||
|
||||
def test_present_test_mode(self):
|
||||
ret = self.run_state('boto_sns.present',
|
||||
name='my-state-test-topic',
|
||||
test=True)
|
||||
self.assertSaltNoneReturn(ret)
|
||||
self.assertInSaltReturn('my-state-test-topic', ret, 'name')
|
||||
self.assertInSaltComment('AWS SNS topic my-state-test-topic is set to be created.', ret)
|
||||
self.assertSaltStateChangesEqual(ret, {})
|
||||
ret = self.run_function('boto_sns.exists', name='my-state-test-topic')
|
||||
self.assertFalse(ret)
|
||||
|
||||
def test_absent_not_exist(self):
|
||||
ret = self.run_state('boto_sns.absent',
|
||||
name='my-state-test-topic')
|
||||
self.assertSaltTrueReturn(ret)
|
||||
self.assertInSaltReturn('my-state-test-topic', ret, 'name')
|
||||
self.assertInSaltComment('AWS SNS topic my-state-test-topic does not exist.', ret)
|
||||
self.assertSaltStateChangesEqual(ret, {})
|
||||
|
||||
def test_absent_already_exists(self):
|
||||
self.run_state('boto_sns.present',
|
||||
name='my-state-test-topic')
|
||||
ret = self.run_state('boto_sns.absent',
|
||||
name='my-state-test-topic')
|
||||
self.assertSaltTrueReturn(ret)
|
||||
self.assertInSaltReturn('my-state-test-topic', ret, 'name')
|
||||
self.assertInSaltComment('AWS SNS topic my-state-test-topic does not exist.', ret)
|
||||
self.assertSaltStateChangesEqual(ret, {'new': None, 'old': {'topic': 'my-state-test-topic'}})
|
||||
|
||||
def test_absent_test_mode(self):
|
||||
self.run_state('boto_sns.present',
|
||||
name='my-state-test-topic')
|
||||
ret = self.run_state('boto_sns.absent',
|
||||
name='my-state-test-topic',
|
||||
test=True)
|
||||
self.assertSaltNoneReturn(ret)
|
||||
self.assertInSaltReturn('my-state-test-topic', ret, 'name')
|
||||
self.assertInSaltComment('AWS SNS topic my-state-test-topic is set to be removed.', ret)
|
||||
self.assertSaltStateChangesEqual(ret, {})
|
||||
ret = self.run_function('boto_sns.exists', name='my-state-test-topic')
|
||||
self.assertTrue(ret)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
run_tests(BotoSNSTest, needs_daemon=False)
|
Loading…
Reference in New Issue
Block a user