summaryrefslogtreecommitdiffstats
path: root/roles/lib_openshift/src
diff options
context:
space:
mode:
Diffstat (limited to 'roles/lib_openshift/src')
-rw-r--r--roles/lib_openshift/src/ansible/oadm_manage_node.py38
-rw-r--r--roles/lib_openshift/src/ansible/oc_serviceaccount.py30
-rw-r--r--roles/lib_openshift/src/class/oadm_manage_node.py209
-rw-r--r--roles/lib_openshift/src/class/oc_serviceaccount.py165
-rw-r--r--roles/lib_openshift/src/doc/manage_node88
-rw-r--r--roles/lib_openshift/src/doc/serviceaccount68
-rw-r--r--roles/lib_openshift/src/lib/base.py25
-rw-r--r--roles/lib_openshift/src/lib/serviceaccount.py129
-rw-r--r--roles/lib_openshift/src/sources.yml19
-rwxr-xr-xroles/lib_openshift/src/test/integration/oadm_manage_node.yml58
-rwxr-xr-xroles/lib_openshift/src/test/integration/oc_serviceaccount.yml101
-rwxr-xr-xroles/lib_openshift/src/test/unit/oadm_manage_node.py177
-rwxr-xr-xroles/lib_openshift/src/test/unit/oc_serviceaccount.py114
13 files changed, 1212 insertions, 9 deletions
diff --git a/roles/lib_openshift/src/ansible/oadm_manage_node.py b/roles/lib_openshift/src/ansible/oadm_manage_node.py
new file mode 100644
index 000000000..b870c1211
--- /dev/null
+++ b/roles/lib_openshift/src/ansible/oadm_manage_node.py
@@ -0,0 +1,38 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+def main():
+ '''
+ ansible oadm module for manage-node
+ '''
+
+ module = AnsibleModule(
+ argument_spec=dict(
+ debug=dict(default=False, type='bool'),
+ kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
+ node=dict(default=None, type='list'),
+ selector=dict(default=None, type='str'),
+ pod_selector=dict(default=None, type='str'),
+ schedulable=dict(default=None, type='bool'),
+ list_pods=dict(default=False, type='bool'),
+ evacuate=dict(default=False, type='bool'),
+ dry_run=dict(default=False, type='bool'),
+ force=dict(default=False, type='bool'),
+ grace_period=dict(default=None, type='int'),
+ ),
+ mutually_exclusive=[["selector", "node"], ['evacuate', 'list_pods'], ['list_pods', 'schedulable']],
+ required_one_of=[["node", "selector"]],
+
+ supports_check_mode=True,
+ )
+ results = ManageNode.run_ansible(module.params, module.check_mode)
+
+ if 'failed' in results:
+ module.fail_json(**results)
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/roles/lib_openshift/src/ansible/oc_serviceaccount.py b/roles/lib_openshift/src/ansible/oc_serviceaccount.py
new file mode 100644
index 000000000..ea9bdb455
--- /dev/null
+++ b/roles/lib_openshift/src/ansible/oc_serviceaccount.py
@@ -0,0 +1,30 @@
+# pylint: skip-file
+# flake8: noqa
+
+def main():
+ '''
+ ansible oc module for route
+ '''
+
+ module = AnsibleModule(
+ argument_spec=dict(
+ kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
+ state=dict(default='present', type='str',
+ choices=['present', 'absent', 'list']),
+ debug=dict(default=False, type='bool'),
+ name=dict(default=None, required=True, type='str'),
+ namespace=dict(default=None, required=True, type='str'),
+ secrets=dict(default=None, type='list'),
+ image_pull_secrets=dict(default=None, type='list'),
+ ),
+ supports_check_mode=True,
+ )
+
+ rval = OCServiceAccount.run_ansible(module.params, module.check_mode)
+ if 'failed' in rval:
+ module.fail_json(**rval)
+
+ module.exit_json(**rval)
+
+if __name__ == '__main__':
+ main()
diff --git a/roles/lib_openshift/src/class/oadm_manage_node.py b/roles/lib_openshift/src/class/oadm_manage_node.py
new file mode 100644
index 000000000..61b6a5ebe
--- /dev/null
+++ b/roles/lib_openshift/src/class/oadm_manage_node.py
@@ -0,0 +1,209 @@
+# pylint: skip-file
+# flake8: noqa
+
+
+class ManageNodeException(Exception):
+ ''' manage-node exception class '''
+ pass
+
+
+class ManageNodeConfig(OpenShiftCLIConfig):
+ ''' ManageNodeConfig is a DTO for the manage-node command.'''
+ def __init__(self, kubeconfig, node_options):
+ super(ManageNodeConfig, self).__init__(None, None, kubeconfig, node_options)
+
+
+# pylint: disable=too-many-instance-attributes
+class ManageNode(OpenShiftCLI):
+ ''' Class to wrap the oc command line tools '''
+
+ # pylint allows 5
+ # pylint: disable=too-many-arguments
+ def __init__(self,
+ config,
+ verbose=False):
+ ''' Constructor for ManageNode '''
+ super(ManageNode, self).__init__(None, config.kubeconfig)
+ self.config = config
+
+ def evacuate(self):
+ ''' formulate the params and run oadm manage-node '''
+ return self._evacuate(node=self.config.config_options['node']['value'],
+ selector=self.config.config_options['selector']['value'],
+ pod_selector=self.config.config_options['pod_selector']['value'],
+ dry_run=self.config.config_options['dry_run']['value'],
+ grace_period=self.config.config_options['grace_period']['value'],
+ force=self.config.config_options['force']['value'],
+ )
+ def get_nodes(self, node=None, selector=''):
+ '''perform oc get node'''
+ _node = None
+ _sel = None
+ if node:
+ _node = node
+ if selector:
+ _sel = selector
+
+ results = self._get('node', rname=_node, selector=_sel)
+ if results['returncode'] != 0:
+ return results
+
+ nodes = []
+ items = None
+ if results['results'][0]['kind'] == 'List':
+ items = results['results'][0]['items']
+ else:
+ items = results['results']
+
+ for node in items:
+ _node = {}
+ _node['name'] = node['metadata']['name']
+ _node['schedulable'] = True
+ if 'unschedulable' in node['spec']:
+ _node['schedulable'] = False
+ nodes.append(_node)
+
+ return nodes
+
+ def get_pods_from_node(self, node, pod_selector=None):
+ '''return pods for a node'''
+ results = self._list_pods(node=[node], pod_selector=pod_selector)
+
+ if results['returncode'] != 0:
+ return results
+
+ # When a selector or node is matched it is returned along with the json.
+ # We are going to split the results based on the regexp and then
+ # load the json for each matching node.
+ # Before we return we are going to loop over the results and pull out the node names.
+ # {'node': [pod, pod], 'node': [pod, pod]}
+ # 3.2 includes the following lines in stdout: "Listing matched pods on node:"
+ all_pods = []
+ if "Listing matched" in results['results']:
+ listing_match = re.compile('\n^Listing matched.*$\n', flags=re.MULTILINE)
+ pods = listing_match.split(results['results'])
+ for pod in pods:
+ if pod:
+ all_pods.extend(json.loads(pod)['items'])
+
+ # 3.3 specific
+ else:
+ # this is gross but I filed a bug...
+ # https://bugzilla.redhat.com/show_bug.cgi?id=1381621
+ # build our own json from the output.
+ all_pods = json.loads(results['results'])['items']
+
+ return all_pods
+
+ def list_pods(self):
+ ''' run oadm manage-node --list-pods'''
+ _nodes = self.config.config_options['node']['value']
+ _selector = self.config.config_options['selector']['value']
+ _pod_selector = self.config.config_options['pod_selector']['value']
+
+ if not _nodes:
+ _nodes = self.get_nodes(selector=_selector)
+ else:
+ _nodes = [{'name': name} for name in _nodes]
+
+ all_pods = {}
+ for node in _nodes:
+ results = self.get_pods_from_node(node['name'], pod_selector=_pod_selector)
+ if isinstance(results, dict):
+ return results
+ all_pods[node['name']] = results
+
+ results = {}
+ results['nodes'] = all_pods
+ results['returncode'] = 0
+ return results
+
+ def schedulable(self):
+ '''oadm manage-node call for making nodes unschedulable'''
+ nodes = self.config.config_options['node']['value']
+ selector = self.config.config_options['selector']['value']
+
+ if not nodes:
+ nodes = self.get_nodes(selector=selector)
+ else:
+ tmp_nodes = []
+ for name in nodes:
+ tmp_result = self.get_nodes(name)
+ if isinstance(tmp_result, dict):
+ tmp_nodes.append(tmp_result)
+ continue
+ tmp_nodes.extend(tmp_result)
+ nodes = tmp_nodes
+
+ # This is a short circuit based on the way we fetch nodes.
+ # If node is a dict/list then we've already fetched them.
+ for node in nodes:
+ if isinstance(node, dict) and 'returncode' in node:
+ return {'results': nodes, 'returncode': node['returncode']}
+ if isinstance(node, list) and 'returncode' in node[0]:
+ return {'results': nodes, 'returncode': node[0]['returncode']}
+ # check all the nodes that were returned and verify they are:
+ # node['schedulable'] == self.config.config_options['schedulable']['value']
+ if any([node['schedulable'] != self.config.config_options['schedulable']['value'] for node in nodes]):
+
+ results = self._schedulable(node=self.config.config_options['node']['value'],
+ selector=self.config.config_options['selector']['value'],
+ schedulable=self.config.config_options['schedulable']['value'])
+
+ # 'NAME STATUS AGE\\nip-172-31-49-140.ec2.internal Ready 4h\\n' # E501
+ # normalize formatting with previous return objects
+ if results['results'].startswith('NAME'):
+ nodes = []
+ # removing header line and trailing new line character of node lines
+ for node_results in results['results'].split('\n')[1:-1]:
+ parts = node_results.split()
+ nodes.append({'name': parts[0], 'schedulable': parts[1] == 'Ready'})
+ results['nodes'] = nodes
+
+ return results
+
+ results = {}
+ results['returncode'] = 0
+ results['changed'] = False
+ results['nodes'] = nodes
+
+ return results
+
+ @staticmethod
+ def run_ansible(params, check_mode):
+ '''run the idempotent ansible code'''
+ nconfig = ManageNodeConfig(params['kubeconfig'],
+ {'node': {'value': params['node'], 'include': True},
+ 'selector': {'value': params['selector'], 'include': True},
+ 'pod_selector': {'value': params['pod_selector'], 'include': True},
+ 'schedulable': {'value': params['schedulable'], 'include': True},
+ 'list_pods': {'value': params['list_pods'], 'include': True},
+ 'evacuate': {'value': params['evacuate'], 'include': True},
+ 'dry_run': {'value': params['dry_run'], 'include': True},
+ 'force': {'value': params['force'], 'include': True},
+ 'grace_period': {'value': params['grace_period'], 'include': True},
+ })
+
+ oadm_mn = ManageNode(nconfig)
+ # Run the oadm manage-node commands
+ results = None
+ changed = False
+ if params['schedulable'] != None:
+ if check_mode:
+ # schedulable returns results after the fact.
+ # We need to redo how this works to support check_mode completely.
+ return {'changed': True, 'msg': 'CHECK_MODE: would have called schedulable.'}
+ results = oadm_mn.schedulable()
+ if 'changed' not in results:
+ changed = True
+
+ if params['evacuate']:
+ results = oadm_mn.evacuate()
+ changed = True
+ elif params['list_pods']:
+ results = oadm_mn.list_pods()
+
+ if not results or results['returncode'] != 0:
+ return {'failed': True, 'msg': results}
+
+ return {'changed': changed, 'results': results, 'state': "present"}
diff --git a/roles/lib_openshift/src/class/oc_serviceaccount.py b/roles/lib_openshift/src/class/oc_serviceaccount.py
new file mode 100644
index 000000000..47c7b5c94
--- /dev/null
+++ b/roles/lib_openshift/src/class/oc_serviceaccount.py
@@ -0,0 +1,165 @@
+# pylint: skip-file
+# flake8: noqa
+
+# pylint: disable=too-many-instance-attributes
+class OCServiceAccount(OpenShiftCLI):
+ ''' Class to wrap the oc command line tools '''
+ kind = 'sa'
+
+ # pylint allows 5
+ # pylint: disable=too-many-arguments
+ def __init__(self,
+ config,
+ verbose=False):
+ ''' Constructor for OCVolume '''
+ super(OCServiceAccount, self).__init__(config.namespace, config.kubeconfig)
+ self.config = config
+ self.namespace = config.namespace
+ self.service_account = None
+
+ def exists(self):
+ ''' return whether a volume exists '''
+ if self.service_account:
+ return True
+
+ return False
+
+ def get(self):
+ '''return volume information '''
+ result = self._get(self.kind, self.config.name)
+ if result['returncode'] == 0:
+ self.service_account = ServiceAccount(content=result['results'][0])
+ elif '\"%s\" not found' % self.config.name in result['stderr']:
+ result['returncode'] = 0
+ result['results'] = [{}]
+
+ return result
+
+ def delete(self):
+ '''delete the object'''
+ return self._delete(self.kind, self.config.name)
+
+ def create(self):
+ '''create the object'''
+ return self._create_from_content(self.config.name, self.config.data)
+
+ def update(self):
+ '''update the object'''
+ # need to update the tls information and the service name
+ for secret in self.config.secrets:
+ result = self.service_account.find_secret(secret)
+ if not result:
+ self.service_account.add_secret(secret)
+
+ for secret in self.config.image_pull_secrets:
+ result = self.service_account.find_image_pull_secret(secret)
+ if not result:
+ self.service_account.add_image_pull_secret(secret)
+
+ return self._replace_content(self.kind, self.config.name, self.config.data)
+
+ def needs_update(self):
+ ''' verify an update is needed '''
+ # since creating an service account generates secrets and imagepullsecrets
+ # check_def_equal will not work
+ # Instead, verify all secrets passed are in the list
+ for secret in self.config.secrets:
+ result = self.service_account.find_secret(secret)
+ if not result:
+ return True
+
+ for secret in self.config.image_pull_secrets:
+ result = self.service_account.find_image_pull_secret(secret)
+ if not result:
+ return True
+
+ return False
+
+ @staticmethod
+ # pylint: disable=too-many-return-statements,too-many-branches
+ # TODO: This function should be refactored into its individual parts.
+ def run_ansible(params, check_mode):
+ '''run the ansible idempotent code'''
+
+ rconfig = ServiceAccountConfig(params['name'],
+ params['namespace'],
+ params['kubeconfig'],
+ params['secrets'],
+ params['image_pull_secrets'],
+ )
+
+ oc_sa = OCServiceAccount(rconfig,
+ verbose=params['debug'])
+
+ state = params['state']
+
+ api_rval = oc_sa.get()
+
+ #####
+ # Get
+ #####
+ if state == 'list':
+ return {'changed': False, 'results': api_rval['results'], 'state': 'list'}
+
+ ########
+ # Delete
+ ########
+ if state == 'absent':
+ if oc_sa.exists():
+
+ if check_mode:
+ return {'changed': True, 'msg': 'Would have performed a delete.'}
+
+ api_rval = oc_sa.delete()
+
+ return {'changed': True, 'results': api_rval, 'state': 'absent'}
+
+ return {'changed': False, 'state': 'absent'}
+
+ if state == 'present':
+ ########
+ # Create
+ ########
+ if not oc_sa.exists():
+
+ if check_mode:
+ return {'changed': True, 'msg': 'Would have performed a create.'}
+
+ # Create it here
+ api_rval = oc_sa.create()
+
+ if api_rval['returncode'] != 0:
+ return {'failed': True, 'msg': api_rval}
+
+ # return the created object
+ api_rval = oc_sa.get()
+
+ if api_rval['returncode'] != 0:
+ return {'failed': True, 'msg': api_rval}
+
+ return {'changed': True, 'results': api_rval, 'state': 'present'}
+
+ ########
+ # Update
+ ########
+ if oc_sa.needs_update():
+ api_rval = oc_sa.update()
+
+ if api_rval['returncode'] != 0:
+ return {'failed': True, 'msg': api_rval}
+
+ # return the created object
+ api_rval = oc_sa.get()
+
+ if api_rval['returncode'] != 0:
+ return {'failed': True, 'msg': api_rval}
+
+ return {'changed': True, 'results': api_rval, 'state': 'present'}
+
+ return {'changed': False, 'results': api_rval, 'state': 'present'}
+
+
+ return {'failed': True,
+ 'changed': False,
+ 'msg': 'Unknown state passed. %s' % state,
+ 'state': 'unknown'}
diff --git a/roles/lib_openshift/src/doc/manage_node b/roles/lib_openshift/src/doc/manage_node
new file mode 100644
index 000000000..382377f3e
--- /dev/null
+++ b/roles/lib_openshift/src/doc/manage_node
@@ -0,0 +1,88 @@
+# flake8: noqa
+# pylint: skip-file
+
+DOCUMENTATION = '''
+---
+module: oadm_manage_node
+short_description: Module to manage openshift nodes
+description:
+ - Manage openshift nodes programmatically.
+options:
+ kubeconfig:
+ description:
+ - The path for the kubeconfig file to use for authentication
+ required: false
+ default: /etc/origin/master/admin.kubeconfig
+ aliases: []
+ debug:
+ description:
+ - Turn on debug output.
+ required: false
+ default: False
+ aliases: []
+ node:
+ description:
+ - A list of the nodes being managed
+ required: false
+ default: None
+ aliases: []
+ selector:
+ description:
+ - The selector when filtering on node labels
+ required: false
+ default: None
+ aliases: []
+ pod_selector:
+ description:
+ - A selector when filtering on pod labels.
+ required: false
+ default: None
+ aliases: []
+ evacuate:
+ description:
+ - Remove all pods from a node.
+ required: false
+ default: False
+ aliases: []
+ schedulable:
+ description:
+ - whether or not openshift can schedule pods on this node
+ required: False
+ default: None
+ aliases: []
+ dry_run:
+ description:
+ - This shows the pods that would be migrated if evacuate were called
+ required: False
+ default: False
+ aliases: []
+ grace_period:
+ description:
+ - Grace period (seconds) for pods being deleted.
+ required: false
+ default: None
+ aliases: []
+ force:
+ description:
+ - Whether or not to attempt to force this action in openshift
+ required: false
+ default: None
+ aliases: []
+author:
+- "Kenny Woodson <kwoodson@redhat.com>"
+extends_documentation_fragment: []
+'''
+
+EXAMPLES = '''
+- name: oadm manage-node --schedulable=true --selector=ops_node=new
+ oadm_manage_node:
+ selector: ops_node=new
+ schedulable: True
+ register: schedout
+
+- name: oadm manage-node my-k8s-node-5 --evacuate
+ oadm_manage_node:
+ node: my-k8s-node-5
+ evacuate: True
+ force: True
+'''
diff --git a/roles/lib_openshift/src/doc/serviceaccount b/roles/lib_openshift/src/doc/serviceaccount
new file mode 100644
index 000000000..b2eafab51
--- /dev/null
+++ b/roles/lib_openshift/src/doc/serviceaccount
@@ -0,0 +1,68 @@
+# flake8: noqa
+# pylint: skip-file
+
+DOCUMENTATION = '''
+---
+module: oc_serviceaccount
+short_description: Module to manage openshift service accounts
+description:
+ - Manage openshift service accounts programmatically.
+options:
+ state:
+ description:
+ - If present, the service account will be created if it doesn't exist or updated if different. If absent, the service account will be removed if present. If list, information about the service account will be gathered and returned as part of the Ansible call results.
+ required: false
+ default: present
+ choices: ["present", "absent", "list"]
+ aliases: []
+ kubeconfig:
+ description:
+ - The path for the kubeconfig file to use for authentication
+ required: false
+ default: /etc/origin/master/admin.kubeconfig
+ aliases: []
+ debug:
+ description:
+ - Turn on debug output.
+ required: false
+ default: false
+ aliases: []
+ name:
+ description:
+ - Name of the service account.
+ required: true
+ default: None
+ aliases: []
+ namespace:
+ description:
+ - Namespace of the service account.
+ required: true
+ default: default
+ aliases: []
+ secrets:
+ description:
+ - A list of secrets that are associated with the service account.
+ required: false
+ default: None
+ aliases: []
+ image_pull_secrets:
+ description:
+ - A list of the image pull secrets that are associated with the service account.
+ required: false
+ default: None
+ aliases: []
+author:
+- "Kenny Woodson <kwoodson@redhat.com>"
+extends_documentation_fragment: []
+'''
+
+EXAMPLES = '''
+- name: create registry serviceaccount
+ oc_serviceaccount:
+ name: registry
+ namespace: default
+ secrets:
+ - docker-registry-config
+ - registry-secret
+ register: sa_out
+'''
diff --git a/roles/lib_openshift/src/lib/base.py b/roles/lib_openshift/src/lib/base.py
index 8b5491d6b..d0d6c7afc 100644
--- a/roles/lib_openshift/src/lib/base.py
+++ b/roles/lib_openshift/src/lib/base.py
@@ -205,6 +205,18 @@ class OpenShiftCLI(object):
cmd.append('--confirm')
return self.openshift_cmd(cmd)
+ def _run(self, cmds, input_data):
+ ''' Actually executes the command. This makes mocking easier. '''
+ proc = subprocess.Popen(cmds,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ env={'KUBECONFIG': self.kubeconfig})
+
+ stdout, stderr = proc.communicate(input_data)
+
+ return proc.returncode, stdout, stderr
+
# pylint: disable=too-many-arguments,too-many-branches
def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
'''Base command for oc '''
@@ -216,7 +228,7 @@ class OpenShiftCLI(object):
if self.all_namespaces:
cmds.extend(['--all-namespaces'])
- elif self.namespace:
+ elif self.namespace is not None and self.namespace.lower() not in ['none', 'emtpy']: # E501
cmds.extend(['-n', self.namespace])
cmds.extend(cmd)
@@ -228,18 +240,13 @@ class OpenShiftCLI(object):
if self.verbose:
print(' '.join(cmds))
- proc = subprocess.Popen(cmds,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- env={'KUBECONFIG': self.kubeconfig})
+ returncode, stdout, stderr = self._run(cmds, input_data)
- stdout, stderr = proc.communicate(input_data)
- rval = {"returncode": proc.returncode,
+ rval = {"returncode": returncode,
"results": results,
"cmd": ' '.join(cmds)}
- if proc.returncode == 0:
+ if returncode == 0:
if output:
if output_type == 'json':
try:
diff --git a/roles/lib_openshift/src/lib/serviceaccount.py b/roles/lib_openshift/src/lib/serviceaccount.py
new file mode 100644
index 000000000..47a55757e
--- /dev/null
+++ b/roles/lib_openshift/src/lib/serviceaccount.py
@@ -0,0 +1,129 @@
+# pylint: skip-file
+# flake8: noqa
+
+class ServiceAccountConfig(object):
+ '''Service account config class
+
+ This class stores the options and returns a default service account
+ '''
+
+ # pylint: disable=too-many-arguments
+ def __init__(self, sname, namespace, kubeconfig, secrets=None, image_pull_secrets=None):
+ self.name = sname
+ self.kubeconfig = kubeconfig
+ self.namespace = namespace
+ self.secrets = secrets or []
+ self.image_pull_secrets = image_pull_secrets or []
+ self.data = {}
+ self.create_dict()
+
+ def create_dict(self):
+ ''' return a properly structured volume '''
+ self.data['apiVersion'] = 'v1'
+ self.data['kind'] = 'ServiceAccount'
+ self.data['metadata'] = {}
+ self.data['metadata']['name'] = self.name
+ self.data['metadata']['namespace'] = self.namespace
+
+ self.data['secrets'] = []
+ if self.secrets:
+ for sec in self.secrets:
+ self.data['secrets'].append({"name": sec})
+
+ self.data['imagePullSecrets'] = []
+ if self.image_pull_secrets:
+ for sec in self.image_pull_secrets:
+ self.data['imagePullSecrets'].append({"name": sec})
+
+class ServiceAccount(Yedit):
+ ''' Class to wrap the oc command line tools '''
+ image_pull_secrets_path = "imagePullSecrets"
+ secrets_path = "secrets"
+
+ def __init__(self, content):
+ '''ServiceAccount constructor'''
+ super(ServiceAccount, self).__init__(content=content)
+ self._secrets = None
+ self._image_pull_secrets = None
+
+ @property
+ def image_pull_secrets(self):
+ ''' property for image_pull_secrets '''
+ if self._image_pull_secrets is None:
+ self._image_pull_secrets = self.get(ServiceAccount.image_pull_secrets_path) or []
+ return self._image_pull_secrets
+
+ @image_pull_secrets.setter
+ def image_pull_secrets(self, secrets):
+ ''' property for secrets '''
+ self._image_pull_secrets = secrets
+
+ @property
+ def secrets(self):
+ ''' property for secrets '''
+ if not self._secrets:
+ self._secrets = self.get(ServiceAccount.secrets_path) or []
+ return self._secrets
+
+ @secrets.setter
+ def secrets(self, secrets):
+ ''' property for secrets '''
+ self._secrets = secrets
+
+ def delete_secret(self, inc_secret):
+ ''' remove a secret '''
+ remove_idx = None
+ for idx, sec in enumerate(self.secrets):
+ if sec['name'] == inc_secret:
+ remove_idx = idx
+ break
+
+ if remove_idx:
+ del self.secrets[remove_idx]
+ return True
+
+ return False
+
+ def delete_image_pull_secret(self, inc_secret):
+ ''' remove a image_pull_secret '''
+ remove_idx = None
+ for idx, sec in enumerate(self.image_pull_secrets):
+ if sec['name'] == inc_secret:
+ remove_idx = idx
+ break
+
+ if remove_idx:
+ del self.image_pull_secrets[remove_idx]
+ return True
+
+ return False
+
+ def find_secret(self, inc_secret):
+ '''find secret'''
+ for secret in self.secrets:
+ if secret['name'] == inc_secret:
+ return secret
+
+ return None
+
+ def find_image_pull_secret(self, inc_secret):
+ '''find secret'''
+ for secret in self.image_pull_secrets:
+ if secret['name'] == inc_secret:
+ return secret
+
+ return None
+
+ def add_secret(self, inc_secret):
+ '''add secret'''
+ if self.secrets:
+ self.secrets.append({"name": inc_secret}) # pylint: disable=no-member
+ else:
+ self.put(ServiceAccount.secrets_path, [{"name": inc_secret}])
+
+ def add_image_pull_secret(self, inc_secret):
+ '''add image_pull_secret'''
+ if self.image_pull_secrets:
+ self.image_pull_secrets.append({"name": inc_secret}) # pylint: disable=no-member
+ else:
+ self.put(ServiceAccount.image_pull_secrets_path, [{"name": inc_secret}])
diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml
index 5afcdc55d..8a825a402 100644
--- a/roles/lib_openshift/src/sources.yml
+++ b/roles/lib_openshift/src/sources.yml
@@ -1,4 +1,13 @@
---
+oadm_manage_node.py:
+- doc/generated
+- doc/license
+- lib/import.py
+- doc/manage_node
+- ../../lib_utils/src/class/yedit.py
+- lib/base.py
+- class/oadm_manage_node.py
+- ansible/oadm_manage_node.py
oc_edit.py:
- doc/generated
- doc/license
@@ -57,3 +66,13 @@ oc_version.py:
- lib/base.py
- class/oc_version.py
- ansible/oc_version.py
+oc_serviceaccount.py:
+- doc/generated
+- doc/license
+- lib/import.py
+- doc/serviceaccount
+- ../../lib_utils/src/class/yedit.py
+- lib/base.py
+- lib/serviceaccount.py
+- class/oc_serviceaccount.py
+- ansible/oc_serviceaccount.py
diff --git a/roles/lib_openshift/src/test/integration/oadm_manage_node.yml b/roles/lib_openshift/src/test/integration/oadm_manage_node.yml
new file mode 100755
index 000000000..69a701b17
--- /dev/null
+++ b/roles/lib_openshift/src/test/integration/oadm_manage_node.yml
@@ -0,0 +1,58 @@
+#!/usr/bin/ansible-playbook --module-path=../../../library/
+# ./oadm_manage_node.yml -M ../../../library -e "cli_master_test=$OPENSHIFT_MASTER cli_node_test=$OPENSHIFT_NODE
+---
+- hosts: "{{ cli_master_test }}"
+ gather_facts: no
+ user: root
+ tasks:
+ - name: list pods from a node
+ oadm_manage_node:
+ list_pods: True
+ node:
+ - "{{ cli_node_test }}"
+ register: podout
+ - debug: var=podout
+
+ - assert:
+ that: "'{{ cli_node_test }}' in podout.results.nodes"
+ msg: Pod data was not returned
+
+ - name: set node to unschedulable
+ oadm_manage_node:
+ schedulable: False
+ node:
+ - "{{ cli_node_test }}"
+ register: nodeout
+ - debug: var=nodeout
+
+ - name: assert that schedulable=False
+ assert:
+ that: nodeout.results.nodes[0]['schedulable'] == False
+ msg: "{{ cli_node_test }} schedulable set to True"
+
+ - name: get node scheduable
+ oc_obj:
+ kind: node
+ state: list
+ name: "{{ cli_node_test }}"
+ namespace: None
+ register: nodeout
+
+ - debug: var=nodeout
+
+ - name: assert that schedulable=False
+ assert:
+ that: nodeout.results.results[0]['spec']['unschedulable']
+
+ - name: set node to schedulable
+ oadm_manage_node:
+ schedulable: True
+ node:
+ - "{{ cli_node_test }}"
+ register: nodeout
+ - debug: var=nodeout
+
+ - name: assert that schedulable=False
+ assert:
+ that: nodeout.results.nodes[0]['schedulable']
+ msg: "{{ cli_node_test }} schedulable set to False"
diff --git a/roles/lib_openshift/src/test/integration/oc_serviceaccount.yml b/roles/lib_openshift/src/test/integration/oc_serviceaccount.yml
new file mode 100755
index 000000000..46369b8f4
--- /dev/null
+++ b/roles/lib_openshift/src/test/integration/oc_serviceaccount.yml
@@ -0,0 +1,101 @@
+#!/usr/bin/ansible-playbook --module-path=../../../library/
+
+---
+- hosts: "{{ cli_master_test }}"
+ gather_facts: no
+ user: root
+ vars_prompt:
+ - name: cli_master_test
+ prompt: "Master to run against"
+ private: false
+ default: localhost
+
+ vars:
+ service_account_name: serviceaccount-int-test
+ ns_name: default
+
+ post_tasks:
+ - name: Make sure we start clean - Arrange
+ oc_serviceaccount:
+ state: absent
+ name: "{{ service_account_name }}"
+ namespace: "{{ ns_name }}"
+
+ - name: List when account does not exist - Act
+ oc_serviceaccount:
+ state: list
+ name: "{{ service_account_name }}"
+ namespace: "{{ ns_name }}"
+ register: saout
+
+ - name: List when account does not exist - Assert
+ assert:
+ that:
+ - "saout.changed == False"
+ - "saout.state == 'list'"
+ - "saout.results == [{}]"
+
+ - name: create serviceaccount - Act
+ oc_serviceaccount:
+ name: "{{ service_account_name }}"
+ namespace: "{{ ns_name }}"
+ secrets:
+ - one
+ - two
+ - three
+ register: saout
+
+ - name: create serviceaccount - Assert
+ assert:
+ that:
+ - "saout.changed == True"
+ - "saout.state == 'present'"
+ - "saout.results.returncode == 0"
+ - "saout.results.results.0.metadata.name == '{{ service_account_name }}'"
+ - "saout.results.results.0.metadata.namespace == '{{ ns_name }}'"
+
+ - name: create serviceaccount - check idempotency - Act
+ oc_serviceaccount:
+ name: "{{ service_account_name }}"
+ namespace: "{{ ns_name }}"
+ secrets:
+ - one
+ - two
+ - three
+ register: saout
+
+ - name: create serviceaccount - check idempotency - Assert
+ assert:
+ that:
+ - "saout.changed == False"
+ - "saout.state == 'present'"
+ - "saout.results.returncode == 0"
+ - "saout.results.results.0.metadata.name == '{{ service_account_name }}'"
+ - "saout.results.results.0.metadata.namespace == '{{ ns_name }}'"
+
+ - name: Delete serviceaccount - Act
+ oc_serviceaccount:
+ state: absent
+ name: "{{ service_account_name }}"
+ namespace: "{{ ns_name }}"
+ register: saout
+
+ - name: Delete serviceaccount - Assert
+ assert:
+ that:
+ - "saout.changed == True"
+ - "saout.state == 'absent'"
+ - "saout.results.returncode == 0"
+
+ - name: Delete serviceaccount - check idempotency - Act
+ oc_serviceaccount:
+ state: absent
+ name: "{{ service_account_name }}"
+ namespace: "{{ ns_name }}"
+ register: saout
+
+ - name: Delete serviceaccount - check idempotency - Assert
+ assert:
+ that:
+ - "saout.changed == False"
+ - "saout.state == 'absent'"
diff --git a/roles/lib_openshift/src/test/unit/oadm_manage_node.py b/roles/lib_openshift/src/test/unit/oadm_manage_node.py
new file mode 100755
index 000000000..8fd6f9c55
--- /dev/null
+++ b/roles/lib_openshift/src/test/unit/oadm_manage_node.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python2
+'''
+ Unit tests for oadm_manage_node
+'''
+# To run
+# python -m unittest version
+#
+# .
+# Ran 2 tests in 0.001s
+#
+# OK
+
+import os
+import sys
+import unittest
+import mock
+
+# Removing invalid variable names for tests so that I can
+# keep them brief
+# pylint: disable=invalid-name,no-name-in-module
+# Disable import-error b/c our libraries aren't loaded in jenkins
+# pylint: disable=import-error
+# place class in our python path
+module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library') # noqa: E501
+sys.path.insert(0, module_path)
+from oadm_manage_node import ManageNode # noqa: E402
+
+
+class ManageNodeTest(unittest.TestCase):
+ '''
+ Test class for oadm_manage_node
+ '''
+
+ def setUp(self):
+ ''' setup method will create a file and set to known configuration '''
+ pass
+
+ @mock.patch('oadm_manage_node.ManageNode.openshift_cmd')
+ def test_list_pods(self, mock_openshift_cmd):
+ ''' Testing a get '''
+ params = {'node': ['ip-172-31-49-140.ec2.internal'],
+ 'schedulable': None,
+ 'selector': None,
+ 'pod_selector': None,
+ 'list_pods': True,
+ 'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+ 'evacuate': False,
+ 'grace_period': False,
+ 'dry_run': False,
+ 'force': False}
+
+ pod_list = '''{
+ "metadata": {},
+ "items": [
+ {
+ "metadata": {
+ "name": "docker-registry-1-xuhik",
+ "generateName": "docker-registry-1-",
+ "namespace": "default",
+ "selfLink": "/api/v1/namespaces/default/pods/docker-registry-1-xuhik",
+ "uid": "ae2a25a2-e316-11e6-80eb-0ecdc51fcfc4",
+ "resourceVersion": "1501",
+ "creationTimestamp": "2017-01-25T15:55:23Z",
+ "labels": {
+ "deployment": "docker-registry-1",
+ "deploymentconfig": "docker-registry",
+ "docker-registry": "default"
+ },
+ "annotations": {
+ "openshift.io/deployment-config.latest-version": "1",
+ "openshift.io/deployment-config.name": "docker-registry",
+ "openshift.io/deployment.name": "docker-registry-1",
+ "openshift.io/scc": "restricted"
+ }
+ },
+ "spec": {}
+ },
+ {
+ "metadata": {
+ "name": "router-1-kp3m3",
+ "generateName": "router-1-",
+ "namespace": "default",
+ "selfLink": "/api/v1/namespaces/default/pods/router-1-kp3m3",
+ "uid": "9e71f4a5-e316-11e6-80eb-0ecdc51fcfc4",
+ "resourceVersion": "1456",
+ "creationTimestamp": "2017-01-25T15:54:56Z",
+ "labels": {
+ "deployment": "router-1",
+ "deploymentconfig": "router",
+ "router": "router"
+ },
+ "annotations": {
+ "openshift.io/deployment-config.latest-version": "1",
+ "openshift.io/deployment-config.name": "router",
+ "openshift.io/deployment.name": "router-1",
+ "openshift.io/scc": "hostnetwork"
+ }
+ },
+ "spec": {}
+ }]
+}'''
+
+ mock_openshift_cmd.side_effect = [
+ {"cmd": "/usr/bin/oadm manage-node ip-172-31-49-140.ec2.internal --list-pods",
+ "results": pod_list,
+ "returncode": 0}
+ ]
+
+ results = ManageNode.run_ansible(params, False)
+
+ # returned a single node
+ self.assertTrue(len(results['results']['nodes']) == 1)
+ # returned 2 pods
+ self.assertTrue(len(results['results']['nodes']['ip-172-31-49-140.ec2.internal']) == 2)
+
+ @mock.patch('oadm_manage_node.ManageNode.openshift_cmd')
+ def test_schedulable_false(self, mock_openshift_cmd):
+ ''' Testing a get '''
+ params = {'node': ['ip-172-31-49-140.ec2.internal'],
+ 'schedulable': False,
+ 'selector': None,
+ 'pod_selector': None,
+ 'list_pods': False,
+ 'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+ 'evacuate': False,
+ 'grace_period': False,
+ 'dry_run': False,
+ 'force': False}
+
+ node = [{
+ "apiVersion": "v1",
+ "kind": "Node",
+ "metadata": {
+ "creationTimestamp": "2017-01-26T14:34:43Z",
+ "labels": {
+ "beta.kubernetes.io/arch": "amd64",
+ "beta.kubernetes.io/instance-type": "m4.large",
+ "beta.kubernetes.io/os": "linux",
+ "failure-domain.beta.kubernetes.io/region": "us-east-1",
+ "failure-domain.beta.kubernetes.io/zone": "us-east-1c",
+ "hostname": "opstest-node-compute-0daaf",
+ "kubernetes.io/hostname": "ip-172-31-51-111.ec2.internal",
+ "ops_node": "old",
+ "region": "us-east-1",
+ "type": "compute"
+ },
+ "name": "ip-172-31-51-111.ec2.internal",
+ "resourceVersion": "6936",
+ "selfLink": "/api/v1/nodes/ip-172-31-51-111.ec2.internal",
+ "uid": "93d7fdfb-e3d4-11e6-a982-0e84250fc302"
+ },
+ "spec": {
+ "externalID": "i-06bb330e55c699b0f",
+ "providerID": "aws:///us-east-1c/i-06bb330e55c699b0f",
+ }}]
+
+ mock_openshift_cmd.side_effect = [
+ {"cmd": "/usr/bin/oc get node -o json ip-172-31-49-140.ec2.internal",
+ "results": node,
+ "returncode": 0},
+ {"cmd": "/usr/bin/oadm manage-node ip-172-31-49-140.ec2.internal --schedulable=False",
+ "results": "NAME STATUS AGE\n" +
+ "ip-172-31-49-140.ec2.internal Ready,SchedulingDisabled 5h\n",
+ "returncode": 0}]
+ results = ManageNode.run_ansible(params, False)
+
+ self.assertTrue(results['changed'])
+ self.assertEqual(results['results']['nodes'][0]['name'], 'ip-172-31-49-140.ec2.internal')
+ self.assertEqual(results['results']['nodes'][0]['schedulable'], False)
+
+ def tearDown(self):
+ '''TearDown method'''
+ pass
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/roles/lib_openshift/src/test/unit/oc_serviceaccount.py b/roles/lib_openshift/src/test/unit/oc_serviceaccount.py
new file mode 100755
index 000000000..faf0bfeb5
--- /dev/null
+++ b/roles/lib_openshift/src/test/unit/oc_serviceaccount.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python2
+'''
+ Unit tests for oc serviceaccount
+'''
+# To run:
+# ./oc_serviceaccount.py
+#
+# .
+# Ran 1 test in 0.002s
+#
+# OK
+
+import os
+import sys
+import unittest
+import mock
+
+# Removing invalid variable names for tests so that I can
+# keep them brief
+# pylint: disable=invalid-name,no-name-in-module
+# Disable import-error b/c our libraries aren't loaded in jenkins
+# pylint: disable=import-error,wrong-import-position
+# place class in our python path
+module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library') # noqa: E501
+sys.path.insert(0, module_path)
+from oc_serviceaccount import OCServiceAccount # noqa: E402
+
+
+class OCServiceAccountTest(unittest.TestCase):
+ '''
+ Test class for OCServiceAccount
+ '''
+
+ def setUp(self):
+ ''' setup method will create a file and set to known configuration '''
+ pass
+
+ @mock.patch('oc_serviceaccount.OCServiceAccount._run')
+ def test_adding_a_serviceaccount(self, mock_cmd):
+ ''' Testing adding a serviceaccount '''
+
+ # Arrange
+
+ # run_ansible input parameters
+ params = {
+ 'kubeconfig': '/etc/origin/master/admin.kubeconfig',
+ 'state': 'present',
+ 'debug': False,
+ 'name': 'testserviceaccountname',
+ 'namespace': 'default',
+ 'secrets': None,
+ 'image_pull_secrets': None,
+ }
+
+ valid_result_json = '''{
+ "kind": "ServiceAccount",
+ "apiVersion": "v1",
+ "metadata": {
+ "name": "testserviceaccountname",
+ "namespace": "default",
+ "selfLink": "/api/v1/namespaces/default/serviceaccounts/testserviceaccountname",
+ "uid": "4d8320c9-e66f-11e6-8edc-0eece8f2ce22",
+ "resourceVersion": "328450",
+ "creationTimestamp": "2017-01-29T22:07:19Z"
+ },
+ "secrets": [
+ {
+ "name": "testserviceaccountname-dockercfg-4lqd0"
+ },
+ {
+ "name": "testserviceaccountname-token-9h0ej"
+ }
+ ],
+ "imagePullSecrets": [
+ {
+ "name": "testserviceaccountname-dockercfg-4lqd0"
+ }
+ ]
+ }'''
+
+ # Return values of our mocked function call. These get returned once per call.
+ mock_cmd.side_effect = [
+ # First call to mock
+ (1, '', 'Error from server: serviceaccounts "testserviceaccountname" not found'),
+
+ # Second call to mock
+ (0, 'serviceaccount "testserviceaccountname" created', ''),
+
+ # Third call to mock
+ (0, valid_result_json, ''),
+ ]
+
+ # Act
+ results = OCServiceAccount.run_ansible(params, False)
+
+ # Assert
+ self.assertTrue(results['changed'])
+ self.assertEqual(results['results']['returncode'], 0)
+ self.assertEqual(results['state'], 'present')
+
+ # Making sure our mock was called as we expected
+ mock_cmd.assert_has_calls([
+ mock.call(['/usr/bin/oc', '-n', 'default', 'get', 'sa', 'testserviceaccountname', '-o', 'json'], None),
+ mock.call(['/usr/bin/oc', '-n', 'default', 'create', '-f', '/tmp/testserviceaccountname'], None),
+ mock.call(['/usr/bin/oc', '-n', 'default', 'get', 'sa', 'testserviceaccountname', '-o', 'json'], None),
+ ])
+
+ def tearDown(self):
+ '''TearDown method'''
+ pass
+
+
+if __name__ == "__main__":
+ unittest.main()