Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
S
slapos.core
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Xavier Thompson
slapos.core
Commits
8a0b4b16
Commit
8a0b4b16
authored
Oct 25, 2022
by
Xavier Thompson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
WIP
parent
07359452
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
218 additions
and
147 deletions
+218
-147
slapos/format.py
slapos/format.py
+218
-147
No files found.
slapos/format.py
View file @
8a0b4b16
...
@@ -25,31 +25,43 @@
...
@@ -25,31 +25,43 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
##############################################################################
##############################################################################
import
configparser
import
ipaddress
import
logging
import
netifaces
from
netifaces
import
AF_INET
,
AF_INET6
from
typing
import
List
,
Union
def
do_format
(
conf
):
def
do_format
(
conf
):
# load configuration
computer
=
Computer
(
conf
)
computer
=
Computer
(
conf
)
computer
.
check
()
# sanity checks
computer
.
validate
()
# format
computer
.
format
()
computer
.
format
()
# ??
computer
.
update
()
computer
.
update
()
# send to master
computer
.
send
()
computer
.
send
()
class
FormatConfig
(
object
):
class
Parameters
(
object
):
class
Parameters
(
object
):
master_url
:
str
master_url
:
str
computer_id
:
str
computer_id
:
str
instance_root
:
str
instance_root
:
str
software_root
:
str
software_root
:
str
partition_amount
:
int
partition_amount
:
int
class
FileOptions
(
object
):
class
Options
(
object
):
input_definition_file
:
str
=
None
computer_cache_file
:
str
=
None
key_file
:
str
=
None
key_file
:
str
=
None
cert_file
:
str
=
None
cert_file
:
str
=
None
master_ca_file
:
str
=
None
input_definition_file
:
str
=
None
class
Options
(
object
):
log_file
:
str
=
None
log_file
:
str
=
None
dry_run
:
bool
=
False
dry_run
:
bool
=
False
...
@@ -72,6 +84,18 @@ class FormatConfig(object):
...
@@ -72,6 +84,18 @@ class FormatConfig(object):
tun_ipv6
:
bool
=
True
tun_ipv6
:
bool
=
True
tun_ipv4_network
:
str
=
'172.16.0.0/12'
tun_ipv4_network
:
str
=
'172.16.0.0/12'
class
FormatConfig
(
Parameters
,
Options
):
DEPRECATED
=
[
'bridge_name'
,
'no_bridge_'
,
'master_ca_file'
,
'alter_network'
,
'alter_user'
,
'computer_json'
,
'computer_xml'
,
]
CHECK_FILES
=
[
'key_file'
,
'cert_file'
,
'input_definition_file'
]
logger
:
logging
.
Logger
logger
:
logging
.
Logger
def
__init__
(
self
,
logger
):
def
__init__
(
self
,
logger
):
...
@@ -85,54 +109,64 @@ class FormatConfig(object):
...
@@ -85,54 +109,64 @@ class FormatConfig(object):
def
parse
(
self
,
name
,
value
,
t
):
def
parse
(
self
,
name
,
value
,
t
):
if
not
isinstance
(
value
,
str
):
if
not
isinstance
(
value
,
str
):
if
not
isinstance
(
value
,
t
):
if
not
isinstance
(
value
,
t
):
self
.
error
(
"Option %s
is not of type %s"
,
name
,
t
.
__name__
)
self
.
error
(
"Option %s
takes type %s, not %r"
,
name
,
t
.
__name__
,
value
)
return
value
return
value
if
t
in
(
int
,):
if
t
in
(
int
,):
try
:
try
:
return
t
(
value
)
return
t
(
value
)
except
ValueError
:
except
ValueError
:
self
.
error
(
"Option %s
must be an integer, not %r"
,
name
,
value
)
self
.
error
(
"Option %s
takes type %s, not %r"
,
name
,
t
.
__name__
,
value
)
if
t
is
bool
:
if
t
is
bool
:
try
:
try
:
return
{
'true'
:
True
,
'false'
:
False
}[
value
.
lower
()]
return
{
'true'
:
True
,
'false'
:
False
}[
value
.
lower
()]
except
KeyError
:
except
KeyError
:
self
.
error
(
"Option %r must be 'True' or 'False', not %r"
,
name
,
value
)
self
.
error
(
"Option %r must be 'true' or 'false', not %r"
,
name
,
value
)
def
get
(
self
,
option
):
try
:
return
gettatr
(
self
,
option
)
except
AttributeError
:
self
.
error
(
"Parameter %r is not defined"
,
option
)
def
mergeConfig
(
self
,
args
,
configp
):
def
mergeConfig
(
self
,
args
,
configp
):
for
cls
in
(
self
.
FileOptions
,
self
.
Options
):
self
.
__dict__
.
update
({
k
:
cls
.
__dict__
[
k
]
for
k
in
cls
.
__annotations__
})
# args (from command line) override configp (from cfg) options
# args (from command line) override configp (from cfg) options
for
section
in
(
'slapformat'
,
'slapos'
):
for
section
in
(
'slapformat'
,
'slapos'
):
self
.
__dict__
.
update
(
configp
.
items
(
section
))
self
.
__dict__
.
update
(
configp
.
items
(
section
))
self
.
__dict__
.
update
(
args
.
__dict__
)
self
.
__dict__
.
update
(
args
.
__dict__
)
def
setConfig
(
self
):
def
setConfig
(
self
):
for
parameter
,
t
in
self
.
Parameters
.
__annotations__
.
items
():
for
option
in
self
.
DEPRECATED
:
try
:
if
option
in
self
.
__dict__
:
value
=
gettatr
(
self
,
parameter
)
if
option
==
'computer_xml'
:
except
AttributeError
:
self
.
error
(
raise
UsageError
(
"Parameter %r is not defined"
%
option
)
"Option %r is no longer supported
\
n
"
setattr
(
self
,
parameter
,
self
.
parse
(
parameters
,
value
,
t
))
"Use --output_definition_file to migrate existing computer_xml"
", then use the generated file as input_definition_file"
,
option
)
else
:
self
.
error
(
"Option %r is no longer supported"
%
option
)
for
option
,
t
in
Parameters
.
__annotations__
.
items
():
setattr
(
self
,
option
,
self
.
parse
(
option
,
self
.
get
(
option
),
t
))
for
option
,
t
in
self
.
Options
.
__annotations__
.
items
():
for
option
,
t
in
self
.
__annotations__
.
items
():
setattr
(
self
,
option
,
self
.
parse
(
option
,
getattr
(
self
,
option
),
t
))
setattr
(
self
,
option
,
self
.
parse
(
option
,
getattr
(
self
,
option
),
t
))
for
option
in
self
.
FileOptions
.
__annotations__
:
for
option
in
self
.
CHECK_FILES
:
path
=
getattr
(
self
,
option
)
path
=
getattr
(
self
,
option
)
if
path
is
not
None
and
not
os
.
path
.
exists
(
path
):
if
path
is
not
None
and
not
os
.
path
.
exists
(
path
):
self
.
error
(
"File %r does not exist or is not readable"
,
path
)
self
.
error
(
"File %r does not exist or is not readable"
,
path
)
setattr
(
self
,
option
,
os
.
path
.
abspath
(
path
))
setattr
(
self
,
option
,
os
.
path
.
abspath
(
path
))
# XXX
# XXX Check command line tools + Logs
# Check command line tools
# Logs
class
Computer
(
object
):
class
Computer
(
object
):
reference
:
str
reference
:
str
interface
:
Interface
interface
:
Interface
partitions
:
[
Partitions
]
partitions
:
List
[
Partition
]
address
:
ipaddress
.
IPv4Interface
,
ipaddress
.
IPv6Interface
address
:
Union
[
ipaddress
.
IPv4Interface
,
ipaddress
.
IPv6Interface
]
user
:
User
user
:
User
conf
:
FormatConfig
conf
:
FormatConfig
...
@@ -141,64 +175,96 @@ class Computer(object):
...
@@ -141,64 +175,96 @@ class Computer(object):
self
.
reference
=
conf
.
computer_id
self
.
reference
=
conf
.
computer_id
self
.
interface
=
Interface
(
conf
)
self
.
interface
=
Interface
(
conf
)
if
conf
.
input_definition_file
:
if
conf
.
input_definition_file
:
computer_
definition
=
configparser
.
ConfigParser
(
interpolation
=
None
)
definition
=
configparser
.
ConfigParser
(
interpolation
=
None
)
computer_
definition
.
read
(
conf
.
input_definition_file
)
definition
.
read
(
conf
.
input_definition_file
)
self
.
from_definition
(
conf
,
computer_
definition
)
self
.
from_definition
(
conf
,
definition
)
else
:
else
:
self
.
from_conf
(
conf
)
self
.
from_conf
(
conf
)
def
from_definition
(
self
,
conf
,
computer_definition
)
def
from_definition
(
self
,
conf
,
definition
)
self
.
partitions
=
[
amount
=
conf
.
partition_amount
Partition
.
from_definition
(
index
,
conf
,
computer_definition
)
self
.
partitions
=
[
Partition
(
i
,
conf
,
definition
)
for
i
in
range
(
amount
)]
for
index
in
conf
.
partition_amount
address
=
definition
.
get
(
'computer'
,
'address'
,
fallback
=
None
)
# XXX fallback
]
address
=
computer_definition
.
get
(
'computer'
,
'address'
,
fallback
=
None
)
# XXX
self
.
address
=
ipaddress
.
ip_interface
(
address
)
self
.
address
=
ipaddress
.
ip_interface
(
address
)
username
=
computer_definition
.
get
(
username
=
definition
.
get
(
'computer'
,
'software_user'
,
fallback
=
conf
.
software_user
)
'computer'
,
'software_user'
,
fallback
=
conf
.
software_user
)
self
.
user
=
User
(
username
)
self
.
user
=
User
(
conf
,
username
)
def
from_conf
(
self
,
conf
):
def
from_conf
(
self
,
conf
):
self
.
partitions
=
[
amount
=
conf
.
partition_amount
Partition
.
from_conf
(
conf
)
self
.
partitions
=
[
Partition
(
i
,
conf
)
for
i
in
range
(
amount
)]
for
index
in
conf
.
partition_amount
]
self
.
address
=
None
# XXX
self
.
address
=
None
# XXX
self
.
user
=
User
(
conf
,
conf
.
software_user
)
self
.
user
=
User
(
conf
.
software_user
)
def
check
(
self
):
def
validate
(
self
):
pass
pass
def
format
(
self
):
def
format
(
self
):
pass
pass
def
update
(
self
):
pass
def
send
(
self
):
pass
class
Interface
(
object
):
class
Interface
(
object
):
ipv4_interface
:
str
ipv4_interface
:
str
ipv6_interface
:
str
ipv6_interface
:
str
ipv4_
local_
network
:
ipaddress
.
IPv4Network
ipv4_network
:
ipaddress
.
IPv4Network
ipv6_network
:
ipaddress
.
IPv6Network
ipv6_network
:
ipaddress
.
IPv6Network
conf
:
FormatConfig
conf
:
FormatConfig
def
__init__
(
self
,
conf
):
def
__init__
(
self
,
conf
):
self
.
conf
self
.
conf
=
conf
self
.
ipv4_interface
=
conf
.
interface_name
self
.
ipv4_interface
=
conf
.
interface_name
self
.
ipv6_interface
=
conf
.
ipv6_interface
or
conf
.
interface_name
self
.
ipv6_interface
=
conf
.
ipv6_interface
or
conf
.
interface_name
self
.
ipv4_local_network
=
conf
.
ipv4_local_network
self
.
ipv4_network
=
self
.
getIPv4Network
(
conf
.
ipv4_local_network
)
self
.
ipv6_interface_address
=
self
.
getIPv6Network
()
self
.
ipv6_network
=
self
.
getIPv6Network
()
def
getIPv4Network
(
self
,
cidr
):
if
cidr
:
# XXX allow ipv4_local_network to be None ?
return
ipaddress
.
IPv4Network
(
cidr
,
strict
=
False
)
def
getIPv6InterfaceAddress
(
self
):
def
getPartitionIPv4
(
self
,
index
):
return
self
.
ipv4_network
[
index
+
2
]
def
getIPv6Network
(
self
):
try
:
try
:
if_addresses
=
netifaces
.
ifaddresses
(
self
.
ipv6_interface
)[
socket
.
AF_INET6
]
addresses
=
netifaces
.
ifaddresses
(
self
.
ipv6_interface
)[
AF_INET6
]
except
KeyError
:
except
KeyError
:
self
.
conf
.
error
(
"%s must have at least one IPv6 address assigned"
,
self
.
ipv6_interface
)
self
.
conf
.
error
(
addr
=
None
"%s must have at least one IPv6 address assigned"
,
for
a
in
if_addresses
:
self
.
ipv6_interface
ip
=
ipaddress
.
ip_interface
((
q
[
'addr'
]
split
(
'%'
)[
0
],
q
[
'netmask'
]))
)
if
isGlobalScopeAddress
(
ip
):
result
=
None
if
not
addr
or
ip
.
network
.
num_addresses
>
addr
.
network
.
num_addresses
:
for
a
in
addresses
:
addr
=
ip
address
=
a
[
'addr'
].
split
(
'%'
)[
0
]
return
addr
netmask
=
a
[
'netmask'
].
split
(
'/'
)[
-
1
]
ip
=
ipaddress
.
IPv6Interface
(
'%s/%s'
%
(
address
,
netmask
))
network
=
ip
.
network
if
network
.
is_global
:
if
not
result
or
network
.
prefixlen
<
result
.
prefixlen
:
result
=
network
return
result
def
getComputerIPv6Addr
(
self
):
network
=
self
.
ipv6_network
return
ipaddress
.
ip_interface
((
network
[
1
],
network
.
prefixlen
))
def
getPartitionIPv6Addr
(
self
,
index
):
network
=
self
.
ipv6_network
return
ipaddress
.
ip_interface
((
network
[
index
+
2
],
network
.
prefixlen
))
def
getPartitionIPv6Range
(
self
,
index
):
network
=
self
.
ipv6_network
prefixlen
=
network
.
prefixlen
+
16
if
prefixlen
>
128
:
self
.
conf
.
error
(
"IPv6 network %s is too small for IPv6 ranges"
,
network
)
bits
=
128
-
network
.
prefixlen
addr
=
network
[(
1
<<
(
bits
-
2
))
+
(
i
<<
14
)]
return
ipaddress
.
ip_interface
((
addr
,
prefixlen
))
class
Partition
(
object
):
class
Partition
(
object
):
...
@@ -206,78 +272,76 @@ class Partition(object):
...
@@ -206,78 +272,76 @@ class Partition(object):
index
:
int
index
:
int
path
:
str
path
:
str
user
:
User
user
:
User
address_list
:
list
ipv4_list
:
List
[
ipaddress
.
IPv4Interface
]
ipv6_list
:
List
[
ipaddress
.
IPv6Interface
]
ipv6_range
:
ipaddress
.
IPv6Interface
tap
:
Tap
tap
:
Tap
tun
:
Tun
tun
:
Tun
external_storage_list
:
list
def
__init__
(
self
,
**
kwargs
):
def
__init__
(
self
,
index
,
computer
,
definition
=
None
):
self
.
__dict__
.
update
(
kwargs
)
self
.
from_conf
(
index
,
computer
)
if
definition
:
self
.
from_definition
(
index
,
computer
.
conf
,
definition
)
@
classmethod
def
from_definition
(
cls
,
index
,
conf
,
definition
):
def
from_definition
(
cls
,
index
,
conf
,
computer_definition
):
section
=
'partition_%d'
%
index
section
=
'partition_%d'
%
index
if
computer_definition
.
has_section
(
section
):
options
=
{}
reference
=
computer_definition
.
get
(
section
,
'pathname'
)
if
definition
.
has_section
(
'default'
):
path
=
os
.
path
.
join
(
conf
.
instance_root
,
reference
)
options
.
update
(
definition
.
items
(
'default'
))
user
=
User
(
conf
,
computer_definition
.
get
(
section
,
'user'
),
path
)
if
definition
.
has_section
(
section
):
address_list
=
[
options
.
update
(
definition
.
items
(
section
))
ipaddress
.
ip_interface
(
addr
)
if
'pathname'
in
options
:
for
add
in
computer_definition
.
get
(
section
,
'address'
)
self
.
reference
=
options
[
'pathname'
]
]
# XXX fallback
self
.
path
=
os
.
path
.
join
(
conf
.
instance_root
,
self
.
reference
)
if
'user'
in
options
:
self
.
user
=
User
(
options
[
'user'
],
self
.
path
)
if
'address'
in
options
:
address_list
=
[
ipaddress
.
ip_interface
(
a
)
for
a
in
options
[
'address'
]]
for
v
in
(
4
,
6
):
ip_list
=
[
ip
for
ip
in
ip_addresses
if
ip
.
version
==
v
]
if
ip_list
:
setattr
(
self
,
'ipv%d_list'
%
v
,
ip_list
)
# tap = Tap(computer_definition.get(section, 'network_interface'))
# tap = Tap(computer_definition.get(section, 'network_interface'))
# tun = Tun.load(conf, index)
# tun = Tun.load(conf, index)
return
Partition
(
reference
=
reference
,
index
=
index
,
path
=
path
,
user
=
user
,
address_list
=
address_list
,
# tap = tap,
# tun = tun
)
# XXX [default] ??
else
:
return
cls
.
from_conf
(
index
,
conf
)
@
classmethod
def
from_conf
(
self
,
index
,
computer
):
def
from_conf
(
cls
,
conf
):
conf
=
computer
.
conf
reference
=
'%s%d'
%
(
conf
.
partition_base_name
,
i
)
self
.
reference
=
'%s%d'
%
(
conf
.
partition_base_name
,
index
)
path
=
os
.
path
.
join
(
conf
.
instance_root
,
reference
)
self
.
path
=
os
.
path
.
join
(
conf
.
instance_root
,
self
.
reference
)
user
=
User
(
conf
,
'%s%d'
%
(
conf
.
user_base_name
,
i
),
path
)
self
.
user
=
User
(
'%s%d'
%
(
conf
.
user_base_name
,
index
),
self
.
path
)
address_list
=
None
# XXX
self
.
ipv4_list
=
[
computer
.
interface
.
getPartitionIPv4
(
index
)]
return
Partition
(
self
.
ipv6_list
=
[
computer
.
interface
.
getPartitionIPv6
(
index
)]
reference
=
reference
,
# XXX Tap & tun
path
=
path
,
user
=
user
,
def
createPath
(
self
):
address_list
=
address_list
,
self
.
path
=
os
.
path
.
abspath
(
self
.
path
)
# tap = tap,
owner
=
self
.
user
if
self
.
user
else
User
(
'root'
)
# tun = tun,
if
not
os
.
path
.
exists
(
self
.
path
):
)
os
.
mkdir
(
self
.
path
,
0o750
)
owner_pw
=
pwd
.
getpwnam
(
owner
.
name
)
os
.
chown
(
self
.
path
,
owner_pw
.
pw_uid
,
owner_pw
.
pw_gid
)
os
.
chmod
(
self
.
path
,
0o750
)
class
User
(
object
):
class
User
(
object
):
name
:
str
name
:
str
path
:
str
path
:
str
additional_group_list
:
list
groups
:
List
[
str
]
SHELL
=
'/bin/sh'
SHELL
=
'/bin/sh'
def
__init__
(
self
,
conf
,
name
,
path
=
None
,
additional_group_list
=
None
):
def
__init__
(
self
,
name
,
path
,
groups
=
None
):
self
.
alter_user
=
conf
.
alter_user
self
.
name
=
name
self
.
name
=
name
self
.
path
=
path
self
.
path
=
path
self
.
additional_group_list
=
additional_group_list
self
.
groups
=
groups
def
apply
(
self
):
def
create
(
self
):
if
not
self
.
alter_user
:
return
grpname
=
'grp_'
+
self
.
name
if
sys
.
platform
==
'cygwin'
else
self
.
name
grpname
=
'grp_'
+
self
.
name
if
sys
.
platform
==
'cygwin'
else
self
.
name
if
not
self
.
isGroupAvailable
(
grpname
):
if
not
self
.
isGroupAvailable
(
grpname
):
callAndRead
([
'groupadd'
,
grpname
])
callAndRead
([
'groupadd'
,
grpname
])
user_parameter_list
=
[
'-d'
,
self
.
path
,
'-g'
,
self
.
name
,
'-s'
,
self
.
SHELL
]
user_parameter_list
=
[
'-d'
,
self
.
path
,
'-g'
,
self
.
name
,
'-s'
,
self
.
SHELL
]
if
self
.
additional_group_list
:
if
self
.
groups
:
user_parameter_list
.
extend
([
'-G'
,
','
.
join
(
self
.
additional_group_list
)
])
user_parameter_list
.
extend
([
'-G'
,
','
.
join
(
self
.
groups
),
'-a'
])
user_parameter_list
.
append
(
self
.
name
)
user_parameter_list
.
append
(
self
.
name
)
if
self
.
isUserAvailable
(
self
.
name
):
if
self
.
isUserAvailable
(
self
.
name
):
# if the user is already created and used we should not fail
# if the user is already created and used we should not fail
...
@@ -305,10 +369,17 @@ class User(object):
...
@@ -305,10 +369,17 @@ class User(object):
return
False
return
False
# Utils
# Util
itie
s
def
isGlobalScopeAddress
(
ip
):
def
callAndRead
(
argument_list
,
raise_on_error
=
True
):
return
(
popen
=
subprocess
.
Popen
(
not
ip
.
is_link_local
and
not
ip
.
is_loopback
argument_list
,
and
not
ip
.
is_reserved
and
not
ip
.
is_multicast
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
STDOUT
,
universal_newlines
=
True
,
)
)
result
=
popen
.
communicate
()[
0
]
if
raise_on_error
and
popen
.
returncode
!=
0
:
raise
ValueError
(
'Issue while invoking %r, result was:
\
n
%s'
%
(
argument_list
,
result
))
return
popen
.
returncode
,
result
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment