Page Menu
Home
VyOS Platform
Search
Configure Global Search
Log In
Files
F38930216
vyos-configd
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
vyos-configd
View Options
#!/usr/bin/env python3
#
# Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
#
#
import
os
import
sys
import
grp
import
re
import
json
import
logging
import
signal
import
importlib.util
import
zmq
from
contextlib
import
contextmanager
from
vyos.defaults
import
directories
from
vyos.util
import
boot_configuration_complete
from
vyos.configsource
import
ConfigSourceString
,
ConfigSourceError
from
vyos.config
import
Config
from
vyos
import
ConfigError
CFG_GROUP
=
'vyattacfg'
script_stdout_log
=
'/tmp/vyos-configd-script-stdout'
debug
=
True
logger
=
logging
.
getLogger
(
__name__
)
logs_handler
=
logging
.
StreamHandler
()
logger
.
addHandler
(
logs_handler
)
if
debug
:
logger
.
setLevel
(
logging
.
DEBUG
)
else
:
logger
.
setLevel
(
logging
.
INFO
)
SOCKET_PATH
=
"ipc:///run/vyos-configd.sock"
# Response error codes
R_SUCCESS
=
1
R_ERROR_COMMIT
=
2
R_ERROR_DAEMON
=
4
R_PASS
=
8
vyos_conf_scripts_dir
=
directories
[
'conf_mode'
]
configd_include_file
=
os
.
path
.
join
(
directories
[
'data'
],
'configd-include.json'
)
configd_env_set_file
=
os
.
path
.
join
(
directories
[
'data'
],
'vyos-configd-env-set'
)
configd_env_unset_file
=
os
.
path
.
join
(
directories
[
'data'
],
'vyos-configd-env-unset'
)
# sourced on entering config session
configd_env_file
=
'/etc/default/vyos-configd-env'
session_out
=
None
session_mode
=
None
def
key_name_from_file_name
(
f
):
return
os
.
path
.
splitext
(
f
)[
0
]
def
module_name_from_key
(
k
):
return
k
.
replace
(
'-'
,
'_'
)
def
path_from_file_name
(
f
):
return
os
.
path
.
join
(
vyos_conf_scripts_dir
,
f
)
# opt-in to be run by daemon
with
open
(
configd_include_file
)
as
f
:
try
:
include
=
json
.
load
(
f
)
except
OSError
as
e
:
logger
.
critical
(
f
"configd include file error:
{
e
}
"
)
sys
.
exit
(
1
)
except
json
.
JSONDecodeError
as
e
:
logger
.
critical
(
f
"JSON load error:
{
e
}
"
)
sys
.
exit
(
1
)
# import conf_mode scripts
(
_
,
_
,
filenames
)
=
next
(
iter
(
os
.
walk
(
vyos_conf_scripts_dir
)))
filenames
.
sort
()
load_filenames
=
[
f
for
f
in
filenames
if
f
in
include
]
imports
=
[
key_name_from_file_name
(
f
)
for
f
in
load_filenames
]
module_names
=
[
module_name_from_key
(
k
)
for
k
in
imports
]
paths
=
[
path_from_file_name
(
f
)
for
f
in
load_filenames
]
to_load
=
list
(
zip
(
module_names
,
paths
))
modules
=
[]
for
x
in
to_load
:
spec
=
importlib
.
util
.
spec_from_file_location
(
x
[
0
],
x
[
1
])
module
=
importlib
.
util
.
module_from_spec
(
spec
)
spec
.
loader
.
exec_module
(
module
)
modules
.
append
(
module
)
conf_mode_scripts
=
dict
(
zip
(
imports
,
modules
))
exclude_set
=
{
key_name_from_file_name
(
f
)
for
f
in
filenames
if
f
not
in
include
}
include_set
=
{
key_name_from_file_name
(
f
)
for
f
in
filenames
if
f
in
include
}
@contextmanager
def
stdout_redirected
(
filename
,
mode
):
saved_stdout_fd
=
None
destination_file
=
None
try
:
sys
.
stdout
.
flush
()
saved_stdout_fd
=
os
.
dup
(
sys
.
stdout
.
fileno
())
destination_file
=
open
(
filename
,
mode
)
os
.
dup2
(
destination_file
.
fileno
(),
sys
.
stdout
.
fileno
())
yield
finally
:
if
saved_stdout_fd
is
not
None
:
os
.
dup2
(
saved_stdout_fd
,
sys
.
stdout
.
fileno
())
os
.
close
(
saved_stdout_fd
)
if
destination_file
is
not
None
:
destination_file
.
close
()
def
explicit_print
(
path
,
mode
,
msg
):
try
:
with
open
(
path
,
mode
)
as
f
:
f
.
write
(
f
"
\n
{
msg
}
\n\n
"
)
except
OSError
:
logger
.
critical
(
"error explicit_print"
)
def
run_script
(
script
,
config
,
args
)
->
int
:
script
.
argv
=
args
config
.
set_level
([])
try
:
c
=
script
.
get_config
(
config
)
script
.
verify
(
c
)
script
.
generate
(
c
)
script
.
apply
(
c
)
except
ConfigError
as
e
:
logger
.
critical
(
e
)
explicit_print
(
session_out
,
session_mode
,
str
(
e
))
return
R_ERROR_COMMIT
except
Exception
as
e
:
logger
.
critical
(
e
)
return
R_ERROR_DAEMON
return
R_SUCCESS
def
initialization
(
socket
):
global
session_out
global
session_mode
# Reset config strings:
active_string
=
''
session_string
=
''
# check first for resent init msg, in case of client timeout
while
True
:
msg
=
socket
.
recv
()
.
decode
(
"utf-8"
,
"ignore"
)
try
:
message
=
json
.
loads
(
msg
)
if
message
[
"type"
]
==
"init"
:
resp
=
"init"
socket
.
send
(
resp
.
encode
())
except
:
break
# zmq synchronous for ipc from single client:
active_string
=
msg
resp
=
"active"
socket
.
send
(
resp
.
encode
())
session_string
=
socket
.
recv
()
.
decode
(
"utf-8"
,
"ignore"
)
resp
=
"session"
socket
.
send
(
resp
.
encode
())
pid_string
=
socket
.
recv
()
.
decode
(
"utf-8"
,
"ignore"
)
resp
=
"pid"
socket
.
send
(
resp
.
encode
())
logger
.
debug
(
f
"config session pid is
{
pid_string
}
"
)
try
:
session_out
=
os
.
readlink
(
f
"/proc/
{
pid_string
}
/fd/1"
)
session_mode
=
'w'
except
FileNotFoundError
:
session_out
=
None
# if not a 'live' session, for example on boot, write to file
if
not
session_out
or
not
boot_configuration_complete
():
session_out
=
script_stdout_log
session_mode
=
'a'
try
:
configsource
=
ConfigSourceString
(
running_config_text
=
active_string
,
session_config_text
=
session_string
)
except
ConfigSourceError
as
e
:
logger
.
debug
(
e
)
return
None
config
=
Config
(
config_source
=
configsource
)
return
config
def
process_node_data
(
config
,
data
)
->
int
:
if
not
config
:
logger
.
critical
(
f
"Empty config"
)
return
R_ERROR_DAEMON
script_name
=
None
args
=
[]
res
=
re
.
match
(
r
'^(VYOS_TAGNODE_VALUE=[^/]+)?.*\/([^/]+).py(.*)'
,
data
)
if
res
.
group
(
1
):
env
=
res
.
group
(
1
)
.
split
(
'='
)
os
.
environ
[
env
[
0
]]
=
env
[
1
]
if
res
.
group
(
2
):
script_name
=
res
.
group
(
2
)
if
not
script_name
:
logger
.
critical
(
f
"Missing script_name"
)
return
R_ERROR_DAEMON
if
res
.
group
(
3
):
args
=
res
.
group
(
3
)
.
split
()
args
.
insert
(
0
,
f
'
{
script_name
}
.py'
)
if
script_name
not
in
include_set
:
return
R_PASS
with
stdout_redirected
(
session_out
,
session_mode
):
result
=
run_script
(
conf_mode_scripts
[
script_name
],
config
,
args
)
return
result
def
remove_if_file
(
f
:
str
):
try
:
os
.
remove
(
f
)
except
FileNotFoundError
:
pass
except
OSError
:
raise
def
shutdown
():
remove_if_file
(
configd_env_file
)
os
.
symlink
(
configd_env_unset_file
,
configd_env_file
)
sys
.
exit
(
0
)
if
__name__
==
'__main__'
:
context
=
zmq
.
Context
()
socket
=
context
.
socket
(
zmq
.
REP
)
# Set the right permissions on the socket, then change it back
o_mask
=
os
.
umask
(
0
)
socket
.
bind
(
SOCKET_PATH
)
os
.
umask
(
o_mask
)
cfg_group
=
grp
.
getgrnam
(
CFG_GROUP
)
os
.
setgid
(
cfg_group
.
gr_gid
)
os
.
environ
[
'SUDO_USER'
]
=
'vyos'
os
.
environ
[
'SUDO_GID'
]
=
str
(
cfg_group
.
gr_gid
)
def
sig_handler
(
signum
,
frame
):
shutdown
()
signal
.
signal
(
signal
.
SIGTERM
,
sig_handler
)
signal
.
signal
(
signal
.
SIGINT
,
sig_handler
)
# Define the vyshim environment variable
remove_if_file
(
configd_env_file
)
os
.
symlink
(
configd_env_set_file
,
configd_env_file
)
config
=
None
while
True
:
# Wait for next request from client
msg
=
socket
.
recv
()
.
decode
()
logger
.
debug
(
f
"Received message:
{
msg
}
"
)
message
=
json
.
loads
(
msg
)
if
message
[
"type"
]
==
"init"
:
resp
=
"init"
socket
.
send
(
resp
.
encode
())
config
=
initialization
(
socket
)
elif
message
[
"type"
]
==
"node"
:
res
=
process_node_data
(
config
,
message
[
"data"
])
response
=
res
.
to_bytes
(
1
,
byteorder
=
sys
.
byteorder
)
logger
.
debug
(
f
"Sending response
{
res
}
"
)
socket
.
send
(
response
)
else
:
logger
.
critical
(
f
"Unexpected message:
{
message
}
"
)
File Metadata
Details
Attached
Mime Type
text/x-script.python
Expires
Tue, Dec 16, 3:59 AM (21 h, 22 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3065554
Default Alt Text
vyos-configd (8 KB)
Attached To
Mode
rVYOSONEX vyos-1x
Attached
Detach File
Event Timeline
Log In to Comment