summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--filter_plugins/oo_filters.py83
-rw-r--r--playbooks/aws/openshift-cluster/launch.yml1
-rw-r--r--playbooks/aws/openshift-cluster/library/ec2_ami_find.py302
-rw-r--r--playbooks/aws/openshift-cluster/tasks/launch_instances.yml76
-rw-r--r--playbooks/aws/openshift-cluster/vars.defaults.yml1
-rw-r--r--playbooks/aws/openshift-cluster/vars.online.int.yml9
-rw-r--r--playbooks/aws/openshift-cluster/vars.online.prod.yml9
-rw-r--r--playbooks/aws/openshift-cluster/vars.online.stage.yml9
-rw-r--r--playbooks/aws/openshift-cluster/vars.yml13
-rw-r--r--roles/openshift_master/tasks/main.yml18
-rwxr-xr-xroles/openshift_register_nodes/library/kubernetes_register_node.py413
-rw-r--r--roles/openshift_register_nodes/tasks/main.yml3
12 files changed, 779 insertions, 158 deletions
diff --git a/filter_plugins/oo_filters.py b/filter_plugins/oo_filters.py
index cf30cde9a..d22b6d188 100644
--- a/filter_plugins/oo_filters.py
+++ b/filter_plugins/oo_filters.py
@@ -1,14 +1,17 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# vim: expandtab:tabstop=4:shiftwidth=4
+'''
+Custom filters for use in openshift-ansible
+'''
-from ansible import errors, runner
-import json
+from ansible import errors
+from operator import itemgetter
import pdb
-import re
def oo_pdb(arg):
- ''' This pops you into a pdb instance where arg is the data passed in from the filter.
+ ''' This pops you into a pdb instance where arg is the data passed in
+ from the filter.
Ex: "{{ hostvars | oo_pdb }}"
'''
pdb.set_trace()
@@ -21,7 +24,8 @@ def oo_len(arg):
return len(arg)
def get_attr(data, attribute=None):
- ''' This looks up dictionary attributes of the form a.b.c and returns the value.
+ ''' This looks up dictionary attributes of the form a.b.c and returns
+ the value.
Ex: data = {'a': {'b': {'c': 5}}}
attribute = "a.b.c"
returns 5
@@ -41,12 +45,13 @@ def oo_flatten(data):
if not issubclass(type(data), list):
raise errors.AnsibleFilterError("|failed expects to flatten a List")
- return [ item for sublist in data for item in sublist ]
+ return [item for sublist in data for item in sublist]
-def oo_collect(data, attribute=None, filters={}):
- ''' This takes a list of dict and collects all attributes specified into a list
- If filter is specified then we will include all items that match _ALL_ of filters.
+def oo_collect(data, attribute=None, filters=None):
+ ''' This takes a list of dict and collects all attributes specified into a
+ list If filter is specified then we will include all items that match
+ _ALL_ of filters.
Ex: data = [ {'a':1, 'b':5, 'z': 'z'}, # True, return
{'a':2, 'z': 'z'}, # True, return
{'a':3, 'z': 'z'}, # True, return
@@ -56,15 +61,18 @@ def oo_collect(data, attribute=None, filters={}):
filters = {'z': 'z'}
returns [1, 2, 3]
'''
-
if not issubclass(type(data), list):
raise errors.AnsibleFilterError("|failed expects to filter on a List")
if not attribute:
raise errors.AnsibleFilterError("|failed expects attribute to be set")
- if filters:
- retval = [get_attr(d, attribute) for d in data if all([ d[key] == filters[key] for key in filters ]) ]
+ if filters is not None:
+ if not issubclass(type(filters), dict):
+ raise errors.AnsibleFilterError("|fialed expects filter to be a"
+ " dict")
+ retval = [get_attr(d, attribute) for d in data if (
+ all([d[key] == filters[key] for key in filters]))]
else:
retval = [get_attr(d, attribute) for d in data]
@@ -78,7 +86,7 @@ def oo_select_keys(data, keys):
'''
if not issubclass(type(data), dict):
- raise errors.AnsibleFilterError("|failed expects to filter on a Dictionary")
+ raise errors.AnsibleFilterError("|failed expects to filter on a dict")
if not issubclass(type(keys), list):
raise errors.AnsibleFilterError("|failed expects first param is a list")
@@ -98,30 +106,43 @@ def oo_prepend_strings_in_list(data, prepend):
if not issubclass(type(data), list):
raise errors.AnsibleFilterError("|failed expects first param is a list")
if not all(isinstance(x, basestring) for x in data):
- raise errors.AnsibleFilterError("|failed expects first param is a list of strings")
+ raise errors.AnsibleFilterError("|failed expects first param is a list"
+ " of strings")
retval = [prepend + s for s in data]
return retval
-def oo_get_deployment_type_from_groups(data):
- ''' This takes a list of groups and returns the associated
- deployment-type
+def oo_ami_selector(data, image_name):
+ ''' This takes a list of amis and an image name and attempts to return
+ the latest ami.
'''
if not issubclass(type(data), list):
raise errors.AnsibleFilterError("|failed expects first param is a list")
- regexp = re.compile('^tag_deployment-type[-_]')
- matches = filter(regexp.match, data)
- if len(matches) > 0:
- return regexp.sub('', matches[0])
- return "Unknown"
-class FilterModule (object):
+ if not data:
+ return None
+ else:
+ if image_name is None or not image_name.endswith('_*'):
+ ami = sorted(data, key=itemgetter('name'), reverse=True)[0]
+ return ami['ami_id']
+ else:
+ ami_info = [(ami, ami['name'].split('_')[-1]) for ami in data]
+ ami = sorted(ami_info, key=itemgetter(1), reverse=True)[0][0]
+ return ami['ami_id']
+
+# disabling pylint checks for too-few-public-methods and no-self-use since we
+# need to expose a FilterModule object that has a filters method that returns
+# a mapping of filter names to methods.
+# pylint: disable=too-few-public-methods, no-self-use
+class FilterModule(object):
+ ''' FilterModule '''
def filters(self):
+ ''' returns a mapping of filters to methods '''
return {
- "oo_select_keys": oo_select_keys,
- "oo_collect": oo_collect,
- "oo_flatten": oo_flatten,
- "oo_len": oo_len,
- "oo_pdb": oo_pdb,
- "oo_prepend_strings_in_list": oo_prepend_strings_in_list,
- "oo_get_deployment_type_from_groups": oo_get_deployment_type_from_groups
- }
+ "oo_select_keys": oo_select_keys,
+ "oo_collect": oo_collect,
+ "oo_flatten": oo_flatten,
+ "oo_len": oo_len,
+ "oo_pdb": oo_pdb,
+ "oo_prepend_strings_in_list": oo_prepend_strings_in_list,
+ "oo_ami_selector": oo_ami_selector
+ }
diff --git a/playbooks/aws/openshift-cluster/launch.yml b/playbooks/aws/openshift-cluster/launch.yml
index a0de00fc3..3eb5496e4 100644
--- a/playbooks/aws/openshift-cluster/launch.yml
+++ b/playbooks/aws/openshift-cluster/launch.yml
@@ -5,6 +5,7 @@
gather_facts: no
vars_files:
- vars.yml
+ - ["vars.{{ deployment_type }}.{{ cluster_id }}.yml", vars.defaults.yml]
tasks:
- fail:
msg: Deployment type not supported for aws provider yet
diff --git a/playbooks/aws/openshift-cluster/library/ec2_ami_find.py b/playbooks/aws/openshift-cluster/library/ec2_ami_find.py
new file mode 100644
index 000000000..29e594a65
--- /dev/null
+++ b/playbooks/aws/openshift-cluster/library/ec2_ami_find.py
@@ -0,0 +1,302 @@
+#!/usr/bin/python
+#pylint: skip-file
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: ec2_ami_find
+version_added: 2.0
+short_description: Searches for AMIs to obtain the AMI ID and other information
+description:
+ - Returns list of matching AMIs with AMI ID, along with other useful information
+ - Can search AMIs with different owners
+ - Can search by matching tag(s), by AMI name and/or other criteria
+ - Results can be sorted and sliced
+author: Tom Bamford
+notes:
+ - This module is not backwards compatible with the previous version of the ec2_search_ami module which worked only for Ubuntu AMIs listed on cloud-images.ubuntu.com.
+ - See the example below for a suggestion of how to search by distro/release.
+options:
+ region:
+ description:
+ - The AWS region to use.
+ required: true
+ aliases: [ 'aws_region', 'ec2_region' ]
+ owner:
+ description:
+ - Search AMIs owned by the specified owner
+ - Can specify an AWS account ID, or one of the special IDs 'self', 'amazon' or 'aws-marketplace'
+ - If not specified, all EC2 AMIs in the specified region will be searched.
+ - You can include wildcards in many of the search options. An asterisk (*) matches zero or more characters, and a question mark (?) matches exactly one character. You can escape special characters using a backslash (\) before the character. For example, a value of \*amazon\?\\ searches for the literal string *amazon?\.
+ required: false
+ default: null
+ ami_id:
+ description:
+ - An AMI ID to match.
+ default: null
+ required: false
+ ami_tags:
+ description:
+ - A hash/dictionary of tags to match for the AMI.
+ default: null
+ required: false
+ architecture:
+ description:
+ - An architecture type to match (e.g. x86_64).
+ default: null
+ required: false
+ hypervisor:
+ description:
+ - A hypervisor type type to match (e.g. xen).
+ default: null
+ required: false
+ is_public:
+ description:
+ - Whether or not the image(s) are public.
+ choices: ['yes', 'no']
+ default: null
+ required: false
+ name:
+ description:
+ - An AMI name to match.
+ default: null
+ required: false
+ platform:
+ description:
+ - Platform type to match.
+ default: null
+ required: false
+ sort:
+ description:
+ - Optional attribute which with to sort the results.
+ - If specifying 'tag', the 'tag_name' parameter is required.
+ choices: ['name', 'description', 'tag']
+ default: null
+ required: false
+ sort_tag:
+ description:
+ - Tag name with which to sort results.
+ - Required when specifying 'sort=tag'.
+ default: null
+ required: false
+ sort_order:
+ description:
+ - Order in which to sort results.
+ - Only used when the 'sort' parameter is specified.
+ choices: ['ascending', 'descending']
+ default: 'ascending'
+ required: false
+ sort_start:
+ description:
+ - Which result to start with (when sorting).
+ - Corresponds to Python slice notation.
+ default: null
+ required: false
+ sort_end:
+ description:
+ - Which result to end with (when sorting).
+ - Corresponds to Python slice notation.
+ default: null
+ required: false
+ state:
+ description:
+ - AMI state to match.
+ default: 'available'
+ required: false
+ virtualization_type:
+ description:
+ - Virtualization type to match (e.g. hvm).
+ default: null
+ required: false
+ no_result_action:
+ description:
+ - What to do when no results are found.
+ - "'success' reports success and returns an empty array"
+ - "'fail' causes the module to report failure"
+ choices: ['success', 'fail']
+ default: 'success'
+ required: false
+requirements:
+ - boto
+
+'''
+
+EXAMPLES = '''
+# Note: These examples do not set authentication details, see the AWS Guide for details.
+
+# Search for the AMI tagged "project:website"
+- ec2_ami_find:
+ owner: self
+ tags:
+ project: website
+ no_result_action: fail
+ register: ami_find
+
+# Search for the latest Ubuntu 14.04 AMI
+- ec2_ami_find:
+ name: "ubuntu/images/ebs/ubuntu-trusty-14.04-amd64-server-*"
+ owner: 099720109477
+ sort: name
+ sort_order: descending
+ sort_end: 1
+ register: ami_find
+
+# Launch an EC2 instance
+- ec2:
+ image: "{{ ami_search.results[0].ami_id }}"
+ instance_type: m3.medium
+ key_name: mykey
+ wait: yes
+'''
+
+try:
+ import boto.ec2
+ HAS_BOTO=True
+except ImportError:
+ HAS_BOTO=False
+
+import json
+
+def main():
+ argument_spec = ec2_argument_spec()
+ argument_spec.update(dict(
+ region = dict(required=True,
+ aliases = ['aws_region', 'ec2_region']),
+ owner = dict(required=False, default=None),
+ ami_id = dict(required=False),
+ ami_tags = dict(required=False, type='dict',
+ aliases = ['search_tags', 'image_tags']),
+ architecture = dict(required=False),
+ hypervisor = dict(required=False),
+ is_public = dict(required=False),
+ name = dict(required=False),
+ platform = dict(required=False),
+ sort = dict(required=False, default=None,
+ choices=['name', 'description', 'tag']),
+ sort_tag = dict(required=False),
+ sort_order = dict(required=False, default='ascending',
+ choices=['ascending', 'descending']),
+ sort_start = dict(required=False),
+ sort_end = dict(required=False),
+ state = dict(required=False, default='available'),
+ virtualization_type = dict(required=False),
+ no_result_action = dict(required=False, default='success',
+ choices = ['success', 'fail']),
+ )
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ )
+
+ if not HAS_BOTO:
+ module.fail_json(msg='boto required for this module, install via pip or your package manager')
+
+ ami_id = module.params.get('ami_id')
+ ami_tags = module.params.get('ami_tags')
+ architecture = module.params.get('architecture')
+ hypervisor = module.params.get('hypervisor')
+ is_public = module.params.get('is_public')
+ name = module.params.get('name')
+ owner = module.params.get('owner')
+ platform = module.params.get('platform')
+ sort = module.params.get('sort')
+ sort_tag = module.params.get('sort_tag')
+ sort_order = module.params.get('sort_order')
+ sort_start = module.params.get('sort_start')
+ sort_end = module.params.get('sort_end')
+ state = module.params.get('state')
+ virtualization_type = module.params.get('virtualization_type')
+ no_result_action = module.params.get('no_result_action')
+
+ filter = {'state': state}
+
+ if ami_id:
+ filter['image_id'] = ami_id
+ if ami_tags:
+ for tag in ami_tags:
+ filter['tag:'+tag] = ami_tags[tag]
+ if architecture:
+ filter['architecture'] = architecture
+ if hypervisor:
+ filter['hypervisor'] = hypervisor
+ if is_public:
+ filter['is_public'] = is_public
+ if name:
+ filter['name'] = name
+ if platform:
+ filter['platform'] = platform
+ if virtualization_type:
+ filter['virtualization_type'] = virtualization_type
+
+ ec2 = ec2_connect(module)
+
+ images_result = ec2.get_all_images(owners=owner, filters=filter)
+
+ if no_result_action == 'fail' and len(images_result) == 0:
+ module.fail_json(msg="No AMIs matched the attributes: %s" % json.dumps(filter))
+
+ results = []
+ for image in images_result:
+ data = {
+ 'ami_id': image.id,
+ 'architecture': image.architecture,
+ 'description': image.description,
+ 'is_public': image.is_public,
+ 'name': image.name,
+ 'owner_id': image.owner_id,
+ 'platform': image.platform,
+ 'root_device_name': image.root_device_name,
+ 'root_device_type': image.root_device_type,
+ 'state': image.state,
+ 'tags': image.tags,
+ 'virtualization_type': image.virtualization_type,
+ }
+
+ if image.kernel_id:
+ data['kernel_id'] = image.kernel_id
+ if image.ramdisk_id:
+ data['ramdisk_id'] = image.ramdisk_id
+
+ results.append(data)
+
+ if sort == 'tag':
+ if not sort_tag:
+ module.fail_json(msg="'sort_tag' option must be given with 'sort=tag'")
+ results.sort(key=lambda e: e['tags'][sort_tag], reverse=(sort_order=='descending'))
+ elif sort:
+ results.sort(key=lambda e: e[sort], reverse=(sort_order=='descending'))
+
+ try:
+ if sort and sort_start and sort_end:
+ results = results[int(sort_start):int(sort_end)]
+ elif sort and sort_start:
+ results = results[int(sort_start):]
+ elif sort and sort_end:
+ results = results[:int(sort_end)]
+ except TypeError:
+ module.fail_json(msg="Please supply numeric values for sort_start and/or sort_end")
+
+ module.exit_json(results=results)
+
+# import module snippets
+from ansible.module_utils.basic import *
+from ansible.module_utils.ec2 import *
+
+if __name__ == '__main__':
+ main()
+
diff --git a/playbooks/aws/openshift-cluster/tasks/launch_instances.yml b/playbooks/aws/openshift-cluster/tasks/launch_instances.yml
index 34172396a..39ad9d089 100644
--- a/playbooks/aws/openshift-cluster/tasks/launch_instances.yml
+++ b/playbooks/aws/openshift-cluster/tasks/launch_instances.yml
@@ -4,32 +4,64 @@
env: "{{ cluster }}"
env_host_type: "{{ cluster }}-openshift-{{ type }}"
host_type: "{{ type }}"
- machine_type: "{{ lookup('env', 'ec2_instance_type')
- | default(deployment_vars[deployment_type].type, true) }}"
- machine_image: "{{ lookup('env', 'ec2_ami')
- | default(deployment_vars[deployment_type].image, true) }}"
- machine_region: "{{ lookup('env', 'ec2_region')
- | default(deployment_vars[deployment_type].region, true) }}"
- machine_keypair: "{{ lookup('env', 'ec2_keypair')
- | default(deployment_vars[deployment_type].keypair, true) }}"
- machine_subnet: "{{ lookup('env', 'ec2_vpc_subnet')
- | default(deployment_vars[deployment_type].vpc_subnet, true) }}"
- machine_public_ip: "{{ lookup('env', 'ec2_public_ip')
- | default(deployment_vars[deployment_type].assign_public_ip, true) }}"
- security_groups: "{{ lookup('env', 'ec2_security_groups')
- | default(deployment_vars[deployment_type].security_groups, true) }}"
+
+- set_fact:
+ ec2_region: "{{ lookup('env', 'ec2_region')
+ | default(deployment_vars[deployment_type].region, true) }}"
+ when: ec2_region is not defined
+- set_fact:
+ ec2_image_name: "{{ lookup('env', 'ec2_image_name')
+ | default(deployment_vars[deployment_type].image_name, true) }}"
+ when: ec2_image_name is not defined and ec2_image is not defined
+- set_fact:
+ ec2_image: "{{ lookup('env', 'ec2_image')
+ | default(deployment_vars[deployment_type].image, true) }}"
+ when: ec2_image is not defined and not ec2_image_name
+- set_fact:
+ ec2_instance_type: "{{ lookup('env', 'ec2_instance_type')
+ | default(deployment_vars[deployment_type].type, true) }}"
+ when: ec2_instance_type is not defined
+- set_fact:
+ ec2_keypair: "{{ lookup('env', 'ec2_keypair')
+ | default(deployment_vars[deployment_type].keypair, true) }}"
+ when: ec2_keypair is not defined
+- set_fact:
+ ec2_vpc_subnet: "{{ lookup('env', 'ec2_vpc_subnet')
+ | default(deployment_vars[deployment_type].vpc_subnet, true) }}"
+ when: ec2_vpc_subnet is not defined
+- set_fact:
+ ec2_assign_public_ip: "{{ lookup('env', 'ec2_assign_public_ip')
+ | default(deployment_vars[deployment_type].assign_public_ip, true) }}"
+ when: ec2_assign_public_ip is not defined
+- set_fact:
+ ec2_security_groups: "{{ lookup('env', 'ec2_security_groups')
+ | default(deployment_vars[deployment_type].security_groups, true) }}"
+ when: ec2_security_groups is not defined
+
+- name: Find amis for deployment_type
+ ec2_ami_find:
+ region: "{{ ec2_region }}"
+ ami_id: "{{ ec2_image | default(omit, true) }}"
+ name: "{{ ec2_image_name | default(omit, true) }}"
+ register: ami_result
+
+- fail: msg="Could not find requested ami"
+ when: not ami_result.results
+
+- set_fact:
+ latest_ami: "{{ ami_result.results | oo_ami_selector(ec2_image_name) }}"
- name: Launch instance(s)
ec2:
state: present
- region: "{{ machine_region }}"
- keypair: "{{ machine_keypair }}"
- group: "{{ security_groups }}"
- instance_type: "{{ machine_type }}"
- image: "{{ machine_image }}"
+ region: "{{ ec2_region }}"
+ keypair: "{{ ec2_keypair }}"
+ group: "{{ ec2_security_groups }}"
+ instance_type: "{{ ec2_instance_type }}"
+ image: "{{ latest_ami }}"
count: "{{ instances | oo_len }}"
- vpc_subnet_id: "{{ machine_subnet | default(omit, true) }}"
- assign_public_ip: "{{ machine_public_ip | default(omit, true) }}"
+ vpc_subnet_id: "{{ ec2_vpc_subnet | default(omit, true) }}"
+ assign_public_ip: "{{ ec2_assign_public_ip | default(omit, true) }}"
wait: yes
instance_tags:
created-by: "{{ created_by }}"
@@ -39,7 +71,7 @@
register: ec2
- name: Add Name tag to instances
- ec2_tag: resource={{ item.1.id }} region={{ machine_region }} state=present
+ ec2_tag: resource={{ item.1.id }} region={{ ec2_region }} state=present
with_together:
- instances
- ec2.instances
diff --git a/playbooks/aws/openshift-cluster/vars.defaults.yml b/playbooks/aws/openshift-cluster/vars.defaults.yml
new file mode 100644
index 000000000..ed97d539c
--- /dev/null
+++ b/playbooks/aws/openshift-cluster/vars.defaults.yml
@@ -0,0 +1 @@
+---
diff --git a/playbooks/aws/openshift-cluster/vars.online.int.yml b/playbooks/aws/openshift-cluster/vars.online.int.yml
new file mode 100644
index 000000000..12f79a9c1
--- /dev/null
+++ b/playbooks/aws/openshift-cluster/vars.online.int.yml
@@ -0,0 +1,9 @@
+---
+ec2_image: ami-906240f8
+ec2_image_name: libra-ops-rhel7*
+ec2_region: us-east-1
+ec2_keypair: mmcgrath_libra
+ec2_instance_type: m3.large
+ec2_security_groups: [ 'int-v3' ]
+ec2_vpc_subnet: subnet-987c0def
+ec2_assign_public_ip: yes
diff --git a/playbooks/aws/openshift-cluster/vars.online.prod.yml b/playbooks/aws/openshift-cluster/vars.online.prod.yml
new file mode 100644
index 000000000..12f79a9c1
--- /dev/null
+++ b/playbooks/aws/openshift-cluster/vars.online.prod.yml
@@ -0,0 +1,9 @@
+---
+ec2_image: ami-906240f8
+ec2_image_name: libra-ops-rhel7*
+ec2_region: us-east-1
+ec2_keypair: mmcgrath_libra
+ec2_instance_type: m3.large
+ec2_security_groups: [ 'int-v3' ]
+ec2_vpc_subnet: subnet-987c0def
+ec2_assign_public_ip: yes
diff --git a/playbooks/aws/openshift-cluster/vars.online.stage.yml b/playbooks/aws/openshift-cluster/vars.online.stage.yml
new file mode 100644
index 000000000..12f79a9c1
--- /dev/null
+++ b/playbooks/aws/openshift-cluster/vars.online.stage.yml
@@ -0,0 +1,9 @@
+---
+ec2_image: ami-906240f8
+ec2_image_name: libra-ops-rhel7*
+ec2_region: us-east-1
+ec2_keypair: mmcgrath_libra
+ec2_instance_type: m3.large
+ec2_security_groups: [ 'int-v3' ]
+ec2_vpc_subnet: subnet-987c0def
+ec2_assign_public_ip: yes
diff --git a/playbooks/aws/openshift-cluster/vars.yml b/playbooks/aws/openshift-cluster/vars.yml
index f87e7aba3..07e453f89 100644
--- a/playbooks/aws/openshift-cluster/vars.yml
+++ b/playbooks/aws/openshift-cluster/vars.yml
@@ -3,6 +3,7 @@ deployment_vars:
origin:
# fedora, since centos requires marketplace
image: ami-acd999c4
+ image_name:
region: us-east-1
ssh_user: fedora
sudo: yes
@@ -13,18 +14,20 @@ deployment_vars:
assign_public_ip:
online:
# private ami
- image: ami-906240f8
+ image: ami-7a9e9812
+ image_name: openshift-rhel7_*
region: us-east-1
ssh_user: root
sudo: no
- keypair: mmcgrath_libra
+ keypair: libra
type: m3.large
- security_groups: [ 'int-v3' ]
- vpc_subnet: subnet-987c0def
- assign_public_ip: yes
+ security_groups: [ 'public' ]
+ vpc_subnet:
+ assign_public_ip:
enterprise:
# rhel-7.1, requires cloud access subscription
image: ami-10663b78
+ image_name:
region: us-east-1
ssh_user: ec2-user
sudo: yes
diff --git a/roles/openshift_master/tasks/main.yml b/roles/openshift_master/tasks/main.yml
index da184e972..28bdda618 100644
--- a/roles/openshift_master/tasks/main.yml
+++ b/roles/openshift_master/tasks/main.yml
@@ -84,15 +84,23 @@
- name: Start and enable openshift-master
service: name=openshift-master enabled=yes state=started
-- name: Create .kube directory
+- name: Create the OpenShift client config dir(s)
file:
- path: /root/.kube
+ path: "~{{ item }}/.config/openshift"
state: directory
mode: 0700
+ owner: "{{ item }}"
+ group: "{{ item }}"
+ with_items:
+ - root
+ - "{{ ansible_ssh_user }}"
# TODO: Update this file if the contents of the source file are not present in
# the dest file, will need to make sure to ignore things that could be added
-- name: Configure root user kubeconfig
- command: cp {{ openshift_cert_dir }}/openshift-client/.kubeconfig /root/.kube/.kubeconfig
+- name: Create the OpenShift client config(s)
+ command: cp {{ openshift_cert_dir }}/openshift-client/.kubeconfig ~{{ item }}/.config/openshift/.config
args:
- creates: /root/.kube/.kubeconfig
+ creates: ~{{ item }}/.config/openshift/.config
+ with_items:
+ - root
+ - "{{ ansible_ssh_user }}"
diff --git a/roles/openshift_register_nodes/library/kubernetes_register_node.py b/roles/openshift_register_nodes/library/kubernetes_register_node.py
index 1ec977716..afa9eb27d 100755
--- a/roles/openshift_register_nodes/library/kubernetes_register_node.py
+++ b/roles/openshift_register_nodes/library/kubernetes_register_node.py
@@ -1,12 +1,21 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# vim: expandtab:tabstop=4:shiftwidth=4
+#
+# disable pylint checks
+# temporarily disabled until items can be addressed:
+# fixme - until all TODO comments have been addressed
+# permanently disabled unless someone wants to refactor the object model:
+# too-few-public-methods
+# no-self-use
+# too-many-arguments
+# too-many-locals
+# too-many-branches
+# pylint:disable=fixme, too-many-arguments, no-self-use
+# pylint:disable=too-many-locals, too-many-branches, too-few-public-methods
+"""Ansible module to register a kubernetes node to the cluster"""
import os
-import multiprocessing
-import socket
-from subprocess import check_output, Popen
-from decimal import *
DOCUMENTATION = '''
---
@@ -93,73 +102,170 @@ EXAMPLES = '''
class ClientConfigException(Exception):
+ """Client Configuration Exception"""
pass
-class ClientConfig:
+class ClientConfig(object):
+ """ Representation of a client config
+
+ Attributes:
+ config (dict): dictionary representing the client configuration
+
+ Args:
+ client_opts (list of str): client options to use
+ module (AnsibleModule):
+
+ Raises:
+ ClientConfigException:
+ """
def __init__(self, client_opts, module):
kubectl = module.params['kubectl_cmd']
- _, output, error = module.run_command(kubectl + ["config", "view", "-o", "json"] + client_opts, check_rc = True)
+ _, output, _ = module.run_command((kubectl +
+ ["config", "view", "-o", "json"] +
+ client_opts), check_rc=True)
self.config = json.loads(output)
if not (bool(self.config['clusters']) or
bool(self.config['contexts']) or
bool(self.config['current-context']) or
bool(self.config['users'])):
- raise ClientConfigException(msg="Client config missing required " \
- "values",
- output=output)
+ raise ClientConfigException(
+ "Client config missing required values: %s" % output
+ )
def current_context(self):
+ """ Gets the current context for the client config
+
+ Returns:
+ str: The current context as set in the config
+ """
return self.config['current-context']
def section_has_value(self, section_name, value):
+ """ Test if specified section contains a value
+
+ Args:
+ section_name (str): config section to test
+ value (str): value to test if present
+ Returns:
+ bool: True if successful, false otherwise
+ """
section = self.config[section_name]
if isinstance(section, dict):
return value in section
else:
val = next((item for item in section
- if item['name'] == value), None)
+ if item['name'] == value), None)
return val is not None
def has_context(self, context):
+ """ Test if specified context exists in config
+
+ Args:
+ context (str): value to test if present
+ Returns:
+ bool: True if successful, false otherwise
+ """
return self.section_has_value('contexts', context)
def has_user(self, user):
+ """ Test if specified user exists in config
+
+ Args:
+ context (str): value to test if present
+ Returns:
+ bool: True if successful, false otherwise
+ """
return self.section_has_value('users', user)
def has_cluster(self, cluster):
+ """ Test if specified cluster exists in config
+
+ Args:
+ context (str): value to test if present
+ Returns:
+ bool: True if successful, false otherwise
+ """
return self.section_has_value('clusters', cluster)
def get_value_for_context(self, context, attribute):
+ """ Get the value of attribute in context
+
+ Args:
+ context (str): context to search
+ attribute (str): attribute wanted
+ Returns:
+ str: The value for attribute in context
+ """
contexts = self.config['contexts']
if isinstance(contexts, dict):
return contexts[context][attribute]
else:
return next((c['context'][attribute] for c in contexts
- if c['name'] == context), None)
+ if c['name'] == context), None)
def get_user_for_context(self, context):
+ """ Get the user attribute in context
+
+ Args:
+ context (str): context to search
+ Returns:
+ str: The value for the attribute in context
+ """
return self.get_value_for_context(context, 'user')
def get_cluster_for_context(self, context):
+ """ Get the cluster attribute in context
+
+ Args:
+ context (str): context to search
+ Returns:
+ str: The value for the attribute in context
+ """
return self.get_value_for_context(context, 'cluster')
def get_namespace_for_context(self, context):
+ """ Get the namespace attribute in context
+
+ Args:
+ context (str): context to search
+ Returns:
+ str: The value for the attribute in context
+ """
return self.get_value_for_context(context, 'namespace')
-class Util:
+class Util(object):
+ """Utility methods"""
@staticmethod
def remove_empty_elements(mapping):
+ """ Recursively removes empty elements from a dict
+
+ Args:
+ mapping (dict): dict to remove empty attributes from
+ Returns:
+ dict: A copy of the dict with empty elements removed
+ """
if isinstance(mapping, dict):
- m = mapping.copy()
+ copy = mapping.copy()
for key, val in mapping.iteritems():
if not val:
- del m[key]
- return m
+ del copy[key]
+ return copy
else:
return mapping
-class NodeResources:
+class NodeResources(object):
+ """ Kubernetes Node Resources
+
+ Attributes:
+ resources (dict): A dictionary representing the node resources
+
+ Args:
+ version (str): kubernetes api version
+ cpu (str): string representation of the cpu resources for the node
+ memory (str): string representation of the memory resources for the
+ node
+ """
def __init__(self, version, cpu=None, memory=None):
if version == 'v1beta1':
self.resources = dict(capacity=dict())
@@ -167,10 +273,31 @@ class NodeResources:
self.resources['capacity']['memory'] = memory
def get_resources(self):
+ """ Get the dict representing the node resources
+
+ Returns:
+ dict: representation of the node resources with any empty
+ elements removed
+ """
return Util.remove_empty_elements(self.resources)
-class NodeSpec:
- def __init__(self, version, cpu=None, memory=None, cidr=None, externalID=None):
+class NodeSpec(object):
+ """ Kubernetes Node Spec
+
+ Attributes:
+ spec (dict): A dictionary representing the node resources
+
+ Args:
+ version (str): kubernetes api version
+ cpu (str): string representation of the cpu resources for the node
+ memory (str): string representation of the memory resources for the
+ node
+ cidr (str): string representation of the cidr block available for
+ the node
+ externalID (str): The external id of the node
+ """
+ def __init__(self, version, cpu=None, memory=None, cidr=None,
+ externalID=None):
if version == 'v1beta3':
self.spec = dict(podCIDR=cidr, externalID=externalID,
capacity=dict())
@@ -178,67 +305,128 @@ class NodeSpec:
self.spec['capacity']['memory'] = memory
def get_spec(self):
+ """ Get the dict representing the node spec
+
+ Returns:
+ dict: representation of the node spec with any empty elements
+ removed
+ """
return Util.remove_empty_elements(self.spec)
-class NodeStatus:
- def addAddresses(self, addressType, addresses):
- addressList = []
+class NodeStatus(object):
+ """ Kubernetes Node Status
+
+ Attributes:
+ status (dict): A dictionary representing the node status
+
+ Args:
+ version (str): kubernetes api version
+ externalIPs (list, optional): externalIPs for the node
+ internalIPs (list, optional): internalIPs for the node
+ hostnames (list, optional): hostnames for the node
+ """
+ def add_addresses(self, address_type, addresses):
+ """ Adds addresses of the specified type
+
+ Args:
+ address_type (str): address type
+ addresses (list): addresses to add
+ """
+ address_list = []
for address in addresses:
- addressList.append(dict(type=addressType, address=address))
- return addressList
+ address_list.append(dict(type=address_type, address=address))
+ return address_list
- def __init__(self, version, externalIPs = [], internalIPs = [],
- hostnames = []):
+ def __init__(self, version, externalIPs=None, internalIPs=None,
+ hostnames=None):
if version == 'v1beta3':
- self.status = dict(addresses = addAddresses('ExternalIP',
- externalIPs) +
- addAddresses('InternalIP',
- internalIPs) +
- addAddresses('Hostname',
- hostnames))
+ addresses = []
+ if externalIPs is not None:
+ addresses += self.add_addresses('ExternalIP', externalIPs)
+ if internalIPs is not None:
+ addresses += self.add_addresses('InternalIP', internalIPs)
+ if hostnames is not None:
+ addresses += self.add_addresses('Hostname', hostnames)
+
+ self.status = dict(addresses=addresses)
def get_status(self):
+ """ Get the dict representing the node status
+
+ Returns:
+ dict: representation of the node status with any empty elements
+ removed
+ """
return Util.remove_empty_elements(self.status)
-class Node:
- def __init__(self, module, client_opts, version='v1beta1', name=None,
- hostIP = None, hostnames=[], externalIPs=[], internalIPs=[],
- cpu=None, memory=None, labels=dict(), annotations=dict(),
- podCIDR=None, externalID=None):
+class Node(object):
+ """ Kubernetes Node
+
+ Attributes:
+ status (dict): A dictionary representing the node
+
+ Args:
+ module (AnsibleModule):
+ client_opts (list): client connection options
+ version (str, optional): kubernetes api version
+ node_name (str, optional): name for node
+ hostIP (str, optional): node host ip
+ hostnames (list, optional): hostnames for the node
+ externalIPs (list, optional): externalIPs for the node
+ internalIPs (list, optional): internalIPs for the node
+ cpu (str, optional): cpu resources for the node
+ memory (str, optional): memory resources for the node
+ labels (list, optional): labels for the node
+ annotations (list, optional): annotations for the node
+ podCIDR (list, optional): cidr block to use for pods
+ externalID (str, optional): external id of the node
+ """
+ def __init__(self, module, client_opts, version='v1beta1', node_name=None,
+ hostIP=None, hostnames=None, externalIPs=None,
+ internalIPs=None, cpu=None, memory=None, labels=None,
+ annotations=None, podCIDR=None, externalID=None):
self.module = module
self.client_opts = client_opts
if version == 'v1beta1':
- self.node = dict(id = name,
- kind = 'Node',
- apiVersion = version,
- hostIP = hostIP,
- resources = NodeResources(version, cpu, memory),
- cidr = podCIDR,
- labels = labels,
- annotations = annotations,
- externalID = externalID
- )
+ self.node = dict(id=node_name,
+ kind='Node',
+ apiVersion=version,
+ hostIP=hostIP,
+ resources=NodeResources(version, cpu, memory),
+ cidr=podCIDR,
+ labels=labels,
+ annotations=annotations,
+ externalID=externalID)
elif version == 'v1beta3':
- metadata = dict(name = name,
- labels = labels,
- annotations = annotations
- )
- self.node = dict(kind = 'Node',
- apiVersion = version,
- metadata = metadata,
- spec = NodeSpec(version, cpu, memory, podCIDR,
- externalID),
- status = NodeStatus(version, externalIPs,
- internalIPs, hostnames),
- )
+ metadata = dict(name=node_name,
+ labels=labels,
+ annotations=annotations)
+ self.node = dict(kind='Node',
+ apiVersion=version,
+ metadata=metadata,
+ spec=NodeSpec(version, cpu, memory, podCIDR,
+ externalID),
+ status=NodeStatus(version, externalIPs,
+ internalIPs, hostnames))
def get_name(self):
+ """ Get the name for the node
+
+ Returns:
+ str: node name
+ """
if self.node['apiVersion'] == 'v1beta1':
return self.node['id']
elif self.node['apiVersion'] == 'v1beta3':
return self.node['name']
def get_node(self):
+ """ Get the dict representing the node
+
+ Returns:
+ dict: representation of the node with any empty elements
+ removed
+ """
node = self.node.copy()
if self.node['apiVersion'] == 'v1beta1':
node['resources'] = self.node['resources'].get_resources()
@@ -248,54 +436,82 @@ class Node:
return Util.remove_empty_elements(node)
def exists(self):
+ """ Tests if the node already exists
+
+ Returns:
+ bool: True if node exists, otherwise False
+ """
kubectl = self.module.params['kubectl_cmd']
- _, output, error = self.module.run_command(kubectl + ["get", "nodes"] + self.client_opts, check_rc = True)
+ _, output, _ = self.module.run_command((kubectl + ["get", "nodes"] +
+ self.client_opts),
+ check_rc=True)
if re.search(self.module.params['name'], output, re.MULTILINE):
return True
return False
def create(self):
+ """ Creates the node
+
+ Returns:
+ bool: True if node creation successful
+ """
kubectl = self.module.params['kubectl_cmd']
cmd = kubectl + self.client_opts + ['create', '-f', '-']
- rc, output, error = self.module.run_command(cmd,
- data=self.module.jsonify(self.get_node()))
- if rc != 0:
+ exit_code, output, error = self.module.run_command(
+ cmd, data=self.module.jsonify(self.get_node())
+ )
+ if exit_code != 0:
if re.search("minion \"%s\" already exists" % self.get_name(),
error):
- self.module.exit_json(changed=False,
- msg="node definition already exists",
- node=self.get_node())
+ self.module.exit_json(msg="node definition already exists",
+ changed=False, node=self.get_node())
else:
- self.module.fail_json(msg="Node creation failed.", rc=rc,
- output=output, error=error,
- node=self.get_node())
+ self.module.fail_json(msg="Node creation failed.",
+ exit_code=exit_code,
+ output=output, error=error,
+ node=self.get_node())
else:
return True
def main():
+ """ main """
module = AnsibleModule(
- argument_spec = dict(
- name = dict(required = True, type = 'str'),
- host_ip = dict(type = 'str'),
- hostnames = dict(type = 'list', default = []),
- external_ips = dict(type = 'list', default = []),
- internal_ips = dict(type = 'list', default = []),
- api_version = dict(type = 'str', default = 'v1beta1', # TODO: after kube rebase, we can default to v1beta3
- choices = ['v1beta1', 'v1beta3']),
- cpu = dict(type = 'str'),
- memory = dict(type = 'str'),
- labels = dict(type = 'dict', default = {}), # TODO: needs documented
- annotations = dict(type = 'dict', default = {}), # TODO: needs documented
- pod_cidr = dict(type = 'str'), # TODO: needs documented
- external_id = dict(type = 'str'), # TODO: needs documented
- client_config = dict(type = 'str'), # TODO: needs documented
- client_cluster = dict(type = 'str', default = 'master'), # TODO: needs documented
- client_context = dict(type = 'str', default = 'default'), # TODO: needs documented
- client_namespace = dict(type = 'str', default = 'default'), # TODO: needs documented
- client_user = dict(type = 'str', default = 'system:openshift-client'), # TODO: needs documented
- kubectl_cmd = dict(type = 'list', default = ['kubectl']) # TODO: needs documented
+ argument_spec=dict(
+ name=dict(required=True, type='str'),
+ host_ip=dict(type='str'),
+ hostnames=dict(type='list', default=[]),
+ external_ips=dict(type='list', default=[]),
+ internal_ips=dict(type='list', default=[]),
+ api_version=dict(type='str', default='v1beta1',
+ choices=['v1beta1', 'v1beta3']),
+ cpu=dict(type='str'),
+ memory=dict(type='str'),
+ # TODO: needs documented
+ labels=dict(type='dict', default={}),
+ # TODO: needs documented
+ annotations=dict(type='dict', default={}),
+ # TODO: needs documented
+ pod_cidr=dict(type='str'),
+ # TODO: needs documented
+ external_id=dict(type='str'),
+ # TODO: needs documented
+ client_config=dict(type='str'),
+ # TODO: needs documented
+ client_cluster=dict(type='str', default='master'),
+ # TODO: needs documented
+ client_context=dict(type='str', default='default'),
+ # TODO: needs documented
+ client_namespace=dict(type='str', default='default'),
+ # TODO: needs documented
+ client_user=dict(type='str', default='system:openshift-client'),
+ # TODO: needs documented
+ kubectl_cmd=dict(type='list', default=['kubectl']),
+ # TODO: needs documented
+ kubeconfig_flag=dict(type='str'),
+ # TODO: needs documented
+ default_client_config=dict(type='str')
),
- mutually_exclusive = [
+ mutually_exclusive=[
['host_ip', 'external_ips'],
['host_ip', 'internal_ips'],
['host_ip', 'hostnames'],
@@ -303,7 +519,10 @@ def main():
supports_check_mode=True
)
- user_has_client_config = os.path.exists(os.path.expanduser('~/.kube/.kubeconfig'))
+ client_config = '~/.kube/.kubeconfig'
+ if 'default_client_config' in module.params:
+ client_config = module.params['default_client_config']
+ user_has_client_config = os.path.exists(os.path.expanduser(client_config))
if not (user_has_client_config or module.params['client_config']):
module.fail_json(msg="Could not locate client configuration, "
"client_config must be specified if "
@@ -311,12 +530,17 @@ def main():
client_opts = []
if module.params['client_config']:
- client_opts.append("--kubeconfig=%s" % module.params['client_config'])
+ kubeconfig_flag = '--kubeconfig'
+ if 'kubeconfig_flag' in module.params:
+ kubeconfig_flag = module.params['kubeconfig_flag']
+ client_opts.append(kubeconfig_flag + '=' +
+ os.path.expanduser(module.params['client_config']))
try:
config = ClientConfig(client_opts, module)
- except ClientConfigException as e:
- module.fail_json(msg="Failed to get client configuration", exception=e)
+ except ClientConfigException as ex:
+ module.fail_json(msg="Failed to get client configuration",
+ exception=str(ex))
client_context = module.params['client_context']
if config.has_context(client_context):
@@ -369,7 +593,8 @@ def main():
module.fail_json(msg="Unknown error creating node",
node=node.get_node())
-
+# ignore pylint errors related to the module_utils import
+# pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import
# import module snippets
from ansible.module_utils.basic import *
if __name__ == '__main__':
diff --git a/roles/openshift_register_nodes/tasks/main.yml b/roles/openshift_register_nodes/tasks/main.yml
index 85bead16d..d4d72d126 100644
--- a/roles/openshift_register_nodes/tasks/main.yml
+++ b/roles/openshift_register_nodes/tasks/main.yml
@@ -39,7 +39,8 @@
- name: Register unregistered nodes
kubernetes_register_node:
- kubectl_cmd: ['openshift', 'kube']
+ kubectl_cmd: ['osc']
+ default_client_config: '~/.config/openshift/.config'
name: "{{ item.openshift.common.hostname }}"
api_version: "{{ openshift_kube_api_version }}"
cpu: "{{ item.openshift.node.resources_cpu | default(None) }}"