Skip to content

Commit 31505ee

Browse files
JPinkneysleshchenko
authored andcommitted
Introduce github action for creating typescript models automatically
from crds Signed-off-by: Josh Pinkney <[email protected]>
1 parent 3ed7181 commit 31505ee

File tree

3 files changed

+332
-0
lines changed

3 files changed

+332
-0
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import os
2+
import json
3+
import yaml
4+
import requests
5+
from collections import OrderedDict
6+
7+
def write_json(filename: str, object: dict) -> None:
8+
"""
9+
Write the json object to the specified filename
10+
"""
11+
with open(filename, 'w') as out:
12+
json.dump(object, out, sort_keys=False, indent=2, separators=(',', ': '), ensure_ascii=True)
13+
14+
def create_ref(path):
15+
"""
16+
Create a json definition reference to a specific path
17+
"""
18+
return '#/definitions/' + path
19+
20+
def consolidate_crds() -> object:
21+
"""
22+
Consolidate all crds in /crds into one json object
23+
"""
24+
crds_dir = os.path.join('crds')
25+
crds = os.listdir(crds_dir)
26+
consolidated_crds_json = {
27+
'definitions': {},
28+
}
29+
for file in crds:
30+
crd_file_path = os.path.join(crds_dir, file)
31+
with open(crd_file_path) as file:
32+
yamlData = yaml.load(file, Loader=yaml.FullLoader)
33+
crd_name = yamlData['spec']['names']['kind']
34+
35+
# Add all the available schema versions
36+
for version in yamlData['spec']['versions']:
37+
new_json_name = version['name'] + '.' + crd_name
38+
new_schema = version['schema']['openAPIV3Schema']
39+
consolidated_crds_json['definitions'][new_json_name] = new_schema
40+
41+
return consolidated_crds_json
42+
43+
def add_property_definition(root_definitions_object: dict, current_path: str, curr_object: dict, queue: list) -> None:
44+
"""
45+
Given an object, convert the child properties into references with new definitions at the root of root_definitions_object.
46+
Also removes oneOf references since they aren't supported by openapi-generator.
47+
48+
Converts:
49+
{
50+
"properties": {
51+
"foo": {
52+
"type": "object",
53+
"properties": {
54+
"bar": {
55+
"type": "string"
56+
}
57+
}
58+
}
59+
}
60+
}
61+
62+
into:
63+
{
64+
"definitions": {
65+
"foo": {
66+
"type": "object",
67+
"properties": {
68+
"bar": {
69+
"$ref": "#/definitions/bar"
70+
}
71+
}
72+
},
73+
"bar": {
74+
"type": string
75+
}
76+
}
77+
}
78+
"""
79+
for prop in curr_object['properties']:
80+
new_path = current_path + '.' + prop
81+
new_object = curr_object['properties'][prop]
82+
83+
# openapi-generator doesn't accept oneOf so we have to remove them
84+
if 'oneOf' in new_object:
85+
del new_object['oneOf']
86+
87+
root_definitions_object[new_path] = new_object
88+
89+
# openapi-generator doesn't accept oneOf so we have to remove them
90+
if 'items' in new_object:
91+
if 'oneOf' in new_object['items']:
92+
del new_object['items']['oneOf']
93+
new_path += ".items"
94+
95+
queue.append({
96+
new_path: new_object
97+
})
98+
curr_object['properties'][prop] = {
99+
'$ref': create_ref(new_path)
100+
}
101+
102+
def add_item_definition(root_definitions_object: dict, current_path: str, curr_object: dict, queue: list) -> None:
103+
"""
104+
Given an object, convert the child properties into references with new definitions at the root of root_definitions_object.
105+
Also removes oneOf references since they aren't supported by openapi-generator.
106+
107+
Converts:
108+
{
109+
"v1devworkspace": {
110+
"properties": {
111+
"spec": {
112+
"items": {
113+
"type": "object",
114+
"properties": {
115+
"foo": {
116+
"type": "string",
117+
"description": "Type of funding or platform through which funding is possible."
118+
},
119+
}
120+
},
121+
"type": "array"
122+
}
123+
}
124+
125+
}
126+
}
127+
128+
into:
129+
{
130+
"definitions": {
131+
"v1devworkspace": {
132+
"properties": {
133+
"spec": {
134+
"$ref": "#/definitions/v1devworkspace.spec.items"
135+
}
136+
}
137+
},
138+
"v1devworkspace.spec.items": {
139+
"items": {
140+
"$ref": "#/definitions/v1devworkspace.spec"
141+
},
142+
"type": "array"
143+
},
144+
"v1devworkspace.spec": {
145+
"type": "object",
146+
"properties": {
147+
"foo": {
148+
"$ref": "#/definitions/v1devworkspace.spec.items.foo"
149+
},
150+
}
151+
}
152+
"v1devworkspace.spec.items.foo": {
153+
"type": "string",
154+
"description": "Type of funding or platform through which funding is possible."
155+
},
156+
}
157+
}
158+
"""
159+
if 'properties' in curr_object['items']:
160+
root_definitions_object[current_path] = curr_object
161+
162+
path = current_path
163+
pathList = current_path.split('.')
164+
if pathList[-1] == 'items':
165+
pathList = pathList[:-1]
166+
path = '.'.join(pathList)
167+
168+
for prop in curr_object['items']['properties']:
169+
new_path = current_path + '.' + prop
170+
new_object = curr_object['items']['properties'][prop]
171+
172+
# openapi-generator doesn't accept oneOf so we have to remove them
173+
if 'oneOf' in new_object:
174+
del new_object['oneOf']
175+
root_definitions_object[new_path] = new_object
176+
queue.append({
177+
new_path: new_object
178+
})
179+
curr_object['items']['properties'][prop] = {
180+
'$ref': create_ref(new_path)
181+
}
182+
root_definitions_object[path] = curr_object['items']
183+
curr_object['items'] = {
184+
'$ref': create_ref(path)
185+
}
186+
else:
187+
root_definitions_object[current_path] = curr_object
188+
189+
def add_definition(root_definitions_object: dict, current_path: str, curr_object: dict, queue: list) -> None:
190+
"""
191+
Create a property or item definition depending on if property or items is in the current_object
192+
"""
193+
if 'properties' in curr_object:
194+
add_property_definition(root_definitions_object, current_path, curr_object, queue)
195+
elif 'items' in curr_object:
196+
add_item_definition(root_definitions_object, current_path, curr_object, queue)
197+
198+
def flatten(consolidated_crds_object: dict) -> None:
199+
"""
200+
Flatten and then produce a new swagger.json file that can be processed by open-api-generator
201+
"""
202+
original_definitions = consolidated_crds_object['definitions']
203+
flattened_swagger_object = {
204+
'definitions': {},
205+
'paths': {},
206+
'info': {
207+
'title': 'Kubernetes',
208+
'version': 'unversioned'
209+
},
210+
'swagger': '2.0'
211+
}
212+
for root in original_definitions:
213+
flattened_swagger_object['definitions'][root] = original_definitions[root]
214+
215+
queue = []
216+
217+
# Add in all the initial properties to the queue
218+
for prop in original_definitions[root]['properties']:
219+
new_path = root + '.' + prop
220+
queue.append({
221+
new_path: original_definitions[root]['properties'][prop]
222+
})
223+
224+
# Create a new definition so that the properties are pulled out correctly
225+
flattened_swagger_object['definitions'][new_path] = original_definitions[root]['properties'][prop]
226+
227+
# Create a ref from the property such as spec to the new path such as v1alpha1.devworkspaces.workspace.devfile.io_spec
228+
original_definitions[root]['properties'][prop] = {
229+
'$ref': create_ref(new_path)
230+
}
231+
232+
# Continue until all properties have been flattened
233+
while len(queue) != 0:
234+
next_item = queue.pop().popitem()
235+
path = next_item[0]
236+
new_object = next_item[1]
237+
add_definition(flattened_swagger_object['definitions'], path, new_object, queue)
238+
239+
write_json('swagger.json', flattened_swagger_object)
240+
241+
if __name__ == "__main__":
242+
swagger_crds_json = consolidate_crds()
243+
flatten(swagger_crds_json)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests==2.24.0
2+
PyYAML==5.4.1
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
2+
# Release a typescript package to npm containing the typescript types generated from the latest merged crds
3+
name: types
4+
5+
on:
6+
push:
7+
branches: [ master ]
8+
9+
jobs:
10+
release-typescript-models:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout devfile/api
14+
uses: actions/checkout@v2
15+
with:
16+
path: api
17+
18+
- name: Checkout kubernetes-client/gen
19+
uses: actions/checkout@v2
20+
with:
21+
repository: kubernetes-client/gen
22+
path: gen
23+
ref: 5c6d90b260fd94af32157f304f971778c899b5e2
24+
25+
- name: Setup python
26+
uses: actions/setup-python@v2
27+
with:
28+
python-version: '3.9.2'
29+
30+
- name: Install Python dependencies
31+
uses: py-actions/py-dependency-install@v2
32+
with:
33+
path: "api/.github/actions/generate_types/requirements.txt"
34+
35+
- name: Generate openapi-generator compatible swagger.json
36+
run: |
37+
python .github/actions/generate_types/generate.py
38+
mkdir -p /tmp/typescript-models
39+
mv swagger.json /tmp/typescript-models/swagger.json.unprocessed
40+
working-directory: api
41+
42+
- name: Create empty client-gen configuration
43+
run: |
44+
{
45+
echo 'export KUBERNETES_BRANCH=""'
46+
echo 'export CLIENT_VERSION=""'
47+
echo 'export PACKAGE_NAME=""'
48+
echo 'export USERNAME=""'
49+
echo 'export REPOSITORY=""'
50+
} >> config.sh
51+
working-directory: /tmp
52+
53+
- name: Generate the typescript models
54+
run: |
55+
# Remove the contents of custom objects spec so that we aren't bundling any extra objects
56+
echo "{}" > custom_objects_spec.json
57+
export OPENAPI_SKIP_FETCH_SPEC=true
58+
./typescript.sh /tmp/typescript-models /tmp/config.sh
59+
working-directory: gen/openapi
60+
61+
- name: Modify package.json
62+
run: |
63+
sed -i 's/\"name\": \".*\"/"name": "@devfile\/typescript-types"/g' /tmp/typescript-models/package.json
64+
sed -i 's/\"description\": \".*\"/"description": "Typescript types for devfile api"/g' /tmp/typescript-models/package.json
65+
sed -i 's/\"repository\": \".*\"/"repository": "devfile\/api"/g' /tmp/typescript-models/package.json
66+
67+
- name: Setup node
68+
uses: actions/setup-node@v1
69+
with:
70+
node-version: 12
71+
registry-url: 'https://registry.npmjs.org'
72+
73+
- name: Install dependencies
74+
run: yarn
75+
working-directory: /tmp/typescript-models
76+
77+
- name: Run build
78+
run: yarn build
79+
working-directory: /tmp/typescript-models
80+
81+
- name: Release typescript models
82+
run: |
83+
yarn --new-version version "0.0.1-$(date +%s)"
84+
yarn publish --access public
85+
env:
86+
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
87+
working-directory: /tmp/typescript-models

0 commit comments

Comments
 (0)