Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F71813258
build-command-op-templates
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
8 KB
Referenced Files
None
Subscribers
None
build-command-op-templates
View Options
#!/usr/bin/env python3
#
# build-command-template: converts new style command definitions in XML
# to the old style (bunch of dirs and node.def's) command templates
#
# Copyright (C) 2017 VyOS maintainers <maintainers@vyos.net>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
# USA
import
sys
import
os
import
argparse
import
copy
import
functools
from
lxml
import
etree
as
ET
from
textwrap
import
fill
# Defaults
validator_dir
=
"/opt/vyatta/libexec/validators"
default_constraint_err_msg
=
"Invalid value"
## Get arguments
parser
=
argparse
.
ArgumentParser
(
description
=
'Converts new-style XML interface definitions to old-style command templates'
)
parser
.
add_argument
(
'--debug'
,
help
=
'Enable debug information output'
,
action
=
'store_true'
)
parser
.
add_argument
(
'INPUT_FILE'
,
type
=
str
,
help
=
"XML interface definition file"
)
parser
.
add_argument
(
'SCHEMA_FILE'
,
type
=
str
,
help
=
"RelaxNG schema file"
)
parser
.
add_argument
(
'OUTPUT_DIR'
,
type
=
str
,
help
=
"Output directory"
)
args
=
parser
.
parse_args
()
input_file
=
args
.
INPUT_FILE
schema_file
=
args
.
SCHEMA_FILE
output_dir
=
args
.
OUTPUT_DIR
debug
=
args
.
debug
## Load and validate the inputs
try
:
xml
=
ET
.
parse
(
input_file
)
except
Exception
as
e
:
print
(
f
"Failed to load interface definition file
{
input_file
}
"
)
print
(
e
)
sys
.
exit
(
1
)
try
:
relaxng_xml
=
ET
.
parse
(
schema_file
)
validator
=
ET
.
RelaxNG
(
relaxng_xml
)
if
not
validator
.
validate
(
xml
):
print
(
validator
.
error_log
)
print
(
f
"Interface definition file
{
input_file
}
does not match the schema!"
)
sys
.
exit
(
1
)
except
Exception
as
e
:
print
(
f
"Failed to load the XML schema
{
schema_file
}
"
)
print
(
e
)
sys
.
exit
(
1
)
if
not
os
.
access
(
output_dir
,
os
.
W_OK
):
print
(
f
"The output directory
{
output_dir
}
is not writeable"
)
sys
.
exit
(
1
)
## If we got this far, everything must be ok and we can convert the file
def
make_path
(
l
):
path
=
functools
.
reduce
(
os
.
path
.
join
,
l
)
if
debug
:
print
(
path
)
return
path
def
get_properties
(
p
):
props
=
{}
if
p
is
None
:
return
props
# Get the help string
try
:
props
[
"help"
]
=
p
.
find
(
"help"
)
.
text
except
:
props
[
"help"
]
=
"No help available"
# Get the completion help strings
try
:
che
=
p
.
findall
(
"completionHelp"
)
ch
=
""
for
c
in
che
:
scripts
=
c
.
findall
(
"script"
)
paths
=
c
.
findall
(
"path"
)
lists
=
c
.
findall
(
"list"
)
comptype
=
c
.
find
(
"imagePath"
)
# Current backend doesn't support multiple allowed: tags
# so we get to emulate it
comp_exprs
=
[]
for
i
in
lists
:
comp_exprs
.
append
(
"echo
\"
{0}
\"
"
.
format
(
i
.
text
))
for
i
in
paths
:
comp_exprs
.
append
(
"/bin/cli-shell-api listActiveNodes
{0}
| sed -e
\"
s/'//g
\"
&& echo"
.
format
(
i
.
text
))
for
i
in
scripts
:
comp_exprs
.
append
(
"
{0}
"
.
format
(
i
.
text
))
if
comptype
is
not
None
:
props
[
"comp_type"
]
=
"imagefiles"
comp_exprs
.
append
(
"echo -n
\"
<imagefiles>
\"
"
)
comp_help
=
" && "
.
join
(
comp_exprs
)
props
[
"comp_help"
]
=
comp_help
except
:
props
[
"comp_help"
]
=
[]
return
props
def
make_node_def
(
props
,
command
):
# XXX: replace with a template processor if it grows
# out of control
node_def
=
""
if
"help"
in
props
:
help
=
props
[
"help"
]
help
=
fill
(
help
,
width
=
64
,
subsequent_indent
=
'
\t\t\t
'
)
node_def
+=
f
'help:
{
help
}
\n
'
if
"comp_type"
in
props
:
node_def
+=
f
'comptype:
{
props
[
"comp_type"
]
}
\n
'
if
"comp_help"
in
props
:
node_def
+=
f
'allowed:
{
props
[
"comp_help"
]
}
\n
'
if
command
is
not
None
:
node_def
+=
f
'run:
{
command
.
text
}
\n
'
if
debug
:
print
(
'Contents of the node.def file:
\n
'
,
node_def
)
return
node_def
def
process_node
(
n
,
tmpl_dir
):
# Avoid mangling the path from the outer call
my_tmpl_dir
=
copy
.
copy
(
tmpl_dir
)
props_elem
=
n
.
find
(
"properties"
)
children
=
n
.
find
(
"children"
)
command
=
n
.
find
(
"command"
)
name
=
n
.
get
(
"name"
)
node_type
=
n
.
tag
my_tmpl_dir
.
append
(
name
)
if
debug
:
print
(
f
"Name of the node:
{
name
}
;
\n
Created directory: "
,
end
=
""
)
os
.
makedirs
(
make_path
(
my_tmpl_dir
),
exist_ok
=
True
)
props
=
get_properties
(
props_elem
)
nodedef_path
=
os
.
path
.
join
(
make_path
(
my_tmpl_dir
),
"node.def"
)
if
node_type
==
"node"
:
if
debug
:
print
(
f
"Processing node
{
name
}
"
)
# Only create the "node.def" file if it exists but is empty, or if it
# does not exist at all.
if
not
os
.
path
.
exists
(
nodedef_path
)
or
os
.
path
.
getsize
(
nodedef_path
)
==
0
:
with
open
(
nodedef_path
,
"w"
)
as
f
:
f
.
write
(
make_node_def
(
props
,
command
))
if
children
is
not
None
:
inner_nodes
=
children
.
iterfind
(
"*"
)
for
inner_n
in
inner_nodes
:
process_node
(
inner_n
,
my_tmpl_dir
)
elif
node_type
==
"tagNode"
:
if
debug
:
print
(
f
"Processing tagNode
{
name
}
"
)
os
.
makedirs
(
make_path
(
my_tmpl_dir
),
exist_ok
=
True
)
# Only create the "node.def" file if it exists but is empty, or if it
# does not exist at all.
if
not
os
.
path
.
exists
(
nodedef_path
)
or
os
.
path
.
getsize
(
nodedef_path
)
==
0
:
with
open
(
nodedef_path
,
"w"
)
as
f
:
f
.
write
(
'help:
{0}
\n
'
.
format
(
props
[
'help'
]))
# Create the inner node.tag part
my_tmpl_dir
.
append
(
"node.tag"
)
os
.
makedirs
(
make_path
(
my_tmpl_dir
),
exist_ok
=
True
)
if
debug
:
print
(
"Created path for the tagNode:
{}
"
.
format
(
make_path
(
my_tmpl_dir
)),
end
=
""
)
# Not sure if we want partially defined tag nodes, write the file unconditionally
nodedef_path
=
os
.
path
.
join
(
make_path
(
my_tmpl_dir
),
"node.def"
)
# Only create the "node.def" file if it exists but is empty, or if it
# does not exist at all.
if
not
os
.
path
.
exists
(
nodedef_path
)
or
os
.
path
.
getsize
(
nodedef_path
)
==
0
:
with
open
(
nodedef_path
,
"w"
)
as
f
:
f
.
write
(
make_node_def
(
props
,
command
))
if
children
is
not
None
:
inner_nodes
=
children
.
iterfind
(
"*"
)
for
inner_n
in
inner_nodes
:
process_node
(
inner_n
,
my_tmpl_dir
)
elif
node_type
==
"leafNode"
:
# This is a leaf node
if
debug
:
print
(
f
"Processing leaf node
{
name
}
"
)
if
not
os
.
path
.
exists
(
nodedef_path
)
or
os
.
path
.
getsize
(
nodedef_path
)
==
0
:
with
open
(
nodedef_path
,
"w"
)
as
f
:
f
.
write
(
make_node_def
(
props
,
command
))
else
:
print
(
f
"Unknown node_type:
{
node_type
}
"
)
def
get_node_key
(
node
,
attr
=
None
):
""" Return the sorting key of an xml node using tag and attributes """
if
attr
is
None
:
return
'
%s
'
%
node
.
tag
+
':'
.
join
([
node
.
get
(
attr
)
for
attr
in
sorted
(
node
.
attrib
)])
if
attr
in
node
.
attrib
:
return
'
%s
:
%s
'
%
(
node
.
tag
,
node
.
get
(
attr
))
return
'
%s
'
%
node
.
tag
def
sort_children
(
node
,
attr
=
None
):
""" Sort children along tag and given attribute. if attr is None, sort
along all attributes """
if
not
isinstance
(
node
.
tag
,
str
):
# PYTHON 2: use basestring instead
# not a TAG, it is comment or DATA
# no need to sort
return
# sort child along attr
node
[:]
=
sorted
(
node
,
key
=
lambda
child
:
get_node_key
(
child
,
attr
))
# and recurse
for
child
in
node
:
sort_children
(
child
,
attr
)
root
=
xml
.
getroot
()
# process_node() processes the XML tree in a fixed order, "node" before "tagNode"
# before "leafNode". If the generator created a "node.def" file, it can no longer
# be overwritten - else we would have some stale "node.def" files with an empty
# help string (T2555). Without the fixed order this would resulted in a case
# where we get a node and a tagNode with the same name, e.g. "show interfaces
# ethernet" and "show interfaces ethernet eth0" that the node implementation
# was not callable from the CLI, rendering this command useless (T3807).
#
# This can be fixed by forcing the "node", "tagNode", "leafNode" order by sorting
# the input XML file automatically (sorting from https://stackoverflow.com/a/46128043)
# thus adding no additional overhead to the user.
sort_children
(
root
,
'name'
)
nodes
=
root
.
iterfind
(
"*"
)
for
n
in
nodes
:
process_node
(
n
,
[
output_dir
])
File Metadata
Details
Attached
Mime Type
text/x-script.python
Expires
Fri, Jan 30, 11:45 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3101273
Default Alt Text
build-command-op-templates (8 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment