mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-07 18:14:10 +00:00
162 lines
5.2 KiB
Python
162 lines
5.2 KiB
Python
"""
|
|
This script updates two files with the RBAC scope descriptions found in
|
|
`scopes.py`.
|
|
|
|
The files are:
|
|
|
|
1. scope-table.md
|
|
|
|
This file is git ignored and referenced by the documentation.
|
|
|
|
2. rest-api.yml
|
|
|
|
This file is JupyterHub's REST API schema. Both a version and the RBAC
|
|
scopes descriptions are updated in it.
|
|
"""
|
|
import os
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
from subprocess import run
|
|
|
|
from pytablewriter import MarkdownTableWriter
|
|
from ruamel.yaml import YAML
|
|
|
|
from jupyterhub import __version__
|
|
from jupyterhub.scopes import scope_definitions
|
|
|
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
|
DOCS = Path(HERE).parent.parent.absolute()
|
|
REST_API_YAML = DOCS.joinpath("source", "_static", "rest-api.yml")
|
|
SCOPE_TABLE_MD = Path(HERE).joinpath("scope-table.md")
|
|
|
|
|
|
class ScopeTableGenerator:
|
|
def __init__(self):
|
|
self.scopes = scope_definitions
|
|
|
|
@classmethod
|
|
def create_writer(cls, table_name, headers, values):
|
|
writer = MarkdownTableWriter()
|
|
writer.table_name = table_name
|
|
writer.headers = headers
|
|
writer.value_matrix = values
|
|
writer.margin = 1
|
|
return writer
|
|
|
|
def _get_scope_relationships(self):
|
|
"""Returns a tuple of dictionary of all scope-subscope pairs and a list of just subscopes:
|
|
|
|
({scope: subscope}, [subscopes])
|
|
|
|
used for creating hierarchical scope table in _parse_scopes()
|
|
"""
|
|
pairs = []
|
|
for scope, data in self.scopes.items():
|
|
subscopes = data.get('subscopes')
|
|
if subscopes is not None:
|
|
for subscope in subscopes:
|
|
pairs.append((scope, subscope))
|
|
else:
|
|
pairs.append((scope, None))
|
|
subscopes = [pair[1] for pair in pairs]
|
|
pairs_dict = defaultdict(list)
|
|
for scope, subscope in pairs:
|
|
pairs_dict[scope].append(subscope)
|
|
return pairs_dict, subscopes
|
|
|
|
def _get_top_scopes(self, subscopes):
|
|
"""Returns a list of highest level scopes
|
|
(not a subscope of any other scopes)"""
|
|
top_scopes = []
|
|
for scope in self.scopes.keys():
|
|
if scope not in subscopes:
|
|
top_scopes.append(scope)
|
|
return top_scopes
|
|
|
|
def _parse_scopes(self):
|
|
"""Returns a list of table rows where row:
|
|
[indented scopename string, scope description string]"""
|
|
scope_pairs, subscopes = self._get_scope_relationships()
|
|
top_scopes = self._get_top_scopes(subscopes)
|
|
|
|
table_rows = []
|
|
md_indent = " "
|
|
|
|
def _add_subscopes(table_rows, scopename, depth=0):
|
|
description = self.scopes[scopename]['description']
|
|
doc_description = self.scopes[scopename].get('doc_description', '')
|
|
if doc_description:
|
|
description = doc_description
|
|
table_row = [f"{md_indent * depth}`{scopename}`", description]
|
|
table_rows.append(table_row)
|
|
for subscope in scope_pairs[scopename]:
|
|
if subscope:
|
|
_add_subscopes(table_rows, subscope, depth + 1)
|
|
|
|
for scope in top_scopes:
|
|
_add_subscopes(table_rows, scope)
|
|
|
|
return table_rows
|
|
|
|
def write_table(self):
|
|
"""Generates the RBAC scopes reference documentation as a markdown table
|
|
and writes it to the .gitignored `scope-table.md`."""
|
|
filename = SCOPE_TABLE_MD
|
|
table_name = ""
|
|
headers = ["Scope", "Grants permission to:"]
|
|
values = self._parse_scopes()
|
|
writer = self.create_writer(table_name, headers, values)
|
|
|
|
title = "Table 1. Available scopes and their hierarchy"
|
|
content = f"{title}\n{writer.dumps()}"
|
|
with open(filename, 'w') as f:
|
|
f.write(content)
|
|
print(f"Generated {filename}.")
|
|
print(
|
|
"Run 'make clean' before 'make html' to ensure the built scopes.html contains latest scope table changes."
|
|
)
|
|
|
|
def write_api(self):
|
|
"""Loads `rest-api.yml` and writes it back with a dynamically set
|
|
JupyterHub version field and list of RBAC scopes descriptions from
|
|
`scopes.py`."""
|
|
filename = REST_API_YAML
|
|
|
|
yaml = YAML(typ="rt")
|
|
yaml.preserve_quotes = True
|
|
yaml.indent(mapping=2, offset=2, sequence=4)
|
|
|
|
scope_dict = {}
|
|
with open(filename) as f:
|
|
content = yaml.load(f.read())
|
|
|
|
content["info"]["version"] = __version__
|
|
for scope in self.scopes:
|
|
description = self.scopes[scope]['description']
|
|
doc_description = self.scopes[scope].get('doc_description', '')
|
|
if doc_description:
|
|
description = doc_description
|
|
scope_dict[scope] = description
|
|
content['components']['securitySchemes']['oauth2']['flows'][
|
|
'authorizationCode'
|
|
]['scopes'] = scope_dict
|
|
|
|
with open(filename, 'w') as f:
|
|
yaml.dump(content, f)
|
|
|
|
run(
|
|
['pre-commit', 'run', 'prettier', '--files', filename],
|
|
cwd=HERE,
|
|
check=False,
|
|
)
|
|
|
|
|
|
def main():
|
|
table_generator = ScopeTableGenerator()
|
|
table_generator.write_table()
|
|
table_generator.write_api()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|