blob: 9e6215402d9efcc623cd79e0d90d5e9c3c716863 [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 +04006
7HELM_HOME = '/srv/helm/home'
8LOG = logging.getLogger(__name__)
9
Yuriy Taraday6618fb92017-08-11 17:11:48 +040010def 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 Taraday6f826492017-08-16 12:40:24 +040020def _helm_cmd(*args, **tiller_kwargs):
tmeneau61efbef2017-10-17 11:19:46 -040021 if tiller_kwargs.get('tiller_host'):
Yuriy Taraday6f826492017-08-16 12:40:24 +040022 tiller_args = ('--host', tiller_kwargs['tiller_host'])
tmeneau61efbef2017-10-17 11:19:46 -040023 elif tiller_kwargs.get('tiller_namespace'):
Yuriy Taraday6f826492017-08-16 12:40:24 +040024 tiller_args = ('--tiller-namespace', tiller_kwargs['tiller_namespace'])
tmeneau61efbef2017-10-17 11:19:46 -040025 else:
26 tiller_args = ()
Yuriy Taradayf9dd0122017-08-17 16:26:16 +040027 env = {'HELM_HOME': HELM_HOME}
tmeneau61efbef2017-10-17 11:19:46 -040028 if tiller_kwargs.get('kube_config'):
Yuriy Taradayf9dd0122017-08-17 16:26:16 +040029 env['KUBECONFIG'] = tiller_kwargs['kube_config']
tmeneau61efbef2017-10-17 11:19:46 -040030 if tiller_kwargs.get('gce_service_token'):
Yuriy Taradaye9f982d2017-08-17 18:06:58 +040031 env['GOOGLE_APPLICATION_CREDENTIALS'] = \
32 tiller_kwargs['gce_service_token']
Yuriy Taraday84a21032017-06-27 11:13:16 +040033 return {
Yuriy Taraday6f826492017-08-16 12:40:24 +040034 'cmd': ('helm',) + tiller_args + args,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +040035 'env': env,
Yuriy Taraday84a21032017-06-27 11:13:16 +040036 }
37
tmeneau61efbef2017-10-17 11:19:46 -040038def _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
45def 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
64def 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
82def 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
96def 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
212def 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 Taraday84a21032017-06-27 11:13:16 +0400219
Yuriy Taraday6f826492017-08-16 12:40:24 +0400220def release_exists(name, namespace='default',
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400221 tiller_namespace='kube-system', tiller_host=None,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400222 kube_config=None, gce_service_token=None):
Yuriy Taraday6f826492017-08-16 12:40:24 +0400223 cmd = _helm_cmd('list', '--short', '--all', '--namespace', namespace, name,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400224 tiller_namespace=tiller_namespace, tiller_host=tiller_host,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400225 kube_config=kube_config,
226 gce_service_token=gce_service_token)
Yuriy Taraday84a21032017-06-27 11:13:16 +0400227 return __salt__['cmd.run_stdout'](**cmd) == name
228
229
Yuriy Taradayf169d822017-08-14 13:40:21 +0400230def release_create(name, chart_name, namespace='default',
Yuriy Taraday6f826492017-08-16 12:40:24 +0400231 version=None, values=None,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400232 tiller_namespace='kube-system', tiller_host=None,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400233 kube_config=None, gce_service_token=None):
Yuriy Taraday6f826492017-08-16 12:40:24 +0400234 tiller_args = {
235 'tiller_namespace': tiller_namespace,
236 'tiller_host': tiller_host,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400237 'kube_config': kube_config,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400238 'gce_service_token': gce_service_token,
Yuriy Taraday6f826492017-08-16 12:40:24 +0400239 }
Yuriy Taraday84a21032017-06-27 11:13:16 +0400240 args = []
241 if version is not None:
242 args += ['--version', version]
243 if values is not None:
244 args += ['--values', '/dev/stdin']
Yuriy Taraday66e61df2017-08-11 15:14:26 +0400245 cmd = _helm_cmd('install', '--namespace', namespace,
Yuriy Taraday6f826492017-08-16 12:40:24 +0400246 '--name', name, chart_name, *args, **tiller_args)
Yuriy Taraday84a21032017-06-27 11:13:16 +0400247 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 Taraday6618fb92017-08-11 17:11:48 +0400250 return ok_or_output(cmd, 'Failed to create release "{}"'.format(name))
Yuriy Taradayaeeaa742017-06-28 15:54:56 +0400251
252
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400253def release_delete(name, tiller_namespace='kube-system', tiller_host=None,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400254 kube_config=None, gce_service_token=None):
Yuriy Taraday6f826492017-08-16 12:40:24 +0400255 cmd = _helm_cmd('delete', '--purge', name,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400256 tiller_namespace=tiller_namespace, tiller_host=tiller_host,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400257 kube_config=kube_config,
258 gce_service_token=gce_service_token)
Yuriy Taraday6618fb92017-08-11 17:11:48 +0400259 return ok_or_output(cmd, 'Failed to delete release "{}"'.format(name))
Yuriy Taraday893b3fb2017-07-03 16:22:57 +0400260
261
Yuriy Taradayf169d822017-08-14 13:40:21 +0400262def release_upgrade(name, chart_name, namespace='default',
Yuriy Taraday6f826492017-08-16 12:40:24 +0400263 version=None, values=None,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400264 tiller_namespace='kube-system', tiller_host=None,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400265 kube_config=None, gce_service_token=None):
Yuriy Taraday6f826492017-08-16 12:40:24 +0400266 tiller_args = {
267 'tiller_namespace': tiller_namespace,
268 'tiller_host': tiller_host,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400269 'kube_config': kube_config,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400270 'gce_service_token': gce_service_token,
Yuriy Taraday6f826492017-08-16 12:40:24 +0400271 }
Yuriy Taradayaeeaa742017-06-28 15:54:56 +0400272 args = []
273 if version is not None:
274 args += ['--version', version]
275 if values is not None:
276 args += ['--values', '/dev/stdin']
Yuriy Taraday66e61df2017-08-11 15:14:26 +0400277 cmd = _helm_cmd('upgrade', '--namespace', namespace,
Yuriy Taraday6f826492017-08-16 12:40:24 +0400278 name, chart_name, *args, **tiller_args)
Yuriy Taradayaeeaa742017-06-28 15:54:56 +0400279 if values is not None:
280 cmd['stdin'] = yaml.serialize(values, default_flow_style=False)
Yuriy Taraday893b3fb2017-07-03 16:22:57 +0400281 LOG.debug('Upgrading release with args: %s', cmd)
Yuriy Taraday6618fb92017-08-11 17:11:48 +0400282 return ok_or_output(cmd, 'Failed to upgrade release "{}"'.format(name))
Yuriy Taradayaeeaa742017-06-28 15:54:56 +0400283
284
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400285def get_values(name, tiller_namespace='kube-system', tiller_host=None,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400286 kube_config=None, gce_service_token=None):
Yuriy Taraday6f826492017-08-16 12:40:24 +0400287 cmd = _helm_cmd('get', 'values', '--all', name,
Yuriy Taradayf9dd0122017-08-17 16:26:16 +0400288 tiller_namespace=tiller_namespace, tiller_host=tiller_host,
Yuriy Taradaye9f982d2017-08-17 18:06:58 +0400289 kube_config=kube_config,
290 gce_service_token=gce_service_token)
Yuriy Taradayaeeaa742017-06-28 15:54:56 +0400291 return yaml.deserialize(__salt__['cmd.run_stdout'](**cmd))