check aws inv plugin (#53435)
* Add the constructed config with legacy settings enabled to match the script * Add interesting characters in tags and security group names * add strict to config * Add a stopped instance in inventory * Create symlinks in the test * Add reservation details to mock * run script and plugin with a virtual env * call the script with ansible-inventory * Fix code coverage collection.
This commit is contained in:
parent
e9c66ffb6f
commit
6d978bc285
16 changed files with 754 additions and 0 deletions
|
@ -0,0 +1,2 @@
|
|||
shippable/posix/group2
|
||||
needs/file/contrib/inventory/ec2.py
|
5
test/integration/targets/inventory_aws_conformance/ec2.sh
Executable file
5
test/integration/targets/inventory_aws_conformance/ec2.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
# Wrapper to use the correct Python interpreter and support code coverage.
|
||||
ABS_SCRIPT=$(python -c "import os; print(os.path.abspath('../../../../contrib/inventory/ec2.py'))")
|
||||
cd "${OUTPUT_DIR}"
|
||||
python.py "${ABS_SCRIPT}" "$@"
|
64
test/integration/targets/inventory_aws_conformance/inventory_diff.py
Executable file
64
test/integration/targets/inventory_aws_conformance/inventory_diff.py
Executable file
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def check_hosts(contrib, plugin):
|
||||
contrib_hosts = sorted(contrib['_meta']['hostvars'].keys())
|
||||
plugin_hosts = sorted(plugin['_meta']['hostvars'].keys())
|
||||
assert contrib_hosts == plugin_hosts
|
||||
return contrib_hosts, plugin_hosts
|
||||
|
||||
|
||||
def check_groups(contrib, plugin):
|
||||
contrib_groups = set(contrib.keys())
|
||||
plugin_groups = set(plugin.keys())
|
||||
missing_groups = contrib_groups.difference(plugin_groups)
|
||||
if missing_groups:
|
||||
print("groups: %s are missing from the plugin" % missing_groups)
|
||||
assert not missing_groups
|
||||
return contrib_groups, plugin_groups
|
||||
|
||||
|
||||
def check_host_vars(key, value, plugin, host):
|
||||
# tags are a dict in the plugin
|
||||
if key.startswith('ec2_tag'):
|
||||
print('assert tag', key, value)
|
||||
assert 'tags' in plugin['_meta']['hostvars'][host], 'b file does not have tags in host'
|
||||
btags = plugin['_meta']['hostvars'][host]['tags']
|
||||
tagkey = key.replace('ec2_tag_', '')
|
||||
assert tagkey in btags, '%s tag not in b file host tags' % tagkey
|
||||
assert value == btags[tagkey], '%s != %s' % (value, btags[tagkey])
|
||||
else:
|
||||
print('assert var', key, value, key in plugin['_meta']['hostvars'][host], plugin['_meta']['hostvars'][host].get(key))
|
||||
assert key in plugin['_meta']['hostvars'][host], "%s not in b's %s hostvars" % (key, host)
|
||||
assert value == plugin['_meta']['hostvars'][host][key], "%s != %s" % (value, plugin['_meta']['hostvars'][host][key])
|
||||
|
||||
|
||||
def main():
|
||||
# a should be the source of truth (the script output)
|
||||
a = sys.argv[1]
|
||||
# b should be the thing to check (the plugin output)
|
||||
b = sys.argv[2]
|
||||
|
||||
with open(a, 'r') as f:
|
||||
adata = json.loads(f.read())
|
||||
with open(b, 'r') as f:
|
||||
bdata = json.loads(f.read())
|
||||
|
||||
# all hosts should be present obviously
|
||||
ahosts, bhosts = check_hosts(adata, bdata)
|
||||
|
||||
# all groups should be present obviously
|
||||
agroups, bgroups = check_groups(adata, bdata)
|
||||
|
||||
# check host vars can be reconstructed
|
||||
for ahost in ahosts:
|
||||
contrib_host_vars = adata['_meta']['hostvars'][ahost]
|
||||
for key, value in contrib_host_vars.items():
|
||||
check_host_vars(key, value, bdata, ahost)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,2 @@
|
|||
import boto.exceptions as exceptions # pylint: disable=useless-import-alias
|
||||
import boto.session as session # pylint: disable=useless-import-alias
|
|
@ -0,0 +1,45 @@
|
|||
# boto2
|
||||
|
||||
from boto.mocks.instances import BotoInstance, Reservation
|
||||
|
||||
|
||||
class Region(object):
|
||||
name = None
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
class Connection(object):
|
||||
region = None
|
||||
instances = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.reservations = [Reservation(
|
||||
owner_id='123456789012',
|
||||
instance_ids=['i-0678e70402c0b434c', 'i-16a83b42f01c082a1'],
|
||||
region=kwargs['region']
|
||||
)]
|
||||
|
||||
def get_all_instances(self, *args, **kwargs):
|
||||
return self.reservations
|
||||
|
||||
def describe_cache_clusters(self, *args, **kwargs):
|
||||
return {}
|
||||
|
||||
def get_all_tags(self, *args, **kwargs):
|
||||
tags = []
|
||||
resid = kwargs['filters']['resource-id'][0]
|
||||
for instance in self.reservations[0].instances:
|
||||
if instance.id == resid:
|
||||
tags = instance._tags[:]
|
||||
break
|
||||
return tags
|
||||
|
||||
|
||||
def connect_to_region(*args, **kwargs):
|
||||
return Connection(region=args[0])
|
||||
|
||||
|
||||
def regions():
|
||||
return [Region('us-east-1')]
|
|
@ -0,0 +1,29 @@
|
|||
class Connection(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_all_instances(self, *args, **kwargs):
|
||||
return []
|
||||
|
||||
def describe_cache_clusters(self, *args, **kwargs):
|
||||
return {
|
||||
'DescribeCacheClustersResponse': {
|
||||
'DescribeCacheClustersResult': {
|
||||
'Marker': None,
|
||||
'CacheClusters': []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def describe_replication_groups(self, *args, **kwargs):
|
||||
return {
|
||||
'DescribeReplicationGroupsResponse': {
|
||||
'DescribeReplicationGroupsResult': {
|
||||
'ReplicationGroups': []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def connect_to_region(*args, **kwargs):
|
||||
return Connection()
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
class BotoServerError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ClientError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PartialCredentialsError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProfileNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BotoCoreError(Exception):
|
||||
pass
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
class BotoServerError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ClientError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PartialCredentialsError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProfileNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BotoCoreError(Exception):
|
||||
pass
|
|
@ -0,0 +1,345 @@
|
|||
from ansible.module_utils.common._collections_compat import MutableMapping
|
||||
|
||||
import datetime
|
||||
from dateutil.tz import tzutc
|
||||
import sys
|
||||
|
||||
try:
|
||||
from ansible.parsing.yaml.objects import AnsibleUnicode
|
||||
except ImportError:
|
||||
AnsibleUnicode = str
|
||||
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
unicode = str
|
||||
|
||||
DNSDOMAIN = "ansible.amazon.com"
|
||||
|
||||
|
||||
class Reservation(object):
|
||||
def __init__(self, owner_id, instance_ids, region):
|
||||
if len(instance_ids) > 1:
|
||||
stopped_instance = instance_ids[-1]
|
||||
self.instances = []
|
||||
for instance_id in instance_ids:
|
||||
stopped = bool(instance_id == stopped_instance)
|
||||
self.instances.append(BotoInstance(instance_id=instance_id, owner_id=owner_id, region=region, stopped=stopped))
|
||||
self.owner_id = owner_id
|
||||
|
||||
|
||||
class Tag(object):
|
||||
res_id = None
|
||||
name = None
|
||||
value = None
|
||||
|
||||
def __init__(self, res_id, name, value):
|
||||
self.res_id = res_id
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
|
||||
class SecurityGroup(object):
|
||||
name = 'sg_default'
|
||||
group_id = 'sg-00000'
|
||||
id = 'sg-00000'
|
||||
|
||||
def __init__(self, group_id, group_name):
|
||||
self.name = group_name
|
||||
self.group_id = group_id
|
||||
self.id = self.group_id
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class NetworkInterfaceBase(list):
|
||||
|
||||
def __init__(self, owner_id=None, private_ip=None, subnet_id=None, vpc_id=None):
|
||||
self.description = 'Primary network interface'
|
||||
self.mac_address = '06:32:7e:30:3a:20'
|
||||
self.owner_id = owner_id
|
||||
self.private_ip_address = private_ip
|
||||
self.status = 'in-use'
|
||||
self.subnet_id = subnet_id
|
||||
self.vpc_id = vpc_id
|
||||
|
||||
super(NetworkInterfaceBase, self).__init__([self.to_dict()])
|
||||
|
||||
def to_dict(self):
|
||||
|
||||
data = {}
|
||||
for attr in dir(self):
|
||||
if attr.startswith('__') or attr == 'boto3':
|
||||
continue
|
||||
|
||||
val = getattr(self, attr)
|
||||
|
||||
if callable(val):
|
||||
continue
|
||||
|
||||
if self.boto3:
|
||||
attr = ''.join(x.capitalize() or '_' for x in attr.split('_'))
|
||||
|
||||
data[attr] = val
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class Boto3NetworkInterface(NetworkInterfaceBase):
|
||||
|
||||
boto3 = True
|
||||
|
||||
def __init__(self, owner_id=None, public_ip=None, public_dns=None, private_ip=None, security_groups=None, subnet_id=None, vpc_id=None):
|
||||
self.association = {
|
||||
'IpOwnerId': 'amazon',
|
||||
'PublicDnsName': public_dns,
|
||||
'PublicIp': public_ip
|
||||
}
|
||||
self.attachment = {
|
||||
'AttachTime': datetime.datetime(2019, 2, 27, 19, 41, 49, tzinfo=tzutc()),
|
||||
'AttachmentId': 'eni-attach-008fda539bfd1877d',
|
||||
'DeleteOnTermination': True,
|
||||
'DeviceIndex': 0,
|
||||
'Status': 'attached'
|
||||
}
|
||||
self.groups = security_groups
|
||||
self.ipv6_addresses = [{'Ipv6Address': '2600:1f18:1af:f6a1:2c8d:7cf:3d14:1224'}]
|
||||
self.network_interface_id = 'eni-00abc58b929197984'
|
||||
self.private_ip_addresses = [{
|
||||
'Association': {
|
||||
'IpOwnerId': 'amazon',
|
||||
'PublicDnsName': public_dns,
|
||||
'PublicIp': public_ip
|
||||
},
|
||||
'Primary': True,
|
||||
'PrivateIpAddress': private_ip
|
||||
}]
|
||||
self.source_dest_check = True
|
||||
|
||||
super(Boto3NetworkInterface, self).__init__(
|
||||
owner_id=owner_id,
|
||||
private_ip=private_ip,
|
||||
subnet_id=subnet_id,
|
||||
vpc_id=vpc_id
|
||||
)
|
||||
|
||||
|
||||
class BotoNetworkInterface(NetworkInterfaceBase):
|
||||
|
||||
boto3 = False
|
||||
|
||||
def __init__(self, owner_id=None, public_ip=None, public_dns=None, private_ip=None, subnet_id=None, vpc_id=None):
|
||||
self.tags = {}
|
||||
self.id = 'eni-00abc58b929197984'
|
||||
self.availability_zone = None
|
||||
self.requester_managed = False
|
||||
self.publicIp = public_ip
|
||||
self.publicDnsName = public_dns
|
||||
self.ipOwnerId = 'amazon'
|
||||
self.association = '\n '
|
||||
self.item = '\n '
|
||||
|
||||
super(BotoNetworkInterface, self).__init__(
|
||||
owner_id=owner_id,
|
||||
private_ip=private_ip,
|
||||
subnet_id=subnet_id,
|
||||
vpc_id=vpc_id
|
||||
)
|
||||
|
||||
|
||||
class Volume(object):
|
||||
def __init__(self, volume_id):
|
||||
self.volume_id = volume_id
|
||||
|
||||
|
||||
class BlockDeviceMapping(MutableMapping):
|
||||
devices = {}
|
||||
|
||||
def __init__(self, devices):
|
||||
for device, volume_id in devices.items():
|
||||
self.devices[device] = Volume(volume_id)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.devices[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.devices[key] = Volume(value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.devices[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.devices)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.devices)
|
||||
|
||||
|
||||
class InstanceBase(object):
|
||||
def __init__(self, stopped=False):
|
||||
# set common ignored attribute to make sure instances have identical tags and security groups
|
||||
self._ignore_security_groups = {
|
||||
'sg-0e1d2bd02b45b712e': 'sgname-with-hyphens',
|
||||
'sg-ae5c262eb5c4d712e': 'name@with?invalid!chars'
|
||||
}
|
||||
self._ignore_tags = {
|
||||
'tag-with-hyphens': 'value:with:colons',
|
||||
b'\xec\xaa\xb4'.decode('utf'): 'value1with@invalid:characters',
|
||||
'tag;me': 'value@noplez',
|
||||
'tag!notit': 'value<=ohwhy?'
|
||||
}
|
||||
if not stopped:
|
||||
self._ignore_state = {'Code': 16, 'Name': 'running'}
|
||||
else:
|
||||
self._ignore_state = {'Code': 80, 'Name': 'stopped'}
|
||||
|
||||
# common attributes
|
||||
self.ami_launch_index = '0'
|
||||
self.architecture = 'x86_64'
|
||||
self.client_token = ''
|
||||
self.ebs_optimized = False
|
||||
self.hypervisor = 'xen'
|
||||
self.image_id = 'ami-0ac019f4fcb7cb7e6'
|
||||
self.instance_type = 't2.micro'
|
||||
self.key_name = 'k!y:2/-n@me'
|
||||
self.private_dns_name = 'ip-20-0-0-20.ec2.internal'
|
||||
self.private_ip_address = '20.0.0.20'
|
||||
self.product_codes = []
|
||||
if not stopped:
|
||||
self.public_dns_name = 'ec2-12-3-456-78.compute-1.amazonaws.com'
|
||||
else:
|
||||
self.public_dns_name = ''
|
||||
self.root_device_name = '/dev/sda1'
|
||||
self.root_device_type = 'ebs'
|
||||
self.subnet_id = 'subnet-09564ba2121bca7bd'
|
||||
self.virtualization_type = 'hvm'
|
||||
self.vpc_id = 'vpc-01ae527fabc81dd04'
|
||||
|
||||
def to_dict(self):
|
||||
|
||||
data = {}
|
||||
for attr in dir(self):
|
||||
if attr.startswith(('__', '_ignore')) or attr in ['to_dict', 'boto3']:
|
||||
continue
|
||||
|
||||
val = getattr(self, attr)
|
||||
|
||||
if self.boto3:
|
||||
attr = ''.join(x.capitalize() or '_' for x in attr.split('_'))
|
||||
|
||||
data[attr] = val
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class BotoInstance(InstanceBase):
|
||||
|
||||
boto3 = False
|
||||
|
||||
def __init__(self, instance_id=None, owner_id=None, region=None, stopped=False):
|
||||
super(BotoInstance, self).__init__(stopped=stopped)
|
||||
|
||||
self._in_monitoring_element = False
|
||||
self._tags = [Tag(instance_id, k, v) for k, v in self._ignore_tags.items()]
|
||||
self.block_device_mapping = BlockDeviceMapping({'/dev/sda1': 'vol-044a646a9292c82af'})
|
||||
self.dns_name = 'ec2-12-3-456-78.compute-1.amazonaws.com'
|
||||
self.eventsSet = None
|
||||
self.group_name = None
|
||||
self.groups = [SecurityGroup(k, v) for k, v in self._ignore_security_groups.items()]
|
||||
self.id = instance_id
|
||||
self.instance_profile = {
|
||||
'arn': 'arn:aws:iam::{0}:instance-profile/developer'.format(owner_id),
|
||||
'id': 'ABCDE2GHIJKLMN8PQRSTU'
|
||||
}
|
||||
if not stopped:
|
||||
self.ip_address = '12.3.456.7'
|
||||
else:
|
||||
self.ip_address = '' # variable is returned as empty by boto if the instance is stopped
|
||||
self.item = '\n '
|
||||
self.kernel = None
|
||||
self.launch_time = '2019-02-27T19:41:49.000Z'
|
||||
self.monitored = False
|
||||
self.monitoring = '\n '
|
||||
self.monitoring_state = 'disabled'
|
||||
self.persistent = False
|
||||
self.placement = region + 'e'
|
||||
self.platform = None
|
||||
self.ramdisk = None
|
||||
self.reason = ''
|
||||
self.region = region
|
||||
self.requester_id = None
|
||||
self.sourceDestCheck = 'true'
|
||||
self.spot_instance_request_id = None
|
||||
self.state = self._ignore_state['Name']
|
||||
self.state_code = self._ignore_state['Code']
|
||||
if not stopped:
|
||||
self.state_reason = None
|
||||
else:
|
||||
self.state_reason = {
|
||||
'code': 'Client.UserInitiatedShutdown',
|
||||
'message': 'Client.UserInitiatedShutdown: User initiated shutdown'
|
||||
}
|
||||
self.tags = dict(self._ignore_tags)
|
||||
|
||||
self.interfaces = BotoNetworkInterface(
|
||||
owner_id=owner_id,
|
||||
public_ip=self.ip_address,
|
||||
public_dns=self.public_dns_name,
|
||||
private_ip=self.private_ip_address,
|
||||
subnet_id=self.subnet_id,
|
||||
vpc_id=self.vpc_id,
|
||||
)
|
||||
|
||||
|
||||
class Boto3Instance(InstanceBase):
|
||||
|
||||
boto3 = True
|
||||
|
||||
def __init__(self, instance_id=None, owner_id=None, region=None, stopped=False):
|
||||
super(Boto3Instance, self).__init__(stopped=stopped)
|
||||
|
||||
self.block_device_mappings = [{
|
||||
'DeviceName': '/dev/sda1',
|
||||
'Ebs': {
|
||||
'AttachTime': datetime.datetime(2019, 2, 27, 19, 41, 50, tzinfo=tzutc()),
|
||||
'DeleteOnTermination': True,
|
||||
'Status': 'attached',
|
||||
'VolumeId': 'vol-044a646a9292c82af'
|
||||
}
|
||||
}]
|
||||
self.capacity_reservation_specification = {'CapacityReservationPreference': 'open'}
|
||||
self.cpu_options = {'CoreCount': 1, 'ThreadsPerCore': 1}
|
||||
self.ena_support = True
|
||||
self.hibernation_options = {'Configured': False}
|
||||
self.iam_instance_profile = {
|
||||
'Arn': 'arn:aws:iam::{0}:instance-profile/developer'.format(owner_id),
|
||||
'Id': 'ABCDE2GHIJKLMN8PQRSTU'
|
||||
}
|
||||
self.instance_id = instance_id
|
||||
self.launch_time = datetime.datetime(2019, 2, 27, 19, 41, 49, tzinfo=tzutc())
|
||||
self.monitoring = {'State': 'disabled'}
|
||||
self.placement = {'AvailabilityZone': region + 'e', 'GroupName': '', 'Tenancy': 'default'}
|
||||
if not stopped:
|
||||
self.public_ip_address = '12.3.456.7' # variable is not returned by boto3 if the instance is stopped
|
||||
self.security_groups = [{'GroupId': key, 'GroupName': value} for key, value in self._ignore_security_groups.items()]
|
||||
self.source_dest_check = True
|
||||
self.state = dict(self._ignore_state)
|
||||
if not stopped:
|
||||
self.state_transition_reason = ''
|
||||
else:
|
||||
self.state_transition_reason = 'User initiated (2019-02-11 12:49:13 GMT)'
|
||||
self.state_reason = { # this variable is only returned by AWS if the instance is stopped
|
||||
'Code': 'Client.UserInitiatedShutdown',
|
||||
'Message': 'Client.UserInitiatedShutdown: User initiated shutdown'
|
||||
}
|
||||
self.tags = [{'Key': k, 'Value': v} for k, v in self._ignore_tags.items()]
|
||||
|
||||
self.network_interfaces = Boto3NetworkInterface(
|
||||
owner_id=owner_id,
|
||||
public_ip=getattr(self, 'public_ip_address', ''),
|
||||
public_dns=self.public_dns_name,
|
||||
private_ip=self.private_ip_address,
|
||||
security_groups=self.security_groups,
|
||||
subnet_id=self.subnet_id,
|
||||
vpc_id=self.vpc_id
|
||||
)
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env python
|
||||
# boto3
|
||||
|
||||
from boto.mocks.instances import Boto3Instance
|
||||
|
||||
|
||||
class Paginator(object):
|
||||
def __init__(self, datalist):
|
||||
self.datalist = datalist
|
||||
|
||||
def paginate(self, *args, **kwargs):
|
||||
'''
|
||||
{'Filters': [{'Name': 'instance-state-name',
|
||||
'Values': ['running', 'pending', 'stopping', 'stopped']}]}
|
||||
'''
|
||||
filters = kwargs.get('Filters', [])
|
||||
if not (filters or any([True for f in filters if f['Name'] == 'instance-state-name'])):
|
||||
self.instance_states = ['running', 'pending', 'stopping', 'stopped']
|
||||
else:
|
||||
self.instance_states = [f['Values'] for f in filters if f['Name'] == 'instance-state-name'][0]
|
||||
return self
|
||||
|
||||
def build_full_result(self):
|
||||
filtered_states = set([x.state['Name'] for x in self.datalist]).difference(set(self.instance_states))
|
||||
return {'Reservations': [{
|
||||
'Instances': [x.to_dict() for x in self.datalist if x.state['Name'] not in filtered_states],
|
||||
'OwnerId': '123456789012',
|
||||
'RequesterId': 'AIDAIS3MMFPO53D2T3WWE',
|
||||
'ReservationId': 'r-07889670a282de964'
|
||||
}]}
|
||||
|
||||
|
||||
class Client(object):
|
||||
cloud = None
|
||||
region = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.cloud = args[0]
|
||||
self.region = args[1]
|
||||
|
||||
def get_paginator(self, method):
|
||||
if method == 'describe_instances':
|
||||
return Paginator(
|
||||
[Boto3Instance(instance_id='i-0678e70402c0b434c', owner_id='123456789012', region=self.region),
|
||||
Boto3Instance(instance_id='i-16a83b42f01c082a1', owner_id='123456789012', region=self.region, stopped=True)]
|
||||
)
|
||||
|
||||
|
||||
class Session(object):
|
||||
profile_name = None
|
||||
region = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
if hasattr(self, k):
|
||||
setattr(self, k, v)
|
||||
|
||||
def client(self, *args, **kwargs):
|
||||
return Client(*args, **kwargs)
|
||||
|
||||
def get_config_variables(self, key):
|
||||
if hasattr(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
def get_available_regions(self, *args):
|
||||
return ['us-east-1']
|
||||
|
||||
def get_credentials(self, *args, **kwargs):
|
||||
raise Exception('not implemented')
|
||||
|
||||
|
||||
def get_session(*args, **kwargs):
|
||||
return Session(*args, **kwargs)
|
151
test/integration/targets/inventory_aws_conformance/runme.sh
Executable file
151
test/integration/targets/inventory_aws_conformance/runme.sh
Executable file
|
@ -0,0 +1,151 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
virtualenv --system-site-packages --python "${ANSIBLE_TEST_PYTHON_INTERPRETER:-python}" "${OUTPUT_DIR}/aws-ec2-inventory"
|
||||
source "${OUTPUT_DIR}/aws-ec2-inventory/bin/activate"
|
||||
pip install "python-dateutil>=2.1,<2.7.0" jmespath "Jinja2>=2.10" PyYaml cryptography paramiko
|
||||
|
||||
# create boto3 symlinks
|
||||
ln -s "$(pwd)/lib/boto" "$(pwd)/lib/boto3"
|
||||
ln -s "$(pwd)/lib/boto" "$(pwd)/lib/botocore"
|
||||
|
||||
# override boto's import path(s)
|
||||
export PYTHONPATH
|
||||
PYTHONPATH="$(pwd)/lib:$PYTHONPATH"
|
||||
|
||||
#################################################
|
||||
# RUN THE SCRIPT
|
||||
#################################################
|
||||
|
||||
# run the script first
|
||||
cat << EOF > "$OUTPUT_DIR/ec2.ini"
|
||||
[ec2]
|
||||
regions = us-east-1
|
||||
cache_path = $(pwd)/.cache
|
||||
cache_max_age = 0
|
||||
group_by_tag_none = False
|
||||
|
||||
[credentials]
|
||||
aws_access_key_id = FOO
|
||||
aws_secret_acccess_key = BAR
|
||||
EOF
|
||||
|
||||
ANSIBLE_JINJA2_NATIVE=1 ansible-inventory -vvvv -i ./ec2.sh --list --output="$OUTPUT_DIR/script.out"
|
||||
RC=$?
|
||||
if [[ $RC != 0 ]]; then
|
||||
exit $RC
|
||||
fi
|
||||
|
||||
#################################################
|
||||
# RUN THE PLUGIN
|
||||
#################################################
|
||||
|
||||
# run the plugin second
|
||||
export ANSIBLE_INVENTORY_ENABLED=aws_ec2
|
||||
export ANSIBLE_INVENTORY=test.aws_ec2.yml
|
||||
export AWS_ACCESS_KEY_ID=FOO
|
||||
export AWS_SECRET_ACCESS_KEY=BAR
|
||||
export ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS=never
|
||||
|
||||
cat << EOF > "$OUTPUT_DIR/test.aws_ec2.yml"
|
||||
plugin: aws_ec2
|
||||
cache: False
|
||||
use_contrib_script_compatible_sanitization: True
|
||||
strict: True
|
||||
regions:
|
||||
- us-east-1
|
||||
hostnames:
|
||||
- network-interface.addresses.association.public-ip
|
||||
- dns-name
|
||||
filters:
|
||||
instance-state-name: running
|
||||
compose:
|
||||
# vars that don't exist anymore in any meaningful way
|
||||
ec2_item: undefined | default("")
|
||||
ec2_monitoring: undefined | default("")
|
||||
ec2_previous_state: undefined | default("")
|
||||
ec2_previous_state_code: undefined | default(0)
|
||||
ec2__in_monitoring_element: undefined | default(false)
|
||||
# the following three will be accessible again after #53645
|
||||
ec2_requester_id: undefined | default("")
|
||||
ec2_eventsSet: undefined | default("")
|
||||
ec2_persistent: undefined | default(false)
|
||||
|
||||
# vars that change
|
||||
ansible_host: public_ip_address
|
||||
ec2_block_devices: dict(block_device_mappings | map(attribute='device_name') | map('basename') | list | zip(block_device_mappings | map(attribute='ebs.volume_id') | list))
|
||||
ec2_dns_name: public_dns_name
|
||||
ec2_group_name: placement['group_name']
|
||||
ec2_id: instance_id
|
||||
ec2_instance_profile: iam_instance_profile | default("")
|
||||
ec2_ip_address: public_ip_address
|
||||
ec2_kernel: kernel_id | default("")
|
||||
ec2_monitored: monitoring['state'] in ['enabled', 'pending']
|
||||
ec2_monitoring_state: monitoring['state']
|
||||
ec2_account_id: owner_id
|
||||
ec2_placement: placement['availability_zone']
|
||||
ec2_ramdisk: ramdisk_id | default("")
|
||||
ec2_reason: state_transition_reason
|
||||
ec2_security_group_ids: security_groups | map(attribute='group_id') | list | join(',')
|
||||
ec2_security_group_names: security_groups | map(attribute='group_name') | list | join(',')
|
||||
ec2_state: state['name']
|
||||
ec2_state_code: state['code']
|
||||
ec2_state_reason: state_reason['message'] if state_reason is defined else ""
|
||||
ec2_sourceDestCheck: source_dest_check | lower | string # butchered snake_case case not a typo.
|
||||
|
||||
# vars that just need ec2_ prefix
|
||||
ec2_ami_launch_index: ami_launch_index | string
|
||||
ec2_architecture: architecture
|
||||
ec2_client_token: client_token
|
||||
ec2_ebs_optimized: ebs_optimized
|
||||
ec2_hypervisor: hypervisor
|
||||
ec2_image_id: image_id
|
||||
ec2_instance_type: instance_type
|
||||
ec2_key_name: key_name
|
||||
ec2_launch_time: 'launch_time | regex_replace(" ", "T") | regex_replace("(\+)(\d\d):(\d)(\d)$", ".\g<2>\g<3>Z")'
|
||||
ec2_platform: platform | default("")
|
||||
ec2_private_dns_name: private_dns_name
|
||||
ec2_private_ip_address: private_ip_address
|
||||
ec2_public_dns_name: public_dns_name
|
||||
ec2_region: placement['region']
|
||||
ec2_root_device_name: root_device_name
|
||||
ec2_root_device_type: root_device_type
|
||||
ec2_spot_instance_request_id: spot_instance_request_id | default("")
|
||||
ec2_subnet_id: subnet_id
|
||||
ec2_virtualization_type: virtualization_type
|
||||
ec2_vpc_id: vpc_id
|
||||
tags: dict(tags.keys() | map('regex_replace', '[^A-Za-z0-9\_]', '_') | list | zip(tags.values() | list))
|
||||
|
||||
keyed_groups:
|
||||
- key: '"ec2"'
|
||||
separator: ""
|
||||
- key: 'instance_id'
|
||||
separator: ""
|
||||
- key: tags
|
||||
prefix: tag
|
||||
- key: key_name | regex_replace('-', '_')
|
||||
prefix: key
|
||||
- key: placement['region']
|
||||
separator: ""
|
||||
- key: placement['availability_zone']
|
||||
separator: ""
|
||||
- key: platform | default('undefined')
|
||||
prefix: platform
|
||||
- key: vpc_id | regex_replace('-', '_')
|
||||
prefix: vpc_id
|
||||
- key: instance_type
|
||||
prefix: type
|
||||
- key: "image_id | regex_replace('-', '_')"
|
||||
separator: ""
|
||||
- key: security_groups | map(attribute='group_name') | map("regex_replace", "-", "_") | list
|
||||
prefix: security_group
|
||||
EOF
|
||||
|
||||
ANSIBLE_JINJA2_NATIVE=1 ansible-inventory -vvvv -i "$OUTPUT_DIR/test.aws_ec2.yml" --list --output="$OUTPUT_DIR/plugin.out"
|
||||
|
||||
#################################################
|
||||
# DIFF THE RESULTS
|
||||
#################################################
|
||||
|
||||
./inventory_diff.py "$OUTPUT_DIR/script.out" "$OUTPUT_DIR/plugin.out"
|
Loading…
Reference in a new issue