1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
Example using GraphQL mutations to configure a DHCP server:
This assumes that the http-api is running:
'set service https api'
One can configure an address on an interface, and configure the DHCP server
to run with that address as default router by requesting these 'mutations':
mutation {
createInterfaceEthernet (data: {interface: "eth1",
address: "192.168.0.1/24",
description: "BOB"}) {
success
errors
data {
address
}
}
}
mutation {
createDhcpServer(data: {sharedNetworkName: "BOB",
subnet: "192.168.0.0/24",
defaultRouter: "192.168.0.1",
dnsServer: "192.168.0.1",
domainName: "vyos.net",
lease: 86400,
range: 0,
start: "192.168.0.9",
stop: "192.168.0.254",
dnsForwardingAllowFrom: "192.168.0.0/24",
dnsForwardingCacheSize: 0,
dnsForwardingListenAddress: "192.168.0.1"}) {
success
errors
data {
defaultRouter
}
}
}
These requests can be set in the GraphQL playground, or made with curl:
curl -k -H 'Content-Type: application/json' -d '{"query": "{hello}"}' https://{{ host_address }}/graphql
replacing the query with the above mutations as JSON.
What's here:
services
├── api
│ └── graphql
│ ├── graphql
│ │ ├── directives.py
│ │ ├── __init__.py
│ │ ├── mutations.py
│ │ └── schema
│ │ ├── dhcp_server.graphql
│ │ ├── interface_ethernet.graphql
│ │ └── schema.graphql
│ ├── recipes
│ │ ├── dhcp_server.py
│ │ ├── __init__.py
│ │ ├── interface_ethernet.py
│ │ ├── recipe.py
│ │ └── templates
│ │ ├── dhcp_server.tmpl
│ │ └── interface_ethernet.tmpl
│ └── state.py
├── vyos-configd
├── vyos-hostsd
└── vyos-http-api-server
The GraphQL library that we are using, Ariadne, advertises itself as a
'schema-first' implementation: define the schema; define resolvers
(handlers) for declared Query and Mutation types (Subscription types are not
currently used).
In the current approach to a high-level API, we consider the
Jinja2-templated collection of configuration mode 'set'/'delete' commands as
the Ur-data; the GraphQL schema is produced from those files, located in
'api/recipes/templates'.
Resolvers for the schema Mutation fields are dynamically generated using a
'directive' added to the respective schema field. The directive,
'@generate', is handled by the class 'DataDirective' in
'api/graphql/directives.py', which calls the 'make_resolver' function in
'api/graphql/mutations.py'; the produced resolver calls the appropriate
wrapper in 'api/recipes', with base class doing the (overridable)
configuration steps of calling all defined 'set'/'delete' commands.
Integrating the above with vyos-http-api-server is ~10 lines of code.
What needs to be done:
• automate generation of schema and wrappers from templated configuration
commands
• investigate whether the inversion of control provided by the named
wrappers in 'api/recipes' is sufficient for use cases which need to modify
data
• encapsulate the manipulation of 'canonical names' which transforms the
prefixed camel-case schema names to various snake-case file/function names
• consider mechanism for migration of templates: offline vs. on-the-fly
• define the naming convention for those schema fields that refer to
configuration mode parameters: e.g. how much of the path is needed as prefix
to uniquely define the term
|