Skip to content

Commit d1fceb1

Browse files
authored
Add auto-filling to PhApps (#78)
* Add auto-filling to PhApps Signed-off-by: Ching Yi, Chan <[email protected]> * Work around the API crash when updating PhApp without instanceType Signed-off-by: Ching Yi, Chan <[email protected]> * Refactoring Signed-off-by: Ching Yi, Chan <[email protected]> * Check the default vars before using Signed-off-by: Ching Yi, Chan <[email protected]>
1 parent 6196927 commit d1fceb1

File tree

8 files changed

+260
-36
lines changed

8 files changed

+260
-36
lines changed

docs/CLI/apps.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Available Commands:
1515
logs Get logs of the PrimeHub Application by id
1616
start Start the PrimeHub Application
1717
stop Stop the PrimeHub Application
18+
update Update an application
1819
1920
Options:
2021
-h, --help Show the help
@@ -135,10 +136,59 @@ primehub apps stop <id>
135136

136137

137138

139+
140+
### update
141+
142+
Update an application
143+
144+
145+
```
146+
primehub apps update <id>
147+
```
148+
149+
* id: The id of PrimeHub application
150+
151+
152+
* *(optional)* file: The file path of PrimeHub application configuration
153+
154+
155+
138156

139157

140158
## Examples
141159

160+
### Fields for creating or updating
161+
162+
| field | required | type | description |
163+
| --- | --- | --- | --- |
164+
| templateId | required | string | The id of a PhAppTemplate *only used with creating*|
165+
| id | required* | string | The id of a PhApp *only used with creating* |
166+
| displayName | required | string | |
167+
| instanceType | required | string | |
168+
| scope | required | string | one of `[public, primehub, group]` |
169+
| env | optional | EnvVar[] | a list of EnvVar |
170+
171+
#### EnvVar
172+
173+
EnvVar is a dict with `name` and `value` with string values:
174+
175+
```json
176+
{
177+
"name": "my_var",
178+
"value": "1"
179+
}
180+
```
181+
182+
### Auto-filling Fields
183+
184+
Auto-filling will happen when the inputs omitted fields
185+
186+
| field | value | comment |
187+
| --- | --- | --- |
188+
| id | {templateId}-{random-hex} | Generate a valid PhApp id from the templateId |
189+
190+
### Creating
191+
142192
The `create` action helps you to install a new PrimeHub application. It shows an example that can be used to create
143193
a `code-server` application:
144194

docs/notebook/apps.ipynb

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,38 @@
6767
"id": "97735451",
6868
"metadata": {},
6969
"source": [
70-
"## Examples"
70+
"## Examples\n",
71+
"\n",
72+
"\n",
73+
"### Fields for creating or updating\n",
74+
"\n",
75+
"| field | required | type | description |\n",
76+
"| --- | --- | --- | --- |\n",
77+
"| templateId | required | string | The id of a PhAppTemplate *only used with creating*|\n",
78+
"| id | required* | string | The id of a PhApp *only used with creating* |\n",
79+
"| displayName | required | string | |\n",
80+
"| instanceType | required | string | |\n",
81+
"| scope | required | string | one of `[public, primehub, group]` |\n",
82+
"| env | optional | EnvVar[] | a list of EnvVar |\n",
83+
"\n",
84+
"#### EnvVar\n",
85+
"\n",
86+
"EnvVar is a dict with `name` and `value` with string values:\n",
87+
"\n",
88+
"```json\n",
89+
"{\n",
90+
" \"name\": \"my_var\",\n",
91+
" \"value\": \"1\"\n",
92+
"}\n",
93+
"```\n",
94+
"\n",
95+
"### Auto-filling Fields\n",
96+
"\n",
97+
"Auto-filling will happen when the inputs omitted fields\n",
98+
"\n",
99+
"| field | value | comment |\n",
100+
"| --- | --- | --- |\n",
101+
"| id | {templateId}-{random-hex} | Generate a valid PhApp id from the templateId |"
71102
]
72103
},
73104
{

primehub/apps.py

Lines changed: 97 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
from primehub import Helpful, cmd, Module, primehub_load_config
55
from primehub.utils import resource_not_found, PrimeHubException
6+
from primehub.utils.core import auto_gen_id
67
from primehub.utils.display import display_tree_like_format
78
from primehub.utils.optionals import toggle_flag, file_flag
9+
from primehub.utils.validator import ValidationSpec
810

911
_query_ph_applications = query = """
1012
query PhApplicationsConnection(
@@ -143,37 +145,107 @@ def create(self, config: dict):
143145
raise PrimeHubException('config is required')
144146
config['groupName'] = self.group_name
145147

146-
# verify required fields in the config
147-
if 'instanceType' not in config:
148-
invalid_config('instanceType is required')
149-
if 'templateId' not in config:
150-
invalid_config('templateId is required')
151-
if 'displayName' not in config:
152-
invalid_config('displayName is required')
153-
if 'scope' not in config:
154-
invalid_config('scope is required')
155-
else:
156-
if config['scope'] not in scope_list:
157-
invalid_config('scope is invalid')
158-
159-
if 'env' in config:
160-
if not isinstance(config['env'], list):
161-
invalid_config('env should be a list of the name and value pair')
162-
for x in config['env']:
163-
if not isinstance(x, dict):
164-
invalid_config('entry in the "env" should be a name and value pair')
165-
valid = 'name' in x and 'value' in x
166-
if not valid:
167-
invalid_config('entry in the "env" should be a name and value pair')
168-
if not isinstance(x['value'], str):
169-
invalid_config(f'value in an entry must be the string type => {x}')
148+
self.apply_auto_filling(config)
149+
self.validate_creation(config)
170150

171-
self._verify_dependency(config)
172151
results = self.request({'data': config}, query)
173152
if 'data' in results:
174153
return results['data']['createPhApplication']
175154
return results
176155

156+
@cmd(name='update', description='Update an application', optionals=[('file', file_flag)])
157+
def _update_cmd(self, id: str, **kwargs):
158+
"""
159+
Update a PrimeHub application
160+
161+
:type id: str
162+
:param id: The id of PrimeHub application
163+
164+
:type file: str
165+
:param file: The file path of PrimeHub application configuration
166+
167+
:rtype dict
168+
:return The information of the PrimeHub application
169+
"""
170+
171+
config = primehub_load_config(filename=kwargs.get('file', None))
172+
if not config:
173+
invalid_config('PrimeHub application configuration is required.')
174+
return self.update(id, config)
175+
176+
def update(self, id: str, config: dict):
177+
"""
178+
Update a PrimeHub application
179+
180+
:type id: str
181+
:param id: The id of PrimeHub application
182+
183+
:type config: dict
184+
:param config: The file path of PrimeHub application configuration
185+
186+
:rtype dict
187+
:return The information of the PrimeHub application
188+
"""
189+
190+
self.validate_updating(config)
191+
query = """
192+
mutation UpdatePhApplication(
193+
$where: PhApplicationWhereUniqueInput!
194+
$data: PhApplicationUpdateInput!
195+
) {
196+
updatePhApplication(where: $where, data: $data) {
197+
id
198+
}
199+
}
200+
"""
201+
202+
# config without instanceType will make the API crash
203+
if 'instanceType' not in config:
204+
current = self.get(id)
205+
if not current:
206+
return None
207+
config['instanceType'] = current['instanceType']
208+
209+
results = self.request({'data': config, 'where': {'id': id}}, query)
210+
if 'data' in results:
211+
return results['data']['updatePhApplication']
212+
return results
213+
214+
def apply_auto_filling(self, config):
215+
if 'id' not in config:
216+
config['id'] = auto_gen_id(config['templateId'])
217+
if 'env' not in config:
218+
template = self.primehub.apptemplates.get(config['templateId'])
219+
if isinstance(template['defaultEnvs'], list):
220+
config['env'] = [{'name': x['name'], 'value': x['defaultValue']} for x in template['defaultEnvs']]
221+
222+
def validate_creation(self, config):
223+
spec = ValidationSpec("""
224+
input PhApplicationCreateInput {
225+
templateId: String!
226+
id: ID!
227+
displayName: String!
228+
groupName: String!
229+
env: EnvList
230+
instanceType: String!
231+
scope: PhAppScope!
232+
}
233+
""")
234+
spec.validate(config)
235+
self._verify_dependency(config)
236+
237+
def validate_updating(self, config):
238+
spec = ValidationSpec("""
239+
input PhApplicationUpdateInput {
240+
env: EnvList
241+
instanceType: String
242+
scope: PhAppScope
243+
displayName: String
244+
}
245+
""")
246+
spec.validate(config)
247+
self._verify_dependency(config)
248+
177249
def _verify_dependency(self, config):
178250
if 'instanceType' in config:
179251
self.primehub.instancetypes.get(config['instanceType'])

primehub/deployments.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from primehub.utils import resource_not_found, PrimeHubException
77
from primehub.utils.optionals import toggle_flag, file_flag
88
from primehub.utils.permission import ask_for_permission
9-
from primehub.utils.auto_fill import auto_gen_id
9+
from primehub.utils.core import auto_gen_id
1010

1111

1212
def _error_handler(response):

primehub/extras/templates/examples/apps.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
1+
### Fields for creating or updating
2+
3+
| field | required | type | description |
4+
| --- | --- | --- | --- |
5+
| templateId | required | string | The id of a PhAppTemplate *only used with creating*|
6+
| id | required* | string | The id of a PhApp *only used with creating* |
7+
| displayName | required | string | |
8+
| instanceType | required | string | |
9+
| scope | required | string | one of `[public, primehub, group]` |
10+
| env | optional | EnvVar[] | a list of EnvVar |
11+
12+
#### EnvVar
13+
14+
EnvVar is a dict with `name` and `value` with string values:
15+
16+
```json
17+
{
18+
"name": "my_var",
19+
"value": "1"
20+
}
21+
```
22+
23+
### Auto-filling Fields
24+
25+
Auto-filling will happen when the inputs omitted fields
26+
27+
| field | value | comment |
28+
| --- | --- | --- |
29+
| id | {templateId}-{random-hex} | Generate a valid PhApp id from the templateId |
30+
31+
### Creating
32+
133
The `create` action helps you to install a new PrimeHub application. It shows an example that can be used to create
234
a `code-server` application:
335

@@ -73,4 +105,4 @@ stop: False
73105
status: Ready
74106
message: Deployment is ready
75107
pods: [{'logEndpoint': 'http://primehub-python-sdk.primehub.io/api/logs/pods/app-code-server-26fcc-765bf579c5-srcft'}]
76-
```
108+
```

primehub/utils/auto_fill.py

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

primehub/utils/core.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import random
2+
import re
13
from collections import UserDict
24

35

@@ -20,3 +22,9 @@ def __contains__(self, item):
2022
if has_item:
2123
return True
2224
return super(CommandContainer, self).__contains__(f':{item}')
25+
26+
27+
def auto_gen_id(name: str):
28+
normalized_name = re.sub(r'[\W_]', '-', name).lower()
29+
random_string = str(float.hex(random.random()))[4:9]
30+
return f'{normalized_name}-{random_string}'

primehub/utils/validator.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import re
23
from typing import Union, Any, Optional, Dict, List
34

@@ -169,6 +170,44 @@ class OpJSON(OpBase):
169170
def __init__(self):
170171
super().__init__([dict])
171172

173+
class OpPhAppScope(OpBase):
174+
def __init__(self):
175+
super().__init__([str])
176+
self.scope_list = ['public', 'primehub', 'group']
177+
178+
def validate(self, value):
179+
return value in self.scope_list
180+
181+
def error_message(self, field: str):
182+
return f'The value of the {field} should be one of [{", ".join(self.scope_list)}]'
183+
184+
class OpEnvList(OpBase):
185+
def __init__(self):
186+
super().__init__([list])
187+
188+
def validate(self, value):
189+
if not isinstance(value, list):
190+
return False
191+
for entry in value:
192+
if not isinstance(entry, dict):
193+
return False
194+
if sorted(entry.keys()) != sorted(['name', 'value']):
195+
return False
196+
if not isinstance(entry['value'], str):
197+
return False
198+
return True
199+
200+
def error_message(self, field: str):
201+
example = [
202+
dict(name="name", value="my-name"),
203+
dict(name="int_value", value="1"),
204+
dict(name="float_value", value="1.5"),
205+
dict(name="bool_value", value="true"),
206+
]
207+
return f'The value of the {field} should be an EnvList. ' \
208+
f'It is a list of {{name, value}} and all values MUST be a string.\n' \
209+
f'For example: {json.dumps(example)}'
210+
172211
def __init__(self, type_def: str):
173212
self.type_def = type_def
174213
self.type_name = type_def.replace('!', '')

0 commit comments

Comments
 (0)