mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #43302 from lyft/upstream-boto_cloudfront
Upstream boto_cloudfront execution and state modules
This commit is contained in:
commit
1a81663e46
7 changed files with 928 additions and 0 deletions
|
@ -44,6 +44,7 @@ execution modules
|
|||
boto_apigateway
|
||||
boto_asg
|
||||
boto_cfn
|
||||
boto_cloudfront
|
||||
boto_cloudtrail
|
||||
boto_cloudwatch
|
||||
boto_cloudwatch_event
|
||||
|
|
6
doc/ref/modules/all/salt.modules.boto_cloudfront.rst
Normal file
6
doc/ref/modules/all/salt.modules.boto_cloudfront.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
============================
|
||||
salt.modules.boto_cloudfront
|
||||
============================
|
||||
|
||||
.. automodule:: salt.modules.boto_cloudfront
|
||||
:members:
|
|
@ -31,6 +31,7 @@ state modules
|
|||
boto_apigateway
|
||||
boto_asg
|
||||
boto_cfn
|
||||
boto_cloudfront
|
||||
boto_cloudtrail
|
||||
boto_cloudwatch_alarm
|
||||
boto_cloudwatch_event
|
||||
|
|
6
doc/ref/states/all/salt.states.boto_cloudfront.rst
Normal file
6
doc/ref/states/all/salt.states.boto_cloudfront.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
===========================
|
||||
salt.states.boto_cloudfront
|
||||
===========================
|
||||
|
||||
.. automodule:: salt.states.boto_cloudfront
|
||||
:members:
|
462
salt/modules/boto_cloudfront.py
Normal file
462
salt/modules/boto_cloudfront.py
Normal file
|
@ -0,0 +1,462 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Connection module for Amazon CloudFront
|
||||
|
||||
.. versionadded:: Oxygen
|
||||
|
||||
:depends: boto3
|
||||
|
||||
:configuration: This module accepts explicit AWS credentials but can also
|
||||
utilize IAM roles assigned to the instance through Instance Profiles or
|
||||
it can read them from the ~/.aws/credentials file or from these
|
||||
environment variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY.
|
||||
Dynamic credentials are then automatically obtained from AWS API and no
|
||||
further configuration is necessary. More information available at:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/
|
||||
iam-roles-for-amazon-ec2.html
|
||||
|
||||
http://boto3.readthedocs.io/en/latest/guide/
|
||||
configuration.html#guide-configuration
|
||||
|
||||
If IAM roles are not used you need to specify them either in a pillar or
|
||||
in the minion's config file:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
cloudfront.keyid: GKTADJGHEIQSXMKKRBJ08H
|
||||
cloudfront.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
|
||||
|
||||
A region may also be specified in the configuration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
cloudfront.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:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
myprofile:
|
||||
keyid: GKTADJGHEIQSXMKKRBJ08H
|
||||
key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
|
||||
region: us-east-1
|
||||
'''
|
||||
# keep lint from choking on _get_conn and _cache_id
|
||||
# pylint: disable=E0602
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
|
||||
# Import Salt libs
|
||||
import salt.ext.six as six
|
||||
from salt.utils.odict import OrderedDict
|
||||
|
||||
import yaml
|
||||
|
||||
# Import third party libs
|
||||
try:
|
||||
# pylint: disable=unused-import
|
||||
import boto3
|
||||
import botocore
|
||||
# pylint: enable=unused-import
|
||||
logging.getLogger('boto3').setLevel(logging.CRITICAL)
|
||||
HAS_BOTO = True
|
||||
except ImportError:
|
||||
HAS_BOTO = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only load if boto3 libraries exist.
|
||||
'''
|
||||
if not HAS_BOTO:
|
||||
msg = 'The boto_cloudfront module could not be loaded: {}.'
|
||||
return (False, msg.format('boto3 libraries not found'))
|
||||
__utils__['boto3.assign_funcs'](__name__, 'cloudfront')
|
||||
return True
|
||||
|
||||
|
||||
def _list_distributions(
|
||||
conn,
|
||||
name=None,
|
||||
region=None,
|
||||
key=None,
|
||||
keyid=None,
|
||||
profile=None,
|
||||
):
|
||||
'''
|
||||
Private function that returns an iterator over all CloudFront distributions.
|
||||
The caller is responsible for all boto-related error handling.
|
||||
|
||||
name
|
||||
(Optional) Only yield the distribution with the given name
|
||||
'''
|
||||
for dl_ in conn.get_paginator('list_distributions').paginate():
|
||||
distribution_list = dl_['DistributionList']
|
||||
if 'Items' not in distribution_list:
|
||||
# If there are no items, AWS omits the `Items` key for some reason
|
||||
continue
|
||||
for partial_dist in distribution_list['Items']:
|
||||
tags = conn.list_tags_for_resource(Resource=partial_dist['ARN'])
|
||||
tags = dict(
|
||||
(kv['Key'], kv['Value']) for kv in tags['Tags']['Items']
|
||||
)
|
||||
|
||||
id_ = partial_dist['Id']
|
||||
if 'Name' not in tags:
|
||||
log.warning(
|
||||
'CloudFront distribution {0} has no Name tag.'.format(id_),
|
||||
)
|
||||
continue
|
||||
distribution_name = tags.pop('Name', None)
|
||||
if name is not None and distribution_name != name:
|
||||
continue
|
||||
|
||||
# NOTE: list_distributions() returns a DistributionList,
|
||||
# which nominally contains a list of Distribution objects.
|
||||
# However, they are mangled in that they are missing values
|
||||
# (`Logging`, `ActiveTrustedSigners`, and `ETag` keys)
|
||||
# and moreover flatten the normally nested DistributionConfig
|
||||
# attributes to the top level.
|
||||
# Hence, we must call get_distribution() to get the full object,
|
||||
# and we cache these objects to help lessen API calls.
|
||||
distribution = _cache_id(
|
||||
'cloudfront',
|
||||
sub_resource=distribution_name,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile,
|
||||
)
|
||||
if distribution:
|
||||
yield (distribution_name, distribution)
|
||||
continue
|
||||
|
||||
dist_with_etag = conn.get_distribution(Id=id_)
|
||||
distribution = {
|
||||
'distribution': dist_with_etag['Distribution'],
|
||||
'etag': dist_with_etag['ETag'],
|
||||
'tags': tags,
|
||||
}
|
||||
_cache_id(
|
||||
'cloudfront',
|
||||
sub_resource=distribution_name,
|
||||
resource_id=distribution,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile,
|
||||
)
|
||||
yield (distribution_name, distribution)
|
||||
|
||||
|
||||
def get_distribution(name, region=None, key=None, keyid=None, profile=None):
|
||||
'''
|
||||
Get information about a CloudFront distribution (configuration, tags) with a given name.
|
||||
|
||||
name
|
||||
Name of the CloudFront distribution
|
||||
|
||||
region
|
||||
Region to connect to
|
||||
|
||||
key
|
||||
Secret key to use
|
||||
|
||||
keyid
|
||||
Access key to use
|
||||
|
||||
profile
|
||||
A dict with region, key, and keyid,
|
||||
or a pillar key (string) that contains such a dict.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion boto_cloudfront.get_distribution name=mydistribution profile=awsprofile
|
||||
|
||||
'''
|
||||
distribution = _cache_id(
|
||||
'cloudfront',
|
||||
sub_resource=name,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile,
|
||||
)
|
||||
if distribution:
|
||||
return {'result': distribution}
|
||||
|
||||
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
|
||||
try:
|
||||
for _, dist in _list_distributions(
|
||||
conn,
|
||||
name=name,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile,
|
||||
):
|
||||
# _list_distributions should only return the one distribution
|
||||
# that we want (with the given name).
|
||||
# In case of multiple distributions with the same name tag,
|
||||
# our use of caching means list_distributions will just
|
||||
# return the first one over and over again,
|
||||
# so only the first result is useful.
|
||||
if distribution is not None:
|
||||
msg = 'More than one distribution found with name {0}'
|
||||
return {'error': msg.format(name)}
|
||||
distribution = dist
|
||||
except botocore.exceptions.ClientError as err:
|
||||
return {'error': __utils__['boto3.get_error'](err)}
|
||||
if not distribution:
|
||||
return {'result': None}
|
||||
|
||||
_cache_id(
|
||||
'cloudfront',
|
||||
sub_resource=name,
|
||||
resource_id=distribution,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile,
|
||||
)
|
||||
return {'result': distribution}
|
||||
|
||||
|
||||
def export_distributions(region=None, key=None, keyid=None, profile=None):
|
||||
'''
|
||||
Get details of all CloudFront distributions.
|
||||
Produces results that can be used to create an SLS file.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt-call boto_cloudfront.export_distributions --out=txt |\
|
||||
sed "s/local: //" > cloudfront_distributions.sls
|
||||
|
||||
'''
|
||||
results = OrderedDict()
|
||||
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
|
||||
try:
|
||||
for name, distribution in _list_distributions(
|
||||
conn,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile,
|
||||
):
|
||||
config = distribution['distribution']['DistributionConfig']
|
||||
tags = distribution['tags']
|
||||
|
||||
distribution_sls_data = [
|
||||
{'name': name},
|
||||
{'config': config},
|
||||
{'tags': tags},
|
||||
]
|
||||
results['Manage CloudFront distribution {0}'.format(name)] = {
|
||||
'boto_cloudfront.present': distribution_sls_data,
|
||||
}
|
||||
except botocore.exceptions.ClientError as err:
|
||||
# Raise an exception, as this is meant to be user-invoked at the CLI
|
||||
# as opposed to being called from execution or state modules
|
||||
raise err
|
||||
|
||||
dumper = __utils__['yamldumper.get_dumper']('IndentedSafeOrderedDumper')
|
||||
return yaml.dump(
|
||||
results,
|
||||
default_flow_style=False,
|
||||
Dumper=dumper,
|
||||
)
|
||||
|
||||
|
||||
def create_distribution(
|
||||
name,
|
||||
config,
|
||||
tags=None,
|
||||
region=None,
|
||||
key=None,
|
||||
keyid=None,
|
||||
profile=None,
|
||||
):
|
||||
'''
|
||||
Create a CloudFront distribution with the given name, config, and (optionally) tags.
|
||||
|
||||
name
|
||||
Name for the CloudFront distribution
|
||||
|
||||
config
|
||||
Configuration for the distribution
|
||||
|
||||
tags
|
||||
Tags to associate with the distribution
|
||||
|
||||
region
|
||||
Region to connect to
|
||||
|
||||
key
|
||||
Secret key to use
|
||||
|
||||
keyid
|
||||
Access key to use
|
||||
|
||||
profile
|
||||
A dict with region, key, and keyid,
|
||||
or a pillar key (string) that contains such a dict.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion boto_cloudfront.create_distribution name=mydistribution profile=awsprofile \
|
||||
config='{"Comment":"partial configuration","Enabled":true}'
|
||||
'''
|
||||
if tags is None:
|
||||
tags = {}
|
||||
if 'Name' in tags:
|
||||
# Be lenient and silently accept if names match, else error
|
||||
if tags['Name'] != name:
|
||||
return {'error': 'Must not pass `Name` in `tags` but as `name`'}
|
||||
tags['Name'] = name
|
||||
tags = {
|
||||
'Items': [{'Key': k, 'Value': v} for k, v in six.iteritems(tags)]
|
||||
}
|
||||
|
||||
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
|
||||
try:
|
||||
conn.create_distribution_with_tags(
|
||||
DistributionConfigWithTags={
|
||||
'DistributionConfig': config,
|
||||
'Tags': tags,
|
||||
},
|
||||
)
|
||||
_cache_id(
|
||||
'cloudfront',
|
||||
sub_resource=name,
|
||||
invalidate=True,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile,
|
||||
)
|
||||
except botocore.exceptions.ClientError as err:
|
||||
return {'error': __utils__['boto3.get_error'](err)}
|
||||
|
||||
return {'result': True}
|
||||
|
||||
|
||||
def update_distribution(
|
||||
name,
|
||||
config,
|
||||
tags=None,
|
||||
region=None,
|
||||
key=None,
|
||||
keyid=None,
|
||||
profile=None,
|
||||
):
|
||||
'''
|
||||
Update the config (and optionally tags) for the CloudFront distribution with the given name.
|
||||
|
||||
name
|
||||
Name of the CloudFront distribution
|
||||
|
||||
config
|
||||
Configuration for the distribution
|
||||
|
||||
tags
|
||||
Tags to associate with the distribution
|
||||
|
||||
region
|
||||
Region to connect to
|
||||
|
||||
key
|
||||
Secret key to use
|
||||
|
||||
keyid
|
||||
Access key to use
|
||||
|
||||
profile
|
||||
A dict with region, key, and keyid,
|
||||
or a pillar key (string) that contains such a dict.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion boto_cloudfront.update_distribution name=mydistribution profile=awsprofile \
|
||||
config='{"Comment":"partial configuration","Enabled":true}'
|
||||
'''
|
||||
distribution_ret = get_distribution(
|
||||
name,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile
|
||||
)
|
||||
if 'error' in distribution_result:
|
||||
return distribution_result
|
||||
dist_with_tags = distribution_result['result']
|
||||
|
||||
current_distribution = dist_with_tags['distribution']
|
||||
current_config = current_distribution['DistributionConfig']
|
||||
current_tags = dist_with_tags['tags']
|
||||
etag = dist_with_tags['etag']
|
||||
|
||||
config_diff = __utils__['dictdiffer.deep_diff'](current_config, config)
|
||||
if tags:
|
||||
tags_diff = __utils__['dictdiffer.deep_diff'](current_tags, tags)
|
||||
|
||||
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
|
||||
try:
|
||||
if 'old' in config_diff or 'new' in config_diff:
|
||||
conn.update_distribution(
|
||||
DistributionConfig=config,
|
||||
Id=current_distribution['Id'],
|
||||
IfMatch=etag,
|
||||
)
|
||||
if tags:
|
||||
arn = current_distribution['ARN']
|
||||
if 'new' in tags_diff:
|
||||
tags_to_add = {
|
||||
'Items': [
|
||||
{'Key': k, 'Value': v}
|
||||
for k, v in six.iteritems(tags_diff['new'])
|
||||
],
|
||||
}
|
||||
conn.tag_resource(
|
||||
Resource=arn,
|
||||
Tags=tags_to_add,
|
||||
)
|
||||
if 'old' in tags_diff:
|
||||
tags_to_remove = {
|
||||
'Items': list(tags_diff['old'].keys()),
|
||||
}
|
||||
conn.untag_resource(
|
||||
Resource=arn,
|
||||
TagKeys=tags_to_remove,
|
||||
)
|
||||
except botocore.exceptions.ClientError as err:
|
||||
return {'error': __utils__['boto3.get_error'](err)}
|
||||
finally:
|
||||
_cache_id(
|
||||
'cloudfront',
|
||||
sub_resource=name,
|
||||
invalidate=True,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile,
|
||||
)
|
||||
|
||||
return {'result': True}
|
229
salt/states/boto_cloudfront.py
Normal file
229
salt/states/boto_cloudfront.py
Normal file
|
@ -0,0 +1,229 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Manage CloudFront distributions
|
||||
|
||||
.. versionadded:: Oxygen
|
||||
|
||||
Create, update and destroy CloudFront distributions.
|
||||
|
||||
This module accepts explicit AWS 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
|
||||
<https://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
|
||||
|
||||
cloudfront.keyid: GKTADJGHEIQSXMKKRBJ08H
|
||||
cloudfront.key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
|
||||
|
||||
It's also possible to specify ``key``, ``keyid``, and ``region`` via a profile,
|
||||
either passed in as a dict, or a string to pull from pillars or minion config:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
myprofile:
|
||||
keyid: GKTADJGHEIQSXMKKRBJ08H
|
||||
key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
|
||||
region: us-east-1
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
aws:
|
||||
region:
|
||||
us-east-1:
|
||||
profile:
|
||||
keyid: GKTADJGHEIQSXMKKRBJ08H
|
||||
key: askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs
|
||||
region: us-east-1
|
||||
|
||||
:depends: boto3
|
||||
'''
|
||||
|
||||
# Import Python Libs
|
||||
from __future__ import absolute_import
|
||||
import difflib
|
||||
import logging
|
||||
|
||||
import yaml
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only load if boto is available.
|
||||
'''
|
||||
if 'boto_cloudfront.get_distribution' not in __salt__:
|
||||
msg = 'The boto_cloudfront state module could not be loaded: {}.'
|
||||
return (False, msg.format('boto_cloudfront exec module unavailable.'))
|
||||
return 'boto_cloudfront'
|
||||
|
||||
|
||||
def present(
|
||||
name,
|
||||
config,
|
||||
tags,
|
||||
region=None,
|
||||
key=None,
|
||||
keyid=None,
|
||||
profile=None,
|
||||
):
|
||||
'''
|
||||
Ensure the CloudFront distribution is present.
|
||||
|
||||
name (string)
|
||||
Name of the CloudFront distribution
|
||||
|
||||
config (dict)
|
||||
Configuration for the distribution
|
||||
|
||||
tags (dict)
|
||||
Tags to associate with the distribution
|
||||
|
||||
region (string)
|
||||
Region to connect to
|
||||
|
||||
key (string)
|
||||
Secret key to use
|
||||
|
||||
keyid (string)
|
||||
Access key to use
|
||||
|
||||
profile (dict or string)
|
||||
A dict with region, key, and keyid,
|
||||
or a pillar key (string) that contains such a dict.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Manage my_distribution CloudFront distribution:
|
||||
boto_cloudfront.present:
|
||||
- name: my_distribution
|
||||
- config:
|
||||
Comment: 'partial config shown, most parameters elided'
|
||||
Enabled: True
|
||||
- tags:
|
||||
testing_key: testing_value
|
||||
'''
|
||||
ret = {
|
||||
'name': name,
|
||||
'comment': '',
|
||||
'changes': {},
|
||||
}
|
||||
|
||||
res = __salt__['boto_cloudfront.get_distribution'](
|
||||
name,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile,
|
||||
)
|
||||
if 'error' in res:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Error checking distribution {0}: {1}'.format(
|
||||
name,
|
||||
res['error'],
|
||||
)
|
||||
return ret
|
||||
|
||||
old = res['result']
|
||||
if old is None:
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'Distribution {0} set for creation.'.format(name)
|
||||
ret['pchanges'] = {'old': None, 'new': name}
|
||||
return ret
|
||||
|
||||
res = __salt__['boto_cloudfront.create_distribution'](
|
||||
name,
|
||||
config,
|
||||
tags,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile,
|
||||
)
|
||||
if 'error' in res:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Error creating distribution {0}: {1}'.format(
|
||||
name,
|
||||
res['error'],
|
||||
)
|
||||
return ret
|
||||
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Created distribution {0}.'.format(name)
|
||||
ret['changes'] = {'old': None, 'new': name}
|
||||
return ret
|
||||
else:
|
||||
full_config_old = {
|
||||
'config': old['distribution']['DistributionConfig'],
|
||||
'tags': old['tags'],
|
||||
}
|
||||
full_config_new = {
|
||||
'config': config,
|
||||
'tags': tags,
|
||||
}
|
||||
diffed_config = __utils__['dictdiffer.deep_diff'](
|
||||
full_config_old,
|
||||
full_config_new,
|
||||
)
|
||||
|
||||
def _yaml_safe_dump(attrs):
|
||||
'''Safely dump YAML using a readable flow style'''
|
||||
dumper_name = 'IndentedSafeOrderedDumper'
|
||||
dumper = __utils__['yamldumper.get_dumper'](dumper_name)
|
||||
return yaml.dump(
|
||||
attrs,
|
||||
default_flow_style=False,
|
||||
Dumper=dumper,
|
||||
)
|
||||
changes_diff = ''.join(difflib.unified_diff(
|
||||
_yaml_safe_dump(full_config_old).splitlines(True),
|
||||
_yaml_safe_dump(full_config_new).splitlines(True),
|
||||
))
|
||||
|
||||
any_changes = bool('old' in diffed_config or 'new' in diffed_config)
|
||||
if not any_changes:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Distribution {0} has correct config.'.format(
|
||||
name,
|
||||
)
|
||||
return ret
|
||||
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = '\n'.join([
|
||||
'Distribution {0} set for new config:'.format(name),
|
||||
changes_diff,
|
||||
])
|
||||
ret['pchanges'] = {'diff': changes_diff}
|
||||
return ret
|
||||
|
||||
res = __salt__['boto_cloudfront.update_distribution'](
|
||||
name,
|
||||
config,
|
||||
tags,
|
||||
region=region,
|
||||
key=key,
|
||||
keyid=keyid,
|
||||
profile=profile,
|
||||
)
|
||||
if 'error' in res:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Error updating distribution {0}: {1}'.format(
|
||||
name,
|
||||
res['error'],
|
||||
)
|
||||
return ret
|
||||
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Updated distribution {0}.'.format(name)
|
||||
ret['changes'] = {'diff': changes_diff}
|
||||
return ret
|
223
tests/unit/states/test_boto_cloudfront.py
Normal file
223
tests/unit/states/test_boto_cloudfront.py
Normal file
|
@ -0,0 +1,223 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Unit tests for the boto_cloudfront state module.
|
||||
'''
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import
|
||||
import copy
|
||||
import textwrap
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.unit import skipIf, TestCase
|
||||
from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.config
|
||||
import salt.loader
|
||||
import salt.states.boto_cloudfront as boto_cloudfront
|
||||
|
||||
|
||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||
class BotoCloudfrontTestCase(TestCase, LoaderModuleMockMixin):
|
||||
'''
|
||||
Test cases for salt.states.boto_cloudfront
|
||||
'''
|
||||
def setup_loader_modules(self):
|
||||
utils = salt.loader.utils(
|
||||
self.opts,
|
||||
whitelist=['boto3', 'dictdiffer', 'yamldumper'],
|
||||
context={},
|
||||
)
|
||||
return {
|
||||
boto_cloudfront: {
|
||||
'__utils__': utils,
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.opts = salt.config.DEFAULT_MINION_OPTS
|
||||
|
||||
cls.name = 'my_distribution'
|
||||
cls.base_ret = {'name': cls.name, 'changes': {}}
|
||||
|
||||
# Most attributes elided since there are so many required ones
|
||||
cls.config = {'Enabled': True, 'HttpVersion': 'http2'}
|
||||
cls.tags = {'test_tag1': 'value1'}
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.opts
|
||||
|
||||
del cls.name
|
||||
del cls.base_ret
|
||||
|
||||
del cls.config
|
||||
del cls.tags
|
||||
|
||||
def base_ret_with(self, extra_ret):
|
||||
new_ret = copy.deepcopy(self.base_ret)
|
||||
new_ret.update(extra_ret)
|
||||
return new_ret
|
||||
|
||||
def test_present_distribution_retrieval_error(self):
|
||||
'''
|
||||
Test for boto_cloudfront.present when we cannot get the distribution.
|
||||
'''
|
||||
mock_get = MagicMock(return_value={'error': 'get_distribution error'})
|
||||
with patch.multiple(boto_cloudfront,
|
||||
__salt__={'boto_cloudfront.get_distribution': mock_get},
|
||||
__opts__={'test': False},
|
||||
):
|
||||
comment = 'Error checking distribution {0}: get_distribution error'
|
||||
self.assertDictEqual(
|
||||
boto_cloudfront.present(self.name, self.config, self.tags),
|
||||
self.base_ret_with({
|
||||
'result': False,
|
||||
'comment': comment.format(self.name),
|
||||
}),
|
||||
)
|
||||
|
||||
def test_present_from_scratch(self):
|
||||
mock_get = MagicMock(return_value={'result': None})
|
||||
|
||||
with patch.multiple(boto_cloudfront,
|
||||
__salt__={'boto_cloudfront.get_distribution': mock_get},
|
||||
__opts__={'test': True},
|
||||
):
|
||||
comment = 'Distribution {0} set for creation.'.format(self.name)
|
||||
self.assertDictEqual(
|
||||
boto_cloudfront.present(self.name, self.config, self.tags),
|
||||
self.base_ret_with({
|
||||
'result': None,
|
||||
'comment': comment,
|
||||
'pchanges': {'old': None, 'new': self.name},
|
||||
}),
|
||||
)
|
||||
|
||||
mock_create_failure = MagicMock(return_value={'error': 'create error'})
|
||||
with patch.multiple(boto_cloudfront,
|
||||
__salt__={
|
||||
'boto_cloudfront.get_distribution': mock_get,
|
||||
'boto_cloudfront.create_distribution': mock_create_failure,
|
||||
},
|
||||
__opts__={'test': False},
|
||||
):
|
||||
comment = 'Error creating distribution {0}: create error'
|
||||
self.assertDictEqual(
|
||||
boto_cloudfront.present(self.name, self.config, self.tags),
|
||||
self.base_ret_with({
|
||||
'result': False,
|
||||
'comment': comment.format(self.name),
|
||||
}),
|
||||
)
|
||||
|
||||
mock_create_success = MagicMock(return_value={'result': True})
|
||||
with patch.multiple(boto_cloudfront,
|
||||
__salt__={
|
||||
'boto_cloudfront.get_distribution': mock_get,
|
||||
'boto_cloudfront.create_distribution': mock_create_success,
|
||||
},
|
||||
__opts__={'test': False},
|
||||
):
|
||||
comment = 'Created distribution {0}.'
|
||||
self.assertDictEqual(
|
||||
boto_cloudfront.present(self.name, self.config, self.tags),
|
||||
self.base_ret_with({
|
||||
'result': True,
|
||||
'comment': comment.format(self.name),
|
||||
'changes': {'old': None, 'new': self.name},
|
||||
}),
|
||||
)
|
||||
|
||||
def test_present_correct_state(self):
|
||||
mock_get = MagicMock(return_value={'result': {
|
||||
'distribution': {'DistributionConfig': self.config},
|
||||
'tags': self.tags,
|
||||
'etag': 'test etag',
|
||||
}})
|
||||
with patch.multiple(boto_cloudfront,
|
||||
__salt__={'boto_cloudfront.get_distribution': mock_get},
|
||||
__opts__={'test': False},
|
||||
):
|
||||
comment = 'Distribution {0} has correct config.'
|
||||
self.assertDictEqual(
|
||||
boto_cloudfront.present(self.name, self.config, self.tags),
|
||||
self.base_ret_with({
|
||||
'result': True,
|
||||
'comment': comment.format(self.name),
|
||||
}),
|
||||
)
|
||||
|
||||
def test_present_update_config_and_tags(self):
|
||||
mock_get = MagicMock(return_value={'result': {
|
||||
'distribution': {'DistributionConfig': {
|
||||
'Enabled': False,
|
||||
'Comment': 'to be removed',
|
||||
}},
|
||||
'tags': {'bad existing tag': 'also to be removed'},
|
||||
'etag': 'test etag',
|
||||
}})
|
||||
|
||||
diff = textwrap.dedent('''\
|
||||
---
|
||||
+++
|
||||
@@ -1,5 +1,5 @@
|
||||
config:
|
||||
- Comment: to be removed
|
||||
- Enabled: false
|
||||
+ Enabled: true
|
||||
+ HttpVersion: http2
|
||||
tags:
|
||||
- bad existing tag: also to be removed
|
||||
+ test_tag1: value1
|
||||
''')
|
||||
|
||||
with patch.multiple(boto_cloudfront,
|
||||
__salt__={'boto_cloudfront.get_distribution': mock_get},
|
||||
__opts__={'test': True},
|
||||
):
|
||||
header = 'Distribution {0} set for new config:'.format(self.name)
|
||||
self.assertDictEqual(
|
||||
boto_cloudfront.present(self.name, self.config, self.tags),
|
||||
self.base_ret_with({
|
||||
'result': None,
|
||||
'comment': '\n'.join([header, diff]),
|
||||
'pchanges': {'diff': diff},
|
||||
}),
|
||||
)
|
||||
|
||||
mock_update_failure = MagicMock(return_value={'error': 'update error'})
|
||||
with patch.multiple(boto_cloudfront,
|
||||
__salt__={
|
||||
'boto_cloudfront.get_distribution': mock_get,
|
||||
'boto_cloudfront.update_distribution': mock_update_failure,
|
||||
},
|
||||
__opts__={'test': False},
|
||||
):
|
||||
comment = 'Error updating distribution {0}: update error'
|
||||
self.assertDictEqual(
|
||||
boto_cloudfront.present(self.name, self.config, self.tags),
|
||||
self.base_ret_with({
|
||||
'result': False,
|
||||
'comment': comment.format(self.name),
|
||||
}),
|
||||
)
|
||||
|
||||
mock_update_success = MagicMock(return_value={'result': True})
|
||||
with patch.multiple(boto_cloudfront,
|
||||
__salt__={
|
||||
'boto_cloudfront.get_distribution': mock_get,
|
||||
'boto_cloudfront.update_distribution': mock_update_success,
|
||||
},
|
||||
__opts__={'test': False},
|
||||
):
|
||||
self.assertDictEqual(
|
||||
boto_cloudfront.present(self.name, self.config, self.tags),
|
||||
self.base_ret_with({
|
||||
'result': True,
|
||||
'comment': 'Updated distribution {0}.'.format(self.name),
|
||||
'changes': {'diff': diff},
|
||||
}),
|
||||
)
|
Loading…
Add table
Reference in a new issue