From ee53af35eb1edb6167a65b290f25a95b2a586498 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 23 Nov 2021 14:10:51 -0600 Subject: graphql: T3993: add requests for manipulating firewall groups --- .../graphql/graphql/schema/firewall_group.graphql | 47 ++++++++++++++++++++++ .../api/graphql/graphql/schema/schema.graphql | 3 ++ .../remove_firewall_address_group_members.py | 21 ++++++++++ src/services/api/graphql/recipes/session.py | 27 ++++++------- .../templates/create_firewall_address_group.tmpl | 4 ++ .../remove_firewall_address_group_members.tmpl | 3 ++ .../update_firewall_address_group_members.tmpl | 3 ++ 7 files changed, 93 insertions(+), 15 deletions(-) create mode 100644 src/services/api/graphql/graphql/schema/firewall_group.graphql create mode 100644 src/services/api/graphql/recipes/remove_firewall_address_group_members.py create mode 100644 src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl create mode 100644 src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl create mode 100644 src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl (limited to 'src/services/api') diff --git a/src/services/api/graphql/graphql/schema/firewall_group.graphql b/src/services/api/graphql/graphql/schema/firewall_group.graphql new file mode 100644 index 000000000..efe7de632 --- /dev/null +++ b/src/services/api/graphql/graphql/schema/firewall_group.graphql @@ -0,0 +1,47 @@ +input CreateFirewallAddressGroupInput { + name: String! + address: [String] +} + +type CreateFirewallAddressGroup { + name: String! + address: [String] +} + +type CreateFirewallAddressGroupResult { + data: CreateFirewallAddressGroup + success: Boolean! + errors: [String] +} + +input UpdateFirewallAddressGroupMembersInput { + name: String! + address: [String!]! +} + +type UpdateFirewallAddressGroupMembers { + name: String! + address: [String!]! +} + +type UpdateFirewallAddressGroupMembersResult { + data: UpdateFirewallAddressGroupMembers + success: Boolean! + errors: [String] +} + +input RemoveFirewallAddressGroupMembersInput { + name: String! + address: [String!]! +} + +type RemoveFirewallAddressGroupMembers { + name: String! + address: [String!]! +} + +type RemoveFirewallAddressGroupMembersResult { + data: RemoveFirewallAddressGroupMembers + success: Boolean! + errors: [String] +} diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql index e34a4eadb..9e97a0d60 100644 --- a/src/services/api/graphql/graphql/schema/schema.graphql +++ b/src/services/api/graphql/graphql/schema/schema.graphql @@ -13,6 +13,9 @@ directive @configfile on FIELD_DEFINITION type Mutation { CreateDhcpServer(data: DhcpServerConfigInput) : CreateDhcpServerResult @configure CreateInterfaceEthernet(data: InterfaceEthernetConfigInput) : CreateInterfaceEthernetResult @configure + CreateFirewallAddressGroup(data: CreateFirewallAddressGroupInput) : CreateFirewallAddressGroupResult @configure + UpdateFirewallAddressGroupMembers(data: UpdateFirewallAddressGroupMembersInput) : UpdateFirewallAddressGroupMembersResult @configure + RemoveFirewallAddressGroupMembers(data: RemoveFirewallAddressGroupMembersInput) : RemoveFirewallAddressGroupMembersResult @configure SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configfile LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile } diff --git a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py new file mode 100644 index 000000000..cde30c27a --- /dev/null +++ b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py @@ -0,0 +1,21 @@ + +from . session import Session + +class RemoveFirewallAddressGroupMembers(Session): + def __init__(self, session, data): + super().__init__(session, data) + + # Define any custom processing of parameters here by overriding + # configure: + # + # def configure(self): + # self._data = transform_data(self._data) + # super().configure() + # self.clean_up() + + def configure(self): + super().configure() + + group_name = self._data['name'] + path = ['firewall', 'group', 'address-group', group_name] + self.delete_path_if_childless(path) diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/recipes/session.py index aa3932ab9..b96cc1753 100644 --- a/src/services/api/graphql/recipes/session.py +++ b/src/services/api/graphql/recipes/session.py @@ -1,27 +1,17 @@ from ariadne import convert_camel_case_to_snake import vyos.defaults +from vyos.config import Config from vyos.template import render class Session(object): def __init__(self, session, data): self._session = session - self.data = data + self._data = data self._name = convert_camel_case_to_snake(type(self).__name__) - @property - def data(self): - return self.__data - - @data.setter - def data(self, data): - if isinstance(data, dict): - self.__data = data - else: - raise ValueError("data must be of type dict") - def configure(self): session = self._session - data = self.data + data = self._data func_base_name = self._name tmpl_file = f'{func_base_name}.tmpl' @@ -46,9 +36,16 @@ class Session(object): except Exception as error: raise error + def delete_path_if_childless(self, path): + session = self._session + config = Config(session.get_session_env()) + if not config.list_nodes(path): + session.delete(path) + session.commit() + def save(self): session = self._session - data = self.data + data = self._data if 'file_name' not in data or not data['file_name']: data['file_name'] = '/config/config.boot' @@ -59,7 +56,7 @@ class Session(object): def load(self): session = self._session - data = self.data + data = self._data try: session.load_config(data['file_name']) diff --git a/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl b/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl new file mode 100644 index 000000000..a890d0086 --- /dev/null +++ b/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl @@ -0,0 +1,4 @@ +set firewall group address-group {{ name }} +{% for add in address %} +set firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl b/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl new file mode 100644 index 000000000..458f3e5fc --- /dev/null +++ b/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +delete firewall group address-group {{ name }} address {{ add }} +{% endfor %} diff --git a/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl b/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl new file mode 100644 index 000000000..f56c61231 --- /dev/null +++ b/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl @@ -0,0 +1,3 @@ +{% for add in address %} +set firewall group address-group {{ name }} address {{ add }} +{% endfor %} -- cgit v1.2.3 From 1f926e1b1fe7d82113be55916a55ca7e3cceac76 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 29 Nov 2021 11:27:40 -0600 Subject: graphql: T3993: add op-mode requests --- src/services/api/graphql/graphql/directives.py | 16 ++++++++++++---- src/services/api/graphql/graphql/mutations.py | 7 ++++++- src/services/api/graphql/graphql/schema/schema.graphql | 2 ++ src/services/api/graphql/graphql/schema/show.graphql | 14 ++++++++++++++ src/services/api/graphql/recipes/session.py | 12 ++++++++++++ 5 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 src/services/api/graphql/graphql/schema/show.graphql (limited to 'src/services/api') diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py index f5cd88acd..55aceca1b 100644 --- a/src/services/api/graphql/graphql/directives.py +++ b/src/services/api/graphql/graphql/directives.py @@ -1,5 +1,5 @@ from ariadne import SchemaDirectiveVisitor, ObjectType -from . mutations import make_configure_resolver, make_config_file_resolver +from . mutations import * def non(arg): pass @@ -19,7 +19,6 @@ class VyosDirective(SchemaDirectiveVisitor): class ConfigureDirective(VyosDirective): """ Class providing implementation of 'configure' directive in schema. - """ def visit_field_definition(self, field, object_type): super().visit_field_definition(field, object_type, @@ -28,10 +27,19 @@ class ConfigureDirective(VyosDirective): class ConfigFileDirective(VyosDirective): """ Class providing implementation of 'configfile' directive in schema. - """ def visit_field_definition(self, field, object_type): super().visit_field_definition(field, object_type, make_resolver=make_config_file_resolver) -directives_dict = {"configure": ConfigureDirective, "configfile": ConfigFileDirective} +class ShowDirective(VyosDirective): + """ + Class providing implementation of 'show' directive in schema. + """ + def visit_field_definition(self, field, object_type): + super().visit_field_definition(field, object_type, + make_resolver=make_show_resolver) + +directives_dict = {"configure": ConfigureDirective, + "configfile": ConfigFileDirective, + "show": ShowDirective} diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index 8a28b13d7..5913ee8b1 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -51,7 +51,8 @@ def make_resolver(mutation_name, class_name, session_func): klass = type(class_name, (Session,), {}) k = klass(session, data) method = getattr(k, session_func) - method() + result = method() + data['result'] = result return { "success": True, @@ -78,3 +79,7 @@ def make_config_file_resolver(mutation_name): return make_resolver(mutation_name, class_name, 'load') else: raise Exception + +def make_show_resolver(mutation_name): + class_name = mutation_name + return make_resolver(mutation_name, class_name, 'show') diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql index 9e97a0d60..764a50130 100644 --- a/src/services/api/graphql/graphql/schema/schema.graphql +++ b/src/services/api/graphql/graphql/schema/schema.graphql @@ -9,6 +9,7 @@ type Query { directive @configure on FIELD_DEFINITION directive @configfile on FIELD_DEFINITION +directive @show on FIELD_DEFINITION type Mutation { CreateDhcpServer(data: DhcpServerConfigInput) : CreateDhcpServerResult @configure @@ -18,4 +19,5 @@ type Mutation { RemoveFirewallAddressGroupMembers(data: RemoveFirewallAddressGroupMembersInput) : RemoveFirewallAddressGroupMembersResult @configure SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configfile LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile + Show(data: ShowInput) : ShowResult @show } diff --git a/src/services/api/graphql/graphql/schema/show.graphql b/src/services/api/graphql/graphql/schema/show.graphql new file mode 100644 index 000000000..c7709e48b --- /dev/null +++ b/src/services/api/graphql/graphql/schema/show.graphql @@ -0,0 +1,14 @@ +input ShowInput { + path: [String!]! +} + +type Show { + path: [String] + result: String +} + +type ShowResult { + data: Show + success: Boolean! + errors: [String] +} diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/recipes/session.py index b96cc1753..c6c3209c0 100644 --- a/src/services/api/graphql/recipes/session.py +++ b/src/services/api/graphql/recipes/session.py @@ -63,3 +63,15 @@ class Session(object): session.commit() except Exception as error: raise error + + def show(self): + session = self._session + data = self._data + out = '' + + try: + out = session.show(data['path']) + except Exception as error: + raise error + + return out -- cgit v1.2.3 From a05866e5301934f61a3c83550f91926e03bfc7b0 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Mon, 29 Nov 2021 16:02:12 -0600 Subject: graphql: T3993: add config session show_config Note that one can also use the mutation Show, with path ["configuration", "json", "pretty"]; that command will obscure passwords and keys, and we may want to disallow this version. --- src/services/api/graphql/graphql/directives.py | 9 +++++++++ src/services/api/graphql/graphql/mutations.py | 4 ++++ .../api/graphql/graphql/schema/schema.graphql | 2 ++ .../api/graphql/graphql/schema/show_config.graphql | 21 +++++++++++++++++++++ src/services/api/graphql/recipes/session.py | 19 +++++++++++++++++++ 5 files changed, 55 insertions(+) create mode 100644 src/services/api/graphql/graphql/schema/show_config.graphql (limited to 'src/services/api') diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py index 55aceca1b..4bc31c6b5 100644 --- a/src/services/api/graphql/graphql/directives.py +++ b/src/services/api/graphql/graphql/directives.py @@ -24,6 +24,14 @@ class ConfigureDirective(VyosDirective): super().visit_field_definition(field, object_type, make_resolver=make_configure_resolver) +class ShowConfigDirective(VyosDirective): + """ + Class providing implementation of 'show' directive in schema. + """ + def visit_field_definition(self, field, object_type): + super().visit_field_definition(field, object_type, + make_resolver=make_show_config_resolver) + class ConfigFileDirective(VyosDirective): """ Class providing implementation of 'configfile' directive in schema. @@ -41,5 +49,6 @@ class ShowDirective(VyosDirective): make_resolver=make_show_resolver) directives_dict = {"configure": ConfigureDirective, + "showconfig": ShowConfigDirective, "configfile": ConfigFileDirective, "show": ShowDirective} diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index 5913ee8b1..0ba2cd4bb 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -70,6 +70,10 @@ def make_configure_resolver(mutation_name): class_name = mutation_name return make_resolver(mutation_name, class_name, 'configure') +def make_show_config_resolver(mutation_name): + class_name = mutation_name + return make_resolver(mutation_name, class_name, 'show_config') + def make_config_file_resolver(mutation_name): if 'Save' in mutation_name: class_name = mutation_name.replace('Save', '', 1) diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql index 764a50130..375b88cc5 100644 --- a/src/services/api/graphql/graphql/schema/schema.graphql +++ b/src/services/api/graphql/graphql/schema/schema.graphql @@ -10,6 +10,7 @@ type Query { directive @configure on FIELD_DEFINITION directive @configfile on FIELD_DEFINITION directive @show on FIELD_DEFINITION +directive @showconfig on FIELD_DEFINITION type Mutation { CreateDhcpServer(data: DhcpServerConfigInput) : CreateDhcpServerResult @configure @@ -20,4 +21,5 @@ type Mutation { SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configfile LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile Show(data: ShowInput) : ShowResult @show + ShowConfig(data: ShowConfigInput) : ShowConfigResult @showconfig } diff --git a/src/services/api/graphql/graphql/schema/show_config.graphql b/src/services/api/graphql/graphql/schema/show_config.graphql new file mode 100644 index 000000000..34afd2aa9 --- /dev/null +++ b/src/services/api/graphql/graphql/schema/show_config.graphql @@ -0,0 +1,21 @@ +""" +Use 'scalar Generic' for show config output, to avoid attempts to +JSON-serialize in case of JSON output. +""" +scalar Generic + +input ShowConfigInput { + path: [String!]! + configFormat: String +} + +type ShowConfig { + path: [String] + result: Generic +} + +type ShowConfigResult { + data: ShowConfig + success: Boolean! + errors: [String] +} diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/recipes/session.py index c6c3209c0..f8c072b39 100644 --- a/src/services/api/graphql/recipes/session.py +++ b/src/services/api/graphql/recipes/session.py @@ -1,6 +1,10 @@ +import json + from ariadne import convert_camel_case_to_snake + import vyos.defaults from vyos.config import Config +from vyos.configtree import ConfigTree from vyos.template import render class Session(object): @@ -43,6 +47,21 @@ class Session(object): session.delete(path) session.commit() + def show_config(self): + session = self._session + data = self._data + out = '' + + try: + out = session.show_config(data['path']) + if data.get('config_format', '') == 'json': + config_tree = vyos.configtree.ConfigTree(out) + out = json.loads(config_tree.to_json()) + except Exception as error: + raise error + + return out + def save(self): session = self._session data = self._data -- cgit v1.2.3 From 9f6ca1e489c0498bfa90ca027d1d7419d4e422b8 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 30 Nov 2021 16:55:03 -0600 Subject: graphql: T3993: update README.graphql --- src/services/api/graphql/README.graphql | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) (limited to 'src/services/api') diff --git a/src/services/api/graphql/README.graphql b/src/services/api/graphql/README.graphql index 29f58f709..a3c30b005 100644 --- a/src/services/api/graphql/README.graphql +++ b/src/services/api/graphql/README.graphql @@ -73,6 +73,20 @@ mutation { } } +Op-mode 'show' commands may be requested by path, e.g.: + +mutation { + Show (data: {path: ["interfaces", "ethernet", "detail"]}) { + success + errors + data { + result + } + } +} + +N.B. to see the output the 'data' field 'result' must be present in the +request. The GraphQL playground will be found at: @@ -97,15 +111,22 @@ services │   │   └── schema │   │   ├── config_file.graphql │   │   ├── dhcp_server.graphql +│   │   ├── firewall_group.graphql │   │   ├── interface_ethernet.graphql -│   │   └── schema.graphql +│   │   ├── schema.graphql +│   │   ├── show_config.graphql +│   │   └── show.graphql │   ├── README.graphql │   ├── recipes │   │   ├── __init__.py +│   │   ├── remove_firewall_address_group_members.py │   │   ├── session.py │   │   └── templates │   │   ├── create_dhcp_server.tmpl -│   │   └── create_interface_ethernet.tmpl +│   │   ├── create_firewall_address_group.tmpl +│   │   ├── create_interface_ethernet.tmpl +│   │   ├── remove_firewall_address_group_members.tmpl +│   │   └── update_firewall_address_group_members.tmpl │   └── state.py ├── vyos-configd ├── vyos-hostsd -- cgit v1.2.3 From 358831c18fcf2937f4bf85a55fa0c8bdc802d817 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 1 Dec 2021 13:14:07 -0600 Subject: graphql: T3993: define add/delete system image request --- src/services/api/graphql/graphql/directives.py | 11 +++++++- src/services/api/graphql/graphql/mutations.py | 10 ++++++++ .../api/graphql/graphql/schema/image.graphql | 29 ++++++++++++++++++++++ .../api/graphql/graphql/schema/schema.graphql | 3 +++ src/services/api/graphql/recipes/session.py | 29 +++++++++++++++++++++- 5 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/services/api/graphql/graphql/schema/image.graphql (limited to 'src/services/api') diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py index 4bc31c6b5..10bc522db 100644 --- a/src/services/api/graphql/graphql/directives.py +++ b/src/services/api/graphql/graphql/directives.py @@ -48,7 +48,16 @@ class ShowDirective(VyosDirective): super().visit_field_definition(field, object_type, make_resolver=make_show_resolver) +class ImageDirective(VyosDirective): + """ + Class providing implementation of 'image' directive in schema. + """ + def visit_field_definition(self, field, object_type): + super().visit_field_definition(field, object_type, + make_resolver=make_image_resolver) + directives_dict = {"configure": ConfigureDirective, "showconfig": ShowConfigDirective, "configfile": ConfigFileDirective, - "show": ShowDirective} + "show": ShowDirective, + "image": ImageDirective} diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py index 0ba2cd4bb..8e5aab56d 100644 --- a/src/services/api/graphql/graphql/mutations.py +++ b/src/services/api/graphql/graphql/mutations.py @@ -87,3 +87,13 @@ def make_config_file_resolver(mutation_name): def make_show_resolver(mutation_name): class_name = mutation_name return make_resolver(mutation_name, class_name, 'show') + +def make_image_resolver(mutation_name): + if 'Add' in mutation_name: + class_name = mutation_name.replace('Add', '', 1) + return make_resolver(mutation_name, class_name, 'add') + elif 'Delete' in mutation_name: + class_name = mutation_name.replace('Delete', '', 1) + return make_resolver(mutation_name, class_name, 'delete') + else: + raise Exception diff --git a/src/services/api/graphql/graphql/schema/image.graphql b/src/services/api/graphql/graphql/schema/image.graphql new file mode 100644 index 000000000..7d1b4f9d0 --- /dev/null +++ b/src/services/api/graphql/graphql/schema/image.graphql @@ -0,0 +1,29 @@ +input AddSystemImageInput { + location: String! +} + +type AddSystemImage { + location: String + result: String +} + +type AddSystemImageResult { + data: AddSystemImage + success: Boolean! + errors: [String] +} + +input DeleteSystemImageInput { + name: String! +} + +type DeleteSystemImage { + name: String + result: String +} + +type DeleteSystemImageResult { + data: DeleteSystemImage + success: Boolean! + errors: [String] +} diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql index 375b88cc5..c6899bee6 100644 --- a/src/services/api/graphql/graphql/schema/schema.graphql +++ b/src/services/api/graphql/graphql/schema/schema.graphql @@ -11,6 +11,7 @@ directive @configure on FIELD_DEFINITION directive @configfile on FIELD_DEFINITION directive @show on FIELD_DEFINITION directive @showconfig on FIELD_DEFINITION +directive @image on FIELD_DEFINITION type Mutation { CreateDhcpServer(data: DhcpServerConfigInput) : CreateDhcpServerResult @configure @@ -22,4 +23,6 @@ type Mutation { LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile Show(data: ShowInput) : ShowResult @show ShowConfig(data: ShowConfigInput) : ShowConfigResult @showconfig + AddSystemImage(data: AddSystemImageInput) : AddSystemImageResult @image + DeleteSystemImage(data: DeleteSystemImageInput) : DeleteSystemImageResult @image } diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/recipes/session.py index f8c072b39..5ece78ee6 100644 --- a/src/services/api/graphql/recipes/session.py +++ b/src/services/api/graphql/recipes/session.py @@ -7,7 +7,12 @@ from vyos.config import Config from vyos.configtree import ConfigTree from vyos.template import render -class Session(object): +class Session: + """ + Wrapper for calling configsession functions based on GraphQL requests. + Non-nullable fields in the respective schema allow avoiding a key check + in 'data'. + """ def __init__(self, session, data): self._session = session self._data = data @@ -94,3 +99,25 @@ class Session(object): raise error return out + + def add(self): + session = self._session + data = self._data + + try: + res = session.install_image(data['location']) + except Exception as error: + raise error + + return res + + def delete(self): + session = self._session + data = self._data + + try: + res = session.remove_image(data['name']) + except Exception as error: + raise error + + return res -- cgit v1.2.3