Merge branch 'devel' of https://github.com/ansible/ansible-modules-core into HEAD
This commit is contained in:
commit
6cbcb8f8ae
17 changed files with 732 additions and 518 deletions
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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" %
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
'''
|
||||
|
|
Loading…
Add table
Reference in a new issue