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