blob: 087c751bc5919a6acf89146829b65c38413e71e3 [file] [log] [blame]
Yuriy Taraday84a21032017-06-27 11:13:16 +04001import logging
2
3from salt.serializers import yaml
tmeneau61efbef2017-10-17 11:19:46 -04004from salt.exceptions import CommandExecutionError
5
Yuriy Taraday84a21032017-06-27 11:13:16 +04006LOG = logging.getLogger(__name__)
7
Yuriy Taraday6618fb92017-08-11 17:11:48 +04008def 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
tmeneau8cf4fce2017-10-17 15:05:35 -040018def _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'])
tmeneau61efbef2017-10-17 11:19:46 -040023 else:
tmeneau8cf4fce2017-10-17 15:05:35 -040024 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 Taradaye9f982d2017-08-17 18:06:58 +040033 env['GOOGLE_APPLICATION_CREDENTIALS'] = \
tmeneau8cf4fce2017-10-17 15:05:35 -040034 kwargs['gce_service_token']
Yuriy Taraday84a21032017-06-27 11:13:16 +040035 return {
tmeneau8cf4fce2017-10-17 15:05:35 -040036 'cmd': ('helm',) + addtl_args + args,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +040037 'env': env,
Yuriy Taraday84a21032017-06-27 11:13:16 +040038 }
39
tmeneau61efbef2017-10-17 11:19:46 -040040def _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
tmeneau8cf4fce2017-10-17 15:05:35 -040047def list_repos(**kwargs):
tmeneau61efbef2017-10-17 11:19:46 -040048 '''
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 '''
tmeneau8cf4fce2017-10-17 15:05:35 -040055 cmd = _helm_cmd('repo', 'list', **kwargs)
tmeneau61efbef2017-10-17 11:19:46 -040056 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
tmeneau8cf4fce2017-10-17 15:05:35 -040066def add_repo(name, url, **kwargs):
tmeneau61efbef2017-10-17 11:19:46 -040067 '''
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 '''
tmeneau8cf4fce2017-10-17 15:05:35 -040078 cmd = _helm_cmd('repo', 'add', name, url, **kwargs)
tmeneau61efbef2017-10-17 11:19:46 -040079 ret = __salt__['cmd.run_all'](**cmd)
80 if ret['retcode'] != 0:
81 raise CommandExecutionError(ret['stderr'])
82 return ret['stdout']
83
tmeneau8cf4fce2017-10-17 15:05:35 -040084def remove_repo(name, **kwargs):
tmeneau61efbef2017-10-17 11:19:46 -040085 '''
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 '''
tmeneau8cf4fce2017-10-17 15:05:35 -040092 cmd = _helm_cmd('repo', 'remove', name, **kwargs)
tmeneau61efbef2017-10-17 11:19:46 -040093 ret = __salt__['cmd.run_all'](**cmd)
94 if ret['retcode'] != 0:
95 raise CommandExecutionError(ret['stderr'])
96 return ret['stdout']
97
tmeneau8cf4fce2017-10-17 15:05:35 -040098def manage_repos(present={}, absent=[], exclusive=False, **kwargs):
tmeneau61efbef2017-10-17 11:19:46 -040099 '''
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 '''
tmeneau8cf4fce2017-10-17 15:05:35 -0400152 existing_repos = list_repos(**kwargs)
tmeneau61efbef2017-10-17 11:19:46 -0400153 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,
tmeneau8cf4fce2017-10-17 15:05:35 -0400174 'stdout': add_repo(name, url, **kwargs)
tmeneau61efbef2017-10-17 11:19:46 -0400175 })
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:
tmeneau8cf4fce2017-10-17 15:05:35 -0400206 result['removed'].append({
207 'name': name,
208 'stdout': remove_repo(name, **kwargs)
209 })
tmeneau61efbef2017-10-17 11:19:46 -0400210 except CommandExecutionError as e:
211 result['failed'].append({
212 "type": "removal", "name": name, "error": '%s' % e
213 })
214
215 return result
216
tmeneau8cf4fce2017-10-17 15:05:35 -0400217def update_repos(**kwargs):
tmeneau61efbef2017-10-17 11:19:46 -0400218 '''
219 Ensures the local helm repository cache for each repository is up to date.
220 Proxies the `helm repo update` command.
221 '''
tmeneau8cf4fce2017-10-17 15:05:35 -0400222 cmd = _helm_cmd('repo', 'update', **kwargs)
tmeneau61efbef2017-10-17 11:19:46 -0400223 return __salt__['cmd.run_stdout'](**cmd)
Yuriy Taraday84a21032017-06-27 11:13:16 +0400224
Yuriy Taraday6f826492017-08-16 12:40:24 +0400225def release_exists(name, namespace='default',
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400226 tiller_namespace='kube-system', tiller_host=None,
tmeneau8cf4fce2017-10-17 15:05:35 -0400227 kube_config=None, gce_service_token=None, helm_home=None):
Yuriy Taraday6f826492017-08-16 12:40:24 +0400228 cmd = _helm_cmd('list', '--short', '--all', '--namespace', namespace, name,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400229 tiller_namespace=tiller_namespace, tiller_host=tiller_host,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400230 kube_config=kube_config,
tmeneau8cf4fce2017-10-17 15:05:35 -0400231 gce_service_token=gce_service_token,
232 helm_home=helm_home)
Yuriy Taraday84a21032017-06-27 11:13:16 +0400233 return __salt__['cmd.run_stdout'](**cmd) == name
234
235
Yuriy Taradayf169d822017-08-14 13:40:21 +0400236def release_create(name, chart_name, namespace='default',
tmeneau94bf68e2017-10-17 15:55:34 -0400237 version=None, values_file=None,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400238 tiller_namespace='kube-system', tiller_host=None,
tmeneau8cf4fce2017-10-17 15:05:35 -0400239 kube_config=None, gce_service_token=None,
240 helm_home=None):
241 kwargs = {
Yuriy Taraday6f826492017-08-16 12:40:24 +0400242 'tiller_namespace': tiller_namespace,
243 'tiller_host': tiller_host,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400244 'kube_config': kube_config,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400245 'gce_service_token': gce_service_token,
tmeneau8cf4fce2017-10-17 15:05:35 -0400246 'helm_home': helm_home
Yuriy Taraday6f826492017-08-16 12:40:24 +0400247 }
Yuriy Taraday84a21032017-06-27 11:13:16 +0400248 args = []
249 if version is not None:
250 args += ['--version', version]
tmeneau94bf68e2017-10-17 15:55:34 -0400251 if values_file is not None:
252 args += ['--values', values_file]
Yuriy Taraday66e61df2017-08-11 15:14:26 +0400253 cmd = _helm_cmd('install', '--namespace', namespace,
tmeneau8cf4fce2017-10-17 15:05:35 -0400254 '--name', name, chart_name, *args, **kwargs)
Yuriy Taraday84a21032017-06-27 11:13:16 +0400255 LOG.debug('Creating release with args: %s', cmd)
Yuriy Taraday6618fb92017-08-11 17:11:48 +0400256 return ok_or_output(cmd, 'Failed to create release "{}"'.format(name))
Yuriy Taradayaeeaa742017-06-28 15:54:56 +0400257
258
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400259def release_delete(name, tiller_namespace='kube-system', tiller_host=None,
tmeneau8cf4fce2017-10-17 15:05:35 -0400260 kube_config=None, gce_service_token=None, helm_home=None):
Yuriy Taraday6f826492017-08-16 12:40:24 +0400261 cmd = _helm_cmd('delete', '--purge', name,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400262 tiller_namespace=tiller_namespace, tiller_host=tiller_host,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400263 kube_config=kube_config,
tmeneau8cf4fce2017-10-17 15:05:35 -0400264 gce_service_token=gce_service_token,
265 helm_home=helm_home)
Yuriy Taraday6618fb92017-08-11 17:11:48 +0400266 return ok_or_output(cmd, 'Failed to delete release "{}"'.format(name))
Yuriy Taraday893b3fb2017-07-03 16:22:57 +0400267
268
Yuriy Taradayf169d822017-08-14 13:40:21 +0400269def release_upgrade(name, chart_name, namespace='default',
tmeneau94bf68e2017-10-17 15:55:34 -0400270 version=None, values_file=None,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400271 tiller_namespace='kube-system', tiller_host=None,
tmeneau8cf4fce2017-10-17 15:05:35 -0400272 kube_config=None, gce_service_token=None, helm_home=None):
273 kwargs = {
Yuriy Taraday6f826492017-08-16 12:40:24 +0400274 'tiller_namespace': tiller_namespace,
275 'tiller_host': tiller_host,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400276 'kube_config': kube_config,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400277 'gce_service_token': gce_service_token,
tmeneau8cf4fce2017-10-17 15:05:35 -0400278 'helm_home': helm_home
Yuriy Taraday6f826492017-08-16 12:40:24 +0400279 }
Yuriy Taradayaeeaa742017-06-28 15:54:56 +0400280 args = []
281 if version is not None:
tmeneau94bf68e2017-10-17 15:55:34 -0400282 args += ['--version', version]
283 if values_file is not None:
284 args += ['--values', values_file]
Yuriy Taraday66e61df2017-08-11 15:14:26 +0400285 cmd = _helm_cmd('upgrade', '--namespace', namespace,
tmeneau8cf4fce2017-10-17 15:05:35 -0400286 name, chart_name, *args, **kwargs)
Yuriy Taraday893b3fb2017-07-03 16:22:57 +0400287 LOG.debug('Upgrading release with args: %s', cmd)
Yuriy Taraday6618fb92017-08-11 17:11:48 +0400288 return ok_or_output(cmd, 'Failed to upgrade release "{}"'.format(name))
Yuriy Taradayaeeaa742017-06-28 15:54:56 +0400289
290
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400291def get_values(name, tiller_namespace='kube-system', tiller_host=None,
tmeneau8cf4fce2017-10-17 15:05:35 -0400292 kube_config=None, gce_service_token=None, helm_home=None):
Yuriy Taraday6f826492017-08-16 12:40:24 +0400293 cmd = _helm_cmd('get', 'values', '--all', name,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400294 tiller_namespace=tiller_namespace, tiller_host=tiller_host,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400295 kube_config=kube_config,
tmeneau8cf4fce2017-10-17 15:05:35 -0400296 gce_service_token=gce_service_token,
297 helm_home=helm_home)
Yuriy Taradayaeeaa742017-06-28 15:54:56 +0400298 return yaml.deserialize(__salt__['cmd.run_stdout'](**cmd))