Yuriy Taraday | 84a2103 | 2017-06-27 11:13:16 +0400 | [diff] [blame] | 1 | import logging |
| 2 | |
| 3 | from salt.serializers import yaml |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 4 | from salt.exceptions import CommandExecutionError |
| 5 | |
Yuriy Taraday | 84a2103 | 2017-06-27 11:13:16 +0400 | [diff] [blame] | 6 | LOG = logging.getLogger(__name__) |
| 7 | |
Yuriy Taraday | 6618fb9 | 2017-08-11 17:11:48 +0400 | [diff] [blame] | 8 | def ok_or_output(cmd, prefix=None): |
| 9 | ret = __salt__['cmd.run_all'](**cmd) |
| 10 | if ret['retcode'] == 0: |
| 11 | return None |
| 12 | msg = "Stdout:\n{0[stdout]}\nStderr:\n{0[stderr]}".format(ret) |
| 13 | if prefix: |
| 14 | msg = prefix + ':\n' + msg |
| 15 | return msg |
| 16 | |
| 17 | |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 18 | def _helm_cmd(*args, **kwargs): |
| 19 | if kwargs.get('tiller_host'): |
| 20 | addtl_args = ('--host', kwargs['tiller_host']) |
| 21 | elif kwargs.get('tiller_namespace'): |
| 22 | addtl_args = ('--tiller-namespace', kwargs['tiller_namespace']) |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 23 | else: |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 24 | addtl_args = () |
| 25 | |
| 26 | if kwargs.get('helm_home'): |
| 27 | addtl_args = addtl_args + ('--home', kwargs['helm_home']) |
| 28 | |
| 29 | env = {} |
| 30 | if kwargs.get('kube_config'): |
| 31 | env['KUBECONFIG'] = kwargs['kube_config'] |
| 32 | if kwargs.get('gce_service_token'): |
Yuriy Taraday | e9f982d | 2017-08-17 18:06:58 +0400 | [diff] [blame] | 33 | env['GOOGLE_APPLICATION_CREDENTIALS'] = \ |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 34 | kwargs['gce_service_token'] |
Yuriy Taraday | 84a2103 | 2017-06-27 11:13:16 +0400 | [diff] [blame] | 35 | return { |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 36 | 'cmd': ('helm',) + addtl_args + args, |
Yuriy Taraday | f9dd012 | 2017-08-17 16:26:16 +0400 | [diff] [blame] | 37 | 'env': env, |
Yuriy Taraday | 84a2103 | 2017-06-27 11:13:16 +0400 | [diff] [blame] | 38 | } |
| 39 | |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 40 | def _parse_repo(repo_string = None): |
| 41 | split_string = repo_string.split('\t') |
| 42 | return { |
| 43 | "name": split_string[0].strip(), |
| 44 | "url": split_string[1].strip() |
| 45 | } |
| 46 | |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 47 | def list_repos(**kwargs): |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 48 | ''' |
| 49 | Get the result of running `helm repo list` on the target minion, formatted |
| 50 | as a list of dicts with two keys: |
| 51 | |
| 52 | * name: the name with which the repository is registered |
| 53 | * url: the url registered for the repository |
| 54 | ''' |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 55 | cmd = _helm_cmd('repo', 'list', **kwargs) |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 56 | result = __salt__['cmd.run_stdout'](**cmd) |
| 57 | if result is None: |
| 58 | return result |
| 59 | |
| 60 | result = result.split("\n") |
| 61 | result.pop(0) |
| 62 | return { |
| 63 | repo['name']: repo['url'] for repo in [_parse_repo(line) for line in result] |
| 64 | } |
| 65 | |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 66 | def add_repo(name, url, **kwargs): |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 67 | ''' |
| 68 | Register the repository located at the supplied url with the supplied name. |
| 69 | Note that re-using an existing name will overwrite the repository url for |
| 70 | that registered repository to point to the supplied url. |
| 71 | |
| 72 | name |
| 73 | The name with which to register the repository with the Helm client. |
| 74 | |
| 75 | url |
| 76 | The url for the chart repository. |
| 77 | ''' |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 78 | cmd = _helm_cmd('repo', 'add', name, url, **kwargs) |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 79 | ret = __salt__['cmd.run_all'](**cmd) |
| 80 | if ret['retcode'] != 0: |
| 81 | raise CommandExecutionError(ret['stderr']) |
| 82 | return ret['stdout'] |
| 83 | |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 84 | def remove_repo(name, **kwargs): |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 85 | ''' |
| 86 | Remove the repository from the Helm client registered with the supplied |
| 87 | name. |
| 88 | |
| 89 | name |
| 90 | The name (as registered with the Helm client) for the repository to remove |
| 91 | ''' |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 92 | cmd = _helm_cmd('repo', 'remove', name, **kwargs) |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 93 | ret = __salt__['cmd.run_all'](**cmd) |
| 94 | if ret['retcode'] != 0: |
| 95 | raise CommandExecutionError(ret['stderr']) |
| 96 | return ret['stdout'] |
| 97 | |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 98 | def manage_repos(present={}, absent=[], exclusive=False, **kwargs): |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 99 | ''' |
| 100 | Manage the repositories registered with the Helm client's local cache. |
| 101 | |
| 102 | *ensuring repositories are present* |
| 103 | Repositories that should be present in the helm client can be supplied via |
| 104 | the `present` dict parameter; each key in the dict is a release name, and the |
| 105 | value is the repository url that should be registered. |
| 106 | |
| 107 | *ensuring repositories are absent* |
| 108 | Repository names supplied via the `absent` parameter must be a string. If the |
| 109 | `exclusive` flag is set to True, the `absent` parameter will be ignored, even |
| 110 | if it has been supplied. |
| 111 | |
| 112 | This function returns a dict with the following keys: |
| 113 | |
| 114 | * already_present: a listing of supplied repository definitions to add that |
| 115 | are already registered with the Helm client |
| 116 | |
| 117 | * added: a list of repositories that are newly registered with the Helm |
| 118 | client. Each item in the list is a dict with the following keys: |
| 119 | * name: the repo name |
| 120 | * url: the repo url |
| 121 | * stdout: the output from the `helm repo add` command call for the repo |
| 122 | |
| 123 | * already_absent: any repository name supplied via the `absent` parameter |
| 124 | that was already not registered with the Helm client |
| 125 | |
| 126 | * removed: the result of attempting to remove any repositories |
| 127 | |
| 128 | * failed: a list of repositores that were unable to be added. Each item in |
| 129 | the list is a dict with the following keys: |
| 130 | * type: the text "removal" or "addition", as appropriate |
| 131 | * name: the repo name |
| 132 | * url: the repo url (if appropriate) |
| 133 | * error: the output from add or remove command attempted for the |
| 134 | repository |
| 135 | |
| 136 | present |
| 137 | The dict of repositories that should be registered with the Helm client. |
| 138 | Each dict key is the name with which the repository url (the corresponding |
| 139 | value) should be registered with the Helm client. |
| 140 | |
| 141 | absent |
| 142 | The list of repositories to ensure are not registered with the Helm client. |
| 143 | Each entry in the list must be the (string) name of the repository. |
| 144 | |
| 145 | exclusive |
| 146 | A flag indicating whether only the supplied repos should be available in |
| 147 | the target minion's Helm client. If configured to true, the `absent` |
| 148 | parameter will be ignored and only the repositories configured via the |
| 149 | `present` parameter will be registered with the Helm client. Defaults to |
| 150 | False. |
| 151 | ''' |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 152 | existing_repos = list_repos(**kwargs) |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 153 | result = { |
| 154 | "already_present": [], |
| 155 | "added": [], |
| 156 | "already_absent": [], |
| 157 | "removed": [], |
| 158 | "failed": [] |
| 159 | } |
| 160 | |
| 161 | for name, url in present.iteritems(): |
| 162 | if not name or not url: |
| 163 | raise CommandExecutionError(('Supplied repo to add must have a name (%s) ' |
| 164 | 'and url (%s)' % (name, url))) |
| 165 | |
| 166 | if name in existing_repos and existing_repos[name] == url: |
| 167 | result['already_present'].append({ "name": name, "url": url }) |
| 168 | continue |
| 169 | |
| 170 | try: |
| 171 | result['added'].append({ |
| 172 | 'name': name, |
| 173 | 'url': url, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 174 | 'stdout': add_repo(name, url, **kwargs) |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 175 | }) |
| 176 | existing_repos = { |
| 177 | n: u for (n, u) in existing_repos.iteritems() if name != n |
| 178 | } |
| 179 | except CommandExecutionError as e: |
| 180 | result['failed'].append({ |
| 181 | "type": "addition", |
| 182 | "name": name, |
| 183 | 'url': url, |
| 184 | 'error': '%s' % e |
| 185 | }) |
| 186 | |
| 187 | # |
| 188 | # Handle removal of repositories configured to be absent (or not configured |
| 189 | # to be present if the `exclusive` flag is set) |
| 190 | # |
| 191 | existing_names = [name for (name, url) in existing_repos.iteritems()] |
| 192 | if exclusive: |
| 193 | present['stable'] = "exclude" |
| 194 | absent = [name for name in existing_names if not name in present] |
| 195 | |
| 196 | for name in absent: |
| 197 | if not name or not isinstance(name, str): |
| 198 | raise CommandExecutionError(('Supplied repo name to be absent must be a ' |
| 199 | 'string: %s' % name)) |
| 200 | |
| 201 | if name not in existing_names: |
| 202 | result['already_absent'].append(name) |
| 203 | continue |
| 204 | |
| 205 | try: |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 206 | result['removed'].append({ |
| 207 | 'name': name, |
| 208 | 'stdout': remove_repo(name, **kwargs) |
| 209 | }) |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 210 | except CommandExecutionError as e: |
| 211 | result['failed'].append({ |
| 212 | "type": "removal", "name": name, "error": '%s' % e |
| 213 | }) |
| 214 | |
| 215 | return result |
| 216 | |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 217 | def update_repos(**kwargs): |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 218 | ''' |
| 219 | Ensures the local helm repository cache for each repository is up to date. |
| 220 | Proxies the `helm repo update` command. |
| 221 | ''' |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 222 | cmd = _helm_cmd('repo', 'update', **kwargs) |
tmeneau | 61efbef | 2017-10-17 11:19:46 -0400 | [diff] [blame] | 223 | return __salt__['cmd.run_stdout'](**cmd) |
Yuriy Taraday | 84a2103 | 2017-06-27 11:13:16 +0400 | [diff] [blame] | 224 | |
Yuriy Taraday | 6f82649 | 2017-08-16 12:40:24 +0400 | [diff] [blame] | 225 | def release_exists(name, namespace='default', |
Yuriy Taraday | f9dd012 | 2017-08-17 16:26:16 +0400 | [diff] [blame] | 226 | tiller_namespace='kube-system', tiller_host=None, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 227 | kube_config=None, gce_service_token=None, helm_home=None): |
Yuriy Taraday | 6f82649 | 2017-08-16 12:40:24 +0400 | [diff] [blame] | 228 | cmd = _helm_cmd('list', '--short', '--all', '--namespace', namespace, name, |
Yuriy Taraday | f9dd012 | 2017-08-17 16:26:16 +0400 | [diff] [blame] | 229 | tiller_namespace=tiller_namespace, tiller_host=tiller_host, |
Yuriy Taraday | e9f982d | 2017-08-17 18:06:58 +0400 | [diff] [blame] | 230 | kube_config=kube_config, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 231 | gce_service_token=gce_service_token, |
| 232 | helm_home=helm_home) |
Yuriy Taraday | 84a2103 | 2017-06-27 11:13:16 +0400 | [diff] [blame] | 233 | return __salt__['cmd.run_stdout'](**cmd) == name |
| 234 | |
| 235 | |
Yuriy Taraday | f169d82 | 2017-08-14 13:40:21 +0400 | [diff] [blame] | 236 | def release_create(name, chart_name, namespace='default', |
tmeneau | 94bf68e | 2017-10-17 15:55:34 -0400 | [diff] [blame^] | 237 | version=None, values_file=None, |
Yuriy Taraday | f9dd012 | 2017-08-17 16:26:16 +0400 | [diff] [blame] | 238 | tiller_namespace='kube-system', tiller_host=None, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 239 | kube_config=None, gce_service_token=None, |
| 240 | helm_home=None): |
| 241 | kwargs = { |
Yuriy Taraday | 6f82649 | 2017-08-16 12:40:24 +0400 | [diff] [blame] | 242 | 'tiller_namespace': tiller_namespace, |
| 243 | 'tiller_host': tiller_host, |
Yuriy Taraday | f9dd012 | 2017-08-17 16:26:16 +0400 | [diff] [blame] | 244 | 'kube_config': kube_config, |
Yuriy Taraday | e9f982d | 2017-08-17 18:06:58 +0400 | [diff] [blame] | 245 | 'gce_service_token': gce_service_token, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 246 | 'helm_home': helm_home |
Yuriy Taraday | 6f82649 | 2017-08-16 12:40:24 +0400 | [diff] [blame] | 247 | } |
Yuriy Taraday | 84a2103 | 2017-06-27 11:13:16 +0400 | [diff] [blame] | 248 | args = [] |
| 249 | if version is not None: |
| 250 | args += ['--version', version] |
tmeneau | 94bf68e | 2017-10-17 15:55:34 -0400 | [diff] [blame^] | 251 | if values_file is not None: |
| 252 | args += ['--values', values_file] |
Yuriy Taraday | 66e61df | 2017-08-11 15:14:26 +0400 | [diff] [blame] | 253 | cmd = _helm_cmd('install', '--namespace', namespace, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 254 | '--name', name, chart_name, *args, **kwargs) |
Yuriy Taraday | 84a2103 | 2017-06-27 11:13:16 +0400 | [diff] [blame] | 255 | LOG.debug('Creating release with args: %s', cmd) |
Yuriy Taraday | 6618fb9 | 2017-08-11 17:11:48 +0400 | [diff] [blame] | 256 | return ok_or_output(cmd, 'Failed to create release "{}"'.format(name)) |
Yuriy Taraday | aeeaa74 | 2017-06-28 15:54:56 +0400 | [diff] [blame] | 257 | |
| 258 | |
Yuriy Taraday | f9dd012 | 2017-08-17 16:26:16 +0400 | [diff] [blame] | 259 | def release_delete(name, tiller_namespace='kube-system', tiller_host=None, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 260 | kube_config=None, gce_service_token=None, helm_home=None): |
Yuriy Taraday | 6f82649 | 2017-08-16 12:40:24 +0400 | [diff] [blame] | 261 | cmd = _helm_cmd('delete', '--purge', name, |
Yuriy Taraday | f9dd012 | 2017-08-17 16:26:16 +0400 | [diff] [blame] | 262 | tiller_namespace=tiller_namespace, tiller_host=tiller_host, |
Yuriy Taraday | e9f982d | 2017-08-17 18:06:58 +0400 | [diff] [blame] | 263 | kube_config=kube_config, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 264 | gce_service_token=gce_service_token, |
| 265 | helm_home=helm_home) |
Yuriy Taraday | 6618fb9 | 2017-08-11 17:11:48 +0400 | [diff] [blame] | 266 | return ok_or_output(cmd, 'Failed to delete release "{}"'.format(name)) |
Yuriy Taraday | 893b3fb | 2017-07-03 16:22:57 +0400 | [diff] [blame] | 267 | |
| 268 | |
Yuriy Taraday | f169d82 | 2017-08-14 13:40:21 +0400 | [diff] [blame] | 269 | def release_upgrade(name, chart_name, namespace='default', |
tmeneau | 94bf68e | 2017-10-17 15:55:34 -0400 | [diff] [blame^] | 270 | version=None, values_file=None, |
Yuriy Taraday | f9dd012 | 2017-08-17 16:26:16 +0400 | [diff] [blame] | 271 | tiller_namespace='kube-system', tiller_host=None, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 272 | kube_config=None, gce_service_token=None, helm_home=None): |
| 273 | kwargs = { |
Yuriy Taraday | 6f82649 | 2017-08-16 12:40:24 +0400 | [diff] [blame] | 274 | 'tiller_namespace': tiller_namespace, |
| 275 | 'tiller_host': tiller_host, |
Yuriy Taraday | f9dd012 | 2017-08-17 16:26:16 +0400 | [diff] [blame] | 276 | 'kube_config': kube_config, |
Yuriy Taraday | e9f982d | 2017-08-17 18:06:58 +0400 | [diff] [blame] | 277 | 'gce_service_token': gce_service_token, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 278 | 'helm_home': helm_home |
Yuriy Taraday | 6f82649 | 2017-08-16 12:40:24 +0400 | [diff] [blame] | 279 | } |
Yuriy Taraday | aeeaa74 | 2017-06-28 15:54:56 +0400 | [diff] [blame] | 280 | args = [] |
| 281 | if version is not None: |
tmeneau | 94bf68e | 2017-10-17 15:55:34 -0400 | [diff] [blame^] | 282 | args += ['--version', version] |
| 283 | if values_file is not None: |
| 284 | args += ['--values', values_file] |
Yuriy Taraday | 66e61df | 2017-08-11 15:14:26 +0400 | [diff] [blame] | 285 | cmd = _helm_cmd('upgrade', '--namespace', namespace, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 286 | name, chart_name, *args, **kwargs) |
Yuriy Taraday | 893b3fb | 2017-07-03 16:22:57 +0400 | [diff] [blame] | 287 | LOG.debug('Upgrading release with args: %s', cmd) |
Yuriy Taraday | 6618fb9 | 2017-08-11 17:11:48 +0400 | [diff] [blame] | 288 | return ok_or_output(cmd, 'Failed to upgrade release "{}"'.format(name)) |
Yuriy Taraday | aeeaa74 | 2017-06-28 15:54:56 +0400 | [diff] [blame] | 289 | |
| 290 | |
Yuriy Taraday | f9dd012 | 2017-08-17 16:26:16 +0400 | [diff] [blame] | 291 | def get_values(name, tiller_namespace='kube-system', tiller_host=None, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 292 | kube_config=None, gce_service_token=None, helm_home=None): |
Yuriy Taraday | 6f82649 | 2017-08-16 12:40:24 +0400 | [diff] [blame] | 293 | cmd = _helm_cmd('get', 'values', '--all', name, |
Yuriy Taraday | f9dd012 | 2017-08-17 16:26:16 +0400 | [diff] [blame] | 294 | tiller_namespace=tiller_namespace, tiller_host=tiller_host, |
Yuriy Taraday | e9f982d | 2017-08-17 18:06:58 +0400 | [diff] [blame] | 295 | kube_config=kube_config, |
tmeneau | 8cf4fce | 2017-10-17 15:05:35 -0400 | [diff] [blame] | 296 | gce_service_token=gce_service_token, |
| 297 | helm_home=helm_home) |
Yuriy Taraday | aeeaa74 | 2017-06-28 15:54:56 +0400 | [diff] [blame] | 298 | return yaml.deserialize(__salt__['cmd.run_stdout'](**cmd)) |