This commit is contained in:
Vic Iglesias 2015-12-21 12:20:50 -06:00
commit 6cbcb8f8ae
17 changed files with 732 additions and 518 deletions

View file

@ -812,21 +812,25 @@ class ElbManager(object):
if self.stickiness['type'] == 'loadbalancer':
policy = []
policy_type = 'LBCookieStickinessPolicyType'
if self.stickiness['enabled'] == True:
if self.module.boolean(self.stickiness['enabled']) == True:
if 'expiration' not in self.stickiness:
self.module.fail_json(msg='expiration must be set when type is loadbalancer')
expiration = self.stickiness['expiration'] if self.stickiness['expiration'] is not 0 else None
policy_attrs = {
'type': policy_type,
'attr': 'lb_cookie_stickiness_policies',
'method': 'create_lb_cookie_stickiness_policy',
'dict_key': 'cookie_expiration_period',
'param_value': self.stickiness['expiration']
'param_value': expiration
}
policy.append(self._policy_name(policy_attrs['type']))
self._set_stickiness_policy(elb_info, listeners_dict, policy, **policy_attrs)
elif self.stickiness['enabled'] == False:
elif self.module.boolean(self.stickiness['enabled']) == False:
if len(elb_info.policies.lb_cookie_stickiness_policies):
if elb_info.policies.lb_cookie_stickiness_policies[0].policy_name == self._policy_name(policy_type):
self.changed = True
@ -838,7 +842,7 @@ class ElbManager(object):
elif self.stickiness['type'] == 'application':
policy = []
policy_type = 'AppCookieStickinessPolicyType'
if self.stickiness['enabled'] == True:
if self.module.boolean(self.stickiness['enabled']) == True:
if 'cookie' not in self.stickiness:
self.module.fail_json(msg='cookie must be set when type is application')
@ -852,7 +856,7 @@ class ElbManager(object):
}
policy.append(self._policy_name(policy_attrs['type']))
self._set_stickiness_policy(elb_info, listeners_dict, policy, **policy_attrs)
elif self.stickiness['enabled'] == False:
elif self.module.boolean(self.stickiness['enabled']) == False:
if len(elb_info.policies.app_cookie_stickiness_policies):
if elb_info.policies.app_cookie_stickiness_policies[0].policy_name == self._policy_name(policy_type):
self.changed = True

View file

@ -379,7 +379,7 @@ def main():
volume_size = dict(),
volume_type = dict(choices=['standard', 'gp2', 'io1'], default='standard'),
iops = dict(),
encrypted = dict(),
encrypted = dict(type='bool', default=False),
device_name = dict(),
zone = dict(aliases=['availability_zone', 'aws_zone', 'ec2_zone']),
snapshot = dict(),

View file

@ -49,19 +49,15 @@ options:
- 'A dictionary array of subnets to add of the form: { cidr: ..., az: ... , resource_tags: ... }. Where az is the desired availability zone of the subnet, but it is not required. Tags (i.e.: resource_tags) is also optional and use dictionary form: { "Environment":"Dev", "Tier":"Web", ...}. All VPC subnets not in this list will be removed. As of 1.8, if the subnets parameter is not specified, no existing subnets will be modified.'
required: false
default: null
aliases: []
vpc_id:
description:
- A VPC id to terminate when state=absent
required: false
default: null
aliases: []
resource_tags:
description:
- 'A dictionary array of resource tags of the form: { tag1: value1, tag2: value2 }. Tags in this list are used in conjunction with CIDR block to uniquely identify a VPC in lieu of vpc_id. Therefore, if CIDR/Tag combination does not exist, a new VPC will be created. VPC tags not on this list will be ignored. Prior to 1.7, specifying a resource tag was optional.'
required: true
default: null
aliases: []
version_added: "1.6"
internet_gateway:
description:
@ -69,31 +65,26 @@ options:
required: false
default: "no"
choices: [ "yes", "no" ]
aliases: []
route_tables:
description:
- 'A dictionary array of route tables to add of the form: { subnets: [172.22.2.0/24, 172.22.3.0/24,], routes: [{ dest: 0.0.0.0/0, gw: igw},], resource_tags: ... }. Where the subnets list is those subnets the route table should be associated with, and the routes list is a list of routes to be in the table. The special keyword for the gw of igw specifies that you should the route should go through the internet gateway attached to the VPC. gw also accepts instance-ids in addition igw. resource_tags is optional and uses dictionary form: { "Name": "public", ... }. This module is currently unable to affect the "main" route table due to some limitations in boto, so you must explicitly define the associated subnets or they will be attached to the main table implicitly. As of 1.8, if the route_tables parameter is not specified, no existing routes will be modified.'
- 'A dictionary array of route tables to add of the form: { subnets: [172.22.2.0/24, 172.22.3.0/24,], routes: [{ dest: 0.0.0.0/0, gw: igw},], resource_tags: ... }. Where the subnets list is those subnets the route table should be associated with, and the routes list is a list of routes to be in the table. The special keyword for the gw of igw specifies that you should the route should go through the internet gateway attached to the VPC. gw also accepts instance-ids, interface-ids, and vpc-peering-connection-ids in addition igw. resource_tags is optional and uses dictionary form: { "Name": "public", ... }. This module is currently unable to affect the "main" route table due to some limitations in boto, so you must explicitly define the associated subnets or they will be attached to the main table implicitly. As of 1.8, if the route_tables parameter is not specified, no existing routes will be modified.'
required: false
default: null
aliases: []
wait:
description:
- wait for the VPC to be in state 'available' before returning
required: false
default: "no"
choices: [ "yes", "no" ]
aliases: []
wait_timeout:
description:
- how long before wait gives up, in seconds
default: 300
aliases: []
state:
description:
- Create or terminate the VPC
required: true
default: present
aliases: []
choices: [ "present", "absent" ]
author: "Carson Gee (@carsongee)"
extends_documentation_fragment:
- aws
@ -234,25 +225,29 @@ def routes_match(rt_list=None, rt=None, igw=None):
Returns:
True when there provided routes and remote routes are the same.
False when provided routes and remote routes are diffrent.
False when provided routes and remote routes are different.
"""
local_routes = []
remote_routes = []
for route in rt_list:
route_kwargs = {}
route_kwargs = {
'gateway_id': None,
'instance_id': None,
'interface_id': None,
'vpc_peering_connection_id': None,
'state': 'active'
}
if route['gw'] == 'igw':
route_kwargs['gateway_id'] = igw.id
route_kwargs['instance_id'] = None
route_kwargs['state'] = 'active'
elif route['gw'].startswith('i-'):
route_kwargs['instance_id'] = route['gw']
route_kwargs['gateway_id'] = None
route_kwargs['state'] = 'active'
elif route['gw'].startswith('eni-'):
route_kwargs['interface_id'] = route['gw']
elif route['gw'].startswith('pcx-'):
route_kwargs['vpc_peering_connection_id'] = route['gw']
else:
route_kwargs['gateway_id'] = route['gw']
route_kwargs['instance_id'] = None
route_kwargs['state'] = 'active'
route_kwargs['destination_cidr_block'] = route['dest']
local_routes.append(route_kwargs)
for j in rt.routes:
@ -280,7 +275,7 @@ def rtb_changed(route_tables=None, vpc_conn=None, module=None, vpc=None, igw=Non
igw : The internet gateway object for this vpc
Returns:
True when there is diffrence beween the provided routes and remote routes and if subnet assosications are diffrent.
True when there is difference between the provided routes and remote routes and if subnet associations are different.
False when both routes and subnet associations matched.
"""
@ -509,6 +504,10 @@ def create_vpc(module, vpc_conn):
route_kwargs['gateway_id'] = igw.id
elif route['gw'].startswith('i-'):
route_kwargs['instance_id'] = route['gw']
elif route['gw'].startswith('eni-'):
route_kwargs['interface_id'] = route['gw']
elif route['gw'].startswith('pcx-'):
route_kwargs['vpc_peering_connection_id'] = route['gw']
else:
route_kwargs['gateway_id'] = route['gw']
vpc_conn.create_route(new_rt.id, route['dest'], **route_kwargs)
@ -652,6 +651,7 @@ def terminate_vpc(module, vpc_conn, vpc_id=None, cidr=None):
msg='Unable to delete VPC {0}, error: {1}'.format(vpc.id, e)
)
changed = True
vpc_dict['state'] = "terminated"
return (changed, vpc_dict, terminated_vpc_id)

View file

