From d9b88cc5ee987f5fed1011583a172f3c76251814 Mon Sep 17 00:00:00 2001 From: jh245g Date: Wed, 27 Jun 2018 14:50:33 -0400 Subject: Update helm plugin source code Change-Id: I1689d8d915c8f18a3e8230dcabb33413a2b9043e Issue-ID: CCSDK-322 Signed-off-by: jh245g --- helm/.travis.yml | 11 + helm/LICENSE | 201 ++++++++++++++ helm/README.md | 12 + helm/circle.yml | 25 ++ helm/dev-requirements.txt | 6 + helm/helm-type.yaml | 129 +++++++++ helm/plugin/__init__.py | 14 + helm/plugin/tasks.py | 305 +++++++++++++++++++++ helm/plugin/tests/__init__.py | 14 + helm/plugin/tests/blueprint/blueprint.yaml | 42 +++ .../plugin/tests/blueprint/plugin/test_plugin.yaml | 20 ++ helm/plugin/tests/test_plugin.py | 47 ++++ helm/plugin/workflows.py | 64 +++++ helm/setup.py | 46 ++++ helm/tox.ini | 18 ++ 15 files changed, 954 insertions(+) create mode 100644 helm/.travis.yml create mode 100644 helm/LICENSE create mode 100644 helm/README.md create mode 100644 helm/circle.yml create mode 100644 helm/dev-requirements.txt create mode 100644 helm/helm-type.yaml create mode 100644 helm/plugin/__init__.py create mode 100644 helm/plugin/tasks.py create mode 100644 helm/plugin/tests/__init__.py create mode 100644 helm/plugin/tests/blueprint/blueprint.yaml create mode 100644 helm/plugin/tests/blueprint/plugin/test_plugin.yaml create mode 100644 helm/plugin/tests/test_plugin.py create mode 100644 helm/plugin/workflows.py create mode 100644 helm/setup.py create mode 100644 helm/tox.ini diff --git a/helm/.travis.yml b/helm/.travis.yml new file mode 100644 index 0000000..edb3cf0 --- /dev/null +++ b/helm/.travis.yml @@ -0,0 +1,11 @@ +sudo: true +language: python +python: + - "2.7" +env: + - TOX_ENV=flake8 + - TOX_ENV=py27 +install: + - pip install tox +script: + - tox -e $TOX_ENV diff --git a/helm/LICENSE b/helm/LICENSE new file mode 100644 index 0000000..ad410e1 --- /dev/null +++ b/helm/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/helm/README.md b/helm/README.md new file mode 100644 index 0000000..4128fba --- /dev/null +++ b/helm/README.md @@ -0,0 +1,12 @@ +cloudify-plugin-template +======================== + +[![Build Status](https://travis-ci.org/cloudify-cosmo/cloudify-plugin-template.svg?branch=master)](https://travis-ci.org/cloudify-cosmo/cloudify-plugin-template) + +ONAP Helm Plugin. +This Plugin will utilize the ONAP helm chart to install, uninstall, upgrade, and rollback ONAP components. + +## Documents + +See https://wiki.onap.org/display/DW/Introduction+of+Helm+Plugin + diff --git a/helm/circle.yml b/helm/circle.yml new file mode 100644 index 0000000..4245150 --- /dev/null +++ b/helm/circle.yml @@ -0,0 +1,25 @@ +checkout: + post: + - > + if [ -n "$CI_PULL_REQUEST" ]; then + PR_ID=${CI_PULL_REQUEST##*/} + git fetch origin +refs/pull/$PR_ID/merge: + git checkout -qf FETCH_HEAD + fi + +dependencies: + pre: + - pyenv local 2.7.9 + override: + - pip install tox + +test: + override: + - tox -e flake8 + - tox -e py27 + +deployment: + release: + tag: /.*/ + commands: + - (true) diff --git a/helm/dev-requirements.txt b/helm/dev-requirements.txt new file mode 100644 index 0000000..48bc5ba --- /dev/null +++ b/helm/dev-requirements.txt @@ -0,0 +1,6 @@ +pyyaml==3.12 + +-e git+https://github.com/cloudify-cosmo/cloudify-dsl-parser@4.1.1-build#egg=cloudify-dsl-parser==4.1.1 +-e git+https://github.com/cloudify-cosmo/cloudify-rest-client@4.1.1-build#egg=cloudify-rest-client==4.1.1 +-e git+https://github.com/cloudify-cosmo/cloudify-plugins-common@4.1.1-build#egg=cloudify-plugins-common==4.1.1 +nose diff --git a/helm/helm-type.yaml b/helm/helm-type.yaml new file mode 100644 index 0000000..2e79849 --- /dev/null +++ b/helm/helm-type.yaml @@ -0,0 +1,129 @@ +# ============LICENSE_START========================================== +# =================================================================== +# Copyright (c) 2017 AT&T +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============LICENSE_END============================================ + +plugins: + helm-plugin: + executor: central_deployment_agent + package_name: onap-helm-plugin + package_version: 2.2.0 + +node_types: + + onap.nodes.component: + derived_from: cloudify.nodes.Root + properties: + tiller-server-ip: + description: IP of tiller server + type: string + tiller-server-port: + default: local + description: Port of tiller server + type: string + chart-repo-url: + default: local + description: helm chart repo url + type: string + component-name: + description: onap component string + type: string + chart-version: + description: helm chart version + type: string + config-dir: + description: config file dir + default: '/opt/manager/resources/' + type: string + namespace: + description: k8s namespace + default: onap + config: + description: String format config file + type: string + default: '' + config-url: + description: String format config file url + type: string + default: '' + runtime-config: + default: '' + description: String format json object. To save the runtime config generate from other nodes. + tls-enable: + description: enable helm TSL + type: boolean + default: false + ca: + description: value of ca.pem + type: string + default: '' + cert: + description: value of cert.pem + type: string + default: '' + key: + description: value of key.pem + type: string + default: '' + stable-repo-url: + description: URL for stable repository + type: string + default: 'https://kubernetes-charts.storage.googleapis.com' + + # This part should handel by Blueprint not plugin + # the default docker values points to ONAP nexus3 docker repo. + # If need point to other private docker repo you can overrite it in blueprint node templates . +# docker-server: +# description: Private Docker Registry FQDN. +# default: nexus3.onap.org:10001 +# docker-username: +# description: Docker username. +# default: docker +# docker-password: +# description: Docker password. +# default: docker + + interfaces: + cloudify.interfaces.lifecycle: + configure: helm-plugin.plugin.tasks.config + start: helm-plugin.plugin.tasks.start + stop: helm-plugin.plugin.tasks.stop + upgrade: helm-plugin.plugin.tasks.upgrade + rollback: helm-plugin.plugin.tasks.rollback + + +workflows: + upgrade: + mapping: helm-plugin.plugin.workflows.upgrade + parameters: + node_instance_id: + description: The id of the node-instance that you want to modify. + config_json: + description: The changes to the new config json + default: '' + config_json_url: + description: The changes to the new config json url + default: '' + chartVersion: + description: chart version + chartRepo: + description: chart repo url + rollback: + mapping: helm-plugin.plugin.workflows.rollback + parameters: + node_instance_id: + description: The id of the node-instance that you want to modify. + revision: + description: Check the node runtime property history, find the revision number you want to rollback to diff --git a/helm/plugin/__init__.py b/helm/plugin/__init__.py new file mode 100644 index 0000000..749f68f --- /dev/null +++ b/helm/plugin/__init__.py @@ -0,0 +1,14 @@ +######## +# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. diff --git a/helm/plugin/tasks.py b/helm/plugin/tasks.py new file mode 100644 index 0000000..8df29ac --- /dev/null +++ b/helm/plugin/tasks.py @@ -0,0 +1,305 @@ +# ============LICENSE_START========================================== +# =================================================================== +# Copyright (c) 2018 AT&T +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============LICENSE_END============================================ + +from cloudify.decorators import operation +import shutil +import errno +import sys +import pwd +import grp +import os +import re +import getpass +import subprocess +from cloudify import ctx +from cloudify.exceptions import OperationRetry +from cloudify_rest_client.exceptions import CloudifyClientError +import pip +import json +import yaml +import urllib2 +from cloudify.decorators import operation +from cloudify import exceptions +from cloudify.exceptions import NonRecoverableError + + + +def execute_command(_command): + ctx.logger.debug('_command {0}.'.format(_command)) + + subprocess_args = { + 'args': _command.split(), + 'stdout': subprocess.PIPE, + 'stderr': subprocess.PIPE + } + + ctx.logger.debug('subprocess_args {0}.'.format(subprocess_args)) + + process = subprocess.Popen(**subprocess_args) + output, error = process.communicate() + + ctx.logger.debug('command: {0} '.format(_command)) + ctx.logger.debug('output: {0} '.format(output)) + ctx.logger.debug('error: {0} '.format(error)) + ctx.logger.debug('process.returncode: {0} '.format(process.returncode)) + + if process.returncode: + ctx.logger.error('Running `{0}` returns error.'.format(_command)) + return False + + return output + + +def configure_admin_conf(): + # Add the kubeadmin config to environment + agent_user = getpass.getuser() + uid = pwd.getpwnam(agent_user).pw_uid + gid = grp.getgrnam('docker').gr_gid + admin_file_dest = os.path.join(os.path.expanduser('~'), 'admin.conf') + + execute_command('sudo cp {0} {1}'.format('/etc/kubernetes/admin.conf', admin_file_dest)) + execute_command('sudo chown {0}:{1} {2}'.format(uid, gid, admin_file_dest)) + + with open(os.path.join(os.path.expanduser('~'), '.bashrc'), 'a') as outfile: + outfile.write('export KUBECONFIG=$HOME/admin.conf') + os.environ['KUBECONFIG'] = admin_file_dest + +def get_current_helm_value(chart_name): + tiller_host= str(ctx.node.properties['tiller-server-ip'])+':'+str(ctx.node.properties['tiller-server-port']) + config_dir_root= str(ctx.node.properties['config-dir']) + config_dir=config_dir_root+str(ctx.deployment.id)+'/' + if str_to_bool(ctx.node.properties['tls-enable']): + getValueCommand=subprocess.Popen(["helm", "get","values","-a",chart_name,'--host',tiller_host,'--tls','--tls-ca-cert',config_dir+'ca.cert.pem','--tls-cert',config_dir+'helm.cert.pem','--tls-key',config_dir+'helm.key.pem'], stdout=subprocess.PIPE) + else: + getValueCommand=subprocess.Popen(["helm", "get","values","-a",chart_name,'--host',tiller_host], stdout=subprocess.PIPE) + value=getValueCommand.communicate()[0] + valueMap= {} + valueMap = yaml.safe_load(value) + ctx.instance.runtime_properties['current-helm-value'] = valueMap + +def get_helm_history(chart_name): + tiller_host= str(ctx.node.properties['tiller-server-ip'])+':'+str(ctx.node.properties['tiller-server-port']) + config_dir_root= str(ctx.node.properties['config-dir']) + config_dir=config_dir_root+str(ctx.deployment.id)+'/' + if str_to_bool(ctx.node.properties['tls-enable']): + getHistoryCommand=subprocess.Popen(["helm", "history",chart_name,'--host',tiller_host,'--tls','--tls-ca-cert',config_dir+'ca.cert.pem','--tls-cert',config_dir+'helm.cert.pem','--tls-key',config_dir+'helm.key.pem'], stdout=subprocess.PIPE) + else: + getHistoryCommand=subprocess.Popen(["helm", "history",chart_name,'--host',tiller_host], stdout=subprocess.PIPE) + history=getHistoryCommand.communicate()[0] + history_start_output = [line.strip() for line in history.split('\n') if line.strip()] + for index in range(len(history_start_output)): + history_start_output[index]=history_start_output[index].replace('\t',' ') + ctx.instance.runtime_properties['helm-history'] = history_start_output + +def mergedict(dict1, dict2): + for key in dict2.keys(): + if key not in dict1.keys(): + dict1[key] = dict2[key] + else: + if type(dict1[key]) == dict and type(dict2[key]) == dict : + mergedict(dict1[key], dict2[key]) + else: + dict1[key] = dict2[key] + +def tls(): + if str_to_bool(ctx.node.properties['tls-enable']): + config_dir_root= str(ctx.node.properties['config-dir']) + config_dir=config_dir_root+str(ctx.deployment.id)+'/' + tls_command= ' --tls --tls-ca-cert '+config_dir+'ca.cert.pem --tls-cert '+config_dir+'helm.cert.pem --tls-key '+config_dir+'helm.key.pem ' + ctx.logger.debug(tls_command) + return tls_command + else : + return '' + +def tiller_host(): + tiller_host= ' --host '+str(ctx.node.properties['tiller-server-ip'])+':'+str(ctx.node.properties['tiller-server-port'])+' ' + ctx.logger.debug(tiller_host) + return tiller_host + + +def str_to_bool(s): + s=str(s) + if s == 'True' or s == 'true': + return True + elif s == 'False' or s== 'false': + return False + else: + raise False + + +@operation +def config(**kwargs): + # create helm value file on K8s master + #configPath = ctx.node.properties['config-path'] + configJson = str(ctx.node.properties['config']) + configJsonUrl = str(ctx.node.properties['config-url']) + runtime_config = str(ctx.node.properties['runtime-config']) #json + componentName = ctx.node.properties['component-name'] + config_dir_root= str(ctx.node.properties['config-dir']) + stable_repo_url = str(ctx.node.properties['stable-repo-url']) + ctx.logger.debug("debug "+ configJson + runtime_config ) + #load input config + config_dir=config_dir_root+str(ctx.deployment.id) + try: + os.makedirs(config_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + ctx.logger.debug('tls-enable type '+str(type(str_to_bool(ctx.node.properties['tls-enable']))) ) + #create TLS cert files + if str_to_bool(ctx.node.properties['tls-enable']): + ctx.logger.debug('tls enable' ) + ca_value = ctx.node.properties['ca'] + cert_value = ctx.node.properties['cert'] + key_value = ctx.node.properties['key'] + ca= open(config_dir+'/ca.cert.pem',"w+") + ca.write(ca_value) + ca.close() + cert= open(config_dir+'/helm.cert.pem',"w+") + cert.write(cert_value) + cert.close() + key= open(config_dir+'/helm.key.pem',"w+") + key.write(key_value) + key.close() + else: + ctx.logger.debug('tls disable' ) + + # create helm value.yaml file + configPath=config_dir_root+str(ctx.deployment.id)+'/'+componentName+'.yaml' + ctx.logger.debug(configPath) + + configObj ={} + if configJson == '' and configJsonUrl == '': + ctx.logger.debug("Will use default HELM value") + elif configJson == '' and configJsonUrl != '': + response = urllib2.urlopen(configJsonUrl) + configObj = json.load(response) + elif configJson != '' and configJsonUrl == '': + configObj = json.loads(configJson) + else: + raise NonRecoverableError("Unable to get Json config input") + + # load runtime config + ctx.logger.debug("debug check runtime config") + if runtime_config == '': + ctx.logger.debug("there is no runtime config value") + else: + runtime_config_obj= json.loads(runtime_config) + mergedict(configObj,runtime_config_obj) + + with open(configPath, 'w') as outfile: + yaml.safe_dump(configObj, outfile, default_flow_style=False) + + output = execute_command('helm init --client-only --stable-repo-url '+stable_repo_url) + if output == False : + raise NonRecoverableError("helm init failed") + + + + +@operation +def start(**kwargs): + # install the ONAP Helm chart + # get properties from node + chartRepo = ctx.node.properties['chart-repo-url'] + componentName = ctx.node.properties['component-name'] + chartVersion = ctx.node.properties['chart-version'] + config_dir_root= str(ctx.node.properties['config-dir']) + configPath=config_dir_root+str(ctx.deployment.id)+'/'+componentName+'.yaml' + namespace = ctx.node.properties['namespace'] + configJson = str(ctx.node.properties['config']) + configJsonUrl = str(ctx.node.properties['config-url']) + runtimeconfigJson = str(ctx.node.properties['runtime-config']) + + + chart = chartRepo + "/" + componentName + "-" + chartVersion + ".tgz" + chartName = namespace + "-" + componentName + + if configJson == '' and runtimeconfigJson == '' and configJsonUrl == '': + installCommand = 'helm install '+ chart + ' --name ' + chartName + ' --namespace ' + namespace+tiller_host()+tls() + else: + installCommand = 'helm install ' + chart + ' --name ' + chartName + ' --namespace ' + namespace + ' -f '+ configPath +tiller_host()+tls() + + output =execute_command(installCommand) + if output == False : + return ctx.operation.retry(message='helm install failed, re-try after 5 second ', + retry_after=5) + + get_current_helm_value(chartName) + get_helm_history(chartName) + +@operation +def stop(**kwargs): + # delete the ONAP helm chart + #configure_admin_conf() + # get properties from node + namespace = ctx.node.properties['namespace'] + component = ctx.node.properties['component-name'] + chartName = namespace + "-" + component + config_dir_root= str(ctx.node.properties['config-dir']) + # Delete helm chart + command = 'helm delete --purge '+ chartName+tiller_host()+tls() + output =execute_command(command) + config_dir=config_dir_root+str(ctx.deployment.id) + shutil.rmtree(config_dir) + if output == False : + raise NonRecoverableError("helm delete failed") + +@operation +def upgrade(**kwargs): + # upgrade the helm chart + componentName = ctx.node.properties['component-name'] + config_dir_root= str(ctx.node.properties['config-dir']) + configPath=config_dir_root+str(ctx.deployment.id)+'/'+componentName+'.yaml' + componentName = ctx.node.properties['component-name'] + namespace = ctx.node.properties['namespace'] + configJson = kwargs['config'] + chartRepo = kwargs['chart_repo'] + chartVersion = kwargs['chart_version'] + + ctx.logger.debug('debug ' + str(configJson)) + chartName = namespace + "-" + componentName + chart=chartRepo + "/" + componentName + "-" + chartVersion + ".tgz" + if str(configJson) == '': + upgradeCommand = 'helm upgrade '+ chartName + ' '+ chart+tiller_host()+tls() + else: + with open(configPath, 'w') as outfile: + yaml.safe_dump(configJson, outfile, default_flow_style=False) + #configure_admin_conf() + upgradeCommand = 'helm upgrade '+ chartName + ' '+ chart + ' -f ' + configPath+tiller_host()+tls() + output=execute_command(upgradeCommand) + if output == False : + return ctx.operation.retry(message='helm upgrade failed, re-try after 5 second ', + retry_after=5) + get_current_helm_value(chartName) + get_helm_history(chartName) + +@operation +def rollback(**kwargs): + # rollback to some revision + componentName = ctx.node.properties['component-name'] + namespace = ctx.node.properties['namespace'] + revision = kwargs['revision'] + #configure_admin_conf() + chartName = namespace + "-" + componentName + rollbackCommand = 'helm rollback '+ chartName + ' '+ revision+tiller_host()+tls() + output=execute_command(rollbackCommand) + if output == False : + return ctx.operation.retry(message='helm rollback failed, re-try after 5 second ', + retry_after=5) + get_current_helm_value(chartName) + get_helm_history(chartName) diff --git a/helm/plugin/tests/__init__.py b/helm/plugin/tests/__init__.py new file mode 100644 index 0000000..749f68f --- /dev/null +++ b/helm/plugin/tests/__init__.py @@ -0,0 +1,14 @@ +######## +# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. diff --git a/helm/plugin/tests/blueprint/blueprint.yaml b/helm/plugin/tests/blueprint/blueprint.yaml new file mode 100644 index 0000000..2588e8d --- /dev/null +++ b/helm/plugin/tests/blueprint/blueprint.yaml @@ -0,0 +1,42 @@ +# DSL version, should appear in the main blueprint.yaml +# and may appear in other imports. In such case, the versions must match +tosca_definitions_version: cloudify_dsl_1_3 + +imports: + # importing cloudify related types, plugins, workflow, etc... + # to speed things up, it is possible downloading this file, + # including it in the blueprint directory and importing it + # instead. + - http://www.getcloudify.org/spec/cloudify/4.1.1/types.yaml + # relative import of plugin.yaml that resides in the blueprint directory + - plugin/test_plugin.yaml + +inputs: + # example input that could be injected by test + test_input: + description: an input for the test + default: default_test_input + +node_templates: + # defining a single node template that will serve as our test node + test_node_template: + # using base cloudify type + type: cloudify.nodes.Root + interfaces: + cloudify.interfaces.lifecycle: + start: + # here we map the single plugin task to the start operation + # of the cloudify.interfaces.lifecycle interface + implementation: plugin_name.plugin.tasks.my_task + inputs: + # my_task accepts a single property named + # some property. Here we inject this property + # from the input provided by the test + # (or 'default_test_input' if no input was provided) + some_property: { get_input: test_input } + +outputs: + # example output the could be used to simplify assertions by test + test_output: + description: an output for the test + value: { get_attribute: [test_node_template, some_property] } diff --git a/helm/plugin/tests/blueprint/plugin/test_plugin.yaml b/helm/plugin/tests/blueprint/plugin/test_plugin.yaml new file mode 100644 index 0000000..9701318 --- /dev/null +++ b/helm/plugin/tests/blueprint/plugin/test_plugin.yaml @@ -0,0 +1,20 @@ +plugins: + # Name could be anything, this name is what appears on the beginning of operation + # mappings. + plugin_name: + # Could be 'central_deployment_agent' or 'host_agent'. + # If 'central_deployment_agent', this plugin will be executed on the + # deployment dedicated agent, other wise it will be executed on the host agent. + # We set it the 'central_deployment_agent' here because 'host_agent' plugins should + # be contained in a host and this is not required for testing purposes + executor: central_deployment_agent + + # Setting install to false in testing environment. In the non-test plugin definition + # this property could be omitted usually (its default is true), in which case + # the source property should be set + install: false + + # source: URL to archive containing the plugin or name of directory containing + # the plugin if it is included in the the blueprint directory under the + # "plugins" directory. Not required in testing environments as the plugin + # need not be installed on any agent diff --git a/helm/plugin/tests/test_plugin.py b/helm/plugin/tests/test_plugin.py new file mode 100644 index 0000000..be0882f --- /dev/null +++ b/helm/plugin/tests/test_plugin.py @@ -0,0 +1,47 @@ +######## +# Copyright (c) 2014 GigaSpaces Technologies Ltd. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# * See the License for the specific language governing permissions and +# * limitations under the License. + + +from os import path +import unittest + +from cloudify.test_utils import workflow_test + + +class TestPlugin(unittest.TestCase): + + @workflow_test(path.join('blueprint', 'blueprint.yaml'), + resources_to_copy=[(path.join('blueprint', 'plugin', + 'test_plugin.yaml'), + 'plugin')], + inputs={'test_input': 'new_test_input'}) + def test_my_task(self, cfy_local): + # execute install workflow + """ + + :param cfy_local: + """ + cfy_local.execute('install', task_retries=0) + + # extract single node instance + instance = cfy_local.storage.get_node_instances()[0] + + # assert runtime properties is properly set in node instance + self.assertEqual(instance.runtime_properties['some_property'], + 'new_test_input') + + # assert deployment outputs are ok + self.assertDictEqual(cfy_local.outputs(), + {'test_output': 'new_test_input'}) diff --git a/helm/plugin/workflows.py b/helm/plugin/workflows.py new file mode 100644 index 0000000..d341bf7 --- /dev/null +++ b/helm/plugin/workflows.py @@ -0,0 +1,64 @@ +# ============LICENSE_START========================================== +# =================================================================== +# Copyright (c) 2018 AT&T +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============LICENSE_END============================================ + +from cloudify.decorators import workflow +from cloudify.workflows import ctx +from cloudify.exceptions import NonRecoverableError +import urllib2 +import json + +@workflow +def upgrade(node_instance_id,config_json,config_json_url,chartVersion,chartRepo,**kwargs): + node_instance = ctx.get_node_instance(node_instance_id) + + if not node_instance_id: + raise NonRecoverableError( + 'No such node_instance_id in deployment: {0}.'.format( + node_instance_id)) + + kwargs = {} + if config_json == '' and config_json_url == '': + kwargs['config'] = config_json + elif config_json == '' and config_json_url != '': + response = urllib2.urlopen(config_json_url) + kwargs['config'] = json.load(response) + elif config_json != '' and config_json_url == '': + kwargs['config'] = config_json + else: + raise NonRecoverableError("Unable to get Json config input") + + kwargs['chart_version'] = str(chartVersion) + kwargs['chart_repo'] = str(chartRepo) + operation_args = {'operation': 'upgrade',} + operation_args['kwargs'] = kwargs + node_instance.execute_operation(**operation_args) + + +@workflow +def rollback(node_instance_id,revision,**kwargs): + node_instance = ctx.get_node_instance(node_instance_id) + + if not node_instance_id: + raise NonRecoverableError( + 'No such node_instance_id in deployment: {0}.'.format( + node_instance_id)) + + kwargs = {} + kwargs['revision'] = str(revision) + operation_args = {'operation': 'rollback',} + operation_args['kwargs'] = kwargs + node_instance.execute_operation(**operation_args) diff --git a/helm/setup.py b/helm/setup.py new file mode 100644 index 0000000..c3bfe88 --- /dev/null +++ b/helm/setup.py @@ -0,0 +1,46 @@ +# ============LICENSE_START========================================== +# =================================================================== +# Copyright (c) 2018 AT&T +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============LICENSE_END============================================ + + +from setuptools import setup + +# Replace the place holders with values for your project + +setup( + + # Do not use underscores in the plugin name. + name='onap-helm-plugin', + version='2.2.0', + author='Nicolas Hu(AT&T)', + author_email='jh245g@att.com', + description='This plugin will install/uninstall/upgrade/rollback helm charts of ONAP components. ', + + # This must correspond to the actual packages in the plugin. + packages=['plugin'], + + license='LICENSE', + zip_safe=False, + install_requires=[ + # Necessary dependency for developing plugins, do not remove! + 'pyyaml>=3.12', + "cloudify-plugins-common>=4.1.1" + ], + test_requires=[ + "cloudify-dsl-parser>=4.1.1" + "nose" + ] +) diff --git a/helm/tox.ini b/helm/tox.ini new file mode 100644 index 0000000..14c340c --- /dev/null +++ b/helm/tox.ini @@ -0,0 +1,18 @@ +# content of: tox.ini , put in same dir as setup.py +[tox] +envlist=flake8,py27 + +[testenv:py27] +deps = + # this fixes issue with tox installing coverage --pre + coverage==3.7.1 + nose-cov + testfixtures + -rdev-requirements.txt +commands=nosetests --with-cov --cov-report term-missing --cov plugin plugin/tests + +[testenv:flake8] +deps = + flake8 + -rdev-requirements.txt +commands=flake8 plugin -- cgit 1.2.3-korg