aioli-sdk 0.2.4.dev10__tar.gz → 0.2.4.dev12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/PKG-INFO +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/__version__.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/cli.py +70 -60
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/deployment.py +42 -5
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/model.py +41 -60
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/test/test_cli.py +14 -29
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/version.py +5 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/PKG-INFO +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/__init__.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/authentication_api.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/deployments_api.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/information_api.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/packaged_models_api.py +18 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/registries_api.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/roles_api.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/templates_api.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/users_api.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api_client.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/configuration.py +2 -2
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/exceptions.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/__init__.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/auto_scaling_template.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/autoscaling.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/configuration_resources.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/deployment.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/deployment_model_version.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/deployment_request.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/deployment_state.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/error_response.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/event_info.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/failure_info.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/login_request.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/login_response.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/model_auth_token.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/model_response.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/observability.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/packaged_model.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/packaged_model_request.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/resource_profile.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/resources_template.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/role.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/role_assignment.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/role_assignments.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/security.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/success_response.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/trained_model_registry.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/trained_model_registry_request.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/user.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/user_patch_request.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/models/user_request.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/rest.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/setup.py +1 -1
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/README.md +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/__init__.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/__init__.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/__main__.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/_util.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/errors.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/registry.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/render.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/role.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/sso.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/test/conftest.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/cli/user.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/__init__.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/__init__.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/_util.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/authentication.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/certs.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/errors.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/api/request.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/check.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/constants.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/declarative_argparse.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/requests.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/common/util.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli/util.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/SOURCES.txt +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/dependency_links.txt +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/entry_points.txt +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/not-zip-safe +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/requires.txt +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aioli_sdk.egg-info/top_level.txt +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api/__init__.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/api_response.py +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/aiolirest/py.typed +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/pyproject.toml +0 -0
- {aioli_sdk-0.2.4.dev10 → aioli_sdk-0.2.4.dev12}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# © Copyright 2024 Hewlett Packard Enterprise Development LP
|
|
2
|
-
__version__ = "0.2.4-
|
|
2
|
+
__version__ = "0.2.4-dev12"
|
|
@@ -5,7 +5,12 @@ import os
|
|
|
5
5
|
import socket
|
|
6
6
|
import ssl
|
|
7
7
|
import sys
|
|
8
|
-
from argparse import
|
|
8
|
+
from argparse import (
|
|
9
|
+
ArgumentDefaultsHelpFormatter,
|
|
10
|
+
ArgumentError,
|
|
11
|
+
ArgumentParser,
|
|
12
|
+
Namespace,
|
|
13
|
+
)
|
|
9
14
|
from typing import List, Optional, Sequence, Union, cast
|
|
10
15
|
|
|
11
16
|
import argcomplete
|
|
@@ -101,65 +106,7 @@ def main(
|
|
|
101
106
|
parser.exit(2, "{}: no subcommand specified\n".format(parser.prog))
|
|
102
107
|
|
|
103
108
|
try:
|
|
104
|
-
|
|
105
|
-
certs.cli_cert = certs.default_load(parsed_args.controller)
|
|
106
|
-
|
|
107
|
-
try:
|
|
108
|
-
check_version(parsed_args)
|
|
109
|
-
except SSLError:
|
|
110
|
-
# An SSLError usually means that we queried a controller over HTTPS and got an
|
|
111
|
-
# untrusted cert, so allow the user to store and trust the current cert. (It
|
|
112
|
-
# could also mean that we tried to talk HTTPS on the HTTP port, but distinguishing
|
|
113
|
-
# that based on the exception is annoying, and we'll figure that out in the next
|
|
114
|
-
# step anyway.)
|
|
115
|
-
addr = api.parse_master_address(parsed_args.controller)
|
|
116
|
-
check_not_none(addr.hostname)
|
|
117
|
-
check_not_none(addr.port)
|
|
118
|
-
try:
|
|
119
|
-
ctx = SSL.Context(SSL.TLSv1_2_METHOD)
|
|
120
|
-
conn = SSL.Connection(ctx, socket.socket())
|
|
121
|
-
conn.set_tlsext_host_name(cast(str, addr.hostname).encode())
|
|
122
|
-
conn.connect(cast(Sequence[Union[str, int]], (addr.hostname, addr.port)))
|
|
123
|
-
conn.do_handshake()
|
|
124
|
-
peer_cert_chain = conn.get_peer_cert_chain()
|
|
125
|
-
if peer_cert_chain is None or len(peer_cert_chain) == 0:
|
|
126
|
-
# Peer presented no cert. It seems unlikely that this is possible after
|
|
127
|
-
# do_handshake() succeeded, but checking for None makes mypy happy.
|
|
128
|
-
raise crypto.Error()
|
|
129
|
-
cert_pem_data = [
|
|
130
|
-
crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode()
|
|
131
|
-
for cert in peer_cert_chain
|
|
132
|
-
]
|
|
133
|
-
except crypto.Error:
|
|
134
|
-
die(
|
|
135
|
-
"Tried to connect over HTTPS but couldn't get a certificate from the "
|
|
136
|
-
"controller; consider using HTTP"
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
# Compute the fingerprint of the certificate; this is the same as the output of
|
|
140
|
-
# `openssl x509 -fingerprint -sha256 -inform pem -noout -in <cert>`.
|
|
141
|
-
cert_hash = hashlib.sha256(ssl.PEM_cert_to_DER_cert(cert_pem_data[0])).hexdigest()
|
|
142
|
-
cert_fingerprint = ":".join(chunks(cert_hash, 2))
|
|
143
|
-
|
|
144
|
-
if not render.yes_or_no(
|
|
145
|
-
"The controller sent an untrusted certificate chain with this SHA256 "
|
|
146
|
-
"fingerprint:\n"
|
|
147
|
-
"{}\nDo you want to trust this certificate from now on?".format(
|
|
148
|
-
cert_fingerprint
|
|
149
|
-
)
|
|
150
|
-
):
|
|
151
|
-
die("Unable to verify controller certificate")
|
|
152
|
-
|
|
153
|
-
joined_certs = "".join(cert_pem_data)
|
|
154
|
-
certs.CertStore(certs.default_store()).set_cert(
|
|
155
|
-
parsed_args.controller, joined_certs
|
|
156
|
-
)
|
|
157
|
-
# Reconfigure the CLI's Cert singleton, but preserve the certificate name.
|
|
158
|
-
old_cert_name = certs.cli_cert.name
|
|
159
|
-
certs.cli_cert = certs.Cert(cert_pem=joined_certs, name=old_cert_name)
|
|
160
|
-
|
|
161
|
-
check_version(parsed_args)
|
|
162
|
-
|
|
109
|
+
configure_certificate_for_controller(parsed_args)
|
|
163
110
|
parsed_args.func(parsed_args)
|
|
164
111
|
except KeyboardInterrupt as e:
|
|
165
112
|
raise e
|
|
@@ -201,6 +148,69 @@ def main(
|
|
|
201
148
|
die("Interrupting...", exit_code=3)
|
|
202
149
|
|
|
203
150
|
|
|
151
|
+
def configure_certificate_for_controller(parsed_args: Namespace) -> None:
|
|
152
|
+
# Configure the CLI's Cert singleton.
|
|
153
|
+
certs.cli_cert = certs.default_load(parsed_args.controller)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
# check_version doesn't require credentials, so we can use it to verify the certificate.
|
|
157
|
+
check_version(parsed_args)
|
|
158
|
+
except SSLError:
|
|
159
|
+
# An SSLError usually means that we queried a controller over HTTPS and got an
|
|
160
|
+
# untrusted cert, so allow the user to store and trust the current cert. (It
|
|
161
|
+
# could also mean that we tried to talk HTTPS on the HTTP port, but distinguishing
|
|
162
|
+
# that based on the exception is annoying, and we'll figure that out in the next
|
|
163
|
+
# step anyway.)
|
|
164
|
+
addr = api.parse_master_address(parsed_args.controller)
|
|
165
|
+
check_not_none(addr.hostname)
|
|
166
|
+
check_not_none(addr.port)
|
|
167
|
+
try:
|
|
168
|
+
ctx = SSL.Context(SSL.TLSv1_2_METHOD)
|
|
169
|
+
conn = SSL.Connection(ctx, socket.socket())
|
|
170
|
+
conn.set_tlsext_host_name(cast(str, addr.hostname).encode())
|
|
171
|
+
conn.connect(cast(Sequence[Union[str, int]], (addr.hostname, addr.port)))
|
|
172
|
+
conn.do_handshake()
|
|
173
|
+
peer_cert_chain = conn.get_peer_cert_chain()
|
|
174
|
+
if peer_cert_chain is None or len(peer_cert_chain) == 0:
|
|
175
|
+
# Peer presented no cert. It seems unlikely that this is possible after
|
|
176
|
+
# do_handshake() succeeded, but checking for None makes mypy happy.
|
|
177
|
+
raise crypto.Error()
|
|
178
|
+
cert_pem_data = [
|
|
179
|
+
crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode()
|
|
180
|
+
for cert in peer_cert_chain
|
|
181
|
+
]
|
|
182
|
+
except crypto.Error:
|
|
183
|
+
die(
|
|
184
|
+
"Tried to connect over HTTPS but couldn't get a certificate from the "
|
|
185
|
+
"controller; consider using HTTP"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Compute the fingerprint of the certificate; this is the same as the output of
|
|
189
|
+
# `openssl x509 -fingerprint -sha256 -inform pem -noout -in <cert>`.
|
|
190
|
+
cert_hash = hashlib.sha256(ssl.PEM_cert_to_DER_cert(cert_pem_data[0])).hexdigest()
|
|
191
|
+
cert_fingerprint = ":".join(chunks(cert_hash, 2))
|
|
192
|
+
|
|
193
|
+
if not render.yes_or_no(
|
|
194
|
+
"The controller sent an untrusted certificate chain with this SHA256 "
|
|
195
|
+
"fingerprint:\n"
|
|
196
|
+
"{}\nDo you want to trust this certificate from now on?".format(cert_fingerprint)
|
|
197
|
+
):
|
|
198
|
+
die("Unable to verify controller certificate")
|
|
199
|
+
|
|
200
|
+
joined_certs = "".join(cert_pem_data)
|
|
201
|
+
certs.CertStore(certs.default_store()).set_cert(parsed_args.controller, joined_certs)
|
|
202
|
+
# Reconfigure the CLI's Cert singleton, but preserve the certificate name.
|
|
203
|
+
old_cert_name = certs.cli_cert.name
|
|
204
|
+
certs.cli_cert = certs.Cert(cert_pem=joined_certs, name=old_cert_name)
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
# let's try that again now that we have reconfigured with the certificate
|
|
208
|
+
# information supplied by the controller.
|
|
209
|
+
check_version(parsed_args)
|
|
210
|
+
except SSLError:
|
|
211
|
+
die("Failed to verify the controller certificate")
|
|
212
|
+
|
|
213
|
+
|
|
204
214
|
def extract_message(e: ApiException) -> Optional[str]:
|
|
205
215
|
decoder = json.JSONDecoder()
|
|
206
216
|
try:
|
|
@@ -46,6 +46,12 @@ def show_deployment(args: Namespace) -> None:
|
|
|
46
46
|
d = deployment.to_dict()
|
|
47
47
|
# Remove clusterName for now - INF-243
|
|
48
48
|
d.pop("clusterName")
|
|
49
|
+
|
|
50
|
+
# Get the model version for the deployment
|
|
51
|
+
deployment_model_versions = packaged_models_api.models_versions_get(deployment.model)
|
|
52
|
+
for r in deployment_model_versions:
|
|
53
|
+
if r.deployment_id == d["id"]:
|
|
54
|
+
d["version"] = r.mdl_version
|
|
49
55
|
if args.json:
|
|
50
56
|
render.print_json(d)
|
|
51
57
|
else:
|
|
@@ -57,7 +63,6 @@ def list_deployments(args: Namespace) -> None:
|
|
|
57
63
|
with cli.setup_session(args) as session:
|
|
58
64
|
api_instance = aiolirest.DeploymentsApi(session)
|
|
59
65
|
response = api_instance.deployments_get()
|
|
60
|
-
|
|
61
66
|
model_api = aiolirest.PackagedModelsApi(session)
|
|
62
67
|
|
|
63
68
|
if args.json:
|
|
@@ -71,14 +76,17 @@ def format_json(response: List[Deployment], model_api: aiolirest.PackagedModelsA
|
|
|
71
76
|
for d in response:
|
|
72
77
|
# Don't use the d.to_json() method as it adds backslash escapes for double quote
|
|
73
78
|
m_dict = d.to_dict()
|
|
74
|
-
m_dict.pop("id")
|
|
75
79
|
m_dict.pop("modifiedAt")
|
|
76
80
|
# Use model name instead of id
|
|
77
81
|
model = model_api.models_id_get(d.model)
|
|
82
|
+
deployment_model_versions = model_api.models_versions_get(model.name)
|
|
83
|
+
for r in deployment_model_versions:
|
|
84
|
+
if r.deployment_id == m_dict["id"]:
|
|
85
|
+
m_dict["version"] = r.mdl_version
|
|
86
|
+
m_dict.pop("id")
|
|
78
87
|
m_dict["model"] = model.name
|
|
79
88
|
m_dict.pop("clusterName", None)
|
|
80
89
|
deps.append(m_dict)
|
|
81
|
-
|
|
82
90
|
render.print_json(deps)
|
|
83
91
|
|
|
84
92
|
|
|
@@ -90,8 +98,14 @@ def format_deployments(
|
|
|
90
98
|
def format_deployment(e: Deployment, models_api: aiolirest.PackagedModelsApi) -> List[Any]:
|
|
91
99
|
model = models_api.models_id_get(e.model)
|
|
92
100
|
state = e.state
|
|
101
|
+
total_failures = 0
|
|
102
|
+
|
|
93
103
|
if state is None:
|
|
94
104
|
state = DeploymentState()
|
|
105
|
+
else:
|
|
106
|
+
failure_info = state.failure_info
|
|
107
|
+
if failure_info:
|
|
108
|
+
total_failures = len(failure_info)
|
|
95
109
|
|
|
96
110
|
secondary_state = e.secondary_state
|
|
97
111
|
if secondary_state is None:
|
|
@@ -103,9 +117,17 @@ def format_deployments(
|
|
|
103
117
|
if auto_scaling is None:
|
|
104
118
|
auto_scaling = Autoscaling()
|
|
105
119
|
|
|
120
|
+
if total_failures > 1:
|
|
121
|
+
e.status = f"{e.status}\n({total_failures} errors)"
|
|
122
|
+
elif total_failures == 1:
|
|
123
|
+
e.status = f"{e.status}\n({total_failures} error)"
|
|
124
|
+
|
|
125
|
+
deployment_model_versions = models_api.models_versions_get(model.name)
|
|
126
|
+
version = next(r.mdl_version for r in deployment_model_versions if r.deployment_id == e.id)
|
|
106
127
|
result = [
|
|
107
128
|
e.name,
|
|
108
129
|
model.name,
|
|
130
|
+
version,
|
|
109
131
|
e.namespace,
|
|
110
132
|
e.status,
|
|
111
133
|
e.security.authentication_required,
|
|
@@ -118,12 +140,14 @@ def format_deployments(
|
|
|
118
140
|
headers = [
|
|
119
141
|
"Name",
|
|
120
142
|
"Model",
|
|
143
|
+
"Version",
|
|
121
144
|
"Namespace",
|
|
122
145
|
"Status",
|
|
123
146
|
"Auth Required",
|
|
124
147
|
"State",
|
|
125
148
|
"Traffic %",
|
|
126
149
|
]
|
|
150
|
+
|
|
127
151
|
values = [format_deployment(r, packaged_models_api) for r in response]
|
|
128
152
|
render.tabulate_or_csv(headers, values, args.csv)
|
|
129
153
|
|
|
@@ -337,7 +361,14 @@ main_cmd = Cmd(
|
|
|
337
361
|
help="The name of the deployment. Must begin with a letter, but may contain "
|
|
338
362
|
"letters, numbers, and hyphen",
|
|
339
363
|
),
|
|
340
|
-
Arg(
|
|
364
|
+
Arg(
|
|
365
|
+
"--model",
|
|
366
|
+
help=(
|
|
367
|
+
"The package model id, name or versioned-name (evaluated in that "
|
|
368
|
+
"order) to be deployed"
|
|
369
|
+
),
|
|
370
|
+
required="true",
|
|
371
|
+
),
|
|
341
372
|
]
|
|
342
373
|
+ common_deployment_args,
|
|
343
374
|
),
|
|
@@ -381,7 +412,13 @@ main_cmd = Cmd(
|
|
|
381
412
|
help="The new name of the deployment. Must begin with a letter, but may "
|
|
382
413
|
"contain letters, numbers, and hyphen",
|
|
383
414
|
),
|
|
384
|
-
Arg(
|
|
415
|
+
Arg(
|
|
416
|
+
"--model",
|
|
417
|
+
help=(
|
|
418
|
+
"The package model id, name or versioned-name (evaluated in that "
|
|
419
|
+
"order) to be deployed"
|
|
420
|
+
),
|
|
421
|
+
),
|
|
385
422
|
Arg("--pause", action="store_true", help="Pause the deployment"),
|
|
386
423
|
Arg("--resume", action="store_true", help="Resume the deployment"),
|
|
387
424
|
]
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# © Copyright 2023-2024 Hewlett Packard Enterprise Development LP
|
|
2
|
-
import re
|
|
3
2
|
from argparse import Namespace
|
|
4
3
|
from typing import Any, List
|
|
5
4
|
|
|
6
|
-
from pydantic import StrictInt
|
|
7
|
-
|
|
8
5
|
import aiolirest
|
|
9
6
|
from aioli import cli
|
|
10
7
|
from aioli.cli import render
|
|
@@ -105,59 +102,29 @@ def create(args: Namespace) -> None:
|
|
|
105
102
|
api_instance.models_post(r)
|
|
106
103
|
|
|
107
104
|
|
|
108
|
-
def lookup_model(name: str,
|
|
105
|
+
def lookup_model(name: str, api: aiolirest.PackagedModelsApi) -> PackagedModel:
|
|
109
106
|
# From the database, get the model record. If the model exists in multiple versions,
|
|
110
107
|
# then sufficient version information must be part of the request.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return model # Found a single version of the specified model
|
|
120
|
-
if model_count > 1 and not args.version:
|
|
121
|
-
raise_version_required(name, model_count)
|
|
122
|
-
|
|
123
|
-
if model_count == 0:
|
|
124
|
-
# The specified model does not exist; extract the version suffix if any
|
|
125
|
-
regexp = re.compile(r"^(.+)\.[Vv](\d+)$")
|
|
126
|
-
m = regexp.match(name)
|
|
127
|
-
if m is None:
|
|
128
|
-
raise NotFoundException(
|
|
129
|
-
f"model {name} not found. Model versions may optionally be specified "
|
|
130
|
-
"using the suffix '.v#', for example, '.v1', '.v100'"
|
|
131
|
-
)
|
|
132
|
-
name = m.group(1)
|
|
133
|
-
version = m.group(2)
|
|
134
|
-
|
|
135
|
-
# if there is an explicit version specified, then use that
|
|
136
|
-
if args.version:
|
|
137
|
-
version = args.version
|
|
138
|
-
|
|
139
|
-
return lookup_model_and_version(name, version, models)
|
|
108
|
+
models: List[PackagedModel] = api.models_get(name)
|
|
109
|
+
if len(models):
|
|
110
|
+
return models[0]
|
|
111
|
+
|
|
112
|
+
raise NotFoundException(
|
|
113
|
+
f"model {name} not found. Model versions may optionally be specified "
|
|
114
|
+
"using the suffix '.v#', for example, '.v1', '.v100'"
|
|
115
|
+
)
|
|
140
116
|
|
|
141
117
|
|
|
142
118
|
def raise_version_required(name: str, count: int) -> None:
|
|
143
119
|
raise VersionRequiredException(f"specify a version as model {name} exists in {count} versions")
|
|
144
120
|
|
|
145
121
|
|
|
146
|
-
def lookup_model_and_version(name: str, version: str, models: List[PackagedModel]) -> PackagedModel:
|
|
147
|
-
# The version may optionally be expressed with a prefix of 'v'
|
|
148
|
-
version_no_prefix: str = version.lstrip("vV")
|
|
149
|
-
for r in models:
|
|
150
|
-
if r.name == name and r.version == StrictInt(version_no_prefix):
|
|
151
|
-
return r
|
|
152
|
-
raise NotFoundException(f"model {name} version {version} not found")
|
|
153
|
-
|
|
154
|
-
|
|
155
122
|
@authentication.required
|
|
156
123
|
def dashboard(args: Namespace) -> None:
|
|
157
124
|
with cli.setup_session(args) as session:
|
|
158
125
|
api_instance = aiolirest.PackagedModelsApi(session)
|
|
159
126
|
|
|
160
|
-
model = lookup_model(args.name,
|
|
127
|
+
model = lookup_model(args.name, api_instance)
|
|
161
128
|
|
|
162
129
|
observability = api_instance.models_id_observability_get(model.id)
|
|
163
130
|
launch_dashboard(args, observability.dashboard_url)
|
|
@@ -168,7 +135,7 @@ def show_model(args: Namespace) -> None:
|
|
|
168
135
|
with cli.setup_session(args) as session:
|
|
169
136
|
api_instance = aiolirest.PackagedModelsApi(session)
|
|
170
137
|
|
|
171
|
-
model = lookup_model(args.name,
|
|
138
|
+
model = lookup_model(args.name, api_instance)
|
|
172
139
|
registries_api = aiolirest.RegistriesApi(session)
|
|
173
140
|
|
|
174
141
|
rname = lookup_registry_name_by_id(model.registry, registries_api)
|
|
@@ -186,7 +153,7 @@ def show_model(args: Namespace) -> None:
|
|
|
186
153
|
def update(args: Namespace) -> None:
|
|
187
154
|
with cli.setup_session(args) as session:
|
|
188
155
|
api_instance = aiolirest.PackagedModelsApi(session)
|
|
189
|
-
found = lookup_model(args.modelname,
|
|
156
|
+
found = lookup_model(args.modelname, api_instance)
|
|
190
157
|
request = PackagedModelRequest(
|
|
191
158
|
description=found.description,
|
|
192
159
|
image=found.image,
|
|
@@ -262,7 +229,7 @@ def update(args: Namespace) -> None:
|
|
|
262
229
|
def delete_model(args: Namespace) -> None:
|
|
263
230
|
with cli.setup_session(args) as session:
|
|
264
231
|
api_instance = aiolirest.PackagedModelsApi(session)
|
|
265
|
-
found =
|
|
232
|
+
found = lookup_model(args.name, api_instance)
|
|
266
233
|
|
|
267
234
|
assert found.id is not None
|
|
268
235
|
api_instance.models_id_delete(found.id)
|
|
@@ -272,7 +239,7 @@ def delete_model(args: Namespace) -> None:
|
|
|
272
239
|
def auth_token(args: Namespace) -> None:
|
|
273
240
|
with cli.setup_session(args) as session:
|
|
274
241
|
api_instance = aiolirest.PackagedModelsApi(session)
|
|
275
|
-
found = lookup_model(args.name,
|
|
242
|
+
found = lookup_model(args.name, api_instance)
|
|
276
243
|
assert found.id is not None
|
|
277
244
|
response = api_instance.models_id_token_get(found.id)
|
|
278
245
|
t = response.to_dict()
|
|
@@ -283,7 +250,7 @@ def auth_token(args: Namespace) -> None:
|
|
|
283
250
|
def list_versions(args: Namespace) -> None:
|
|
284
251
|
with cli.setup_session(args) as session:
|
|
285
252
|
api_instance = aiolirest.PackagedModelsApi(session)
|
|
286
|
-
found = lookup_model(args.name,
|
|
253
|
+
found = lookup_model(args.name, api_instance)
|
|
287
254
|
assert found.id is not None
|
|
288
255
|
response = api_instance.models_versions_get(found.id)
|
|
289
256
|
|
|
@@ -339,6 +306,14 @@ common_model_args: ArgsDescription = [
|
|
|
339
306
|
Arg("--requests-gpu", help="GPU request"),
|
|
340
307
|
]
|
|
341
308
|
|
|
309
|
+
VERSIONED_MODEL_HELP_MSG = (
|
|
310
|
+
"The packaged model id, name or versioned-name (evaluated in that order). "
|
|
311
|
+
"A versioned-name is the package model name with suffix of the version "
|
|
312
|
+
"with the format 'name.V###' where '###' is the version number. For example, "
|
|
313
|
+
"a model named 'my-model' with a version of '23' would be represented by "
|
|
314
|
+
"versioned-name of: my-model.V23"
|
|
315
|
+
)
|
|
316
|
+
|
|
342
317
|
main_cmd = Cmd(
|
|
343
318
|
"m|odel|s",
|
|
344
319
|
None,
|
|
@@ -378,9 +353,8 @@ main_cmd = Cmd(
|
|
|
378
353
|
[
|
|
379
354
|
Arg(
|
|
380
355
|
"name",
|
|
381
|
-
help=
|
|
356
|
+
help=VERSIONED_MODEL_HELP_MSG,
|
|
382
357
|
),
|
|
383
|
-
Arg("--version", help="The packaged model version to show"),
|
|
384
358
|
],
|
|
385
359
|
),
|
|
386
360
|
# Show command.
|
|
@@ -391,13 +365,12 @@ main_cmd = Cmd(
|
|
|
391
365
|
[
|
|
392
366
|
Arg(
|
|
393
367
|
"name",
|
|
394
|
-
help=
|
|
368
|
+
help=VERSIONED_MODEL_HELP_MSG,
|
|
395
369
|
),
|
|
396
370
|
Group(
|
|
397
371
|
Arg("--yaml", action="store_true", help="print as YAML", default=True),
|
|
398
372
|
Arg("--json", action="store_true", help="print as JSON"),
|
|
399
373
|
),
|
|
400
|
-
Arg("--version", help="The packaged model version to show"),
|
|
401
374
|
],
|
|
402
375
|
),
|
|
403
376
|
# Update command
|
|
@@ -406,14 +379,16 @@ main_cmd = Cmd(
|
|
|
406
379
|
update,
|
|
407
380
|
"modify a packaged model",
|
|
408
381
|
[
|
|
409
|
-
Arg(
|
|
382
|
+
Arg(
|
|
383
|
+
"modelname",
|
|
384
|
+
help=VERSIONED_MODEL_HELP_MSG,
|
|
385
|
+
),
|
|
410
386
|
Arg(
|
|
411
387
|
"--name",
|
|
412
388
|
help="The new name of the packaged model. Must begin with a letter, but may "
|
|
413
389
|
"contain letters, numbers, and hyphen",
|
|
414
390
|
),
|
|
415
391
|
Arg("--image", help="Docker container image servicing the packaged model"),
|
|
416
|
-
Arg("--version", help="The packaged model version to update"),
|
|
417
392
|
]
|
|
418
393
|
+ common_model_args,
|
|
419
394
|
),
|
|
@@ -422,8 +397,10 @@ main_cmd = Cmd(
|
|
|
422
397
|
delete_model,
|
|
423
398
|
"delete a packaged model",
|
|
424
399
|
[
|
|
425
|
-
Arg(
|
|
426
|
-
|
|
400
|
+
Arg(
|
|
401
|
+
"name",
|
|
402
|
+
help=VERSIONED_MODEL_HELP_MSG,
|
|
403
|
+
),
|
|
427
404
|
],
|
|
428
405
|
),
|
|
429
406
|
Cmd(
|
|
@@ -431,8 +408,10 @@ main_cmd = Cmd(
|
|
|
431
408
|
auth_token,
|
|
432
409
|
"get packaged model auth token",
|
|
433
410
|
[
|
|
434
|
-
Arg(
|
|
435
|
-
|
|
411
|
+
Arg(
|
|
412
|
+
"name",
|
|
413
|
+
help=VERSIONED_MODEL_HELP_MSG,
|
|
414
|
+
),
|
|
436
415
|
],
|
|
437
416
|
),
|
|
438
417
|
Cmd(
|
|
@@ -441,8 +420,10 @@ main_cmd = Cmd(
|
|
|
441
420
|
"list of deployment versions for a packaged model",
|
|
442
421
|
[
|
|
443
422
|
Arg("--csv", action="store_true", help="print as CSV"),
|
|
444
|
-
Arg(
|
|
445
|
-
|
|
423
|
+
Arg(
|
|
424
|
+
"name",
|
|
425
|
+
help=VERSIONED_MODEL_HELP_MSG,
|
|
426
|
+
),
|
|
446
427
|
],
|
|
447
428
|
),
|
|
448
429
|
],
|
|
@@ -327,21 +327,6 @@ class TestCli:
|
|
|
327
327
|
).decode("utf-8")
|
|
328
328
|
assert yaml_output == yaml_output2, "Expect the same output using .V1"
|
|
329
329
|
|
|
330
|
-
yaml_output2 = subprocess.check_output(
|
|
331
|
-
["aioli", "model", "show", "iris-tf-keras", "--version", "1"]
|
|
332
|
-
).decode("utf-8")
|
|
333
|
-
assert yaml_output == yaml_output2, "Expect the same output using --version 1"
|
|
334
|
-
|
|
335
|
-
yaml_output2 = subprocess.check_output(
|
|
336
|
-
["aioli", "model", "show", "iris-tf-keras", "--version", "v1"]
|
|
337
|
-
).decode("utf-8")
|
|
338
|
-
assert yaml_output == yaml_output2, "Expect the same output using --version v1"
|
|
339
|
-
|
|
340
|
-
yaml_output2 = subprocess.check_output(
|
|
341
|
-
["aioli", "model", "show", "iris-tf-keras", "--version", "V1"]
|
|
342
|
-
).decode("utf-8")
|
|
343
|
-
assert yaml_output == yaml_output2, "Expect the same output using --version V1"
|
|
344
|
-
|
|
345
330
|
# fmt: off
|
|
346
331
|
def test_model_list_json(self, setup_login: None) -> None:
|
|
347
332
|
expected = (
|
|
@@ -402,7 +387,7 @@ class TestCli:
|
|
|
402
387
|
actual = subprocess.check_output(["aioli", "model", "list"]).decode("utf-8")
|
|
403
388
|
|
|
404
389
|
assert (actual.find(expected)) > 0
|
|
405
|
-
assert os.system("aioli model delete openllm
|
|
390
|
+
assert os.system("aioli model delete openllm.v1") == 0
|
|
406
391
|
|
|
407
392
|
def test_model_update_bad_model_name(self, setup_login: None) -> None:
|
|
408
393
|
# Attempt to update model specifying the version incorrectly. Here we specify
|
|
@@ -414,7 +399,7 @@ class TestCli:
|
|
|
414
399
|
)
|
|
415
400
|
except subprocess.CalledProcessError as e:
|
|
416
401
|
assert e.returncode == 1
|
|
417
|
-
expected = "Failed to modify a packaged model: model iris-tf-keras.1 not
|
|
402
|
+
expected = "Failed to modify a packaged model: model 'iris-tf-keras.1' does not exist."
|
|
418
403
|
actual: str = e.output.decode("utf-8")
|
|
419
404
|
assert actual.find(expected) == 0
|
|
420
405
|
|
|
@@ -472,8 +457,7 @@ class TestCli:
|
|
|
472
457
|
# Create a third version of the model using the second version
|
|
473
458
|
assert (
|
|
474
459
|
os.system(
|
|
475
|
-
"aioli model update iris-tf-keras --name iris-tf-keras "
|
|
476
|
-
"--version 2 "
|
|
460
|
+
"aioli model update iris-tf-keras.v2 --name iris-tf-keras "
|
|
477
461
|
"--registry bento-registry1 "
|
|
478
462
|
"--url s3://demo-bento-registry/iris-tf-keras_updated_v3 "
|
|
479
463
|
"--image fictional.registry.example/updated_imagename "
|
|
@@ -495,8 +479,7 @@ class TestCli:
|
|
|
495
479
|
# (specifying the version with the optional v{n} format)
|
|
496
480
|
assert (
|
|
497
481
|
os.system(
|
|
498
|
-
"aioli model update iris-tf-keras --name iris-tf-keras-v4 "
|
|
499
|
-
"--version v2 "
|
|
482
|
+
"aioli model update iris-tf-keras.v2 --name iris-tf-keras-v4 "
|
|
500
483
|
"--registry bento-registry1 "
|
|
501
484
|
"--url s3://demo-bento-registry/iris-tf-keras_updated_v3 "
|
|
502
485
|
"--image fictional.registry.example/updated_imagename "
|
|
@@ -527,7 +510,7 @@ class TestCli:
|
|
|
527
510
|
)
|
|
528
511
|
actual = subprocess.check_output(["aioli", "deployment", "list"]).decode("utf-8")
|
|
529
512
|
expected = (
|
|
530
|
-
"iris-tf-keras-deployment | iris-tf-keras | aioli "
|
|
513
|
+
"iris-tf-keras-deployment | iris-tf-keras | 3 | aioli "
|
|
531
514
|
"| Deploying | False | Deploying | 0"
|
|
532
515
|
)
|
|
533
516
|
|
|
@@ -565,8 +548,9 @@ class TestCli:
|
|
|
565
548
|
== 0
|
|
566
549
|
)
|
|
567
550
|
actual = subprocess.check_output(["aioli", "deployment", "list"]).decode("utf-8")
|
|
551
|
+
|
|
568
552
|
expected = (
|
|
569
|
-
"iris-tf-keras-deployment | iris-tf-keras | aioli "
|
|
553
|
+
"iris-tf-keras-deployment | iris-tf-keras | 3 | aioli "
|
|
570
554
|
"| Deploying | True | Deploying | 0"
|
|
571
555
|
)
|
|
572
556
|
assert actual.find(expected) > 0
|
|
@@ -621,7 +605,8 @@ class TestCli:
|
|
|
621
605
|
' "status": "Deploying",\n'
|
|
622
606
|
' "trafficPercentage": 0\n'
|
|
623
607
|
' },\n' # noqa: Q000
|
|
624
|
-
' "status": "Deploying"
|
|
608
|
+
' "status": "Deploying",\n'
|
|
609
|
+
' "version": "3"\n'
|
|
625
610
|
' }\n' # noqa: Q000
|
|
626
611
|
']\n' # noqa: Q000
|
|
627
612
|
)
|
|
@@ -710,11 +695,11 @@ class TestCli:
|
|
|
710
695
|
assert os.system("aioli deployment delete iris-tf-keras-deployment-2") == 0
|
|
711
696
|
|
|
712
697
|
def test_model_delete(self, setup_login: None) -> None:
|
|
713
|
-
assert os.system("aioli model delete iris-tf-keras1
|
|
714
|
-
assert os.system("aioli model delete iris-tf-keras
|
|
715
|
-
assert os.system("aioli model delete iris-tf-keras
|
|
716
|
-
assert os.system("aioli model delete iris-tf-keras
|
|
717
|
-
assert os.system("aioli model delete iris-tf-keras-v4
|
|
698
|
+
assert os.system("aioli model delete iris-tf-keras1") == 0
|
|
699
|
+
assert os.system("aioli model delete iris-tf-keras.v1") == 0
|
|
700
|
+
assert os.system("aioli model delete iris-tf-keras.v2") == 0
|
|
701
|
+
assert os.system("aioli model delete iris-tf-keras.v3") == 0
|
|
702
|
+
assert os.system("aioli model delete iris-tf-keras-v4.v1") == 0
|
|
718
703
|
|
|
719
704
|
def test_registry_delete(self, setup_login: None) -> None:
|
|
720
705
|
# Delete the registry entry
|
|
@@ -77,7 +77,11 @@ def check_version(parsed_args: argparse.Namespace) -> None:
|
|
|
77
77
|
print(
|
|
78
78
|
termcolor.colored(
|
|
79
79
|
"Controller version {} is less than CLI version {}. "
|
|
80
|
-
"
|
|
80
|
+
"This CLI may utilize features not supported by an older "
|
|
81
|
+
"controller. Install a compatible CLI with: "
|
|
82
|
+
" pip install aioli-sdk=={}".format(
|
|
83
|
+
controller_version, client_version, controller_version
|
|
84
|
+
),
|
|
81
85
|
"yellow",
|
|
82
86
|
),
|
|
83
87
|
file=sys.stderr,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
HPE MLIS is *Aioli* -- The AI On-line Inference Platform that enables easy deployment, tracking, and serving of your packaged models regardless of your preferred AI framework.
|
|
9
9
|
|
|
10
|
-
The version of the OpenAPI document: 0.2.4-
|
|
10
|
+
The version of the OpenAPI document: 0.2.4-dev12
|
|
11
11
|
Contact: community@determined-ai
|
|
12
12
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
13
13
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
HPE MLIS is *Aioli* -- The AI On-line Inference Platform that enables easy deployment, tracking, and serving of your packaged models regardless of your preferred AI framework.
|
|
7
7
|
|
|
8
|
-
The version of the OpenAPI document: 0.2.4-
|
|
8
|
+
The version of the OpenAPI document: 0.2.4-dev12
|
|
9
9
|
Contact: community@determined-ai
|
|
10
10
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
11
11
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
HPE MLIS is *Aioli* -- The AI On-line Inference Platform that enables easy deployment, tracking, and serving of your packaged models regardless of your preferred AI framework.
|
|
7
7
|
|
|
8
|
-
The version of the OpenAPI document: 0.2.4-
|
|
8
|
+
The version of the OpenAPI document: 0.2.4-dev12
|
|
9
9
|
Contact: community@determined-ai
|
|
10
10
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
11
11
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
HPE MLIS is *Aioli* -- The AI On-line Inference Platform that enables easy deployment, tracking, and serving of your packaged models regardless of your preferred AI framework.
|
|
7
7
|
|
|
8
|
-
The version of the OpenAPI document: 0.2.4-
|
|
8
|
+
The version of the OpenAPI document: 0.2.4-dev12
|
|
9
9
|
Contact: community@determined-ai
|
|
10
10
|
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
11
11
|
|