Add temporary Ansible keystone modules
Due to the licensing issues and the modules not existing in upstream Ansible yet, I have written a simple module to fill the gaps. This also uses Keystone v2.0 for all create of users, roles and endpoints. The implementation of Keystone v3 must be discussed after the new modules arrive. Partially-Implements: blueprint ansible-service Change-Id: I389edd56741360dd26fbbc0a982f365ca27ff446
This commit is contained in:
parent
2d61572e17
commit
05a6640a0f
@ -1,301 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: docker_compose
|
||||
version_added: 1.8.4
|
||||
short_description: Manage docker containers with docker-compose
|
||||
description:
|
||||
- Manage the lifecycle of groups of docker containers with docker-compose
|
||||
options:
|
||||
command:
|
||||
description:
|
||||
- Select the compose action to perform
|
||||
required: true
|
||||
choices: ['build', 'kill', 'pull', 'rm', 'scale', 'start', 'stop', 'restart', 'up']
|
||||
compose_file:
|
||||
description:
|
||||
- Specify the compose file to build from
|
||||
required: true
|
||||
type: bool
|
||||
insecure_registry:
|
||||
description:
|
||||
- Allow access to insecure registry (HTTP or TLS with a CA not known by the Docker daemon)
|
||||
type: bool
|
||||
kill_signal:
|
||||
description:
|
||||
- The SIG to send to the docker container process when killing it
|
||||
default: SIGKILL
|
||||
no_build:
|
||||
description:
|
||||
- Do not build an image, even if it's missing
|
||||
type: bool
|
||||
no_deps:
|
||||
description:
|
||||
- Don't start linked services
|
||||
type: bool
|
||||
no_recreate:
|
||||
description:
|
||||
- If containers already exist, don't recreate them
|
||||
type: bool
|
||||
project_name:
|
||||
description:
|
||||
- Specify project name (defaults to directory name of the compose file)
|
||||
type: str
|
||||
service_names:
|
||||
description:
|
||||
- Only modify services in this list (can be used in conjunction with no_deps)
|
||||
type: list
|
||||
stop_timeout:
|
||||
description:
|
||||
- The amount of time in seconds to wait for an instance to cleanly terminate before killing it (can be used in conjunction with stop_timeout)
|
||||
default: 10
|
||||
type: int
|
||||
|
||||
author: Sam Yaple
|
||||
requirements: [ "docker-compose", "docker >= 1.3" ]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
Compose web application:
|
||||
|
||||
- hosts: web
|
||||
tasks:
|
||||
- name: compose super important weblog
|
||||
docker_compose: command="up" compose_file="/opt/compose/weblog.yml"
|
||||
|
||||
Compose only mysql server from previous example:
|
||||
|
||||
- hosts: web
|
||||
tasks:
|
||||
- name: compose mysql
|
||||
docker_compose: command="up" compose_file="/opt/compose/weblog.yml" service_names=['mysql']
|
||||
|
||||
Compose project with specified prefix:
|
||||
|
||||
- hosts: web
|
||||
tasks:
|
||||
- name: compose weblog named "myproject_weblog_1"
|
||||
docker_compose: command="up" compose_file="/opt/compose/weblog.yml" project_name="myproject"
|
||||
|
||||
Compose project only if already built (or no build instructions). Explicitly refuse to build the container image(s):
|
||||
|
||||
- hosts: web
|
||||
tasks:
|
||||
- name: compose weblog
|
||||
docker_compose: command="up" compose_file="/opt/compose/weblog.yml" no_build=True
|
||||
|
||||
Allow the container image to be pulled from an insecure registry (this requires the docker daemon to allow the insecure registry as well):
|
||||
|
||||
- hosts: web
|
||||
tasks:
|
||||
- name: compose weblog from local registry
|
||||
docker_compose: command="up" compose_file="/opt/compose/weblog.yml" insecure_registry=True
|
||||
|
||||
Start the containers in the compose project, but do not create any:
|
||||
|
||||
- hosts: web
|
||||
tasks:
|
||||
- name: compose weblog
|
||||
docker_compose: command="start" compose_file="/opt/compose/weblog.yml"
|
||||
|
||||
Removed all the containers associated with a project; only wait 5 seconds for the container to respond to a SIGTERM:
|
||||
|
||||
- hosts: web
|
||||
tasks:
|
||||
- name: Destroy ALL containers for project "devweblog"
|
||||
docker_compose: command="rm" stop_timeout=5 compose_file="/opt/compose/weblog.yml" project_name="devweblog"
|
||||
'''
|
||||
|
||||
HAS_DOCKER_COMPOSE = True
|
||||
|
||||
import re
|
||||
|
||||
try:
|
||||
from compose import config
|
||||
from compose.cli.command import Command as compose_command
|
||||
from compose.cli.docker_client import docker_client
|
||||
except ImportError, e:
|
||||
HAS_DOCKER_COMPOSE = False
|
||||
|
||||
TIMEMAP = {
|
||||
'second': 0,
|
||||
'seconds': 0,
|
||||
'minute': 1,
|
||||
'minutes': 1,
|
||||
'hour': 2,
|
||||
'hours': 2,
|
||||
'weeks': 3,
|
||||
'months': 4,
|
||||
'years': 5,
|
||||
}
|
||||
|
||||
class DockerComposer:
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
self.compose_file = self.module.params.get('compose_file')
|
||||
self.insecure_registry = self.module.params.get('insecure_registry')
|
||||
self.no_build = self.module.params.get('no_build')
|
||||
self.no_cache = self.module.params.get('no_cache')
|
||||
self.no_deps = self.module.params.get('no_deps')
|
||||
self.no_recreate = self.module.params.get('no_recreate')
|
||||
self.project_name = self.module.params.get('project_name')
|
||||
self.stop_timeout = self.module.params.get('stop_timeout')
|
||||
self.scale = self.module.params.get('scale')
|
||||
self.service_names = self.module.params.get('service_names')
|
||||
|
||||
|
||||
self.project = compose_command.get_project(compose_command(),
|
||||
self.compose_file,
|
||||
self.project_name,
|
||||
)
|
||||
self.containers = self.project.client.containers(all=True)
|
||||
|
||||
|
||||
def build(self):
|
||||
self.project.build(no_cache = self.no_cache,
|
||||
service_names = self.service_names,
|
||||
)
|
||||
|
||||
def kill(self):
|
||||
self.project.kill(signal = self.kill_signal,
|
||||
service_names = self.service_names,
|
||||
)
|
||||
|
||||
def pull(self):
|
||||
self.project.pull(insecure_registry = self.insecure_registry,
|
||||
service_names = self.service_names,
|
||||
)
|
||||
|
||||
def rm(self):
|
||||
self.stop()
|
||||
self.project.remove_stopped(service_names = self.service_names)
|
||||
|
||||
def scale(self):
|
||||
for s in self.service_names:
|
||||
try:
|
||||
num = int(self.scale[s])
|
||||
except ValueError:
|
||||
msg = ('Value for scaling service "%s" should be an int, but '
|
||||
'value "%s" was recieved' % (s, self.scale[s]))
|
||||
module.fail_json(msg=msg, failed=True)
|
||||
|
||||
try:
|
||||
project.get_service(s).scale(num)
|
||||
except CannotBeScaledError:
|
||||
msg = ('Service "%s" cannot be scaled because it specifies a '
|
||||
'port on the host.' % s)
|
||||
module.fail_json(msg=msg, failed=True)
|
||||
|
||||
def start(self):
|
||||
self.project.start(service_names = self.service_names)
|
||||
|
||||
def stop(self):
|
||||
self.project.stop(service_names = self.service_names,
|
||||
timeout = self.stop_timeout,
|
||||
)
|
||||
|
||||
def restart(self):
|
||||
self.project.restart(service_names = self.service_names)
|
||||
|
||||
def up(self):
|
||||
self.project_contains = self.project.up(
|
||||
do_build = not self.no_build,
|
||||
insecure_registry = self.insecure_registry,
|
||||
allow_recreate = not self.no_recreate,
|
||||
service_names = self.service_names,
|
||||
start_deps = not self.no_deps,
|
||||
)
|
||||
|
||||
def check_if_changed(self):
|
||||
new_containers = self.project.client.containers(all=True)
|
||||
|
||||
for pc in self.project_contains:
|
||||
old_container = None
|
||||
new_container = None
|
||||
name = '/' + re.split(' |>',str(pc))[1]
|
||||
|
||||
for c in self.containers:
|
||||
if c['Names'][0] == name:
|
||||
old_container = c
|
||||
|
||||
for c in new_containers:
|
||||
if c['Names'][0] == name:
|
||||
new_container = c
|
||||
|
||||
if not old_container or not new_container:
|
||||
return True
|
||||
|
||||
if old_container['Created'] != new_container['Created']:
|
||||
return True
|
||||
|
||||
old_status = re.split(' ', old_container['Status'])
|
||||
new_status = re.split(' ', new_container['Status'])
|
||||
|
||||
if old_status[0] != new_status[0]:
|
||||
return True
|
||||
|
||||
if old_status[0] == 'Up':
|
||||
if TIMEMAP[old_status[-1]] > TIMEMAP[new_status[-1]]:
|
||||
return True
|
||||
else:
|
||||
if TIMEMAP[old_status[-2]] < TIMEMAP[new_status[-2]]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def check_dependencies(module):
|
||||
if not HAS_DOCKER_COMPOSE:
|
||||
msg = ('`docker-compose` does not seem to be installed, but is required '
|
||||
'by the Ansible docker-compose module.')
|
||||
module.fail_json(failed=True, msg=msg)
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
command = dict(required=True,
|
||||
choices=['build', 'kill', 'pull', 'rm',
|
||||
'scale', 'start', 'stop',
|
||||
'restart', 'up']
|
||||
),
|
||||
compose_file = dict(required=True, type='str'),
|
||||
insecure_registry = dict(type='bool'),
|
||||
kill_signal = dict(default='SIGKILL'),
|
||||
no_build = dict(type='bool'),
|
||||
no_cache = dict(type='bool'),
|
||||
no_deps = dict(type='bool'),
|
||||
no_recreate = dict(type='bool'),
|
||||
project_name = dict(type='str'),
|
||||
scale = dict(type='dict'),
|
||||
service_names = dict(type='list'),
|
||||
stop_timeout = dict(default=10, type='int')
|
||||
)
|
||||
)
|
||||
|
||||
check_dependencies(module)
|
||||
|
||||
|
||||
try:
|
||||
composer = DockerComposer(module)
|
||||
command = getattr(composer, module.params.get('command'))
|
||||
|
||||
command()
|
||||
|
||||
changed = composer.check_if_changed()
|
||||
|
||||
module.exit_json(changed=changed)
|
||||
except Exception, e:
|
||||
try:
|
||||
changed = composer.check_if_changed()
|
||||
except Exception:
|
||||
changed = True
|
||||
|
||||
module.exit_json(failed=True, changed=changed, msg=repr(e))
|
||||
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
84
ansible/library/kolla_keystone_service.py
Normal file
84
ansible/library/kolla_keystone_service.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Copyright 2015 Sam Yaple
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This file is a barebones file needed to file a gap until Ansible 2.0. No error
|
||||
# checking, no deletions, no updates. Idempotent creation only.
|
||||
|
||||
# If you look closely, you will see we arent _really_ using the shade module
|
||||
# we just use it to slightly abstract the authentication model. As patches land
|
||||
# in upstream shade we will be able to use more of the shade module. Until then
|
||||
# if we want to be 'stable' we really need to be using it as a passthrough
|
||||
|
||||
import shade
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = openstack_full_argument_spec(
|
||||
description = dict(required=True, type='str'),
|
||||
service_name = dict(required=True, type='str'),
|
||||
service_type = dict(required=True, type='str'),
|
||||
admin_url = dict(required=True, type='str'),
|
||||
internal_url = dict(required=True, type='str'),
|
||||
public_url = dict(required=True, type='str'),
|
||||
endpoint_region = dict(required=True, type='str')
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
description = module.params.pop('description')
|
||||
service_name = module.params.pop('service_name')
|
||||
service_type = module.params.pop('service_type')
|
||||
admin_url = module.params.pop('admin_url')
|
||||
internal_url = module.params.pop('internal_url')
|
||||
public_url = module.params.pop('public_url')
|
||||
endpoint_region = module.params.pop('endpoint_region')
|
||||
|
||||
changed = False
|
||||
service = None
|
||||
endpoint = None
|
||||
|
||||
cloud = shade.operator_cloud(**module.params)
|
||||
|
||||
for _service in cloud.keystone_client.services.list():
|
||||
if _service.type == service_type:
|
||||
service = _service
|
||||
|
||||
if service is not None:
|
||||
for _endpoint in cloud.keystone_client.endpoints.list():
|
||||
if _endpoint.service_id == service.id:
|
||||
endpoint = _endpoint
|
||||
else:
|
||||
service = cloud.keystone_client.services.create(name=service_name,
|
||||
service_type=service_type,
|
||||
description=description)
|
||||
|
||||
if endpoint is None:
|
||||
changed = True
|
||||
cloud.keystone_client.endpoints.create(service_id=service.id,
|
||||
adminurl=admin_url,
|
||||
internalurl=public_url,
|
||||
publicurl=public_url,
|
||||
region=endpoint_region)
|
||||
|
||||
module.exit_json(changed=changed)
|
||||
except Exception as e:
|
||||
module.exit_json(failed=True, changed=True, msg=e)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.openstack import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
83
ansible/library/kolla_keystone_user.py
Normal file
83
ansible/library/kolla_keystone_user.py
Normal file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# Copyright 2015 Sam Yaple
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This file is a barebones file needed to file a gap until Ansible 2.0. No error
|
||||
# checking, no deletions, no updates. Idempotent creation only.
|
||||
|
||||
# If you look closely, you will see we arent _really_ using the shade module
|
||||
# we just use it to slightly abstract the authentication model. As patches land
|
||||
# in upstream shade we will be able to use more of the shade module. Until then
|
||||
# if we want to be 'stable' we really need to be using it as a passthrough
|
||||
|
||||
import shade
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = openstack_full_argument_spec(
|
||||
password = dict(required=True, type='str'),
|
||||
project = dict(required=True, type='str'),
|
||||
role = dict(required=True, type='str'),
|
||||
user = dict(required=True, type='str')
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
password = module.params.pop('password')
|
||||
project_name = module.params.pop('project')
|
||||
role_name = module.params.pop('role')
|
||||
user_name = module.params.pop('user')
|
||||
|
||||
changed = False
|
||||
project = None
|
||||
role = None
|
||||
user = None
|
||||
|
||||
cloud = shade.operator_cloud(**module.params)
|
||||
|
||||
for _project in cloud.keystone_client.tenants.list():
|
||||
if _project.name == project_name:
|
||||
project = _project
|
||||
|
||||
for _role in cloud.keystone_client.roles.list():
|
||||
if _role.name == role_name:
|
||||
role = _role
|
||||
|
||||
for _user in cloud.keystone_client.users.list():
|
||||
if _user.name == user_name:
|
||||
user = _user
|
||||
|
||||
if not project:
|
||||
changed = True
|
||||
project = cloud.keystone_client.tenants.create(tenant_name=project_name)
|
||||
|
||||
if not role:
|
||||
changed = True
|
||||
role = cloud.keystone_client.roles.create(name=role_name)
|
||||
|
||||
if not user:
|
||||
changed = True
|
||||
user = cloud.keystone_client.users.create(name=user_name, password=password, tenant_id=project.id)
|
||||
cloud.keystone_client.roles.add_user_role(role=role.id, user=user.id, tenant=project.id)
|
||||
|
||||
module.exit_json(changed=changed)
|
||||
except Exception as e:
|
||||
module.exit_json(failed=True, changed=True, msg=e)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.openstack import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,8 +1,8 @@
|
||||
---
|
||||
- include: register.yml
|
||||
|
||||
- include: config.yml
|
||||
|
||||
- include: bootstrap.yml
|
||||
|
||||
- include: start.yml
|
||||
|
||||
#- include: register.yml
|
||||
|
@ -1,12 +1,21 @@
|
||||
---
|
||||
# NB: Not an Attorney
|
||||
#
|
||||
# Upstream ansible will have all of the new modules we need based on
|
||||
# the shade library. They are written, but the keystone modules haven't3
|
||||
# been merged yet. None of the modules will land before Ansible 2.0.
|
||||
#
|
||||
# These new modules will be relicensed using ASL2.0 as the result of a
|
||||
# gentlemen's agreement that the Kolla authors will not alter the Shade code.
|
||||
# This does not place additional restrictions on the license of this work. The
|
||||
# relicense agreement is based upon trust, not something legally binding and
|
||||
# has no binding impact on the license of Kolla..
|
||||
- name: Creating the Glance service and endpoint
|
||||
kolla_keystone_service:
|
||||
service_name: "glance"
|
||||
service_type: "image"
|
||||
description: "Openstack Image"
|
||||
endpoint_region: "{{ openstack_region_name }}"
|
||||
admin_url: "http://{{ kolla_internal_address }}:{{ glance_api_port }}"
|
||||
internal_url: "http://{{ kolla_internal_address }}:{{ glance_api_port }}"
|
||||
public_url: "http://{{ kolla_external_address }}:{{ glance_api_port }}"
|
||||
auth: "{{ openstack_auth_v2 }}"
|
||||
region_name: "{{ openstack_region_name }}"
|
||||
|
||||
- name: Creating the Glance project, user, and role
|
||||
kolla_keystone_user:
|
||||
project: "service"
|
||||
user: "glance"
|
||||
password: "{{ glance_keystone_password }}"
|
||||
role: "admin"
|
||||
auth: "{{ openstack_auth_v2 }}"
|
||||
region_name: "{{ openstack_region_name }}"
|
||||
|
@ -72,6 +72,20 @@ openstack_region_name: "RegionOne"
|
||||
keystone_public_port: "5000"
|
||||
keystone_admin_port: "35357"
|
||||
|
||||
openstack_auth:
|
||||
auth_url: "http://{{ kolla_internal_address }}:{{ keystone_admin_port }}"
|
||||
username: "admin"
|
||||
password: "{{ keystone_admin_password }}"
|
||||
project_name: "admin"
|
||||
|
||||
# This shouldn't be needed for long. It is only temporary until we get the
|
||||
# ansible modules sorted out
|
||||
openstack_auth_v2:
|
||||
auth_url: "http://{{ kolla_internal_address }}:{{ keystone_admin_port }}/v2.0"
|
||||
username: "admin"
|
||||
password: "{{ keystone_admin_password }}"
|
||||
project_name: "admin"
|
||||
|
||||
|
||||
####################
|
||||
# RabbitMQ options
|
||||
|
Loading…
Reference in New Issue
Block a user