| import difflib |
| import os |
| import logging |
| |
| from salt.exceptions import CommandExecutionError |
| from salt.serializers import yaml |
| |
| LOG = logging.getLogger(__name__) |
| |
| def _get_values_from_file(values_file=None): |
| if values_file: |
| try: |
| with open(values_file) as values_stream: |
| values = yaml.deserialize(values_stream) |
| return values |
| except e: |
| raise CommandExecutionError("encountered error reading from values " |
| "file (%s): %s" % (values_file, e)) |
| return None |
| |
| def _get_yaml_diff(new_yaml=None, old_yaml=None): |
| if not new_yaml and not old_yaml: |
| return None |
| |
| old_str = yaml.serialize(old_yaml, default_flow_style=False) |
| new_str = yaml.serialize(new_yaml, default_flow_style=False) |
| return difflib.unified_diff(old_str.split('\n'), new_str.split('\n')) |
| |
| def _failure(name, message, changes={}): |
| return { |
| 'name': name, |
| 'changes': changes, |
| 'result': False, |
| 'comment': message, |
| } |
| |
| def present(name, chart_name, namespace, version=None, values_file=None, |
| tiller_namespace='kube-system', **kwargs): |
| ''' |
| Ensure that a release with the supplied name is in the desired state in the |
| Tiller installation. This state will handle change detection to determine |
| whether an installation or update needs to be made. |
| |
| In the event the namespace to which a release is installed changes, the |
| state will first delete and purge the release and the re-install it into |
| the new namespace, since Helm does not support updating a release into a |
| new namespace. |
| |
| name |
| The name of the release to ensure is present |
| |
| chart_name |
| The name of the chart to install, including the repository name as |
| applicable (such as `stable/mysql`) |
| |
| namespace |
| The namespace to which the release should be (re-)installed |
| |
| version |
| The version of the chart to install. Defaults to the latest version |
| |
| values_file |
| The path to the a values file containing all the chart values that |
| should be applied to the release. Note that this should not be passed |
| if there are not chart value overrides required. |
| |
| ''' |
| kwargs['tiller_namespace'] = tiller_namespace |
| old_release = __salt__['helm.get_release'](name, **kwargs) |
| if not old_release: |
| try: |
| result = __salt__['helm.release_create']( |
| name, chart_name, namespace, version, values_file, **kwargs |
| ) |
| return { |
| 'name': name, |
| 'changes': { |
| 'name': name, |
| 'chart_name': chart_name, |
| 'namespace': namespace, |
| 'version': version, |
| 'values': _get_values_from_file(values_file), |
| 'stdout': result.get('stdout') |
| }, |
| 'result': True, |
| 'comment': ('Release "%s" was created' % name + |
| '\nExecuted command: %s' % result['cmd']) |
| } |
| except CommandExecutionError as e: |
| msg = (("Failed to create new release: %s" % e.error) + |
| "\nExecuted command: %s" % e.cmd) |
| return _failure(name, msg) |
| |
| changes = {} |
| warnings = [] |
| if old_release.get('chart') != chart_name.split("/")[1]: |
| changes['chart'] = { 'old': old_release['chart'], 'new': chart_name } |
| |
| if old_release.get('version') != version: |
| changes['version'] = { 'old': old_release['version'], 'new': version } |
| |
| if old_release.get('namespace') != namespace: |
| changes['namespace'] = { 'old': old_release['namespace'], 'new': namespace } |
| |
| if (not values_file and old_release.get("values") or |
| not old_release.get("values") and values_file): |
| changes['values'] = { 'old': old_release['values'], 'new': values_file } |
| |
| values = _get_values_from_file(values_file) |
| diff = _get_yaml_diff(values, old_release.get('values')) |
| |
| if diff: |
| diff_string = '\n'.join(diff) |
| if diff_string: |
| changes['values'] = diff_string |
| |
| if not changes: |
| return { |
| 'name': name, |
| 'result': True, |
| 'changes': {}, |
| 'comment': 'Release "{}" is already in the desired state'.format(name) |
| } |
| |
| module_fn = 'helm.release_upgrade' |
| if changes.get("namespace"): |
| LOG.debug("purging old release (%s) due to namespace change" % name) |
| try: |
| result = __salt__['helm.release_delete'](name, **kwargs) |
| except CommandExecutionError as e: |
| msg = ("Failed to delete release for namespace change: %s" % e.error + |
| "\nExecuted command: %s" % e.cmd) |
| return _failure(name, msg, changes) |
| |
| module_fn = 'helm.release_create' |
| warnings.append('Release (%s) was replaced due to namespace change' % name) |
| |
| try: |
| result = __salt__[module_fn]( |
| name, chart_name, namespace, version, values_file, **kwargs |
| ) |
| changes.update({ 'stdout': result.get('stdout') }) |
| ret = { |
| 'name': name, |
| 'changes': changes, |
| 'result': True, |
| 'comment': 'Release "%s" was updated\nExecuted command: %s' % (name, result['cmd']) |
| } |
| if warnings: |
| ret['warnings'] = warnings |
| |
| return ret |
| except CommandExecutionError as e: |
| msg = ("Failed to delete release for namespace change: %s" % e.error + |
| "\nExecuted command: %s" % e.cmd) |
| return _failure(name, msg, changes) |
| |
| |
| def absent(name, tiller_namespace='kube-system', **kwargs): |
| ''' |
| Ensure that any release with the supplied release name is absent from the |
| tiller installation. |
| |
| name |
| The name of the release to ensure is absent |
| ''' |
| kwargs['tiller_namespace'] = tiller_namespace |
| exists = __salt__['helm.release_exists'](name, **kwargs) |
| if not exists: |
| return { |
| 'name': name, |
| 'changes': {}, |
| 'result': True, |
| 'comment': 'Release "%s" doesn\'t exist' % name |
| } |
| try: |
| result = __salt__['helm.release_delete'](name, **kwargs) |
| return { |
| 'name': name, |
| 'changes': { name: 'DELETED', 'stdout': result['stdout'] }, |
| 'result': True, |
| 'comment': 'Release "%s" was deleted\nExecuted command: %s' % (name, result['cmd']) |
| } |
| except CommandExecutionError as e: |
| return _failure(e.cmd, e.error) |
| |