@ -46,6 +46,14 @@ options:
default: missing
choices: [ "missing", "always" ]
version_added: "1.9"
entrypoint:
description:
- Corresponds to ``--entrypoint`` option of ``docker run`` command and
``ENTRYPOINT`` directive of Dockerfile.
Used to match and launch containers.
default: null
required: false
version_added: "2.1"
command:
description:
- Command used to match and launch containers.
@ -95,6 +103,12 @@ options:
- 'alias. Use docker CLI-style syntax: C(redis:myredis).'
default: null
version_added: "1.5"
devices:
description:
- List of host devices to expose to container
default: null
required: false
version_added: "2.1"
log_driver:
description:
- You can specify a different logging driver for the container than for the daemon.
@ -329,6 +343,18 @@ options:
default: false
aliases: []
version_added: "2.0"
labels:
description:
- Set container labels. Requires docker >= 1.6 and docker-py >= 1.2.0.
required: false
default: null
version_added: "2.1"
stop_timeout:
description:
- How many seconds to wait for the container to stop before killing it.
required: false
default: 10
version_added: "2.0"
author:
- "Cove Schneider (@cove)"
- "Joshua Conner (@joshuaconner)"
@ -380,6 +406,8 @@ EXAMPLES = '''
# stopped and removed, and a new one will be launched in its place.
# - link this container to the existing redis container launched above with
# an alias.
# - grant the container read write permissions for the host's /dev/sda device
# through a node named /dev/xvda
# - bind TCP port 9000 within the container to port 8080 on all interfaces
# on the host.
# - bind UDP port 9001 within the container to port 8081 on the host, only
@ -394,6 +422,8 @@ EXAMPLES = '''
pull: always
links:
- "myredis:aliasedredis"
devices:
- "/dev/sda:/dev/xvda:rwm"
ports:
- "8080:9000"
- "127.0.0.1:8081:9001/udp"
@ -596,6 +626,7 @@ class DockerManager(object):
# docker-py version is a tuple of ints because we have to compare them
# server APIVersion is passed to a docker-py function that takes strings
_cap_ver_req = {
'devices': ((0, 7, 0), '1.2'),
'dns': ((0, 3, 0), '1.10'),
'volumes_from': ((0, 3, 0), '1.10'),
'restart_policy': ((0, 5, 0), '1.14'),
@ -608,6 +639,8 @@ class DockerManager(object):
'cap_add': ((0, 5, 0), '1.14'),
'cap_drop': ((0, 5, 0), '1.14'),
'read_only': ((1, 0, 0), '1.17'),
'labels': ((1, 2, 0), '1.18'),
'stop_timeout': ((0, 5, 0), '1.0'),
# Clientside only
'insecure_registry': ((0, 5, 0), '0.0')
}
@ -832,11 +865,15 @@ class DockerManager(object):
}
optionals = {}
for optional_param in ('dns', 'volumes_from', 'restart_policy',
'restart_policy_retry', 'pid', 'extra_hosts', 'log_driver',
'cap_add', 'cap_drop', 'read_only', 'log_opt'):
for optional_param in ('devices', 'dns', 'volumes_from',
'restart_policy', 'restart_policy_retry', 'pid', 'extra_hosts',
'log_driver', 'cap_add', 'cap_drop', 'read_only', 'log_opt'):
optionals[optional_param] = self.module.params.get(optional_param)
if optionals['devices'] is not None:
self.ensure_capability('devices')
params['devices'] = optionals['devices']
if optionals['dns'] is not None:
self.ensure_capability('dns')
params['dns'] = optionals['dns']
@ -1054,6 +1091,21 @@ class DockerManager(object):
differing.append(container)
continue
# ENTRYPOINT
expected_entrypoint = self.module.params.get('entrypoint')
if expected_entrypoint:
expected_entrypoint = shlex.split(expected_entrypoint)
actual_entrypoint = container["Config"]["Entrypoint"]
if actual_entrypoint != expected_entrypoint:
self.reload_reasons.append(
'entrypoint ({0} => {1})'
.format(actual_entrypoint, expected_entrypoint)
)
differing.append(container)
continue
# COMMAND
expected_command = self.module.params.get('command')
@ -1099,7 +1151,7 @@ class DockerManager(object):
self.module.fail_json(msg=str(e))
#For v1.19 API and above use HostConfig, otherwise use Config
if api_version >= 1.19:
if docker.utils.compare_version('1.19', api_version) >= 0:
actual_mem = container['HostConfig']['Memory']
else:
actual_mem = container['Config']['Memory']
@ -1134,6 +1186,22 @@ class DockerManager(object):
differing.append(container)
continue
# LABELS
expected_labels = {}
for name, value in self.module.params.get('labels').iteritems():
expected_labels[name] = str(value)
actual_labels = {}
for container_label in container['Config']['Labels'] or []:
name, value = container_label.split('=', 1)
actual_labels[name] = value
if actual_labels != expected_labels:
self.reload_reasons.append('labels {0} => {1}'.format(actual_labels, expected_labels))
differing.append(container)
continue
# HOSTNAME
expected_hostname = self.module.params.get('hostname')
@ -1276,6 +1344,24 @@ class DockerManager(object):
differing.append(container)
continue
# DEVICES
expected_devices = set()
for device in (self.module.params.get('devices') or []):
if len(device.split(':')) == 2:
expected_devices.add(device + ":rwm")
else:
expected_devices.add(device)
actual_devices = set()
for device in (container['HostConfig']['Devices'] or []):
actual_devices.add("{PathOnHost}:{PathInContainer}:{CgroupPermissions}".format(**device))
if actual_devices != expected_devices:
self.reload_reasons.append('devices ({0} => {1})'.format(actual_devices, expected_devices))
differing.append(container)
continue
# DNS
expected_dns = set(self.module.params.get('dns') or [])
@ -1321,9 +1407,12 @@ class DockerManager(object):
Return any matching containers that are already present.
"""
entrypoint = self.module.params.get('entrypoint')
if entrypoint is not None:
entrypoint = shlex.split(entrypoint)
command = self.module.params.get('command')
if command:
command = command.strip()
if command is not None:
command = shlex.split(command)
name = self.module.params.get('name')
if name and not name.startswith('/'):
name = '/' + name
@ -1350,15 +1439,16 @@ class DockerManager(object):
details = _docker_id_quirk(details)
running_image = normalize_image(details['Config']['Image'])
running_command = container['Command'].strip()
image_matches = running_image in repo_tags
# if a container has an entrypoint, `command` will actually equal
# '{} {}'.format(entrypoint, command)
command_matches = (not command or running_command.endswith(command))
command_matches = command == details['Config']['Cmd']
entrypoint_matches = (
entrypoint == details['Config']['Entrypoint']
)
matches = image_matches and command_matches
matches = (image_matches and command_matches and
entrypoint_matches)
if matches:
if not details:
@ -1423,10 +1513,12 @@ class DockerManager(object):
api_version = self.client.version()['ApiVersion']
params = {'image': self.module.params.get('image'),
'entrypoint': self.module.params.get('entrypoint'),
'command': self.module.params.get('command'),
'ports': self.exposed_ports,
'volumes': self.volumes,
'environment': self.env,
'labels': self.module.params.get('labels'),
'hostname': self.module.params.get('hostname'),
'domainname': self.module.params.get('domainname'),
'detach': self.module.params.get('detach'),
@ -1440,7 +1532,7 @@ class DockerManager(object):
params['host_config'] = self.create_host_config()
#For v1.19 API and above use HostConfig, otherwise use Config
if api_version < 1.19:
if docker.utils.compare_version('1.19', api_version) < 0:
params['mem_limit'] = mem_limit
else:
params['host_config']['Memory'] = mem_limit
@ -1485,7 +1577,7 @@ class DockerManager(object):
def stop_containers(self, containers):
for i in containers:
self.client.stop(i['Id'])
self.client.stop(i['Id'], self.module.params.get('stop_timeout'))
self.increment_counter('stopped')
return [self.client.wait(i['Id']) for i in containers]
@ -1602,6 +1694,10 @@ def restarted(manager, containers, count, name):
containers.refresh()
for container in manager.get_differing_containers():
manager.stop_containers([container])
manager.remove_containers([container])
manager.restart_containers(containers.running)
started(manager, containers, count, name)
@ -1636,6 +1732,7 @@ def main():
count = dict(default=1),
image = dict(required=True),
pull = dict(required=False, default='missing', choices=['missing', 'always']),
entrypoint = dict(required=False, default=None, type='str'),
command = dict(required=False, default=None),
expose = dict(required=False, default=None, type='list'),
ports = dict(required=False, default=None, type='list'),
@ -1643,6 +1740,7 @@ def main():
volumes = dict(default=None, type='list'),
volumes_from = dict(default=None),
links = dict(default=None, type='list'),
devices = dict(default=None, type='list'),
memory_limit = dict(default=0),
memory_swap = dict(default=0),
docker_url = dict(),
@ -1682,6 +1780,8 @@ def main():
cap_add = dict(default=None, type='list'),
cap_drop = dict(default=None, type='list'),
read_only = dict(default=None, type='bool'),
labels = dict(default={}, type='dict'),
stop_timeout = dict(default=10, type='int'),
),
required_together = (
['tls_client_cert', 'tls_client_key'],

View file

@ -44,7 +44,8 @@ options:
default: "n1-standard-1"
metadata:
description:
- a hash/dictionary of custom data for the instance; '{"key":"value",...}'
- a hash/dictionary of custom data for the instance;
'{"key":"value", ...}'
required: false
default: null
service_account_email:
@ -56,10 +57,17 @@ options:
service_account_permissions:
version_added: "2.0"
description:
- service account permissions (see U(https://cloud.google.com/sdk/gcloud/reference/compute/instances/create), --scopes section for detailed information)
- service account permissions (see
U(https://cloud.google.com/sdk/gcloud/reference/compute/instances/create),
--scopes section for detailed information)
required: false
default: null
choices: ["bigquery", "cloud-platform", "compute-ro", "compute-rw", "computeaccounts-ro", "computeaccounts-rw", "datastore", "logging-write", "monitoring", "sql", "sql-admin", "storage-full", "storage-ro", "storage-rw", "taskqueue", "userinfo-email"]
choices: [
"bigquery", "cloud-platform", "compute-ro", "compute-rw",
"computeaccounts-ro", "computeaccounts-rw", "datastore", "logging-write",
"monitoring", "sql", "sql-admin", "storage-full", "storage-ro",
"storage-rw", "taskqueue", "userinfo-email"
]
pem_file:
version_added: "1.5.1"
description:
@ -94,7 +102,10 @@ options:
default: "false"
disks:
description:
- a list of persistent disks to attach to the instance; a string value gives the name of the disk; alternatively, a dictionary value can define 'name' and 'mode' ('READ_ONLY' or 'READ_WRITE'). The first entry will be the boot disk (which must be READ_WRITE).
- a list of persistent disks to attach to the instance; a string value
gives the name of the disk; alternatively, a dictionary value can
define 'name' and 'mode' ('READ_ONLY' or 'READ_WRITE'). The first entry
will be the boot disk (which must be READ_WRITE).
required: false
default: null
version_added: "1.7"
@ -117,7 +128,8 @@ options:
ip_forward:
version_added: "1.9"
description:
- set to true if the instance can forward ip packets (useful for gateways)
- set to true if the instance can forward ip packets (useful for
gateways)
required: false
default: "false"
external_ip:
@ -173,8 +185,10 @@ EXAMPLES = '''
tasks:
- name: Launch instances
local_action: gce instance_names={{names}} machine_type={{machine_type}}
image={{image}} zone={{zone}} service_account_email={{ service_account_email }}
credentials_file={{ credentials_file }} project_id={{ project_id }}
image={{image}} zone={{zone}}
service_account_email={{ service_account_email }}
credentials_file={{ credentials_file }}
project_id={{ project_id }}
register: gce
- name: Wait for SSH to come up
local_action: wait_for host={{item.public_ip}} port=22 delay=10
@ -201,10 +215,11 @@ EXAMPLES = '''
'''
try:
import libcloud
from libcloud.compute.types import Provider
from libcloud.compute.providers import get_driver
from libcloud.common.google import GoogleBaseError, QuotaExceededError, \
ResourceExistsError, ResourceInUseError, ResourceNotFoundError
ResourceExistsError, ResourceInUseError, ResourceNotFoundError
_ = Provider.GCE
HAS_LIBCLOUD = True
except ImportError:
@ -245,7 +260,7 @@ def get_instance_info(inst):
public_ip = inst.public_ips[0]
return({
'image': not inst.image is None and inst.image.split('/')[-1] or None,
'image': inst.image is not None and inst.image.split('/')[-1] or None,
'disks': disk_names,
'machine_type': inst.size,
'metadata': metadata,
@ -256,7 +271,8 @@ def get_instance_info(inst):
'status': ('status' in inst.extra) and inst.extra['status'] or None,
'tags': ('tags' in inst.extra) and inst.extra['tags'] or [],
'zone': ('zone' in inst.extra) and inst.extra['zone'].name or None,
})
})
def create_instances(module, gce, instance_names):
"""Creates new instances. Attributes other than instance_names are picked
@ -314,25 +330,31 @@ def create_instances(module, gce, instance_names):
# with:
# [ {'key': key1, 'value': value1}, {'key': key2, 'value': value2}, ...]
if metadata:
try:
md = literal_eval(str(metadata))
if not isinstance(md, dict):
raise ValueError('metadata must be a dict')
except ValueError, e:
module.fail_json(msg='bad metadata: %s' % str(e))
except SyntaxError, e:
module.fail_json(msg='bad metadata syntax')
if isinstance(metadata, dict):
md = metadata
else:
try:
md = literal_eval(str(metadata))
if not isinstance(md, dict):
raise ValueError('metadata must be a dict')
except ValueError as e:
module.fail_json(msg='bad metadata: %s' % str(e))
except SyntaxError as e:
module.fail_json(msg='bad metadata syntax')
if hasattr(libcloud, '__version__') and libcloud.__version__ < '0.15':
items = []
for k,v in md.items():
items.append({"key": k,"value": v})
for k, v in md.items():
items.append({"key": k, "value": v})
metadata = {'items': items}
else:
metadata = md
ex_sa_perms = []
bad_perms = []
if service_account_permissions:
for perm in service_account_permissions:
if not perm in gce.SA_SCOPES_MAP.keys():
if perm not in gce.SA_SCOPES_MAP.keys():
bad_perms.append(perm)
if len(bad_perms) > 0:
module.fail_json(msg='bad permissions: %s' % str(bad_perms))
@ -345,7 +367,7 @@ def create_instances(module, gce, instance_names):
# These variables all have default values but check just in case
if not lc_image or not lc_network or not lc_machine_type or not lc_zone:
module.fail_json(msg='Missing required create instance variable',
changed=False)
changed=False)
for name in instance_names:
pd = None
@ -358,16 +380,19 @@ def create_instances(module, gce, instance_names):
pd = gce.ex_get_volume("%s" % name, lc_zone)
inst = None
try:
inst = gce.create_node(name, lc_machine_type, lc_image,
location=lc_zone, ex_network=network, ex_tags=tags,
ex_metadata=metadata, ex_boot_disk=pd, ex_can_ip_forward=ip_forward,
external_ip=external_ip, ex_disk_auto_delete=disk_auto_delete, ex_service_accounts=ex_sa_perms)
inst = gce.create_node(
name, lc_machine_type, lc_image, location=lc_zone,
ex_network=network, ex_tags=tags, ex_metadata=metadata,
ex_boot_disk=pd, ex_can_ip_forward=ip_forward,
external_ip=external_ip, ex_disk_auto_delete=disk_auto_delete,
ex_service_accounts=ex_sa_perms
)
changed = True
except ResourceExistsError:
inst = gce.ex_get_node(name, lc_zone)
except GoogleBaseError, e:
module.fail_json(msg='Unexpected error attempting to create ' + \
'instance %s, error: %s' % (name, e.value))
except GoogleBaseError as e:
module.fail_json(msg='Unexpected error attempting to create ' +
'instance %s, error: %s' % (name, e.value))
for i, lc_disk in enumerate(lc_disks):
# Check whether the disk is already attached
@ -423,7 +448,7 @@ def terminate_instances(module, gce, instance_names, zone_name):
inst = gce.ex_get_node(name, zone_name)
except ResourceNotFoundError:
pass
except Exception, e:
except Exception as e:
module.fail_json(msg=unexpected_error_msg(e), changed=False)
if inst:
gce.destroy_node(inst)
@ -445,7 +470,7 @@ def main():
persistent_boot_disk = dict(type='bool', default=False),
disks = dict(type='list'),
state = dict(choices=['active', 'present', 'absent', 'deleted'],
default='present'),
default='present'),
tags = dict(type='list'),
zone = dict(default='us-central1-a'),
service_account_email = dict(),
@ -455,7 +480,7 @@ def main():
project_id = dict(),
ip_forward = dict(type='bool', default=False),
external_ip = dict(choices=['ephemeral', 'none'],
default='ephemeral'),
default='ephemeral'),
disk_auto_delete = dict(type='bool', default=True),
)
)
@ -489,15 +514,15 @@ def main():
inames.append(name)
if not inames:
module.fail_json(msg='Must specify a "name" or "instance_names"',
changed=False)
changed=False)
if not zone:
module.fail_json(msg='Must specify a "zone"', changed=False)
json_output = {'zone': zone}
if state in ['absent', 'deleted']:
json_output['state'] = 'absent'
(changed, terminated_instance_names) = terminate_instances(module,
gce, inames, zone)
(changed, terminated_instance_names) = terminate_instances(
module, gce, inames, zone)
# based on what user specified, return the same variable, although
# value could be different if an instance could not be destroyed
@ -508,15 +533,14 @@ def main():
elif state in ['active', 'present']:
json_output['state'] = 'present'
(changed, instance_data,instance_name_list) = create_instances(
module, gce, inames)
(changed, instance_data, instance_name_list) = create_instances(
module, gce, inames)
json_output['instance_data'] = instance_data
if instance_names:
json_output['instance_names'] = instance_name_list
elif name:
json_output['name'] = name
json_output['changed'] = changed
module.exit_json(**json_output)

