trainml 0.5.8__py3-none-any.whl → 0.5.9__py3-none-any.whl
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.
- tests/integration/projects/__init__.py +0 -0
- tests/integration/projects/conftest.py +8 -0
- tests/integration/projects/test_projects_integration.py +38 -0
- tests/integration/projects/test_projects_keys_integration.py +43 -0
- tests/integration/projects/test_projects_secrets_integration.py +44 -0
- tests/unit/conftest.py +72 -2
- tests/unit/projects/__init__.py +0 -0
- tests/unit/projects/test_project_data_connectors_unit.py +102 -0
- tests/unit/projects/test_project_datastores_unit.py +96 -0
- tests/unit/projects/test_project_keys_unit.py +96 -0
- tests/unit/projects/test_project_secrets_unit.py +101 -0
- tests/unit/projects/test_project_services_unit.py +102 -0
- tests/unit/projects/test_projects_unit.py +128 -0
- tests/unit/test_auth_unit.py +30 -0
- tests/unit/test_trainml_unit.py +54 -0
- trainml/__init__.py +1 -1
- trainml/cli/project/__init__.py +153 -0
- trainml/cli/project/key.py +124 -0
- trainml/cli/project/secret.py +71 -0
- trainml/projects/__init__.py +3 -0
- trainml/projects/data_connectors.py +63 -0
- trainml/projects/datastores.py +58 -0
- trainml/projects/keys.py +71 -0
- trainml/projects/projects.py +83 -0
- trainml/projects/secrets.py +70 -0
- trainml/projects/services.py +63 -0
- {trainml-0.5.8.dist-info → trainml-0.5.9.dist-info}/METADATA +1 -1
- {trainml-0.5.8.dist-info → trainml-0.5.9.dist-info}/RECORD +32 -8
- {trainml-0.5.8.dist-info → trainml-0.5.9.dist-info}/LICENSE +0 -0
- {trainml-0.5.8.dist-info → trainml-0.5.9.dist-info}/WHEEL +0 -0
- {trainml-0.5.8.dist-info → trainml-0.5.9.dist-info}/entry_points.txt +0 -0
- {trainml-0.5.8.dist-info → trainml-0.5.9.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from unittest.mock import AsyncMock, patch
|
|
5
|
+
from pytest import mark, fixture, raises
|
|
6
|
+
from aiohttp import WSMessage, WSMsgType
|
|
7
|
+
|
|
8
|
+
import trainml.projects.services as specimen
|
|
9
|
+
from trainml.exceptions import (
|
|
10
|
+
ApiError,
|
|
11
|
+
SpecificationError,
|
|
12
|
+
TrainMLException,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
pytestmark = [mark.sdk, mark.unit, mark.projects]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@fixture
|
|
19
|
+
def project_services(mock_trainml):
|
|
20
|
+
yield specimen.ProjectServices(mock_trainml, project_id="1")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@fixture
|
|
24
|
+
def project_service(mock_trainml):
|
|
25
|
+
yield specimen.ProjectService(
|
|
26
|
+
mock_trainml,
|
|
27
|
+
id="res-id-1",
|
|
28
|
+
name="service 1",
|
|
29
|
+
project_uuid="proj-id-1",
|
|
30
|
+
region_uuid="reg-id-1",
|
|
31
|
+
public=False,
|
|
32
|
+
hostname="asdf.proximl.cloud",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ProjectServicesTests:
|
|
37
|
+
@mark.asyncio
|
|
38
|
+
async def test_project_services_refresh(self, project_services, mock_trainml):
|
|
39
|
+
api_response = dict()
|
|
40
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
41
|
+
await project_services.refresh()
|
|
42
|
+
mock_trainml._query.assert_called_once_with("/project/1/services", "PATCH")
|
|
43
|
+
|
|
44
|
+
@mark.asyncio
|
|
45
|
+
async def test_project_services_list(self, project_services, mock_trainml):
|
|
46
|
+
api_response = [
|
|
47
|
+
{
|
|
48
|
+
"project_uuid": "proj-id-1",
|
|
49
|
+
"region_uuid": "reg-id-1",
|
|
50
|
+
"id": "res-id-1",
|
|
51
|
+
"type": "port",
|
|
52
|
+
"name": "On-Prem Service A",
|
|
53
|
+
"resource": "8001",
|
|
54
|
+
"hostname": "service-a.local",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"project_uuid": "proj-id-1",
|
|
58
|
+
"region_uuid": "reg-id-2",
|
|
59
|
+
"id": "res-id-2",
|
|
60
|
+
"type": "port",
|
|
61
|
+
"name": "Cloud Service B",
|
|
62
|
+
"resource": "8001",
|
|
63
|
+
"hostname": "service-b.local",
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
67
|
+
resp = await project_services.list()
|
|
68
|
+
mock_trainml._query.assert_called_once_with(
|
|
69
|
+
"/project/1/services", "GET", dict()
|
|
70
|
+
)
|
|
71
|
+
assert len(resp) == 2
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ProjectServiceTests:
|
|
75
|
+
def test_project_service_properties(self, project_service):
|
|
76
|
+
assert isinstance(project_service.id, str)
|
|
77
|
+
assert isinstance(project_service.name, str)
|
|
78
|
+
assert isinstance(project_service.project_uuid, str)
|
|
79
|
+
assert isinstance(project_service.hostname, str)
|
|
80
|
+
assert isinstance(project_service.public, bool)
|
|
81
|
+
assert isinstance(project_service.region_uuid, str)
|
|
82
|
+
|
|
83
|
+
def test_project_service_str(self, project_service):
|
|
84
|
+
string = str(project_service)
|
|
85
|
+
regex = r"^{.*\"id\": \"" + project_service.id + r"\".*}$"
|
|
86
|
+
assert isinstance(string, str)
|
|
87
|
+
assert re.match(regex, string)
|
|
88
|
+
|
|
89
|
+
def test_project_service_repr(self, project_service):
|
|
90
|
+
string = repr(project_service)
|
|
91
|
+
regex = (
|
|
92
|
+
r"^ProjectService\( trainml , \*\*{.*'id': '"
|
|
93
|
+
+ project_service.id
|
|
94
|
+
+ r"'.*}\)$"
|
|
95
|
+
)
|
|
96
|
+
assert isinstance(string, str)
|
|
97
|
+
assert re.match(regex, string)
|
|
98
|
+
|
|
99
|
+
def test_project_service_bool(self, project_service, mock_trainml):
|
|
100
|
+
empty_project_service = specimen.ProjectService(mock_trainml)
|
|
101
|
+
assert bool(project_service)
|
|
102
|
+
assert not bool(empty_project_service)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from unittest.mock import AsyncMock, patch
|
|
5
|
+
from pytest import mark, fixture, raises
|
|
6
|
+
from aiohttp import WSMessage, WSMsgType
|
|
7
|
+
|
|
8
|
+
import trainml.projects as specimen
|
|
9
|
+
from trainml.exceptions import (
|
|
10
|
+
ApiError,
|
|
11
|
+
SpecificationError,
|
|
12
|
+
TrainMLException,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
pytestmark = [mark.sdk, mark.unit, mark.projects]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@fixture
|
|
19
|
+
def projects(mock_trainml):
|
|
20
|
+
yield specimen.Projects(mock_trainml)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@fixture
|
|
24
|
+
def project(mock_trainml):
|
|
25
|
+
yield specimen.Project(
|
|
26
|
+
mock_trainml,
|
|
27
|
+
id="1",
|
|
28
|
+
name="My Mock Project",
|
|
29
|
+
owner=True,
|
|
30
|
+
owner_name="Me",
|
|
31
|
+
created_name="Me",
|
|
32
|
+
job_all=True,
|
|
33
|
+
dataset_all=True,
|
|
34
|
+
model_all=True,
|
|
35
|
+
createdAt="2020-12-31T23:59:59.000Z",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ProjectsTests:
|
|
40
|
+
@mark.asyncio
|
|
41
|
+
async def test_get_project(
|
|
42
|
+
self,
|
|
43
|
+
projects,
|
|
44
|
+
mock_trainml,
|
|
45
|
+
):
|
|
46
|
+
api_response = dict()
|
|
47
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
48
|
+
await projects.get("1234")
|
|
49
|
+
mock_trainml._query.assert_called_once_with("/project/1234", "GET", dict())
|
|
50
|
+
|
|
51
|
+
@mark.asyncio
|
|
52
|
+
async def test_list_projects(
|
|
53
|
+
self,
|
|
54
|
+
projects,
|
|
55
|
+
mock_trainml,
|
|
56
|
+
):
|
|
57
|
+
api_response = dict()
|
|
58
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
59
|
+
await projects.list()
|
|
60
|
+
mock_trainml._query.assert_called_once_with("/project", "GET", dict())
|
|
61
|
+
|
|
62
|
+
@mark.asyncio
|
|
63
|
+
async def test_remove_project(
|
|
64
|
+
self,
|
|
65
|
+
projects,
|
|
66
|
+
mock_trainml,
|
|
67
|
+
):
|
|
68
|
+
api_response = dict()
|
|
69
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
70
|
+
await projects.remove("4567")
|
|
71
|
+
mock_trainml._query.assert_called_once_with("/project/4567", "DELETE", dict())
|
|
72
|
+
|
|
73
|
+
@mark.asyncio
|
|
74
|
+
async def test_create_project_simple(self, projects, mock_trainml):
|
|
75
|
+
requested_config = dict(
|
|
76
|
+
name="new project",
|
|
77
|
+
)
|
|
78
|
+
expected_payload = dict(name="new project", copy_keys=False, copy_secrets=False)
|
|
79
|
+
api_response = {
|
|
80
|
+
"id": "project-id-1",
|
|
81
|
+
"name": "new project",
|
|
82
|
+
"owner": True,
|
|
83
|
+
"owner_name": "Me",
|
|
84
|
+
"created_name": "Me",
|
|
85
|
+
"job_all": True,
|
|
86
|
+
"dataset_all": True,
|
|
87
|
+
"model_all": True,
|
|
88
|
+
"createdAt": "2020-12-31T23:59:59.000Z",
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
92
|
+
response = await projects.create(**requested_config)
|
|
93
|
+
mock_trainml._query.assert_called_once_with(
|
|
94
|
+
"/project", "POST", None, expected_payload
|
|
95
|
+
)
|
|
96
|
+
assert response.id == "project-id-1"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ProjectTests:
|
|
100
|
+
def test_project_properties(self, project):
|
|
101
|
+
assert isinstance(project.id, str)
|
|
102
|
+
assert isinstance(project.name, str)
|
|
103
|
+
assert isinstance(project.owner_name, str)
|
|
104
|
+
assert isinstance(project.is_owner, bool)
|
|
105
|
+
|
|
106
|
+
def test_project_str(self, project):
|
|
107
|
+
string = str(project)
|
|
108
|
+
regex = r"^{.*\"id\": \"" + project.id + r"\".*}$"
|
|
109
|
+
assert isinstance(string, str)
|
|
110
|
+
assert re.match(regex, string)
|
|
111
|
+
|
|
112
|
+
def test_project_repr(self, project):
|
|
113
|
+
string = repr(project)
|
|
114
|
+
regex = r"^Project\( trainml , \*\*{.*'id': '" + project.id + r"'.*}\)$"
|
|
115
|
+
assert isinstance(string, str)
|
|
116
|
+
assert re.match(regex, string)
|
|
117
|
+
|
|
118
|
+
def test_project_bool(self, project, mock_trainml):
|
|
119
|
+
empty_project = specimen.Project(mock_trainml)
|
|
120
|
+
assert bool(project)
|
|
121
|
+
assert not bool(empty_project)
|
|
122
|
+
|
|
123
|
+
@mark.asyncio
|
|
124
|
+
async def test_project_remove(self, project, mock_trainml):
|
|
125
|
+
api_response = dict()
|
|
126
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
127
|
+
await project.remove()
|
|
128
|
+
mock_trainml._query.assert_called_once_with("/project/1", "DELETE")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import logging
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from unittest.mock import AsyncMock, patch, mock_open, MagicMock
|
|
6
|
+
from pytest import mark, fixture, raises
|
|
7
|
+
from aiohttp import WSMessage, WSMsgType
|
|
8
|
+
|
|
9
|
+
import trainml.auth as specimen
|
|
10
|
+
|
|
11
|
+
pytestmark = [mark.sdk, mark.unit]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@patch.dict(
|
|
15
|
+
os.environ,
|
|
16
|
+
{
|
|
17
|
+
"TRAINML_USER": "user-id",
|
|
18
|
+
"TRAINML_KEY": "key",
|
|
19
|
+
"TRAINML_REGION": "ap-east-1",
|
|
20
|
+
"TRAINML_CLIENT_ID": "client_id",
|
|
21
|
+
"TRAINML_POOL_ID": "pool_id",
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
def test_auth_from_envs():
|
|
25
|
+
auth = specimen.Auth(config_dir=os.path.expanduser("~/.trainml"))
|
|
26
|
+
assert auth.__dict__.get("username") == "user-id"
|
|
27
|
+
assert auth.__dict__.get("password") == "key"
|
|
28
|
+
assert auth.__dict__.get("region") == "ap-east-1"
|
|
29
|
+
assert auth.__dict__.get("client_id") == "client_id"
|
|
30
|
+
assert auth.__dict__.get("pool_id") == "pool_id"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import logging
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from unittest.mock import AsyncMock, patch, mock_open
|
|
6
|
+
from pytest import mark, fixture, raises
|
|
7
|
+
from aiohttp import WSMessage, WSMsgType
|
|
8
|
+
|
|
9
|
+
import trainml.trainml as specimen
|
|
10
|
+
|
|
11
|
+
pytestmark = [mark.sdk, mark.unit]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@patch.dict(
|
|
15
|
+
os.environ,
|
|
16
|
+
{
|
|
17
|
+
"TRAINML_USER": "user-id",
|
|
18
|
+
"TRAINML_KEY": "key",
|
|
19
|
+
"TRAINML_REGION": "region",
|
|
20
|
+
"TRAINML_CLIENT_ID": "client_id",
|
|
21
|
+
"TRAINML_POOL_ID": "pool_id",
|
|
22
|
+
"TRAINML_API_URL": "api.example.com",
|
|
23
|
+
"TRAINML_WS_URL": "api-ws.example.com",
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
def test_trainml_from_envs():
|
|
27
|
+
trainml = specimen.TrainML()
|
|
28
|
+
assert trainml.__dict__.get("api_url") == "api.example.com"
|
|
29
|
+
assert trainml.__dict__.get("ws_url") == "api-ws.example.com"
|
|
30
|
+
assert trainml.auth.__dict__.get("username") == "user-id"
|
|
31
|
+
assert trainml.auth.__dict__.get("password") == "key"
|
|
32
|
+
assert trainml.auth.__dict__.get("region") == "region"
|
|
33
|
+
assert trainml.auth.__dict__.get("client_id") == "client_id"
|
|
34
|
+
assert trainml.auth.__dict__.get("pool_id") == "pool_id"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_trainml_env_from_files():
|
|
38
|
+
with patch(
|
|
39
|
+
"trainml.trainml.open",
|
|
40
|
+
mock_open(
|
|
41
|
+
read_data=json.dumps(
|
|
42
|
+
dict(
|
|
43
|
+
region="region_file",
|
|
44
|
+
client_id="client_id_file",
|
|
45
|
+
pool_id="pool_id_file",
|
|
46
|
+
api_url="api.example.com_file",
|
|
47
|
+
ws_url="api-ws.example.com_file",
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
),
|
|
51
|
+
):
|
|
52
|
+
trainml = specimen.TrainML()
|
|
53
|
+
assert trainml.__dict__.get("api_url") == "api.example.com_file"
|
|
54
|
+
assert trainml.__dict__.get("ws_url") == "api-ws.example.com_file"
|
trainml/__init__.py
CHANGED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from trainml.cli import cli, pass_config, search_by_id_name
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@cli.group()
|
|
6
|
+
@pass_config
|
|
7
|
+
def project(config):
|
|
8
|
+
"""trainML project commands."""
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@project.command()
|
|
13
|
+
@pass_config
|
|
14
|
+
def list(config):
|
|
15
|
+
"""List projects."""
|
|
16
|
+
data = [
|
|
17
|
+
["ID", "NAME", "OWNER", "MINE"],
|
|
18
|
+
[
|
|
19
|
+
"-" * 80,
|
|
20
|
+
"-" * 80,
|
|
21
|
+
"-" * 80,
|
|
22
|
+
"-" * 80,
|
|
23
|
+
],
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
projects = config.trainml.run(config.trainml.client.projects.list())
|
|
27
|
+
|
|
28
|
+
for project in projects:
|
|
29
|
+
data.append(
|
|
30
|
+
[
|
|
31
|
+
project.id,
|
|
32
|
+
project.name,
|
|
33
|
+
project.owner_name,
|
|
34
|
+
"X" if project.is_owner else "",
|
|
35
|
+
]
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
for row in data:
|
|
39
|
+
click.echo(
|
|
40
|
+
"{: >38.36} {: >30.28} {: >15.13} {: >4.4}" "".format(*row),
|
|
41
|
+
file=config.stdout,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@project.command()
|
|
46
|
+
@click.argument("name", type=click.STRING)
|
|
47
|
+
@pass_config
|
|
48
|
+
def create(config, name):
|
|
49
|
+
"""
|
|
50
|
+
Create a project.
|
|
51
|
+
|
|
52
|
+
Project is created with the specified NAME.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
return config.trainml.run(
|
|
56
|
+
config.trainml.client.projects.create(
|
|
57
|
+
name=name,
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@project.command()
|
|
63
|
+
@click.argument("project", type=click.STRING)
|
|
64
|
+
@pass_config
|
|
65
|
+
def remove(config, project):
|
|
66
|
+
"""
|
|
67
|
+
Remove a project.
|
|
68
|
+
|
|
69
|
+
PROJECT may be specified by name or ID, but ID is preferred.
|
|
70
|
+
"""
|
|
71
|
+
projects = config.trainml.run(config.trainml.client.projects.list())
|
|
72
|
+
|
|
73
|
+
found = search_by_id_name(project, projects)
|
|
74
|
+
if None is found:
|
|
75
|
+
raise click.UsageError("Cannot find specified project.")
|
|
76
|
+
|
|
77
|
+
return config.trainml.run(found.remove())
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@project.command()
|
|
81
|
+
@pass_config
|
|
82
|
+
def list_datastores(config):
|
|
83
|
+
"""List project datastores."""
|
|
84
|
+
data = [
|
|
85
|
+
["ID", "NAME", "TYPE", "REGION_UUID"],
|
|
86
|
+
[
|
|
87
|
+
"-" * 80,
|
|
88
|
+
"-" * 80,
|
|
89
|
+
"-" * 80,
|
|
90
|
+
"-" * 80,
|
|
91
|
+
],
|
|
92
|
+
]
|
|
93
|
+
project = config.trainml.run(
|
|
94
|
+
config.trainml.client.projects.get(config.trainml.client.project)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
datastores = config.trainml.run(project.list_datastores())
|
|
98
|
+
|
|
99
|
+
for datastore in datastores:
|
|
100
|
+
data.append(
|
|
101
|
+
[
|
|
102
|
+
datastore.id,
|
|
103
|
+
datastore.name,
|
|
104
|
+
datastore.type,
|
|
105
|
+
datastore.region_uuid,
|
|
106
|
+
]
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
for row in data:
|
|
110
|
+
click.echo(
|
|
111
|
+
"{: >38.36} {: >30.28} {: >15.13} {: >38.36}" "".format(*row),
|
|
112
|
+
file=config.stdout,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@project.command()
|
|
117
|
+
@pass_config
|
|
118
|
+
def list_services(config):
|
|
119
|
+
"""List project services."""
|
|
120
|
+
data = [
|
|
121
|
+
["ID", "NAME", "HOSTNAME", "REGION_UUID"],
|
|
122
|
+
[
|
|
123
|
+
"-" * 80,
|
|
124
|
+
"-" * 80,
|
|
125
|
+
"-" * 80,
|
|
126
|
+
"-" * 80,
|
|
127
|
+
],
|
|
128
|
+
]
|
|
129
|
+
project = config.trainml.run(
|
|
130
|
+
config.trainml.client.projects.get(config.trainml.client.project)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
services = config.trainml.run(project.list_services())
|
|
134
|
+
|
|
135
|
+
for service in services:
|
|
136
|
+
data.append(
|
|
137
|
+
[
|
|
138
|
+
service.id,
|
|
139
|
+
service.name,
|
|
140
|
+
service.hostname,
|
|
141
|
+
service.region_uuid,
|
|
142
|
+
]
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
for row in data:
|
|
146
|
+
click.echo(
|
|
147
|
+
"{: >38.36} {: >30.28} {: >30.28} {: >38.36}" "".format(*row),
|
|
148
|
+
file=config.stdout,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
from trainml.cli.project.secret import secret
|
|
153
|
+
from trainml.cli.project.key import key
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import base64
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from trainml.cli import pass_config
|
|
7
|
+
from trainml.cli.project import project
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@project.group()
|
|
11
|
+
@pass_config
|
|
12
|
+
def key(config):
|
|
13
|
+
"""trainML project key commands."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@key.command()
|
|
18
|
+
@pass_config
|
|
19
|
+
def list(config):
|
|
20
|
+
"""List keys."""
|
|
21
|
+
data = [
|
|
22
|
+
["TYPE", "KEY ID", "UPDATED AT"],
|
|
23
|
+
[
|
|
24
|
+
"-" * 80,
|
|
25
|
+
"-" * 80,
|
|
26
|
+
"-" * 80,
|
|
27
|
+
],
|
|
28
|
+
]
|
|
29
|
+
project = config.trainml.run(config.trainml.client.projects.get_current())
|
|
30
|
+
keys = config.trainml.run(project.keys.list())
|
|
31
|
+
|
|
32
|
+
for key in keys:
|
|
33
|
+
data.append(
|
|
34
|
+
[
|
|
35
|
+
key.type,
|
|
36
|
+
key.key_id,
|
|
37
|
+
key.updated_at.isoformat(timespec="seconds"),
|
|
38
|
+
]
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
for row in data:
|
|
42
|
+
click.echo(
|
|
43
|
+
"{: >13.11} {: >37.35} {: >28.26}" "".format(*row),
|
|
44
|
+
file=config.stdout,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@key.command()
|
|
49
|
+
@click.argument(
|
|
50
|
+
"type",
|
|
51
|
+
type=click.Choice(
|
|
52
|
+
[
|
|
53
|
+
"aws",
|
|
54
|
+
"azure",
|
|
55
|
+
"docker",
|
|
56
|
+
"gcp",
|
|
57
|
+
"huggingface",
|
|
58
|
+
"kaggle",
|
|
59
|
+
"ngc",
|
|
60
|
+
"wasabi",
|
|
61
|
+
],
|
|
62
|
+
case_sensitive=False,
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
@pass_config
|
|
66
|
+
def put(config, type):
|
|
67
|
+
"""
|
|
68
|
+
Set a key.
|
|
69
|
+
|
|
70
|
+
A key is uploaded.
|
|
71
|
+
"""
|
|
72
|
+
project = config.trainml.run(config.trainml.client.projects.get_current())
|
|
73
|
+
|
|
74
|
+
tenant = None
|
|
75
|
+
|
|
76
|
+
if type in ["aws", "wasabi"]:
|
|
77
|
+
key_id = click.prompt("Enter the key ID", type=str, hide_input=False)
|
|
78
|
+
secret = click.prompt("Enter the secret key", type=str, hide_input=True)
|
|
79
|
+
elif type == "azure":
|
|
80
|
+
key_id = click.prompt(
|
|
81
|
+
"Enter the Application (client) ID", type=str, hide_input=False
|
|
82
|
+
)
|
|
83
|
+
tenant = click.prompt(
|
|
84
|
+
"Enter the Directory (tenant) ley", type=str, hide_input=False
|
|
85
|
+
)
|
|
86
|
+
secret = click.prompt("Enter the client secret", type=str, hide_input=True)
|
|
87
|
+
elif type in ["docker", "huggingface"]:
|
|
88
|
+
key_id = click.prompt("Enter the username", type=str, hide_input=False)
|
|
89
|
+
secret = click.prompt("Enter the access token", type=str, hide_input=True)
|
|
90
|
+
elif type in ["gcp", "kaggle"]:
|
|
91
|
+
file_name = click.prompt(
|
|
92
|
+
"Enter the path of the credentials file",
|
|
93
|
+
type=click.Path(
|
|
94
|
+
exists=True, file_okay=True, dir_okay=False, resolve_path=True
|
|
95
|
+
),
|
|
96
|
+
hide_input=False,
|
|
97
|
+
)
|
|
98
|
+
key_id = os.path.basename(file_name)
|
|
99
|
+
with open(file_name) as f:
|
|
100
|
+
secret = json.load(f)
|
|
101
|
+
secret = json.dumps(secret)
|
|
102
|
+
elif type == "ngc":
|
|
103
|
+
key_id = "$oauthtoken"
|
|
104
|
+
secret = click.prompt("Enter the access token", type=str, hide_input=True)
|
|
105
|
+
else:
|
|
106
|
+
raise click.UsageError("Unsupported key type")
|
|
107
|
+
|
|
108
|
+
return config.trainml.run(
|
|
109
|
+
project.keys.put(type=type, key_id=key_id, secret=secret, tenant=tenant)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@key.command()
|
|
114
|
+
@click.argument("name", type=click.STRING)
|
|
115
|
+
@pass_config
|
|
116
|
+
def remove(config, name):
|
|
117
|
+
"""
|
|
118
|
+
Remove a key.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
project = config.trainml.run(config.trainml.client.projects.get_current())
|
|
123
|
+
|
|
124
|
+
return config.trainml.run(project.key.remove(name))
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from trainml.cli import pass_config
|
|
3
|
+
from trainml.cli.project import project
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@project.group()
|
|
7
|
+
@pass_config
|
|
8
|
+
def secret(config):
|
|
9
|
+
"""trainML project secret commands."""
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@secret.command()
|
|
14
|
+
@pass_config
|
|
15
|
+
def list(config):
|
|
16
|
+
"""List secrets."""
|
|
17
|
+
data = [
|
|
18
|
+
["NAME", "CREATED BY", "UPDATED AT"],
|
|
19
|
+
[
|
|
20
|
+
"-" * 80,
|
|
21
|
+
"-" * 80,
|
|
22
|
+
"-" * 80,
|
|
23
|
+
],
|
|
24
|
+
]
|
|
25
|
+
project = config.trainml.run(config.trainml.client.projects.get_current())
|
|
26
|
+
secrets = config.trainml.run(project.secrets.list())
|
|
27
|
+
|
|
28
|
+
for secret in secrets:
|
|
29
|
+
data.append(
|
|
30
|
+
[
|
|
31
|
+
secret.name,
|
|
32
|
+
secret.created_by,
|
|
33
|
+
secret.updated_at.isoformat(timespec="seconds"),
|
|
34
|
+
]
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
for row in data:
|
|
38
|
+
click.echo(
|
|
39
|
+
"{: >38.36} {: >30.28} {: >28.26}" "".format(*row),
|
|
40
|
+
file=config.stdout,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@secret.command()
|
|
45
|
+
@click.argument("name", type=click.STRING)
|
|
46
|
+
@pass_config
|
|
47
|
+
def put(config, name):
|
|
48
|
+
"""
|
|
49
|
+
Set a secret value.
|
|
50
|
+
|
|
51
|
+
Secret is created with the specified NAME.
|
|
52
|
+
"""
|
|
53
|
+
project = config.trainml.run(config.trainml.client.projects.get_current())
|
|
54
|
+
|
|
55
|
+
value = click.prompt("Enter the secret value", type=str, hide_input=True)
|
|
56
|
+
|
|
57
|
+
return config.trainml.run(project.secrets.put(name=name, value=value))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@secret.command()
|
|
61
|
+
@click.argument("name", type=click.STRING)
|
|
62
|
+
@pass_config
|
|
63
|
+
def remove(config, name):
|
|
64
|
+
"""
|
|
65
|
+
Remove a secret.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
project = config.trainml.run(config.trainml.client.projects.get_current())
|
|
70
|
+
|
|
71
|
+
return config.trainml.run(project.secret.remove(name))
|