Skip to content

Commit

Permalink
Add auto-filling to PhApps (#78)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
qrtt1 authored Nov 25, 2021
1 parent 6196927 commit d1fceb1
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 36 deletions.
50 changes: 50 additions & 0 deletions docs/CLI/apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Available Commands:
logs Get logs of the PrimeHub Application by id
start Start the PrimeHub Application
stop Stop the PrimeHub Application
update Update an application
Options:
-h, --help Show the help
Expand Down Expand Up @@ -135,10 +136,59 @@ primehub apps stop <id>




### update

Update an application


```
primehub apps update <id>
```

* id: The id of PrimeHub application


* *(optional)* file: The file path of PrimeHub application configuration





## Examples

### Fields for creating or updating

| field | required | type | description |
| --- | --- | --- | --- |
| templateId | required | string | The id of a PhAppTemplate *only used with creating*|
| id | required* | string | The id of a PhApp *only used with creating* |
| displayName | required | string | |
| instanceType | required | string | |
| scope | required | string | one of `[public, primehub, group]` |
| env | optional | EnvVar[] | a list of EnvVar |

#### EnvVar

EnvVar is a dict with `name` and `value` with string values:

```json
{
"name": "my_var",
"value": "1"
}
```

### Auto-filling Fields

Auto-filling will happen when the inputs omitted fields

| field | value | comment |
| --- | --- | --- |
| id | {templateId}-{random-hex} | Generate a valid PhApp id from the templateId |

### Creating

The `create` action helps you to install a new PrimeHub application. It shows an example that can be used to create
a `code-server` application:

Expand Down
33 changes: 32 additions & 1 deletion docs/notebook/apps.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,38 @@
"id": "97735451",
"metadata": {},
"source": [
"## Examples"
"## Examples\n",
"\n",
"\n",
"### Fields for creating or updating\n",
"\n",
"| field | required | type | description |\n",
"| --- | --- | --- | --- |\n",
"| templateId | required | string | The id of a PhAppTemplate *only used with creating*|\n",
"| id | required* | string | The id of a PhApp *only used with creating* |\n",
"| displayName | required | string | |\n",
"| instanceType | required | string | |\n",
"| scope | required | string | one of `[public, primehub, group]` |\n",
"| env | optional | EnvVar[] | a list of EnvVar |\n",
"\n",
"#### EnvVar\n",
"\n",
"EnvVar is a dict with `name` and `value` with string values:\n",
"\n",
"```json\n",
"{\n",
" \"name\": \"my_var\",\n",
" \"value\": \"1\"\n",
"}\n",
"```\n",
"\n",
"### Auto-filling Fields\n",
"\n",
"Auto-filling will happen when the inputs omitted fields\n",
"\n",
"| field | value | comment |\n",
"| --- | --- | --- |\n",
"| id | {templateId}-{random-hex} | Generate a valid PhApp id from the templateId |"
]
},
{
Expand Down
122 changes: 97 additions & 25 deletions primehub/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

from primehub import Helpful, cmd, Module, primehub_load_config
from primehub.utils import resource_not_found, PrimeHubException
from primehub.utils.core import auto_gen_id
from primehub.utils.display import display_tree_like_format
from primehub.utils.optionals import toggle_flag, file_flag
from primehub.utils.validator import ValidationSpec

_query_ph_applications = query = """
query PhApplicationsConnection(
Expand Down Expand Up @@ -143,37 +145,107 @@ def create(self, config: dict):
raise PrimeHubException('config is required')
config['groupName'] = self.group_name

# verify required fields in the config
if 'instanceType' not in config:
invalid_config('instanceType is required')
if 'templateId' not in config:
invalid_config('templateId is required')
if 'displayName' not in config:
invalid_config('displayName is required')
if 'scope' not in config:
invalid_config('scope is required')
else:
if config['scope'] not in scope_list:
invalid_config('scope is invalid')

if 'env' in config:
if not isinstance(config['env'], list):
invalid_config('env should be a list of the name and value pair')
for x in config['env']:
if not isinstance(x, dict):
invalid_config('entry in the "env" should be a name and value pair')
valid = 'name' in x and 'value' in x
if not valid:
invalid_config('entry in the "env" should be a name and value pair')
if not isinstance(x['value'], str):
invalid_config(f'value in an entry must be the string type => {x}')
self.apply_auto_filling(config)
self.validate_creation(config)

self._verify_dependency(config)
results = self.request({'data': config}, query)
if 'data' in results:
return results['data']['createPhApplication']
return results

@cmd(name='update', description='Update an application', optionals=[('file', file_flag)])
def _update_cmd(self, id: str, **kwargs):
"""
Update a PrimeHub application
:type id: str
:param id: The id of PrimeHub application
:type file: str
:param file: The file path of PrimeHub application configuration
:rtype dict
:return The information of the PrimeHub application
"""

config = primehub_load_config(filename=kwargs.get('file', None))
if not config:
invalid_config('PrimeHub application configuration is required.')
return self.update(id, config)

def update(self, id: str, config: dict):
"""
Update a PrimeHub application
:type id: str
:param id: The id of PrimeHub application
:type config: dict
:param config: The file path of PrimeHub application configuration
:rtype dict
:return The information of the PrimeHub application
"""

self.validate_updating(config)
query = """
mutation UpdatePhApplication(
$where: PhApplicationWhereUniqueInput!
$data: PhApplicationUpdateInput!
) {
updatePhApplication(where: $where, data: $data) {
id
}
}
"""

# config without instanceType will make the API crash
if 'instanceType' not in config:
current = self.get(id)
if not current:
return None
config['instanceType'] = current['instanceType']

results = self.request({'data': config, 'where': {'id': id}}, query)
if 'data' in results:
return results['data']['updatePhApplication']
return results

def apply_auto_filling(self, config):
if 'id' not in config:
config['id'] = auto_gen_id(config['templateId'])
if 'env' not in config:
template = self.primehub.apptemplates.get(config['templateId'])
if isinstance(template['defaultEnvs'], list):
config['env'] = [{'name': x['name'], 'value': x['defaultValue']} for x in template['defaultEnvs']]

def validate_creation(self, config):
spec = ValidationSpec("""
input PhApplicationCreateInput {
templateId: String!
id: ID!
displayName: String!
groupName: String!
env: EnvList
instanceType: String!
scope: PhAppScope!
}
""")
spec.validate(config)
self._verify_dependency(config)

def validate_updating(self, config):
spec = ValidationSpec("""
input PhApplicationUpdateInput {
env: EnvList
instanceType: String
scope: PhAppScope
displayName: String
}
""")
spec.validate(config)
self._verify_dependency(config)

def _verify_dependency(self, config):
if 'instanceType' in config:
self.primehub.instancetypes.get(config['instanceType'])
Expand Down
2 changes: 1 addition & 1 deletion primehub/deployments.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from primehub.utils import resource_not_found, PrimeHubException
from primehub.utils.optionals import toggle_flag, file_flag
from primehub.utils.permission import ask_for_permission
from primehub.utils.auto_fill import auto_gen_id
from primehub.utils.core import auto_gen_id


def _error_handler(response):
Expand Down
34 changes: 33 additions & 1 deletion primehub/extras/templates/examples/apps.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
### Fields for creating or updating

| field | required | type | description |
| --- | --- | --- | --- |
| templateId | required | string | The id of a PhAppTemplate *only used with creating*|
| id | required* | string | The id of a PhApp *only used with creating* |
| displayName | required | string | |
| instanceType | required | string | |
| scope | required | string | one of `[public, primehub, group]` |
| env | optional | EnvVar[] | a list of EnvVar |

#### EnvVar

EnvVar is a dict with `name` and `value` with string values:

```json
{
"name": "my_var",
"value": "1"
}
```

### Auto-filling Fields

Auto-filling will happen when the inputs omitted fields

| field | value | comment |
| --- | --- | --- |
| id | {templateId}-{random-hex} | Generate a valid PhApp id from the templateId |

### Creating

The `create` action helps you to install a new PrimeHub application. It shows an example that can be used to create
a `code-server` application:

Expand Down Expand Up @@ -73,4 +105,4 @@ stop: False
status: Ready
message: Deployment is ready
pods: [{'logEndpoint': 'http://primehub-python-sdk.primehub.io/api/logs/pods/app-code-server-26fcc-765bf579c5-srcft'}]
```
```
8 changes: 0 additions & 8 deletions primehub/utils/auto_fill.py

This file was deleted.

8 changes: 8 additions & 0 deletions primehub/utils/core.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import random
import re
from collections import UserDict


Expand All @@ -20,3 +22,9 @@ def __contains__(self, item):
if has_item:
return True
return super(CommandContainer, self).__contains__(f':{item}')


def auto_gen_id(name: str):
normalized_name = re.sub(r'[\W_]', '-', name).lower()
random_string = str(float.hex(random.random()))[4:9]
return f'{normalized_name}-{random_string}'
39 changes: 39 additions & 0 deletions primehub/utils/validator.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import re
from typing import Union, Any, Optional, Dict, List

Expand Down Expand Up @@ -169,6 +170,44 @@ class OpJSON(OpBase):
def __init__(self):
super().__init__([dict])

class OpPhAppScope(OpBase):
def __init__(self):
super().__init__([str])
self.scope_list = ['public', 'primehub', 'group']

def validate(self, value):
return value in self.scope_list

def error_message(self, field: str):
return f'The value of the {field} should be one of [{", ".join(self.scope_list)}]'

class OpEnvList(OpBase):
def __init__(self):
super().__init__([list])

def validate(self, value):
if not isinstance(value, list):
return False
for entry in value:
if not isinstance(entry, dict):
return False
if sorted(entry.keys()) != sorted(['name', 'value']):
return False
if not isinstance(entry['value'], str):
return False
return True

def error_message(self, field: str):
example = [
dict(name="name", value="my-name"),
dict(name="int_value", value="1"),
dict(name="float_value", value="1.5"),
dict(name="bool_value", value="true"),
]
return f'The value of the {field} should be an EnvList. ' \
f'It is a list of {{name, value}} and all values MUST be a string.\n' \
f'For example: {json.dumps(example)}'

def __init__(self, type_def: str):
self.type_def = type_def
self.type_name = type_def.replace('!', '')
Expand Down

0 comments on commit d1fceb1

Please sign in to comment.