View file

@ -40,6 +40,7 @@ options:
ipv4_range:
description:
- the IPv4 address range in CIDR notation for the network
this parameter is not mandatory when you specified existing network in name parameter, but when you create new network, this parameter is mandatory
required: false
aliases: ['cidr']
fwname:
@ -220,7 +221,7 @@ def main():
# user wants to create a new network that doesn't yet exist
if name and not network:
if not ipv4_range:
module.fail_json(msg="Missing required 'ipv4_range' parameter",
module.fail_json(msg="Network '" + name + "' is not found. To create network, 'ipv4_range' parameter is required",
changed=False)
try:

View file

@ -77,7 +77,14 @@ options:
security_groups:
description:
- Names of the security groups to which the instance should be
added. This may be a YAML list or a common separated string.
added. This may be a YAML list or a comma separated string.
required: false
default: None
network:
description:
- Name or ID of a network to attach this instance to. A simpler
version of the nics parameter, only one of network or nics should
be supplied.
required: false
default: None
nics:
@ -86,7 +93,8 @@ options:
be attached. Networks may be referenced by net-id/net-name/port-id
or port-name.
- 'Also this accepts a string containing a list of (net/port)-(id/name)
Eg: nics: "net-id=uuid-1,port-name=myport"'
Eg: nics: "net-id=uuid-1,port-name=myport"
Only one of network or nics should be supplied.'
required: false
default: None
auto_ip:
@ -133,15 +141,32 @@ options:
- Opaque blob of data which is made available to the instance
required: false
default: None
root_volume:
boot_from_volume:
description:
- Boot instance from a volume
- Should the instance boot from a persistent volume created based on
the image given. Mututally exclusive with boot_volume.
required: false
default: false
volume_size:
description:
- The size of the volume to create in GB if booting from volume based
on an image.
boot_volume:
description:
- Volume name or id to use as the volume to boot from. Implies
boot_from_volume. Mutually exclusive with image and boot_from_volume.
required: false
default: None
aliases: ['root_volume']
terminate_volume:
description:
- If true, delete volume when deleting instance (if booted from volume)
default: false
volumes:
description:
- A list of preexisting volumes names or ids to attach to the instance
required: false
default: []
state:
description:
- Should the resource be present or absent.
@ -280,6 +305,52 @@ EXAMPLES = '''
- net-id: 34605f38-e52a-25d2-b6ec-754a13ffb723
- net-name: another_network
meta: "hostname=test1,group=uge_master"
# Creates a new instance and attaches to a specific network
- os_server:
state: present
auth:
auth_url: https://region-b.geo-1.identity.hpcloudsvc.com:35357/v2.0/
username: admin
password: admin
project_name: admin
name: vm1
image: 4f905f38-e52a-43d2-b6ec-754a13ffb529
key_name: ansible_key
timeout: 200
flavor: 4
network: another_network
# Creates a new instance with 4G of RAM on a 75G Ubuntu Trusty volume
- name: launch a compute instance
hosts: localhost
tasks:
- name: launch an instance
os_server:
name: vm1
state: present
cloud: mordred
region_name: ams01
image: Ubuntu Server 14.04
flavor_ram: 4096
boot_from_volume: True
volume_size: 75
# Creates a new instance with 2 volumes attached
- name: launch a compute instance
hosts: localhost
tasks:
- name: launch an instance
os_server:
name: vm1
state: present
cloud: mordred
region_name: ams01
image: Ubuntu Server 14.04
flavor_ram: 4096
volumes:
- photos
- music
'''
@ -301,7 +372,14 @@ def _network_args(module, cloud):
args = []
nics = module.params['nics']
if type(nics) != list:
module.fail_json(msg='The \'nics\' parameter must be a list.')
for net in _parse_nics(nics):
if type(net) != dict:
module.fail_json(
msg='Each entry in the \'nics\' parameter must be a dict.')
if net.get('net-id'):
args.append(net)
elif net.get('net-name'):
@ -339,7 +417,7 @@ def _create_server(module, cloud):
flavor_include = module.params['flavor_include']
image_id = None
if not module.params['root_volume']:
if not module.params['boot_volume']:
image_id = cloud.get_image_id(
module.params['image'], module.params['image_exclude'])
@ -371,7 +449,9 @@ def _create_server(module, cloud):
userdata=module.params['userdata'],
config_drive=module.params['config_drive'],
)
for optional_param in ('region_name', 'key_name', 'availability_zone'):
for optional_param in (
'region_name', 'key_name', 'availability_zone', 'network',
'volume_size', 'volumes'):
if module.params[optional_param]:
bootkwargs[optional_param] = module.params[optional_param]
@ -379,7 +459,8 @@ def _create_server(module, cloud):
ip_pool=module.params['floating_ip_pools'],
ips=module.params['floating_ips'],
auto_ip=module.params['auto_ip'],
root_volume=module.params['root_volume'],
boot_volume=module.params['boot_volume'],
boot_from_volume=module.params['boot_from_volume'],
terminate_volume=module.params['terminate_volume'],
wait=module.params['wait'], timeout=module.params['timeout'],
**bootkwargs
@ -461,6 +542,7 @@ def main():
flavor_include = dict(default=None),
key_name = dict(default=None),
security_groups = dict(default=['default'], type='list'),
network = dict(default=None),
nics = dict(default=[], type='list'),
meta = dict(default=None),
userdata = dict(default=None),
@ -468,8 +550,11 @@ def main():
auto_ip = dict(default=True, type='bool', aliases=['auto_floating_ip', 'public_ip']),
floating_ips = dict(default=None),
floating_ip_pools = dict(default=None),
root_volume = dict(default=None),
volume_size = dict(default=False, type='int'),
boot_from_volume = dict(default=False, type='bool'),
boot_volume = dict(default=None, aliases=['root_volume']),
terminate_volume = dict(default=False, type='bool'),
volumes = dict(default=[], type='list'),
state = dict(default='present', choices=['absent', 'present']),
)
module_kwargs = openstack_module_kwargs(
@ -478,7 +563,12 @@ def main():
['auto_ip', 'floating_ip_pools'],
['floating_ips', 'floating_ip_pools'],
['flavor', 'flavor_ram'],
['image', 'root_volume'],
['image', 'boot_volume'],
['boot_from_volume', 'boot_volume'],
['nics', 'network'],
],
required_if=[
('boot_from_volume', True, ['volume_size', 'image']),
],
)
module = AnsibleModule(argument_spec, **module_kwargs)
@ -488,14 +578,14 @@ def main():
state = module.params['state']
image = module.params['image']
root_volume = module.params['root_volume']
boot_volume = module.params['boot_volume']
flavor = module.params['flavor']
flavor_ram = module.params['flavor_ram']
if state == 'present':
if not (image or root_volume):
if not (image or boot_volume):
module.fail_json(
msg="Parameter 'image' or 'root_volume' is required "
msg="Parameter 'image' or 'boot_volume' is required "
"if state == 'present'"
)
if not flavor and not flavor_ram:

