From d338c1aefc702919e6fac553c60a69455ae37d05 Mon Sep 17 00:00:00 2001 From: Kenny Woodson Date: Tue, 7 Feb 2017 21:44:47 -0500 Subject: Adding oc_env to lib_openshift. --- roles/lib_openshift/src/ansible/oc_env.py | 32 +++ roles/lib_openshift/src/class/oc_env.py | 144 ++++++++++ roles/lib_openshift/src/doc/env | 76 +++++ roles/lib_openshift/src/sources.yml | 11 + roles/lib_openshift/src/test/unit/oc_env.py | 430 ++++++++++++++++++++++++++++ 5 files changed, 693 insertions(+) create mode 100644 roles/lib_openshift/src/ansible/oc_env.py create mode 100644 roles/lib_openshift/src/class/oc_env.py create mode 100644 roles/lib_openshift/src/doc/env create mode 100755 roles/lib_openshift/src/test/unit/oc_env.py (limited to 'roles/lib_openshift/src') diff --git a/roles/lib_openshift/src/ansible/oc_env.py b/roles/lib_openshift/src/ansible/oc_env.py new file mode 100644 index 000000000..4a58f7ec1 --- /dev/null +++ b/roles/lib_openshift/src/ansible/oc_env.py @@ -0,0 +1,32 @@ +# pylint: skip-file + +def main(): + ''' + ansible oc module for environment variables + ''' + + 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'), + kind=dict(default='rc', choices=['dc', 'rc', 'pods'], type='str'), + namespace=dict(default='default', type='str'), + name=dict(default=None, required=True, type='str'), + env_vars=dict(default=None, type='dict'), + ), + mutually_exclusive=[["content", "files"]], + + supports_check_mode=True, + ) + results = OCEnv.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/class/oc_env.py b/roles/lib_openshift/src/class/oc_env.py new file mode 100644 index 000000000..a7ea0c9eb --- /dev/null +++ b/roles/lib_openshift/src/class/oc_env.py @@ -0,0 +1,144 @@ +# pylint: skip-file + +# pylint: disable=too-many-instance-attributes +class OCEnv(OpenShiftCLI): + ''' Class to wrap the oc command line tools ''' + + container_path = {"pod": "spec.containers[0].env", + "dc": "spec.template.spec.containers[0].env", + "rc": "spec.template.spec.containers[0].env", + } + + # pylint allows 5. we need 6 + # pylint: disable=too-many-arguments + def __init__(self, + namespace, + kind, + env_vars, + resource_name=None, + kubeconfig='/etc/origin/master/admin.kubeconfig', + verbose=False): + ''' Constructor for OpenshiftOC ''' + super(OCEnv, self).__init__(namespace, kubeconfig) + self.kind = kind + self.name = resource_name + self.namespace = namespace + self.env_vars = env_vars + self.kubeconfig = kubeconfig + self.verbose = verbose + self._resource = None + + @property + def resource(self): + ''' property function for resource var''' + if not self._resource: + self.get() + return self._resource + + @resource.setter + def resource(self, data): + ''' setter function for resource var''' + self._resource = data + + def value_exists(self, key, value): + ''' return whether a key, value pair exists ''' + return self.resource.exists_env_value(key, value) + + def key_exists(self, key): + ''' return whether a key, value pair exists ''' + return self.resource.exists_env_key(key) + + def get(self): + '''return a environment variables ''' + result = self._get(self.kind, self.name) + if result['returncode'] == 0: + if self.kind == 'dc': + self.resource = DeploymentConfig(content=result['results'][0]) + result['results'] = self.resource.get(OCEnv.container_path[self.kind]) or [] + return result + + def delete(self): + '''return all pods ''' + #yed.put(OCEnv.container_path[self.kind], env_vars_array) + if self.resource.delete_env_var(self.env_vars.keys()): + return self._replace_content(self.kind, self.name, self.resource.yaml_dict) + + return {'returncode': 0, 'changed': False} + + # pylint: disable=too-many-function-args + def put(self): + '''place env vars into dc ''' + for update_key, update_value in self.env_vars.items(): + self.resource.update_env_var(update_key, update_value) + + return self._replace_content(self.kind, self.name, self.resource.yaml_dict) + + @staticmethod + def run_ansible(params, check_mode): + '''run the idempotent ansible code''' + + ocenv = OCEnv(params['namespace'], + params['kind'], + params['env_vars'], + resource_name=params['name'], + kubeconfig=params['kubeconfig'], + verbose=params['debug']) + + state = params['state'] + + api_rval = ocenv.get() + + ##### + # Get + ##### + if state == 'list': + return {'changed': False, 'results': api_rval['results'], 'state': "list"} + + ######## + # Delete + ######## + if state == 'absent': + for key in params.get('env_vars', {}).keys(): + if ocenv.resource.exists_env_key(key): + + if check_mode: + return {'changed': False, + 'msg': 'CHECK_MODE: Would have performed a delete.'} + + api_rval = ocenv.delete() + + return {'changed': True, 'results': results, 'state': 'absent'} + + return {'changed': False, 'state': 'absent'} + + if state == 'present': + ######## + # Create + ######## + for key, value in params.get('env_vars', {}).items(): + if not ocenv.value_exists(key, value): + + if check_mode: + return {'changed': False, + 'msg': 'CHECK_MODE: Would have performed a create.'} + + # Create it here + api_rval = ocenv.put() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + # return the created object + api_rval = ocenv.get() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + return {'changed': True, 'results': results, 'state': 'present'} + + return {'changed': False, 'results': results, 'state': 'present'} + + + return {'failed': True, + 'changed': False, + 'msg': 'Unknown state passed. %s' % state} diff --git a/roles/lib_openshift/src/doc/env b/roles/lib_openshift/src/doc/env new file mode 100644 index 000000000..0544014e6 --- /dev/null +++ b/roles/lib_openshift/src/doc/env @@ -0,0 +1,76 @@ +# flake8: noqa +# pylint: skip-file + +DOCUMENTATION = ''' +--- +module: oc_env +short_description: Modify, and idempotently manage openshift environment variables on pods, deploymentconfigs, and replication controllers. +description: + - Modify openshift environment variables programmatically. +options: + state: + description: + - Supported states, present, absent, list + - present - will ensure object is created or updated to the value specified + - list - will return a list of environment variables + - absent - will remove the environment varibale from the object + 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 object that is being queried. + required: false + default: None + aliases: [] + namespace: + description: + - The namespace where the object lives. + required: false + default: str + aliases: [] + kind: + description: + - The kind attribute of the object. + required: False + default: dc + choices: + - rc + - dc + - pods + aliases: [] +author: +- "Kenny Woodson " +extends_documentation_fragment: [] +''' + +EXAMPLES = ''' +- name: query a list of env vars on dc + oc_env: + kind: dc + name: myawesomedc + namespace: phpapp + +- name: Set the following variables. + oc_env: + kind: dc + name: myawesomedc + namespace: phpapp + env_vars: + SUPER_TURBO_MODE: 'true' + ENABLE_PORTS: 'false' + SERVICE_PORT: 9999 +''' diff --git a/roles/lib_openshift/src/sources.yml b/roles/lib_openshift/src/sources.yml index aa02ce120..badb1b1a4 100644 --- a/roles/lib_openshift/src/sources.yml +++ b/roles/lib_openshift/src/sources.yml @@ -19,6 +19,17 @@ oc_edit.py: - class/oc_edit.py - ansible/oc_edit.py +oc_env.py: +- doc/generated +- doc/license +- lib/import.py +- doc/env +- ../../lib_utils/src/class/yedit.py +- lib/base.py +- lib/deploymentconfig.py +- class/oc_env.py +- ansible/oc_env.py + oc_label.py: - doc/generated - doc/license diff --git a/roles/lib_openshift/src/test/unit/oc_env.py b/roles/lib_openshift/src/test/unit/oc_env.py new file mode 100755 index 000000000..5661065b9 --- /dev/null +++ b/roles/lib_openshift/src/test/unit/oc_env.py @@ -0,0 +1,430 @@ +#!/usr/bin/env python2 +''' + Unit tests for oc_env +''' +# To run: +# ./oc_env.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_env import OCEnv # noqa: E402 + + +class OCEnvTest(unittest.TestCase): + ''' + Test class for OCEnv + ''' + + def setUp(self): + ''' setup method will create a file and set to known configuration ''' + pass + + @mock.patch('oc_env.OCEnv._run') + def test_listing_all_env_vars(self, mock_cmd): + ''' Testing listing all environment variables from a dc''' + + # Arrange + + # run_ansible input parameters + params = { + 'state': 'list', + 'namespace': 'default', + 'name': 'router', + 'kind': 'dc', + 'list_all': False, + 'env_vars': None, + 'kubeconfig': '/etc/origin/master/admin.kubeconfig', + 'debug': False, + } + + dc_results = '''{ + "apiVersion": "v1", + "kind": "DeploymentConfig", + "metadata": { + "creationTimestamp": "2017-02-02T15:58:49Z", + "generation": 8, + "labels": { + "router": "router" + }, + "name": "router", + "namespace": "default", + "resourceVersion": "513678", + "selfLink": "/oapi/v1/namespaces/default/deploymentconfigs/router", + "uid": "7c705902-e960-11e6-b041-0ed9df7abc38" + }, + "spec": { + "replicas": 2, + "selector": { + "router": "router" + }, + "strategy": { + "activeDeadlineSeconds": 21600, + "resources": {}, + "rollingParams": { + "intervalSeconds": 1, + "maxSurge": "50%", + "maxUnavailable": "50%", + "timeoutSeconds": 600, + "updatePeriodSeconds": 1 + }, + "type": "Rolling" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "router": "router" + } + }, + "spec": { + "containers": [ + { + "env": [ + { + "name": "DEFAULT_CERTIFICATE_DIR", + "value": "/etc/pki/tls/private" + }, + { + "name": "DEFAULT_CERTIFICATE_PATH", + "value": "/etc/pki/tls/private/tls.crt" + }, + { + "name": "ROUTER_EXTERNAL_HOST_HOSTNAME" + }, + { + "name": "ROUTER_EXTERNAL_HOST_HTTPS_VSERVER" + }, + { + "name": "ROUTER_EXTERNAL_HOST_HTTP_VSERVER" + }, + { + "name": "ROUTER_EXTERNAL_HOST_INSECURE", + "value": "false" + }, + { + "name": "ROUTER_EXTERNAL_HOST_INTERNAL_ADDRESS" + }, + { + "name": "ROUTER_EXTERNAL_HOST_PARTITION_PATH" + }, + { + "name": "ROUTER_EXTERNAL_HOST_PASSWORD" + }, + { + "name": "ROUTER_EXTERNAL_HOST_PRIVKEY", + "value": "/etc/secret-volume/router.pem" + }, + { + "name": "ROUTER_EXTERNAL_HOST_USERNAME" + }, + { + "name": "ROUTER_EXTERNAL_HOST_VXLAN_GW_CIDR" + }, + { + "name": "ROUTER_SERVICE_HTTPS_PORT", + "value": "443" + }, + { + "name": "ROUTER_SERVICE_HTTP_PORT", + "value": "80" + }, + { + "name": "ROUTER_SERVICE_NAME", + "value": "router" + }, + { + "name": "ROUTER_SERVICE_NAMESPACE", + "value": "default" + }, + { + "name": "ROUTER_SUBDOMAIN" + }, + { + "name": "STATS_PASSWORD", + "value": "UEKR5GCWGI" + }, + { + "name": "STATS_PORT", + "value": "1936" + }, + { + "name": "STATS_USERNAME", + "value": "admin" + }, + { + "name": "EXTENDED_VALIDATION", + "value": "false" + }, + { + "name": "ROUTER_USE_PROXY_PROTOCOL", + "value": "true" + } + ], + "image": "openshift3/ose-haproxy-router:v3.5.0.17", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "failureThreshold": 3, + "httpGet": { + "host": "localhost", + "path": "/healthz", + "port": 1936, + "scheme": "HTTP" + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "name": "router", + "ports": [ + { + "containerPort": 80, + "hostPort": 80, + "protocol": "TCP" + }, + { + "containerPort": 443, + "hostPort": 443, + "protocol": "TCP" + }, + { + "containerPort": 5000, + "hostPort": 5000, + "protocol": "TCP" + }, + { + "containerPort": 1936, + "hostPort": 1936, + "name": "stats", + "protocol": "TCP" + } + ], + "readinessProbe": { + "failureThreshold": 3, + "httpGet": { + "host": "localhost", + "path": "/healthz", + "port": 1936, + "scheme": "HTTP" + }, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "resources": { + "requests": { + "cpu": "100m", + "memory": "256Mi" + } + }, + "terminationMessagePath": "/dev/termination-log", + "volumeMounts": [ + { + "mountPath": "/etc/pki/tls/private", + "name": "server-certificate", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "hostNetwork": true, + "nodeSelector": { + "type": "infra" + }, + "restartPolicy": "Always", + "securityContext": {}, + "serviceAccount": "router", + "serviceAccountName": "router", + "terminationGracePeriodSeconds": 30, + "volumes": [ + { + "name": "server-certificate", + "secret": { + "defaultMode": 420, + "secretName": "router-certs" + } + } + ] + } + }, + "test": false, + "triggers": [ + { + "type": "ConfigChange" + } + ] + }, + "status": { + "availableReplicas": 2, + "conditions": [ + { + "lastTransitionTime": "2017-02-02T15:59:12Z", + "lastUpdateTime": null, + "message": "Deployment config has minimum availability.", + "status": "True", + "type": "Available" + }, + { + "lastTransitionTime": "2017-02-07T19:55:26Z", + "lastUpdateTime": "2017-02-07T19:55:26Z", + "message": "replication controller router-2 has failed progressing", + "reason": "ProgressDeadlineExceeded", + "status": "False", + "type": "Progressing" + } + ], + "details": { + "causes": [ + { + "type": "ConfigChange" + } + ], + "message": "config change" + }, + "latestVersion": 2, + "observedGeneration": 8, + "readyReplicas": 2, + "replicas": 2, + "unavailableReplicas": 0, + "updatedReplicas": 0 + } + }''' + + # Return values of our mocked function call. These get returned once per call. + mock_cmd.side_effect = [ + (0, dc_results, ''), # First call to the mock + ] + + # Act + results = OCEnv.run_ansible(params, False) + + # Assert + self.assertFalse(results['changed']) + for env_var in results['results']: + if env_var == {'name': 'DEFAULT_CERTIFICATE_DIR', 'value': '/etc/pki/tls/private'}: + break + else: + self.fail('Did not find envionrment variables in results.') + self.assertEqual(results['state'], 'list') + + # Making sure our mocks were called as we expected + mock_cmd.assert_has_calls([ + mock.call(['oc', '-n', 'default', 'get', 'dc', 'router', '-o', 'json'], None), + ]) + +# @mock.patch('oc_serviceaccount_secret.Yedit._write') +# @mock.patch('oc_serviceaccount_secret.OCServiceAccountSecret._run') +# def test_removing_a_secret_to_a_serviceaccount(self, mock_cmd, mock_write): +# ''' Testing adding a secret to a service account ''' +# +# # Arrange +# +# # run_ansible input parameters +# params = { +# 'state': 'absent', +# 'namespace': 'default', +# 'secret': 'newsecret', +# 'service_account': 'builder', +# 'kubeconfig': '/etc/origin/master/admin.kubeconfig', +# 'debug': False, +# } +# +# oc_get_sa_before = '''{ +# "kind": "ServiceAccount", +# "apiVersion": "v1", +# "metadata": { +# "name": "builder", +# "namespace": "default", +# "selfLink": "/api/v1/namespaces/default/serviceaccounts/builder", +# "uid": "cf47bca7-ebc4-11e6-b041-0ed9df7abc38", +# "resourceVersion": "302879", +# "creationTimestamp": "2017-02-05T17:02:00Z" +# }, +# "secrets": [ +# { +# "name": "builder-dockercfg-rsrua" +# }, +# { +# "name": "builder-token-akqxi" +# }, +# { +# "name": "newsecret" +# } +# +# ], +# "imagePullSecrets": [ +# { +# "name": "builder-dockercfg-rsrua" +# } +# ] +# } +# ''' +# +# builder_yaml_file = '''\ +#secrets: +#- name: builder-dockercfg-rsrua +#- name: builder-token-akqxi +#kind: ServiceAccount +#imagePullSecrets: +#- name: builder-dockercfg-rsrua +#apiVersion: v1 +#metadata: +# name: builder +# namespace: default +# resourceVersion: '302879' +# creationTimestamp: '2017-02-05T17:02:00Z' +# selfLink: /api/v1/namespaces/default/serviceaccounts/builder +# uid: cf47bca7-ebc4-11e6-b041-0ed9df7abc38 +#''' +# +# # Return values of our mocked function call. These get returned once per call. +# mock_cmd.side_effect = [ +# (0, oc_get_sa_before, ''), # First call to the mock +# (0, oc_get_sa_before, ''), # Second call to the mock +# (0, 'serviceaccount "builder" replaced', ''), # Third call to the mock +# ] +# +# # Act +# results = OCServiceAccountSecret.run_ansible(params, False) +# +# # Assert +# self.assertTrue(results['changed']) +# self.assertEqual(results['results']['returncode'], 0) +# self.assertEqual(results['state'], 'absent') +# +# # Making sure our mocks were called as we expected +# mock_cmd.assert_has_calls([ +# mock.call(['oc', '-n', 'default', 'get', 'sa', 'builder', '-o', 'json'], None), +# mock.call(['oc', '-n', 'default', 'get', 'sa', 'builder', '-o', 'json'], None), +# mock.call(['oc', '-n', 'default', 'replace', '-f', '/tmp/builder'], None), +# ]) +# +# mock_write.assert_has_calls([ +# mock.call('/tmp/builder', builder_yaml_file) +# ]) + + def tearDown(self): + '''TearDown method''' + pass + + +if __name__ == "__main__": + unittest.main() -- cgit v1.2.3