Skip to content
This repository was archived by the owner on Mar 17, 2024. It is now read-only.

Commit 8c6a86a

Browse files
authored
Merge pull request #171 from scaleoutsystems/release/v0.3.0
release/v0.3.0
2 parents c1c7267 + 6f3e5ab commit 8c6a86a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1932
-645
lines changed

.github/workflows/publish-docs.yaml

Lines changed: 0 additions & 16 deletions
This file was deleted.

README.md

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,19 +125,6 @@ You can lint or check the deployment with the flags —dry-run —debug.
125125

126126
Make sure to assign the chart to the right namespace with —namespace yournamespace (when deploying to the default namespace this can be omitted.)
127127

128-
### 5. Setup a user
129-
130-
You will need to create a user to login into Studio. Click the login button in the lower left corner, and click register. By default, Keycloak is configured not to require email verification, but this can be changed by logging into the Keycloak admin console and updating the STACKn realm login settings.
131-
132-
To access the admin page of Studio, you will need to create a Django user with admin rights. First find the pod name to the Studio deployment:
133-
```bash
134-
$ kubectl get pods -n yournamespace
135-
```
136-
and get the pod id that correspond to the studio pod running. Replace `pod-Id` in the command below.
137-
```bash
138-
$ kubectl exec -it pod-Id python manage.py createsuperuser
139-
```
140-
141128
### Additional - Upgrading STACKn
142129

143130
Similar to how you install a chart you may also upgrade a chart.
@@ -175,4 +162,4 @@ STACKn is used in various places, examples include [SciLifeLab Data Center](http
175162
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
176163

177164
## License
178-
> See [LICENSE](LICENCE.md) for details.
165+
> See [LICENSE](LICENSE) for details.

cli/scaleout/auth.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,21 +142,29 @@ def get_token(client_id='studio-api', realm='STACKn', secure=True):
142142
else:
143143
print('Failed to authenticate with token, please login again.')
144144
print(res.text)
145-
access_token = login()
145+
access_token = login(deployment=stackn_config['active'], keycloak_host=token_config['keycloak_url'], studio_host=token_config['studio_url'], secure=secure)
146146

147147
return access_token, token_config
148148

149149

150150

151-
def login(client_id='studio-api', realm='STACKn', deployment=[], keycloak_host=[], studio_host=[], secure=True):
151+
def login(client_id='studio-api', realm='STACKn', deployment=[], keycloak_host=[], studio_host=[], username=[], secure=True):
152152
""" Login to Studio services. """
153153
if not deployment:
154154
deployment = input('Name: ')
155-
if not keycloak_host:
156-
keycloak_host = input('Keycloak host: ')
157155
if not studio_host:
158156
studio_host = input('Studio host: ')
159-
username = input('Username: ')
157+
158+
url = "{}/api/settings".format(studio_host)
159+
r = requests.get(url)
160+
if (r.status_code >= 200 or r.status_code <= 299):
161+
studio_settings = json.loads(r.content)["data"]
162+
keycloak_host = next(item for item in studio_settings if item["name"] == "keycloak_host")["value"]
163+
164+
if not keycloak_host:
165+
keycloak_host = input('Keycloak host: ')
166+
if not username:
167+
username = input('Username: ')
160168
password = getpass()
161169
access_token, refresh_token, public_key = keycloak_user_auth(username, password, keycloak_host, secure=secure)
162170
# dirname = base64.urlsafe_b64encode(host.encode("utf-8")).decode("utf-8")

cli/scaleout/cli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from .create_cmd import create_cmd
55
from .get_cmd import get_cmd
66
from .delete_cmd import delete_cmd
7-
from .stackn_cmd import setup_cmd, status_cmd, predict_cmd
7+
from .stackn_cmd import setup_cmd, status_cmd, predict_cmd, train_cmd, test_cmd
88
from .set_cmd import set_cmd
99
from .update_cmd import update_cmd
1010
from .init_cmd import init_cmd

cli/scaleout/cli/create_cmd.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,26 @@ def create_project_cmd(ctx, name, description='', repository=''):
6363
@create_cmd.command('lab')
6464
@click.option('-f', '--flavor', required=True)
6565
@click.option('-e', '--environment', required=True)
66+
@click.option('-v', '--volumes', required=False, default=[])
6667
@click.pass_context
67-
def create_session(ctx, flavor, environment):
68+
def create_session(ctx, flavor, environment, volumes):
6869
client = ctx.obj['CLIENT']
69-
client.create_session(flavor_slug=flavor, environment_slug=environment)
70+
client.create_session(flavor_slug=flavor, environment_slug=environment, volumes=volumes)
7071

72+
@create_cmd.command('volume')
73+
@click.option('-s', '--size', required=True)
74+
@click.option('-n', '--name', required=True)
75+
@click.pass_context
76+
def create_volume(ctx, size, name):
77+
client = ctx.obj['CLIENT']
78+
client.create_volume(name=name, size=size)
79+
80+
@create_cmd.command('job')
81+
@click.option('-c', '--config', required=True)
82+
@click.pass_context
83+
def create_job(ctx, config):
84+
client = ctx.obj['CLIENT']
85+
client.create_job(config)
7186

7287
# Create dataset
7388

@@ -85,4 +100,4 @@ def create_dataset(ctx, name, directory=[], filenames=[], release_type='minor',
85100
filenames,
86101
directory,
87102
description=description,
88-
bucket=bucket)
103+
bucket=bucket)
1.24 KB
Binary file not shown.