View file

@ -176,6 +176,10 @@ EXAMPLES = '''
size_gb: 10
type: thin
datastore: storage001
# VMs can be put into folders. The value given here is either the full path
# to the folder (e.g. production/customerA/lamp) or just the last component
# of the path (e.g. lamp):
folder: production/customerA/lamp
vm_nic:
nic1:
type: vmxnet3
@ -656,7 +660,7 @@ def deploy_template(vsphere_client, guest, resource_pool, template_src, esxi, mo
elif resource_pool:
try:
cluster = [k for k,
v in vsphere_client.get_clusters().items() if v == cluster_name][0]
v in vsphere_client.get_clusters().items() if v == cluster_name][0] if cluster_name else None
except IndexError, e:
vsphere_client.disconnect()
module.fail_json(msg="Cannot find Cluster named: %s" %
@ -991,6 +995,48 @@ def reconfigure_net(vsphere_client, vm, module, esxi, resource_pool, guest, vm_n
elif len(nics) == 0:
return(False)
def _build_folder_tree(nodes, parent):
tree = {}
for node in nodes:
if node['parent'] == parent:
tree[node['name']] = dict.copy(node)
tree[node['name']]['subfolders'] = _build_folder_tree(nodes, node['id'])
del tree[node['name']]['parent']
return tree
def _find_path_in_tree(tree, path):
for name, o in tree.iteritems():
if name == path[0]:
if len(path) == 1:
return o
else:
return _find_path_in_tree(o['subfolders'], path[1:])
return None
def _get_folderid_for_path(vsphere_client, datacenter, path):
content = vsphere_client._retrieve_properties_traversal(property_names=['name', 'parent'], obj_type=MORTypes.Folder)
if not content: return {}
node_list = [
{
'id': o.Obj,
'name': o.PropSet[0].Val,
'parent': (o.PropSet[1].Val if len(o.PropSet) > 1 else None)
} for o in content
]
tree = _build_folder_tree(node_list, datacenter)
tree = _find_path_in_tree(tree, ['vm'])['subfolders']
folder = _find_path_in_tree(tree, path.split('/'))
return folder['id'] if folder else None
def create_vm(vsphere_client, module, esxi, resource_pool, cluster_name, guest, vm_extra_config, vm_hardware, vm_disk, vm_nic, vm_hw_version, state):
datacenter = esxi['datacenter']
@ -1011,13 +1057,19 @@ def create_vm(vsphere_client, module, esxi, resource_pool, cluster_name, guest,
# virtualmachineFolder managed object reference
if vm_extra_config.get('folder'):
if vm_extra_config['folder'] not in vsphere_client._get_managed_objects(MORTypes.Folder).values():
# try to find the folder by its full path, e.g. 'production/customerA/lamp'
vmfmor = _get_folderid_for_path(vsphere_client, dcmor, vm_extra_config.get('folder'))
# try the legacy behaviour of just matching the folder name, so 'lamp' alone matches 'production/customerA/lamp'
if vmfmor is None:
for mor, name in vsphere_client._get_managed_objects(MORTypes.Folder).iteritems():
if name == vm_extra_config['folder']:
vmfmor = mor
# if neither of strategies worked, bail out
if vmfmor is None:
vsphere_client.disconnect()
module.fail_json(msg="Cannot find folder named: %s" % vm_extra_config['folder'])
for mor, name in vsphere_client._get_managed_objects(MORTypes.Folder).iteritems():
if name == vm_extra_config['folder']:
vmfmor = mor
else:
vmfmor = dcprops.vmFolder._obj
@ -1059,7 +1111,7 @@ def create_vm(vsphere_client, module, esxi, resource_pool, cluster_name, guest,
if resource_pool:
try:
cluster = [k for k,
v in vsphere_client.get_clusters().items() if v == cluster_name][0]
v in vsphere_client.get_clusters().items() if v == cluster_name][0] if cluster_name else None
except IndexError, e:
vsphere_client.disconnect()
module.fail_json(msg="Cannot find Cluster named: %s" %

View file

@ -35,31 +35,6 @@ options:
required: true
default: null
aliases: [ db ]
login_user:
description:
- The username used to authenticate with
required: false
default: null
login_password:
description:
- The password used to authenticate with
required: false
default: null
login_host:
description:
- Host running the database
required: false
default: localhost
login_port:
description:
- Port of the MySQL server. Requires login_host be defined as other then localhost if login_port is used
required: false
default: 3306
login_unix_socket:
description:
- The path to a Unix domain socket for local connections
required: false
default: null
state:
description:
- The database state
@ -81,19 +56,8 @@ options:
- Location, on the remote host, of the dump file to read from or write to. Uncompressed SQL
files (C(.sql)) as well as bzip2 (C(.bz2)), gzip (C(.gz)) and xz (Added in 2.0) compressed files are supported.
required: false
notes:
- Requires the MySQLdb Python package on the remote host. For Ubuntu, this
is as easy as apt-get install python-mysqldb. (See M(apt).) For CentOS/Fedora, this
is as easy as yum install MySQL-python. (See M(yum).)
- Requires the mysql command line client. For Centos/Fedora, this is as easy as
yum install mariadb (See M(yum).). For Debian/Ubuntu this is as easy as
apt-get install mariadb-client. (See M(apt).)
- Both I(login_password) and I(login_user) are required when you are
passing credentials. If none are present, the module will attempt to read
the credentials from C(~/.my.cnf), and finally fall back to using the MySQL
default login of C(root) with no password.
requirements: [ ConfigParser ]
author: "Ansible Core Team"
extends_documentation_fragment: mysql
'''
EXAMPLES = '''
@ -111,11 +75,11 @@ EXAMPLES = '''
- mysql_db: state=import name=all target=/tmp/{{ inventory_hostname }}.sql
'''
import ConfigParser
import os
import pipes
import stat
import subprocess
try:
import MySQLdb
except ImportError:
@ -136,9 +100,22 @@ def db_delete(cursor, db):
cursor.execute(query)
return True
def db_dump(module, host, user, password, db_name, target, all_databases, port, socket=None):
def db_dump(module, host, user, password, db_name, target, all_databases, port, config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None):
cmd = module.get_bin_path('mysqldump', True)
cmd += " --quick --user=%s --password=%s" % (pipes.quote(user), pipes.quote(password))
# If defined, mysqldump demands --defaults-extra-file be the first option
if config_file:
cmd += " --defaults-extra-file=%s" % pipes.quote(config_file)
cmd += " --quick"
if user is not None:
cmd += " --user=%s" % pipes.quote(user)
if password is not None:
cmd += " --password=%s" % pipes.quote(password)
if ssl_cert is not None:
cmd += " --ssl-cert=%s" % pipes.quote(ssl_cert)
if ssl_key is not None:
cmd += " --ssl-key=%s" % pipes.quote(ssl_key)
if ssl_cert is not None:
cmd += " --ssl-ca=%s" % pipes.quote(ssl_ca)
if socket is not None:
cmd += " --socket=%s" % pipes.quote(socket)
else:
@ -164,17 +141,26 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port,
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True)
return rc, stdout, stderr
def db_import(module, host, user, password, db_name, target, all_databases, port, socket=None):
def db_import(module, host, user, password, db_name, target, all_databases, port, config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None):
if not os.path.exists(target):
return module.fail_json(msg="target %s does not exist on the host" % target)
cmd = [module.get_bin_path('mysql', True)]
# --defaults-file must go first, or errors out
if config_file:
cmd.append("--defaults-extra-file=%s" % pipes.quote(config_file))
if user:
cmd.append("--user=%s" % pipes.quote(user))
if password:
cmd.append("--password=%s" % pipes.quote(password))
if socket is not None:
cmd.append("--socket=%s" % pipes.quote(socket))
if ssl_cert is not None:
cmd.append("--ssl-cert=%s" % pipes.quote(ssl_cert))
if ssl_key is not None:
cmd.append("--ssl-key=%s" % pipes.quote(ssl_key))
if ssl_cert is not None:
cmd.append("--ssl-ca=%s" % pipes.quote(ssl_ca))
else:
cmd.append("--host=%s" % pipes.quote(host))
cmd.append("--port=%i" % port)
@ -218,61 +204,6 @@ def db_create(cursor, db, encoding, collation):
res = cursor.execute(query, query_params)
return True
def strip_quotes(s):
""" Remove surrounding single or double quotes
>>> print strip_quotes('hello')
hello
>>> print strip_quotes('"hello"')
hello
>>> print strip_quotes("'hello'")
hello
>>> print strip_quotes("'hello")
'hello
"""
single_quote = "'"
double_quote = '"'
if s.startswith(single_quote) and s.endswith(single_quote):
s = s.strip(single_quote)
elif s.startswith(double_quote) and s.endswith(double_quote):
s = s.strip(double_quote)
return s
def config_get(config, section, option):
""" Calls ConfigParser.get and strips quotes
See: http://dev.mysql.com/doc/refman/5.0/en/option-files.html
"""
return strip_quotes(config.get(section, option))
def load_mycnf():
config = ConfigParser.RawConfigParser()
mycnf = os.path.expanduser('~/.my.cnf')
if not os.path.exists(mycnf):
return False
try:
config.readfp(open(mycnf))
except (IOError):
return False
# We support two forms of passwords in .my.cnf, both pass= and password=,
# as these are both supported by MySQL.
try:
passwd = config_get(config, 'client', 'password')
except (ConfigParser.NoOptionError):
try:
passwd = config_get(config, 'client', 'pass')
except (ConfigParser.NoOptionError):
return False
try:
creds = dict(user=config_get(config, 'client', 'user'),passwd=passwd)
except (ConfigParser.NoOptionError):
return False
return creds
# ===========================================
# Module execution.
#
@ -290,6 +221,10 @@ def main():
collation=dict(default=""),
target=dict(default=None),
state=dict(default="present", choices=["absent", "present","dump", "import"]),
ssl_cert=dict(default=None),
ssl_key=dict(default=None),
ssl_ca=dict(default=None),
config_file=dict(default="~/.my.cnf"),
)
)
@ -305,64 +240,41 @@ def main():
login_port = module.params["login_port"]
if login_port < 0 or login_port > 65535:
module.fail_json(msg="login_port must be a valid unix port number (0-65535)")
ssl_cert = module.params["ssl_cert"]
ssl_key = module.params["ssl_key"]
ssl_ca = module.params["ssl_ca"]
config_file = module.params['config_file']
config_file = os.path.expanduser(os.path.expandvars(config_file))
login_password = module.params["login_password"]
login_user = module.params["login_user"]
login_host = module.params["login_host"]
# make sure the target path is expanded for ~ and $HOME
if target is not None:
target = os.path.expandvars(os.path.expanduser(target))
# Either the caller passes both a username and password with which to connect to
# mysql, or they pass neither and allow this module to read the credentials from
# ~/.my.cnf.
login_password = module.params["login_password"]
login_user = module.params["login_user"]
if login_user is None and login_password is None:
mycnf_creds = load_mycnf()
if mycnf_creds is False:
login_user = "root"
login_password = ""
else:
login_user = mycnf_creds["user"]
login_password = mycnf_creds["passwd"]
elif login_password is None or login_user is None:
module.fail_json(msg="when supplying login arguments, both login_user and login_password must be provided")
login_host = module.params["login_host"]
if state in ['dump','import']:
if target is None:
module.fail_json(msg="with state=%s target is required" % (state))
if db == 'all':
connect_to_db = 'mysql'
db = 'mysql'
all_databases = True
else:
connect_to_db = db
all_databases = False
else:
if db == 'all':
module.fail_json(msg="name is not allowed to equal 'all' unless state equals import, or dump.")
connect_to_db = ''
try:
if socket:
try:
socketmode = os.stat(socket).st_mode
if not stat.S_ISSOCK(socketmode):
module.fail_json(msg="%s, is not a socket, unable to connect" % socket)
except OSError:
module.fail_json(msg="%s, does not exist, unable to connect" % socket)
db_connection = MySQLdb.connect(host=module.params["login_host"], unix_socket=socket, user=login_user, passwd=login_password, db=connect_to_db)
elif login_port != 3306 and module.params["login_host"] == "localhost":
module.fail_json(msg="login_host is required when login_port is defined, login_host cannot be localhost when login_port is defined")
else:
db_connection = MySQLdb.connect(host=module.params["login_host"], port=login_port, user=login_user, passwd=login_password, db=connect_to_db)
cursor = db_connection.cursor()
cursor = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca)
except Exception, e:
errno, errstr = e.args
if "Unknown database" in str(e):
module.fail_json(msg="ERROR: %s %s" % (errno, errstr))
if os.path.exists(config_file):
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. Exception message: %s" % (config_file, e))
else:
module.fail_json(msg="unable to connect, check login credentials (login_user, and login_password, which can be defined in ~/.my.cnf), check that mysql socket exists and mysql server is running (ERROR: %s %s)" % (errno, errstr))
module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, e))
changed = False
if not os.path.exists(config_file):
config_file = None
if db_exists(cursor, db):
if state == "absent":
try:
@ -372,8 +284,7 @@ def main():
elif state == "dump":
rc, stdout, stderr = db_dump(module, login_host, login_user,
login_password, db, target, all_databases,
port=login_port,
socket=module.params['login_unix_socket'])
login_port, config_file, socket, ssl_cert, ssl_key, ssl_ca)
if rc != 0:
module.fail_json(msg="%s" % stderr)
else:
@ -381,8 +292,7 @@ def main():
elif state == "import":
rc, stdout, stderr = db_import(module, login_host, login_user,
login_password, db, target, all_databases,
port=login_port,
socket=module.params['login_unix_socket'])
login_port, config_file, socket, ssl_cert, ssl_key, ssl_ca)
if rc != 0:
module.fail_json(msg="%s" % stderr)
else:
@ -399,5 +309,6 @@ def main():
# import module snippets
from ansible.module_utils.basic import *
from ansible.module_utils.database import *
from ansible.module_utils.mysql import *
if __name__ == '__main__':
main()

View file

@ -47,32 +47,15 @@ options:
- the 'host' part of the MySQL username
required: false
default: localhost
login_user:
host_all:
description:
- The username used to authenticate with
- override the host option, making ansible apply changes to
all hostnames for a given user. This option cannot be used
when creating users
required: false
default: null
login_password:
description:
- The password used to authenticate with
required: false
default: null
login_host:
description:
- Host running the database
required: false
default: localhost
login_port:
description:
- Port of the MySQL server
required: false
default: 3306
version_added: '1.4'
login_unix_socket:
description:
- The path to a Unix domain socket for local connections
required: false
default: null
choices: [ "yes", "no" ]
default: "no"
version_added: "2.1"
priv:
description:
- "MySQL privileges string in the format: C(db.table:priv1,priv2)"
@ -107,19 +90,7 @@ options:
version_added: "2.0"
description:
- C(always) will update passwords if they differ. C(on_create) will only set the password for newly created users.
config_file:
description:
- Specify a config file from which user and password are to be read
required: false
default: '~/.my.cnf'
version_added: "2.0"
notes:
- Requires the MySQLdb Python package on the remote host. For Ubuntu, this
is as easy as apt-get install python-mysqldb.
- Both C(login_password) and C(login_user) are required when you are
passing credentials. If none are present, the module will attempt to read
the credentials from C(~/.my.cnf), and finally fall back to using the MySQL
default login of 'root' with no password.
- "MySQL server installs with default login_user of 'root' and no password. To secure this user
as part of an idempotent playbook, you must create at least two tasks: the first must change the root user's password,
without providing any login_user/login_password details. The second must drop a ~/.my.cnf file containing
@ -127,11 +98,17 @@ notes:
the file."
- Currently, there is only support for the `mysql_native_password` encryted password hash module.
requirements: [ "MySQLdb" ]
author: "Jonathan Mainguy (@Jmainguy)"
extends_documentation_fragment: mysql
'''
EXAMPLES = """
# Removes anonymous user account for localhost
- mysql_user: name='' host=localhost state=absent
# Removes all anonymous user accounts
- mysql_user: name='' host_all=yes state=absent
# Create database user with name 'bob' and password '12345' with all database privileges
- mysql_user: name=bob password=12345 priv=*.*:ALL state=present
@ -144,9 +121,12 @@ EXAMPLES = """
# Modify user Bob to require SSL connections. Note that REQUIRESSL is a special privilege that should only apply to *.* by itself.
- mysql_user: name=bob append_privs=true priv=*.*:REQUIRESSL state=present
# Ensure no user named 'sally' exists, also passing in the auth credentials.
# Ensure no user named 'sally'@'localhost' exists, also passing in the auth credentials.
- mysql_user: login_user=root login_password=123456 name=sally state=absent
# Ensure no user named 'sally' exists at all
- mysql_user: name=sally host_all=yes state=absent
# Specify grants composed of more than one word
- mysql_user: name=replication password=12345 priv=*.*:"REPLICATION CLIENT" state=present
@ -194,30 +174,6 @@ class InvalidPrivsError(Exception):
# MySQL module specific support methods.
#
def connect(module, login_user=None, login_password=None, config_file=''):
config = {
'host': module.params['login_host'],
'db': 'mysql'
}
if module.params['login_unix_socket']:
config['unix_socket'] = module.params['login_unix_socket']
else:
config['port'] = module.params['login_port']
if os.path.exists(config_file):
config['read_default_file'] = config_file
# If login_user or login_password are given, they should override the
# config file
if login_user is not None:
config['user'] = login_user
if login_password is not None:
config['passwd'] = login_password
db_connection = MySQLdb.connect(**config)
return db_connection.cursor()
# User Authentication Management was change in MySQL 5.7
# This is a generic check for if the server version is less than version 5.7
def server_version_check(cursor):
@ -226,21 +182,34 @@ def server_version_check(cursor):
version_str = result[0]
version = version_str.split('.')
# Currently we have no facility to handle new-style password update on
# mariadb and the old-style update continues to work
if 'mariadb' in version_str.lower():
return True
if (int(version[0]) <= 5 and int(version[1]) < 7):
return True
return True
else:
return False
return False
def user_exists(cursor, user, host, host_all):
if host_all:
cursor.execute("SELECT count(*) FROM user WHERE user = %s", user)
else:
cursor.execute("SELECT count(*) FROM user WHERE user = %s AND host = %s", (user,host))
def user_exists(cursor, user, host):
cursor.execute("SELECT count(*) FROM user WHERE user = %s AND host = %s", (user,host))
count = cursor.fetchone()
return count[0] > 0
def user_add(cursor, user, host, password, encrypted, new_priv):
def user_add(cursor, user, host, host_all, password, encrypted, new_priv):
# we cannot create users without a proper hostname
if host_all:
return False
if password and encrypted:
cursor.execute("CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user,host,password))
elif password and not encrypted:
cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user,host,password))
if new_priv is not None:
for db_table, priv in new_priv.iteritems():
privileges_grant(cursor, user,host,db_table,priv)
@ -253,85 +222,130 @@ def is_hash(password):
ishash = True
return ishash
def user_mod(cursor, user, host, password, encrypted, new_priv, append_privs):
def user_mod(cursor, user, host, host_all, password, encrypted, new_priv, append_privs):
changed = False
grant_option = False
# Handle clear text and hashed passwords.
if bool(password):
# Determine what user management method server uses
old_user_mgmt = server_version_check(cursor)
if host_all:
hostnames = user_get_hostnames(cursor, user)
else:
hostnames = [host]
if old_user_mgmt:
cursor.execute("SELECT password FROM user WHERE user = %s AND host = %s", (user,host))
else:
cursor.execute("SELECT authentication_string FROM user WHERE user = %s AND host = %s", (user,host))
current_pass_hash = cursor.fetchone()
if encrypted:
encrypted_string = (password)
if is_hash(password):
if current_pass_hash[0] != encrypted_string:
if old_user_mgmt:
cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, password))
else:
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, password))
changed = True
else:
module.fail_json(msg="encrypted was specified however it does not appear to be a valid hash expecting: *SHA1(SHA1(your_password))")
else:
for host in hostnames:
# Handle clear text and hashed passwords.
if bool(password):
# Determine what user management method server uses
old_user_mgmt = server_version_check(cursor)
if old_user_mgmt:
cursor.execute("SELECT PASSWORD(%s)", (password,))
cursor.execute("SELECT password FROM user WHERE user = %s AND host = %s", (user,host))
else:
cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,))
new_pass_hash = cursor.fetchone()
if current_pass_hash[0] != new_pass_hash[0]:
if old_user_mgmt:
cursor.execute("SET PASSWORD FOR %s@%s = PASSWORD(%s)", (user, host, password))
cursor.execute("SELECT authentication_string FROM user WHERE user = %s AND host = %s", (user,host))
current_pass_hash = cursor.fetchone()
if encrypted:
encrypted_string = (password)
if is_hash(password):
if current_pass_hash[0] != encrypted_string:
if old_user_mgmt:
cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, password))
else:
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, password))
changed = True
else:
cursor.execute("ALTER USER %s@%s IDENTIFIED BY %s", (user, host, password))
changed = True
# Handle privileges
if new_priv is not None:
curr_priv = privileges_get(cursor, user,host)
# If the user has privileges on a db.table that doesn't appear at all in
# the new specification, then revoke all privileges on it.
for db_table, priv in curr_priv.iteritems():
# If the user has the GRANT OPTION on a db.table, revoke it first.
if "GRANT" in priv:
grant_option = True
if db_table not in new_priv:
if user != "root" and "PROXY" not in priv and not append_privs:
privileges_revoke(cursor, user,host,db_table,priv,grant_option)
module.fail_json(msg="encrypted was specified however it does not appear to be a valid hash expecting: *SHA1(SHA1(your_password))")
else:
if old_user_mgmt:
cursor.execute("SELECT PASSWORD(%s)", (password,))
else:
cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,))
new_pass_hash = cursor.fetchone()
if current_pass_hash[0] != new_pass_hash[0]:
if old_user_mgmt:
cursor.execute("SET PASSWORD FOR %s@%s = PASSWORD(%s)", (user, host, password))
else:
cursor.execute("ALTER USER %s@%s IDENTIFIED BY %s", (user, host, password))
changed = True
# Handle privileges
if new_priv is not None:
curr_priv = privileges_get(cursor, user,host)
# If the user has privileges on a db.table that doesn't appear at all in
# the new specification, then revoke all privileges on it.
for db_table, priv in curr_priv.iteritems():
# If the user has the GRANT OPTION on a db.table, revoke it first.
if "GRANT" in priv:
grant_option = True
if db_table not in new_priv:
if user != "root" and "PROXY" not in priv and not append_privs:
privileges_revoke(cursor, user,host,db_table,priv,grant_option)
changed = True
# If the user doesn't currently have any privileges on a db.table, then
# we can perform a straight grant operation.
for db_table, priv in new_priv.iteritems():
if db_table not in curr_priv:
privileges_grant(cursor, user,host,db_table,priv)
changed = True
# If the user doesn't currently have any privileges on a db.table, then
# we can perform a straight grant operation.
for db_table, priv in new_priv.iteritems():
if db_table not in curr_priv:
privileges_grant(cursor, user,host,db_table,priv)
changed = True
# Handle privileges
if new_priv is not None:
curr_priv = privileges_get(cursor, user,host)
# If the db.table specification exists in both the user's current privileges
# and in the new privileges, then we need to see if there's a difference.
db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys())
for db_table in db_table_intersect:
priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table])
if (len(priv_diff) > 0):
if not append_privs:
privileges_revoke(cursor, user,host,db_table,curr_priv[db_table],grant_option)
privileges_grant(cursor, user,host,db_table,new_priv[db_table])
changed = True
# If the user has privileges on a db.table that doesn't appear at all in
# the new specification, then revoke all privileges on it.
for db_table, priv in curr_priv.iteritems():
# If the user has the GRANT OPTION on a db.table, revoke it first.
if "GRANT" in priv:
grant_option = True
if db_table not in new_priv:
if user != "root" and "PROXY" not in priv and not append_privs:
privileges_revoke(cursor, user,host,db_table,priv,grant_option)
changed = True
# If the user doesn't currently have any privileges on a db.table, then
# we can perform a straight grant operation.
for db_table, priv in new_priv.iteritems():
if db_table not in curr_priv:
privileges_grant(cursor, user,host,db_table,priv)
changed = True
# If the db.table specification exists in both the user's current privileges
# and in the new privileges, then we need to see if there's a difference.
db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys())
for db_table in db_table_intersect:
priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table])
if (len(priv_diff) > 0):
if not append_privs:
privileges_revoke(cursor, user,host,db_table,curr_priv[db_table],grant_option)
privileges_grant(cursor, user,host,db_table,new_priv[db_table])
changed = True
return changed
def user_delete(cursor, user, host):
cursor.execute("DROP USER %s@%s", (user, host))
def user_delete(cursor, user, host, host_all):
if host_all:
hostnames = user_get_hostnames(cursor, user)
for hostname in hostnames:
cursor.execute("DROP USER %s@%s", (user, hostname))
else:
cursor.execute("DROP USER %s@%s", (user, host))
return True
def user_get_hostnames(cursor, user):
cursor.execute("SELECT Host FROM mysql.user WHERE user = %s", user)
hostnames_raw = cursor.fetchall()
hostnames = []
for hostname_raw in hostnames_raw:
hostnames.append(hostname_raw[0])
return hostnames
def privileges_get(cursor, user,host):
""" MySQL doesn't have a better method of getting privileges aside from the
SHOW GRANTS query syntax, which requires us to then parse the returned string.
@ -451,12 +465,16 @@ def main():
password=dict(default=None, no_log=True),
encrypted=dict(default=False, type='bool'),
host=dict(default="localhost"),
host_all=dict(type="bool", default="no"),
state=dict(default="present", choices=["absent", "present"]),
priv=dict(default=None),
append_privs=dict(default=False, type='bool'),
check_implicit_admin=dict(default=False, type='bool'),
update_password=dict(default="always", choices=["always", "on_create"]),
config_file=dict(default="~/.my.cnf"),
ssl_cert=dict(default=None),
ssl_key=dict(default=None),
ssl_ca=dict(default=None),
)
)
login_user = module.params["login_user"]
@ -465,12 +483,17 @@ def main():
password = module.params["password"]
encrypted = module.boolean(module.params["encrypted"])
host = module.params["host"].lower()
host_all = module.params["host_all"]
state = module.params["state"]
priv = module.params["priv"]
check_implicit_admin = module.params['check_implicit_admin']
config_file = module.params['config_file']
append_privs = module.boolean(module.params["append_privs"])
update_password = module.params['update_password']
ssl_cert = module.params["ssl_cert"]
ssl_key = module.params["ssl_key"]
ssl_ca = module.params["ssl_ca"]
db = 'mysql'
config_file = os.path.expanduser(os.path.expandvars(config_file))
if not mysqldb_found:
@ -486,35 +509,37 @@ def main():
try:
if check_implicit_admin:
try:
cursor = connect(module, 'root', '', config_file)
cursor = mysql_connect(module, 'root', '', config_file, ssl_cert, ssl_key, ssl_ca, db)
except:
pass
if not cursor:
cursor = connect(module, login_user, login_password, config_file)
cursor = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, db)
except Exception, e:
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or ~/.my.cnf has the credentials. Exception message: %s" % e)
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. Exception message: %s" % (config_file, e))
if state == "present":
if user_exists(cursor, user, host):
if user_exists(cursor, user, host, host_all):
try:
if update_password == 'always':
changed = user_mod(cursor, user, host, password, encrypted, priv, append_privs)
changed = user_mod(cursor, user, host, host_all, password, encrypted, priv, append_privs)
else:
changed = user_mod(cursor, user, host, None, encrypted, priv, append_privs)
changed = user_mod(cursor, user, host, host_all, None, encrypted, priv, append_privs)
except (SQLParseError, InvalidPrivsError, MySQLdb.Error), e:
module.fail_json(msg=str(e))
else:
if password is None:
module.fail_json(msg="password parameter required when adding a user")
if host_all:
module.fail_json(msg="host_all parameter cannot be used when adding a user")
try:
changed = user_add(cursor, user, host, password, encrypted, priv)
changed = user_add(cursor, user, host, host_all, password, encrypted, priv)
except (SQLParseError, InvalidPrivsError, MySQLdb.Error), e:
module.fail_json(msg=str(e))
elif state == "absent":
if user_exists(cursor, user, host):
changed = user_delete(cursor, user, host)
if user_exists(cursor, user, host, host_all):
changed = user_delete(cursor, user, host, host_all)
else:
changed = False
module.exit_json(changed=changed, user=user)
@ -522,5 +547,6 @@ def main():
# import module snippets
from ansible.module_utils.basic import *
from ansible.module_utils.database import *
from ansible.module_utils.mysql import *
if __name__ == '__main__':
main()

View file

@ -40,26 +40,7 @@ options:
description:
- If set, then sets variable value to this
required: False
login_user:
description:
- username to connect mysql host, if defined login_password also needed.
required: False
login_password:
description:
- password to connect mysql host, if defined login_user also needed.
required: False
login_host:
description:
- mysql host to connect
required: False
login_port:
version_added: "2.0"
description:
- mysql port to connect
required: False
login_unix_socket:
description:
- unix socket to connect mysql server
extends_documentation_fragment: mysql
'''
EXAMPLES = '''
# Check for sync_binlog setting
@ -70,7 +51,6 @@ EXAMPLES = '''
'''
import ConfigParser
import os
import warnings
from re import match
@ -134,66 +114,6 @@ def setvariable(cursor, mysqlvar, value):
result = str(e)
return result
def strip_quotes(s):
""" Remove surrounding single or double quotes
>>> print strip_quotes('hello')
hello
>>> print strip_quotes('"hello"')
hello
>>> print strip_quotes("'hello'")
hello
>>> print strip_quotes("'hello")
'hello
"""
single_quote = "'"
double_quote = '"'
if s.startswith(single_quote) and s.endswith(single_quote):
s = s.strip(single_quote)
elif s.startswith(double_quote) and s.endswith(double_quote):
s = s.strip(double_quote)
return s
def config_get(config, section, option):
""" Calls ConfigParser.get and strips quotes
See: http://dev.mysql.com/doc/refman/5.0/en/option-files.html
"""
return strip_quotes(config.get(section, option))
def load_mycnf():
config = ConfigParser.RawConfigParser()
mycnf = os.path.expanduser('~/.my.cnf')
if not os.path.exists(mycnf):
return False
try:
config.readfp(open(mycnf))
except (IOError):
return False
# We support two forms of passwords in .my.cnf, both pass= and password=,
# as these are both supported by MySQL.
try:
passwd = config_get(config, 'client', 'password')
except (ConfigParser.NoOptionError):
try:
passwd = config_get(config, 'client', 'pass')
except (ConfigParser.NoOptionError):
return False
# If .my.cnf doesn't specify a user, default to user login name
try:
user = config_get(config, 'client', 'user')
except (ConfigParser.NoOptionError):
user = getpass.getuser()
creds = dict(user=user, passwd=passwd)
return creds
def main():
module = AnsibleModule(
argument_spec = dict(
@ -203,14 +123,24 @@ def main():
login_port=dict(default="3306", type='int'),
login_unix_socket=dict(default=None),
variable=dict(default=None),
value=dict(default=None)
value=dict(default=None),
ssl_cert=dict(default=None),
ssl_key=dict(default=None),
ssl_ca=dict(default=None),
config_file=dict(default="~/.my.cnf")
)
)
user = module.params["login_user"]
password = module.params["login_password"]
host = module.params["login_host"]
port = module.params["login_port"]
ssl_cert = module.params["ssl_cert"]
ssl_key = module.params["ssl_key"]
ssl_ca = module.params["ssl_ca"]
config_file = module.params['config_file']
config_file = os.path.expanduser(os.path.expandvars(config_file))
db = 'mysql'
mysqlvar = module.params["variable"]
value = module.params["value"]
if mysqlvar is None:
@ -222,30 +152,14 @@ def main():
else:
warnings.filterwarnings('error', category=MySQLdb.Warning)
# Either the caller passes both a username and password with which to connect to
# mysql, or they pass neither and allow this module to read the credentials from
# ~/.my.cnf.
login_password = module.params["login_password"]
login_user = module.params["login_user"]
if login_user is None and login_password is None:
mycnf_creds = load_mycnf()
if mycnf_creds is False:
login_user = "root"
login_password = ""
else:
login_user = mycnf_creds["user"]
login_password = mycnf_creds["passwd"]
elif login_password is None or login_user is None:
module.fail_json(msg="when supplying login arguments, both login_user and login_password must be provided")
try:
if module.params["login_unix_socket"]:
db_connection = MySQLdb.connect(host=module.params["login_host"], port=module.params["login_port"], unix_socket=module.params["login_unix_socket"], user=login_user, passwd=login_password, db="mysql")
else:
db_connection = MySQLdb.connect(host=module.params["login_host"], port=module.params["login_port"], user=login_user, passwd=login_password, db="mysql")
cursor = db_connection.cursor()
cursor = mysql_connect(module, user, password, config_file, ssl_cert, ssl_key, ssl_ca, db)
except Exception, e:
errno, errstr = e.args
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or ~/.my.cnf has the credentials (ERROR: %s %s)" % (errno, errstr))
if os.path.exists(config_file):
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. Exception message: %s" % (config_file, e))
else:
module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, e))
mysqlvar_val = getvariable(cursor, mysqlvar)
if mysqlvar_val is None:
module.fail_json(msg="Variable not available \"%s\"" % mysqlvar, changed=False)
@ -269,4 +183,5 @@ def main():
# import module snippets
from ansible.module_utils.basic import *
from ansible.module_utils.database import *
from ansible.module_utils.mysql import *
main()

View file

@ -55,6 +55,15 @@ options:
default: sha1
aliases: [ 'checksum_algo' ]
version_added: "2.0"
mime:
description:
- Use file magic and return data about the nature of the file. this uses the 'file' utility found on most Linux/Unix systems.
- This will add both `mime_type` and 'charset' fields to the return, if possible.
required: false
choices: [ Yes, No ]
default: No
version_added: "2.1"
aliases: [ 'mime_type', 'mime-type' ]
author: "Bruce Pennypacker (@bpennypacker)"
'''
@ -278,6 +287,16 @@ stat:
returned: success, path exists and user can read stats and installed python supports it
type: string
sample: www-data
mime_type:
description: file magic data or mime-type
returned: success, path exists and user can read stats and installed python supports it and the `mime` option was true, will return 'unknown' on error.
type: string
sample: PDF document, version 1.2
charset:
description: file character set or encoding
returned: success, path exists and user can read stats and installed python supports it and the `mime` option was true, will return 'unknown' on error.
type: string
sample: us-ascii
'''
import os
@ -293,7 +312,8 @@ def main():
follow = dict(default='no', type='bool'),
get_md5 = dict(default='yes', type='bool'),
get_checksum = dict(default='yes', type='bool'),
checksum_algorithm = dict(default='sha1', type='str', choices=['sha1', 'sha224', 'sha256', 'sha384', 'sha512'], aliases=['checksum_algo'])
checksum_algorithm = dict(default='sha1', type='str', choices=['sha1', 'sha224', 'sha256', 'sha384', 'sha512'], aliases=['checksum_algo']),
mime = dict(default=False, type='bool', aliases=['mime_type', 'mime-type']),
),
supports_check_mode = True
)
@ -376,6 +396,19 @@ def main():
except:
pass
if module.params.get('mime'):
d['mime_type'] = 'unknown'
d['charset'] = 'unknown'
filecmd = [module.get_bin_path('file', True),'-i', path]
try:
rc, out, err = module.run_command(filecmd)
if rc == 0:
mtype, chset = out.split(':')[1].split(';')
d['mime_type'] = mtype.strip()
d['charset'] = chset.split('=')[1].strip()
except:
pass
module.exit_json(changed=False, stat=d)

View file

@ -20,6 +20,7 @@
#
import tempfile
import re
import os
DOCUMENTATION = '''
@ -321,17 +322,15 @@ def main():
# Automatically apply -e option to extra_args when source is a VCS url. VCS
# includes those beginning with svn+, git+, hg+ or bzr+
if name:
if module.params['editable']:
if name.startswith('svn+') or name.startswith('git+') or \
name.startswith('hg+') or name.startswith('bzr+'):
args_list = [] # used if extra_args is not used at all
if extra_args:
args_list = extra_args.split(' ')
if '-e' not in args_list:
args_list.append('-e')
# Ok, we will reconstruct the option string
extra_args = ' '.join(args_list)
has_vcs = bool(name and re.match(r'(svn|git|hg|bzr)\+', name))
if has_vcs and module.params['editable']:
args_list = [] # used if extra_args is not used at all
if extra_args:
args_list = extra_args.split(' ')
if '-e' not in args_list:
args_list.append('-e')
# Ok, we will reconstruct the option string
extra_args = ' '.join(args_list)
if extra_args:
cmd += ' %s' % extra_args
@ -344,8 +343,7 @@ def main():
if module.check_mode:
if extra_args or requirements or state == 'latest' or not name:
module.exit_json(changed=True)
elif name.startswith('svn+') or name.startswith('git+') or \
name.startswith('hg+') or name.startswith('bzr+'):
elif has_vcs:
module.exit_json(changed=True)
freeze_cmd = '%s freeze' % pip
@ -363,6 +361,12 @@ def main():
changed = (state == 'present' and not is_present) or (state == 'absent' and is_present)
module.exit_json(changed=changed, cmd=freeze_cmd, stdout=out, stderr=err)
if requirements or has_vcs:
freeze_cmd = '%s freeze' % pip
out_freeze_before = module.run_command(freeze_cmd, cwd=chdir)[1]
else:
out_freeze_before = None
rc, out_pip, err_pip = module.run_command(cmd, path_prefix=path_prefix, cwd=chdir)
out += out_pip
err += err_pip
@ -375,7 +379,11 @@ def main():
if state == 'absent':
changed = 'Successfully uninstalled' in out_pip
else:
changed = 'Successfully installed' in out_pip
if out_freeze_before is None:
changed = 'Successfully installed' in out_pip
else:
out_freeze_after = module.run_command(freeze_cmd, cwd=chdir)[1]
changed = out_freeze_before != out_freeze_after
module.exit_json(changed=changed, cmd=cmd, name=name, version=version,
state=state, requirements=requirements, virtualenv=env,

View file

@ -21,8 +21,6 @@
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import traceback
import os
import yum
import rpm
@ -189,6 +187,7 @@ EXAMPLES = '''
BUFSIZE = 65536
def_qf = "%{name}-%{version}-%{release}.%{arch}"
rpmbin = None
def yum_base(conf_file=None):
@ -232,8 +231,8 @@ def is_installed(module, repoq, pkgspec, conf_file, qf=def_qf, en_repos=None, di
en_repos = []
if dis_repos is None:
dis_repos = []
if not repoq:
if not repoq:
pkgs = []
try:
my = yum_base(conf_file)
@ -241,10 +240,10 @@ def is_installed(module, repoq, pkgspec, conf_file, qf=def_qf, en_repos=None, di
my.repos.disableRepo(rid)
for rid in en_repos:
my.repos.enableRepo(rid)
e, m, u = my.rpmdb.matchPackageNames([pkgspec])
pkgs = e + m
if not pkgs:
if not pkgs and not is_pkg:
pkgs.extend(my.returnInstalledPackagesByDep(pkgspec))
except Exception, e:
module.fail_json(msg="Failure talking to yum: %s" % e)
@ -252,21 +251,31 @@ def is_installed(module, repoq, pkgspec, conf_file, qf=def_qf, en_repos=None, di
return [ po_to_nevra(p) for p in pkgs ]
else:
global rpmbin
if not rpmbin:
rpmbin = module.get_bin_path('rpm', required=True)
cmd = repoq + ["--disablerepo=*", "--pkgnarrow=installed", "--qf", qf, pkgspec]
cmd = [rpmbin, '-q', '--qf', qf, pkgspec]
rc, out, err = module.run_command(cmd)
if not is_pkg:
cmd = repoq + ["--disablerepo=*", "--pkgnarrow=installed", "--qf", qf, "--whatprovides", pkgspec]
if rc != 0 and 'is not installed' not in out:
module.fail_json(msg='Error from rpm: %s: %s' % (cmd, err))
if 'is not installed' in out:
out = ''
pkgs = [p for p in out.replace('(none)', '0').split('\n') if p.strip()]
if not pkgs and not is_pkg:
cmd = [rpmbin, '-q', '--qf', qf, '--whatprovides', pkgspec]
rc2, out2, err2 = module.run_command(cmd)
else:
rc2, out2, err2 = (0, '', '')
if rc == 0 and rc2 == 0:
out += out2
return [p for p in out.split('\n') if p.strip()]
else:
module.fail_json(msg='Error from repoquery: %s: %s' % (cmd, err + err2))
if rc2 != 0 and 'no package provides' not in out2:
module.fail_json(msg='Error from rpm: %s: %s' % (cmd, err + err2))
if 'no package provides' in out2:
out2 = ''
pkgs += [p for p in out2.replace('(none)', '0').split('\n') if p.strip()]
return pkgs
return []
def is_available(module, repoq, pkgspec, conf_file, qf=def_qf, en_repos=None, dis_repos=None):
@ -506,20 +515,22 @@ def repolist(module, repoq, qf="%{repoid}"):
def list_stuff(module, repoquerybin, conf_file, stuff):
qf = "%{name}|%{epoch}|%{version}|%{release}|%{arch}|%{repoid}"
# is_installed goes through rpm instead of repoquery so it needs a slightly different format
is_installed_qf = "%{name}|%{epoch}|%{version}|%{release}|%{arch}|installed\n"
repoq = [repoquerybin, '--show-duplicates', '--plugins', '--quiet']
if conf_file and os.path.exists(conf_file):
repoq += ['-c', conf_file]
if stuff == 'installed':
return [ pkg_to_dict(p) for p in is_installed(module, repoq, '-a', conf_file, qf=qf) if p.strip() ]
return [ pkg_to_dict(p) for p in sorted(is_installed(module, repoq, '-a', conf_file, qf=is_installed_qf)) if p.strip() ]
elif stuff == 'updates':
return [ pkg_to_dict(p) for p in is_update(module, repoq, '-a', conf_file, qf=qf) if p.strip() ]
return [ pkg_to_dict(p) for p in sorted(is_update(module, repoq, '-a', conf_file, qf=qf)) if p.strip() ]
elif stuff == 'available':
return [ pkg_to_dict(p) for p in is_available(module, repoq, '-a', conf_file, qf=qf) if p.strip() ]
return [ pkg_to_dict(p) for p in sorted(is_available(module, repoq, '-a', conf_file, qf=qf)) if p.strip() ]
elif stuff == 'repos':
return [ dict(repoid=name, state='enabled') for name in repolist(module, repoq) if name.strip() ]
return [ dict(repoid=name, state='enabled') for name in sorted(repolist(module, repoq)) if name.strip() ]
else:
return [ pkg_to_dict(p) for p in is_installed(module, repoq, stuff, conf_file, qf=qf) + is_available(module, repoq, stuff, conf_file, qf=qf) if p.strip() ]
return [ pkg_to_dict(p) for p in sorted(is_installed(module, repoq, stuff, conf_file, qf=is_installed_qf) + is_available(module, repoq, stuff, conf_file, qf=qf)) if p.strip() ]
def install(module, items, repoq, yum_basecmd, conf_file, en_repos, dis_repos):
@ -951,6 +962,7 @@ def ensure(module, state, pkgs, conf_file, enablerepo, disablerepo,
return res
def main():
# state=installed name=pkgspec
@ -1022,7 +1034,8 @@ def main():
results = ensure(module, state, pkg, params['conf_file'], enablerepo,
disablerepo, disable_gpg_check, exclude, repoquery)
if repoquery:
results['msg'] = '%s %s' % (results.get('msg',''), 'Warning: Due to potential bad behaviour with rhnplugin and certificates, used slower repoquery calls instead of Yum API.')
results['msg'] = '%s %s' % (results.get('msg',''),
'Warning: Due to potential bad behaviour with rhnplugin and certificates, used slower repoquery calls instead of Yum API.')
module.exit_json(**results)

View file

@ -471,8 +471,7 @@ class LinuxService(Service):
self.enable_cmd = location['chkconfig']
if self.enable_cmd is None:
# exiting without change on non-existent service
self.module.exit_json(changed=False, exists=False)
self.module.fail_json(msg="no service or tool found for: %s" % self.name)
# If no service control tool selected yet, try to see if 'service' is available
if self.svc_cmd is None and location.get('service', False):
@ -480,7 +479,7 @@ class LinuxService(Service):
# couldn't find anything yet
if self.svc_cmd is None and not self.svc_initscript:
self.module.exit_json(changed=False, exists=False)
self.module.fail_json(msg='cannot find \'service\' binary or init script for service, possible typo in service name?, aborting')
if location.get('initctl', False):
self.svc_initctl = location['initctl']

View file

@ -44,6 +44,10 @@ $skip_certificate_validation = Get-Attr $params "skip_certificate_validation" $f
$username = Get-Attr $params "username"
$password = Get-Attr $params "password"
$proxy_url = Get-Attr $params "proxy_url"
$proxy_username = Get-Attr $params "proxy_username"
$proxy_password = Get-Attr $params "proxy_password"
if($skip_certificate_validation){
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
}
@ -52,6 +56,14 @@ $force = Get-Attr -obj $params -name "force" "yes" | ConvertTo-Bool
If ($force -or -not (Test-Path $dest)) {
$client = New-Object System.Net.WebClient
if($proxy_url) {
$proxy_server = New-Object System.Net.WebProxy($proxy_url, $true)
if($proxy_username -and $proxy_password){
$proxy_credential = New-Object System.Net.NetworkCredential($proxy_username, $proxy_password)
$proxy_server.Credentials = $proxy_credential
}
$client.Proxy = $proxy_server
}
if($username -and $password){
$client.Credentials = New-Object System.Net.NetworkCredential($username, $password)

View file

@ -65,6 +65,24 @@ options:
- Skip SSL certificate validation if true
required: false
default: false
proxy_url:
description:
- The full URL of the proxy server to download through.
version_added: "2.0"
required: false
proxy_username:
description:
- Proxy authentication username
version_added: "2.0"
required: false
proxy_password:
description:
- Proxy authentication password
version_added: "2.0"
required: false
author:
- "Paul Durivage (@angstwad)"
- "Takeshi Kuramochi (tksarah)"
'''
EXAMPLES = '''
@ -83,4 +101,12 @@ $ ansible -i hosts -c winrm -m win_get_url -a "url=http://www.example.com/earthr
url: 'http://www.example.com/earthrise.jpg'
dest: 'C:\Users\RandomUser\earthrise.jpg'
force: no
- name: Download earthrise.jpg to 'C:\Users\RandomUser\earthrise.jpg' through a proxy server.
win_get_url:
url: 'http://www.example.com/earthrise.jpg'
dest: 'C:\Users\RandomUser\earthrise.jpg'
proxy_url: 'http://10.0.0.1:8080'
proxy_username: 'username'
proxy_password: 'password'
'''