From 3204cdb1f92b8880fcc7481d17ae2a00e78c6d96 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 1 Mar 2023 08:46:32 -0600 Subject: graphql: T5040: use nullable key field to allow schema static generation Schema had been dynamically generated, based on configuration setting for authentication. Add nullable field 'key' for static generation of schema regardless of key/token use. --- .../api/graphql/generate/schema_from_composite.py | 49 ++-------------------- .../graphql/generate/schema_from_config_session.py | 49 ++-------------------- .../api/graphql/generate/schema_from_op_mode.py | 49 ++-------------------- 3 files changed, 9 insertions(+), 138 deletions(-) diff --git a/src/services/api/graphql/generate/schema_from_composite.py b/src/services/api/graphql/generate/schema_from_composite.py index 61a08cb2f..d7b6e0035 100755 --- a/src/services/api/graphql/generate/schema_from_composite.py +++ b/src/services/api/graphql/generate/schema_from_composite.py @@ -29,9 +29,6 @@ if __package__ is None or __package__ == '': sys.path.append("/usr/libexec/vyos/services/api") from graphql.libs.op_mode import snake_to_pascal_case, map_type_name from composite_function import queries, mutations - from vyos.config import Config - from vyos.configdict import dict_merge - from vyos.xml import defaults else: from .. libs.op_mode import snake_to_pascal_case, map_type_name from . composite_function import queries, mutations @@ -39,40 +36,16 @@ else: SCHEMA_PATH = directories['api_schema'] -if __package__ is None or __package__ == '': - # allow running stand-alone - conf = Config() - base = ['service', 'https', 'api'] - graphql_dict = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True) - if 'graphql' not in graphql_dict: - exit("graphql is not configured") - - graphql_dict = dict_merge(defaults(base), graphql_dict) - auth_type = graphql_dict['graphql']['authentication']['type'] -else: - auth_type = state.settings['app'].state.vyos_auth_type - -schema_data: dict = {'auth_type': auth_type, - 'schema_name': '', +schema_data: dict = {'schema_name': '', 'schema_fields': []} query_template = """ -{%- if auth_type == 'key' %} input {{ schema_name }}Input { - key: String! + key: String {%- for field_entry in schema_fields %} {{ field_entry }} {%- endfor %} } -{%- elif schema_fields %} -input {{ schema_name }}Input { - {%- for field_entry in schema_fields %} - {{ field_entry }} - {%- endfor %} -} -{%- endif %} type {{ schema_name }} { result: Generic @@ -85,29 +58,17 @@ type {{ schema_name }}Result { } extend type Query { -{%- if auth_type == 'key' or schema_fields %} {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @compositequery -{%- else %} - {{ schema_name }} : {{ schema_name }}Result @compositequery -{%- endif %} } """ mutation_template = """ -{%- if auth_type == 'key' %} -input {{ schema_name }}Input { - key: String! - {%- for field_entry in schema_fields %} - {{ field_entry }} - {%- endfor %} -} -{%- elif schema_fields %} input {{ schema_name }}Input { + key: String {%- for field_entry in schema_fields %} {{ field_entry }} {%- endfor %} } -{%- endif %} type {{ schema_name }} { result: Generic @@ -120,11 +81,7 @@ type {{ schema_name }}Result { } extend type Mutation { -{%- if auth_type == 'key' or schema_fields %} {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @compositemutation -{%- else %} - {{ schema_name }} : {{ schema_name }}Result @compositemutation -{%- endif %} } """ diff --git a/src/services/api/graphql/generate/schema_from_config_session.py b/src/services/api/graphql/generate/schema_from_config_session.py index 49bf2440e..f929e16b8 100755 --- a/src/services/api/graphql/generate/schema_from_config_session.py +++ b/src/services/api/graphql/generate/schema_from_config_session.py @@ -29,9 +29,6 @@ if __package__ is None or __package__ == '': sys.path.append("/usr/libexec/vyos/services/api") from graphql.libs.op_mode import snake_to_pascal_case, map_type_name from config_session_function import queries, mutations - from vyos.config import Config - from vyos.configdict import dict_merge - from vyos.xml import defaults else: from .. libs.op_mode import snake_to_pascal_case, map_type_name from . config_session_function import queries, mutations @@ -39,40 +36,16 @@ else: SCHEMA_PATH = directories['api_schema'] -if __package__ is None or __package__ == '': - # allow running stand-alone - conf = Config() - base = ['service', 'https', 'api'] - graphql_dict = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True) - if 'graphql' not in graphql_dict: - exit("graphql is not configured") - - graphql_dict = dict_merge(defaults(base), graphql_dict) - auth_type = graphql_dict['graphql']['authentication']['type'] -else: - auth_type = state.settings['app'].state.vyos_auth_type - -schema_data: dict = {'auth_type': auth_type, - 'schema_name': '', +schema_data: dict = {'schema_name': '', 'schema_fields': []} query_template = """ -{%- if auth_type == 'key' %} input {{ schema_name }}Input { - key: String! + key: String {%- for field_entry in schema_fields %} {{ field_entry }} {%- endfor %} } -{%- elif schema_fields %} -input {{ schema_name }}Input { - {%- for field_entry in schema_fields %} - {{ field_entry }} - {%- endfor %} -} -{%- endif %} type {{ schema_name }} { result: Generic @@ -85,29 +58,17 @@ type {{ schema_name }}Result { } extend type Query { -{%- if auth_type == 'key' or schema_fields %} {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionquery -{%- else %} - {{ schema_name }} : {{ schema_name }}Result @configsessionquery -{%- endif %} } """ mutation_template = """ -{%- if auth_type == 'key' %} -input {{ schema_name }}Input { - key: String! - {%- for field_entry in schema_fields %} - {{ field_entry }} - {%- endfor %} -} -{%- elif schema_fields %} input {{ schema_name }}Input { + key: String {%- for field_entry in schema_fields %} {{ field_entry }} {%- endfor %} } -{%- endif %} type {{ schema_name }} { result: Generic @@ -120,11 +81,7 @@ type {{ schema_name }}Result { } extend type Mutation { -{%- if auth_type == 'key' or schema_fields %} {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @configsessionmutation -{%- else %} - {{ schema_name }} : {{ schema_name }}Result @configsessionmutation -{%- endif %} } """ diff --git a/src/services/api/graphql/generate/schema_from_op_mode.py b/src/services/api/graphql/generate/schema_from_op_mode.py index b320a529e..32f314c9b 100755 --- a/src/services/api/graphql/generate/schema_from_op_mode.py +++ b/src/services/api/graphql/generate/schema_from_op_mode.py @@ -31,9 +31,6 @@ if __package__ is None or __package__ == '': sys.path.append("/usr/libexec/vyos/services/api") from graphql.libs.op_mode import is_show_function_name from graphql.libs.op_mode import snake_to_pascal_case, map_type_name - from vyos.config import Config - from vyos.configdict import dict_merge - from vyos.xml import defaults else: from .. libs.op_mode import is_show_function_name from .. libs.op_mode import snake_to_pascal_case, map_type_name @@ -46,40 +43,16 @@ DATA_DIR = directories['data'] op_mode_include_file = os.path.join(DATA_DIR, 'op-mode-standardized.json') op_mode_error_schema = 'op_mode_error.graphql' -if __package__ is None or __package__ == '': - # allow running stand-alone - conf = Config() - base = ['service', 'https', 'api'] - graphql_dict = conf.get_config_dict(base, key_mangling=('-', '_'), - no_tag_node_value_mangle=True, - get_first_key=True) - if 'graphql' not in graphql_dict: - exit("graphql is not configured") - - graphql_dict = dict_merge(defaults(base), graphql_dict) - auth_type = graphql_dict['graphql']['authentication']['type'] -else: - auth_type = state.settings['app'].state.vyos_auth_type - -schema_data: dict = {'auth_type': auth_type, - 'schema_name': '', +schema_data: dict = {'schema_name': '', 'schema_fields': []} query_template = """ -{%- if auth_type == 'key' %} input {{ schema_name }}Input { - key: String! + key: String {%- for field_entry in schema_fields %} {{ field_entry }} {%- endfor %} } -{%- elif schema_fields %} -input {{ schema_name }}Input { - {%- for field_entry in schema_fields %} - {{ field_entry }} - {%- endfor %} -} -{%- endif %} type {{ schema_name }} { result: Generic @@ -93,29 +66,17 @@ type {{ schema_name }}Result { } extend type Query { -{%- if auth_type == 'key' or schema_fields %} {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @genopquery -{%- else %} - {{ schema_name }} : {{ schema_name }}Result @genopquery -{%- endif %} } """ mutation_template = """ -{%- if auth_type == 'key' %} -input {{ schema_name }}Input { - key: String! - {%- for field_entry in schema_fields %} - {{ field_entry }} - {%- endfor %} -} -{%- elif schema_fields %} input {{ schema_name }}Input { + key: String {%- for field_entry in schema_fields %} {{ field_entry }} {%- endfor %} } -{%- endif %} type {{ schema_name }} { result: Generic @@ -129,11 +90,7 @@ type {{ schema_name }}Result { } extend type Mutation { -{%- if auth_type == 'key' or schema_fields %} {{ schema_name }}(data: {{ schema_name }}Input) : {{ schema_name }}Result @genopmutation -{%- else %} - {{ schema_name }} : {{ schema_name }}Result @genopquery -{%- endif %} } """ -- cgit v1.2.3 From ee95772c8e72a80091e5daa863fa1a76cdedda95 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 1 Mar 2023 08:47:41 -0600 Subject: graphql: T5040: fail gracefully if not on live system; needed for import For type introspection of op-mode scripts, scripts are loaded as modules. For generation of schema from type introspection, it is useful to load scripts during package installation, hence to fail gracefully if not on live system. --- python/vyos/configquery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/vyos/configquery.py b/python/vyos/configquery.py index 5b097b312..85fef8777 100644 --- a/python/vyos/configquery.py +++ b/python/vyos/configquery.py @@ -88,7 +88,7 @@ class ConfigTreeQuery(GenericConfigQuery): with open(config_file) as f: config_string = f.read() except OSError as err: - raise ConfigQueryError('No config file available') from err + config_string = '' config_source = ConfigSourceString(running_config_text=config_string, session_config_text=config_string) -- cgit v1.2.3 From 57ca4de2160254cc5a14d35efaba8123c4e9d542 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 1 Mar 2023 08:53:09 -0600 Subject: graphql: T5040: generate schema in vyos-1x.postinst --- debian/vyos-1x.postinst | 3 +++ src/services/api/graphql/bindings.py | 7 ------ .../api/graphql/generate/generate_schema.py | 26 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100755 src/services/api/graphql/generate/generate_schema.py diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index b2f6a7399..f6693c799 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -114,3 +114,6 @@ done # Remove logrotate items controlled via CLI and VyOS defaults sed -i '/^\/var\/log\/messages$/d' /etc/logrotate.d/rsyslog sed -i '/^\/var\/log\/auth.log$/d' /etc/logrotate.d/rsyslog + +# Generate API GraphQL schema +/usr/libexec/vyos/services/api/graphql/generate/generate_schema.py diff --git a/src/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py index aa1ba0eb0..ef4966466 100644 --- a/src/services/api/graphql/bindings.py +++ b/src/services/api/graphql/bindings.py @@ -19,9 +19,6 @@ from . graphql.mutations import mutation from . graphql.directives import directives_dict from . graphql.errors import op_mode_error from . graphql.auth_token_mutation import auth_token_mutation -from . generate.schema_from_op_mode import generate_op_mode_definitions -from . generate.schema_from_config_session import generate_config_session_definitions -from . generate.schema_from_composite import generate_composite_definitions from . libs.token_auth import init_secret from . import state from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers @@ -29,10 +26,6 @@ from ariadne import make_executable_schema, load_schema_from_path, snake_case_fa def generate_schema(): api_schema_dir = vyos.defaults.directories['api_schema'] - generate_op_mode_definitions() - generate_config_session_definitions() - generate_composite_definitions() - if state.settings['app'].state.vyos_auth_type == 'token': init_secret() diff --git a/src/services/api/graphql/generate/generate_schema.py b/src/services/api/graphql/generate/generate_schema.py new file mode 100755 index 000000000..dd5e7ea56 --- /dev/null +++ b/src/services/api/graphql/generate/generate_schema.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# + +from schema_from_op_mode import generate_op_mode_definitions +from schema_from_config_session import generate_config_session_definitions +from schema_from_composite import generate_composite_definitions + +if __name__ == '__main__': + generate_op_mode_definitions() + generate_config_session_definitions() + generate_composite_definitions() -- cgit v1.2.3 From babff628aa9a48ced57100e3879232d94bf19d70 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 1 Mar 2023 10:49:54 -0600 Subject: graphql: T5040: adjust smoketest for nullable key Since 'key' field is no longer required, a missing key will register an error in the resolver, instead of being rejected as bad request. --- smoketest/scripts/cli/test_service_https.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py index 0f4b1393c..1adf1f5cf 100755 --- a/smoketest/scripts/cli/test_service_https.py +++ b/smoketest/scripts/cli/test_service_https.py @@ -193,7 +193,8 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): """ r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_no_key}) - self.assertEqual(r.status_code, 400) + success = r.json()['data']['SystemStatus']['success'] + self.assertFalse(success) # GraphQL token authentication test: request token; pass in header # of query. -- cgit v1.2.3 From 7ab3b9e021e7ce1c56b9f252a04400a37fd33e71 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 3 Mar 2023 08:36:18 -0600 Subject: graphql: T5040: use path from defaults --- python/vyos/defaults.py | 1 + src/services/api/graphql/generate/schema_from_composite.py | 2 +- src/services/api/graphql/generate/schema_from_config_session.py | 2 +- src/services/api/graphql/generate/schema_from_op_mode.py | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index db0def8ed..060a0ba03 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -22,6 +22,7 @@ directories = { 'data' : '/usr/share/vyos/', 'conf_mode' : f'{base_dir}/conf_mode', 'op_mode' : f'{base_dir}/op_mode', + 'services' : f'{base_dir}/services', 'config' : '/opt/vyatta/etc/config', 'current' : '/opt/vyatta/etc/config-migrate/current', 'migrate' : '/opt/vyatta/etc/config-migrate/migrate', diff --git a/src/services/api/graphql/generate/schema_from_composite.py b/src/services/api/graphql/generate/schema_from_composite.py index d7b6e0035..50c5d24f8 100755 --- a/src/services/api/graphql/generate/schema_from_composite.py +++ b/src/services/api/graphql/generate/schema_from_composite.py @@ -26,7 +26,7 @@ from jinja2 import Template from vyos.defaults import directories if __package__ is None or __package__ == '': - sys.path.append("/usr/libexec/vyos/services/api") + sys.path.append(os.path.join(directories['services'], 'api')) from graphql.libs.op_mode import snake_to_pascal_case, map_type_name from composite_function import queries, mutations else: diff --git a/src/services/api/graphql/generate/schema_from_config_session.py b/src/services/api/graphql/generate/schema_from_config_session.py index f929e16b8..831faa41e 100755 --- a/src/services/api/graphql/generate/schema_from_config_session.py +++ b/src/services/api/graphql/generate/schema_from_config_session.py @@ -26,7 +26,7 @@ from jinja2 import Template from vyos.defaults import directories if __package__ is None or __package__ == '': - sys.path.append("/usr/libexec/vyos/services/api") + sys.path.append(os.path.join(directories['services'], 'api')) from graphql.libs.op_mode import snake_to_pascal_case, map_type_name from config_session_function import queries, mutations else: diff --git a/src/services/api/graphql/generate/schema_from_op_mode.py b/src/services/api/graphql/generate/schema_from_op_mode.py index 32f314c9b..98b2ad7b7 100755 --- a/src/services/api/graphql/generate/schema_from_op_mode.py +++ b/src/services/api/graphql/generate/schema_from_op_mode.py @@ -28,7 +28,7 @@ from vyos.defaults import directories from vyos.opmode import _is_op_mode_function_name as is_op_mode_function_name from vyos.util import load_as_module if __package__ is None or __package__ == '': - sys.path.append("/usr/libexec/vyos/services/api") + sys.path.append(os.path.join(directories['services'], 'api')) from graphql.libs.op_mode import is_show_function_name from graphql.libs.op_mode import snake_to_pascal_case, map_type_name else: -- cgit v1.2.3