cli/scaleout/cli/delete_cmd.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ def delete_dataset_cmd(ctx, name, version=None):
4444
client = ctx.obj['CLIENT']
4545
client.delete_dataset(name, version)
4646

47+
@delete_cmd.command('volume')
48+
@click.option('-n', '--name', required=True)
49+
@click.pass_context
50+
def delete_volume_cmd(ctx, name):
51+
""" Delete a volume """
52+
client = ctx.obj['CLIENT']
53+
client.delete_volume(name)
54+
4755
# @delete_cmd.command('deployments')
4856
# @click.pass_context
4957
# def delete_deployment_cmd(ctx):

cli/scaleout/cli/get_cmd.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import json
12
import click
23
from .main import main
34
import requests
4-
from scaleout.studioclient import StudioClient
5-
from .helpers import create_table
5+
from .helpers import create_table, PrettyTable
66

77
@click.option('--daemon',
88
is_flag=True,
@@ -17,6 +17,32 @@ def get_cmd(ctx, daemon):
1717
if daemon:
1818
print('{} NYI should run as daemon...'.format(__file__))
1919

20+
@get_cmd.command('settings')
21+
@click.pass_context
22+
def get_settings_cmd(ctx):
23+
"""
24+
List STACKn settings needed to set up the CLI client.
25+
"""
26+
studio_host = input("Studio host: ")
27+
url = "{}/api/settings".format(studio_host)
28+
try:
29+
r = requests.get(url)
30+
studio_settings = json.loads(r.content)["data"]
31+
32+
names = ['Setting', 'Value']
33+
keys = ['name', 'value']
34+
x = PrettyTable()
35+
x.field_names = names
36+
for item in studio_settings:
37+
row = [item[k] for k in keys]
38+
x.add_row(row)
39+
print(x)
40+
except Exception as e:
41+
print("Couldn't get studio settings.")
42+
print("Returned status code: {}".format(r.status_code))
43+
print("Reason: {}".format(r.reason))
44+
print("Error: {}".format(e))
45+
2046
@get_cmd.command('models')
2147
@click.pass_context
2248
def get_models_cmd(ctx):
@@ -44,6 +70,7 @@ def get_deploymentdefinitions_cmd(ctx):
4470
@get_cmd.command('projects')
4571
@click.pass_context
4672
def get_projects_cmd(ctx):
73+
""" List all projects. """
4774
names = ["Name","Created", "Last updated"]
4875
keys = ["name", "created_at", "updated_at"]
4976
create_table(ctx, "projects", names, keys)
@@ -56,6 +83,22 @@ def lab_list_all_cmd(ctx):
5683
keys = ["name", "flavor_slug", "environment_slug", "status", "created_at"]
5784
create_table(ctx, "labs", names, keys)
5885

86+
@get_cmd.command('volumes')
87+
@click.pass_context
88+
def get_volumes_cmd(ctx):
89+
""" List all volumes """
90+
names = ["Name","Size", "Created by","Created"]
91+
keys = ['name', 'size', 'created_by', 'created_on']
92+
create_table(ctx, 'volumes', names, keys)
93+
94+
@get_cmd.command('jobs')
95+
@click.pass_context
96+
def get_jobs_cmd(ctx):
97+
""" List all jobs """
98+
names = ["User","command", "Environment","Schedule"]
99+
keys = ['username', 'command', 'environment', 'schedule']
100+
create_table(ctx, 'jobs', names, keys)
101+
59102
@get_cmd.command('members')
60103
@click.pass_context
61104
def members_list_cmd(ctx):

cli/scaleout/cli/helpers.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import sys
22
from prettytable import PrettyTable
3+
import click
4+
import uuid
35

46

57
def prompt(question, default="yes"):
@@ -45,4 +47,47 @@ def _print_table(resource, names, keys):
4547
def create_table(ctx, resource, names, keys):
4648
client = ctx.obj['CLIENT']
4749
objects = client.create_list(resource)
48-
_print_table(objects, names, keys)
50+
_print_table(objects, names, keys)
51+
52+
def search_for_model(ctx, resource, name):
53+
client = ctx.obj['CLIENT']
54+
objects = client.create_list(resource)
55+
model_exists = False
56+
for item in objects:
57+
if item['name'] == name:
58+
model_exists = True
59+
return model_exists
60+
61+
def new_id(run_id):
62+
new_id = input("A log object with ID = {} already exists in 'src/models/tracking' directory. \n".format(run_id) \
63+
+ "Please provide a unique ID for the current run or press enter to use a randomly generated ID: ")
64+
if new_id:
65+
confirmed = False
66+
question = "Do you want to assign this training run with the ID '{}'?".format(new_id)
67+
while not confirmed:
68+
confirmed = prompt(question)
69+
if confirmed:
70+
return new_id
71+
else:
72+
new_id = input("Assign a new unique ID or press enter to assign a random ID: ")
73+
print(new_id)
74+
if not new_id:
75+
break
76+
new_id = str(uuid.uuid1().hex)
77+
return new_id
78+
79+
class Determinant(click.Option):
80+
def __init__(self, *args, **kwargs):
81+
self.determinant = kwargs.pop('determinant')
82+
assert self.determinant, "'determinant' parameter required"
83+
super(Determinant, self).__init__(*args, **kwargs)
84+
85+
def handle_parse_result(self, ctx, opts, args):
86+
unallowed_present = self.name in opts
87+
determinant_present = self.determinant in opts
88+
if determinant_present:
89+
if unallowed_present:
90+
raise click.UsageError("Illegal usage: Cannot pass a value for '{}' together with '{}' when running 'stackn train'".format(self.name, self.determinant))
91+
else:
92+
self.prompt = None
93+
return super(Determinant, self).handle_parse_result(ctx, opts, args)

cli/scaleout/cli/stackn_cmd.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22
from .main import main
33
import requests
44
from scaleout.auth import login, get_stackn_config, get_remote_config, get_token
5-
from .helpers import create_table
5+
from .helpers import create_table, search_for_model, new_id, Determinant
6+
import os
7+
import random
8+
import string
9+
import json
10+
import uuid
11+
from scaleout.details import get_run_details
612

713
# @click.option('--daemon',
814
# is_flag=True,
@@ -57,3 +63,45 @@ def predict_cmd(ctx, model, version, inp):
5763
# res = requests.post(url,
5864
# headers={"Authorization": "Token "+token},
5965
# json = inp)
66+
67+
68+
# ------------------- Question ---------------------
69+
# Is it a good idea to make it possible to pass --log-off as an argument if the user does not want to log the run to Studio?
70+
# In that case, the model name and code version is not possible to pass to the stackn train command and train.py will run without logging.
71+
# Not sure if this is a good idea
72+
# --------------------------------------------------
73+
@main.command('train')
74+
@click.option('--log-off', flag_value='log-off', default=False)
75+
@click.option('-m', '--model', prompt=True, cls=Determinant, determinant='log_off')
76+
@click.option('-i', '--run-id', required=False, default=str(uuid.uuid1().hex))
77+
@click.option('-f', '--training-file', required=False, default="src/models/train.py")
78+
@click.option('-v', '--version', prompt=True, cls=Determinant, determinant='log_off')
79+
@click.pass_context
80+
def train_cmd(ctx, log_off, model, run_id, training_file, version):
81+
""" Train a model and log metadata """
82+
83+
if os.path.isfile('src/models/tracking/metadata/{}.pkl'.format(run_id)): # Only checks locally. Should we check if there exists a log on Studio with the same ID as well?
84+
run_id = new_id(run_id)
85+
print("Preparing to start training session with '{}' as unique ID.".format(run_id))
86+
if os.path.isfile(training_file):
87+
if log_off:
88+
import subprocess
89+
subprocess.run(['python', training_file, run_id])
90+
else:
91+
model_exists = search_for_model(ctx, "models", model)
92+
if model_exists:
93+
client = ctx.obj['CLIENT']
94+
client.train(model, run_id, training_file, version)
95+
else:
96+
print("The model '{}' does not exist in the active project and cannot be trained.".format(model))
97+
else:
98+
current_dir = os.getcwd()
99+
print("Could not start a training session. Check that you have initialized a model "\
100+
+ "in '{}' and that the file '{}' exists.".format(current_dir, training_file))
101+
102+
103+
104+
@main.command('test')
105+
@click.pass_context
106+
def test_cmd(ctx):
107+
get_run_details('12')

0 commit comments

Comments
 (0)