1# --------------------------------------------------------------------------------------------
2# Copyright (c) Microsoft Corporation. All rights reserved.
3# Licensed under the MIT License. See License.txt in the project root for license information.
4# --------------------------------------------------------------------------------------------
5
6from knack.util import CLIError
7from knack.log import get_logger
8
9from ._utils import user_confirmation
10from ._docker_utils import get_access_credentials, request_data_from_registry, RegistryException
11
12
13logger = get_logger(__name__)
14
15
16def acr_helm_list(cmd,
17                  registry_name,
18                  repository='repo',
19                  resource_group_name=None,  # pylint: disable=unused-argument
20                  tenant_suffix=None,
21                  username=None,
22                  password=None):
23    login_server, username, password = get_access_credentials(
24        cmd=cmd,
25        registry_name=registry_name,
26        tenant_suffix=tenant_suffix,
27        username=username,
28        password=password,
29        artifact_repository=repository,
30        permission='pull')
31
32    return request_data_from_registry(
33        http_method='get',
34        login_server=login_server,
35        path=_get_charts_path(repository),
36        username=username,
37        password=password)[0]
38
39
40def acr_helm_show(cmd,
41                  registry_name,
42                  chart,
43                  version=None,
44                  repository='repo',
45                  resource_group_name=None,  # pylint: disable=unused-argument
46                  tenant_suffix=None,
47                  username=None,
48                  password=None):
49    login_server, username, password = get_access_credentials(
50        cmd=cmd,
51        registry_name=registry_name,
52        tenant_suffix=tenant_suffix,
53        username=username,
54        password=password,
55        artifact_repository=repository,
56        permission='pull')
57
58    return request_data_from_registry(
59        http_method='get',
60        login_server=login_server,
61        path=_get_charts_path(repository, chart, version),
62        username=username,
63        password=password)[0]
64
65
66def acr_helm_delete(cmd,
67                    registry_name,
68                    chart,
69                    version=None,
70                    repository='repo',
71                    resource_group_name=None,  # pylint: disable=unused-argument
72                    tenant_suffix=None,
73                    username=None,
74                    password=None,
75                    prov=False,
76                    yes=False):
77    if version:
78        message = "This operation will delete the chart package '{}'".format(
79            _get_chart_package_name(chart, version, prov))
80    else:
81        message = "This operation will delete all versions of the chart '{}'".format(chart)
82    user_confirmation("{}.\nAre you sure you want to continue?".format(message), yes)
83
84    login_server, username, password = get_access_credentials(
85        cmd=cmd,
86        registry_name=registry_name,
87        tenant_suffix=tenant_suffix,
88        username=username,
89        password=password,
90        artifact_repository=repository,
91        permission='delete')
92
93    return request_data_from_registry(
94        http_method='delete',
95        login_server=login_server,
96        path=_get_blobs_path(repository, chart, version, prov) if version else _get_charts_path(repository, chart),
97        username=username,
98        password=password)[0]
99
100
101def acr_helm_push(cmd,
102                  registry_name,
103                  chart_package,
104                  repository='repo',
105                  force=False,
106                  resource_group_name=None,  # pylint: disable=unused-argument
107                  tenant_suffix=None,
108                  username=None,
109                  password=None):
110    from os.path import isdir, basename
111
112    if isdir(chart_package):
113        raise CLIError("Please run 'helm package {}' to generate a chart package first.".format(chart_package))
114
115    login_server, username, password = get_access_credentials(
116        cmd=cmd,
117        registry_name=registry_name,
118        tenant_suffix=tenant_suffix,
119        username=username,
120        password=password,
121        artifact_repository=repository,
122        permission='push,pull')
123
124    path = _get_blobs_path(repository, basename(chart_package))
125
126    try:
127        result = request_data_from_registry(
128            http_method='patch' if force else 'put',
129            login_server=login_server,
130            path=path,
131            username=username,
132            password=password,
133            file_payload=chart_package)[0]
134        return result
135    except RegistryException as e:
136        # Fallback using PUT if the chart doesn't exist
137        if e.status_code == 404 and force:
138            return request_data_from_registry(
139                http_method='put',
140                login_server=login_server,
141                path=path,
142                username=username,
143                password=password,
144                file_payload=chart_package)[0]
145        raise
146
147
148def acr_helm_repo_add(cmd,
149                      registry_name,
150                      repository='repo',
151                      resource_group_name=None,  # pylint: disable=unused-argument
152                      tenant_suffix=None,
153                      username=None,
154                      password=None):
155    helm_command, _ = get_helm_command()
156
157    login_server, username, password = get_access_credentials(
158        cmd=cmd,
159        registry_name=registry_name,
160        tenant_suffix=tenant_suffix,
161        username=username,
162        password=password,
163        artifact_repository=repository,
164        permission='pull')
165
166    from subprocess import Popen
167    p = Popen([helm_command, 'repo', 'add', registry_name,
168               'https://{}/helm/v1/{}'.format(login_server, repository),
169               '--username', username, '--password', password])
170    p.wait()
171
172
173def get_helm_command(is_diagnostics_context=False):
174    from ._errors import HELM_COMMAND_ERROR
175    helm_command = 'helm'
176
177    from subprocess import PIPE, Popen
178    try:
179        p = Popen([helm_command, "--help"], stdout=PIPE, stderr=PIPE)
180        _, stderr = p.communicate()
181    except OSError as e:
182        logger.debug("Could not run '%s' command. Exception: %s", helm_command, str(e))
183        # The executable may not be discoverable in WSL so retry *.exe once
184        try:
185            helm_command = 'helm.exe'
186            p = Popen([helm_command, "--help"], stdout=PIPE, stderr=PIPE)
187            _, stderr = p.communicate()
188        except OSError as inner:
189            logger.debug("Could not run '%s' command. Exception: %s", helm_command, str(inner))
190            if is_diagnostics_context:
191                return None, HELM_COMMAND_ERROR
192            raise CLIError(HELM_COMMAND_ERROR.get_error_message())
193
194    if stderr:
195        if is_diagnostics_context:
196            return None, HELM_COMMAND_ERROR.append_error_message(stderr.decode())
197        raise CLIError(HELM_COMMAND_ERROR.append_error_message(stderr.decode()).get_error_message())
198
199    return helm_command, None
200
201
202def _get_charts_path(repository, chart=None, version=None):
203    if chart and version:
204        return '/helm/v1/{}/_charts/{}/{}'.format(repository, chart, version)
205
206    if chart:
207        return '/helm/v1/{}/_charts/{}'.format(repository, chart)
208
209    return '/helm/v1/{}/_charts'.format(repository)
210
211
212def _get_blobs_path(repository, chart, version=None, prov=False):
213    path = '/helm/v1/{}/_blobs'.format(repository)
214
215    if version:
216        return '{}/{}'.format(path, _get_chart_package_name(chart, version, prov))
217
218    return '{}/{}'.format(path, chart)
219
220
221def _get_chart_package_name(chart, version, prov=False):
222    chart_package_name = '{}-{}.tgz'.format(chart, version)
223
224    if prov:
225        return '{}.prov'.format(chart_package_name)
226
227    return chart_package_name
228