@ -3,8 +3,6 @@
from __future__ import print_function
import argparse
#import fs
#from fs import tempfs, path
import fs . tempfs
import fs . path
import ipaddress
@ -25,6 +23,7 @@ class ConfigDrive:
self . _interfaces = [ ]
self . _pubkeys = [ ]
self . _verbose = verbose
self . _clean_metadata = False
self . _added_resolv_module_call = False
@ -92,6 +91,9 @@ class ConfigDrive:
self . add_command ( " cloud-init single --name cc_resolv_conf " , True )
self . _added_resolv_module_call = True
def set_clean_metadata ( self , do_clean_metadata ) :
self . _clean_metadata = do_clean_metadata
def add_user ( self , name , keys = None , gecos = None , sudo = False , password = None ) :
if " users " not in self . _user_data :
self . _user_data [ " users " ] = [ ]
@ -127,7 +129,6 @@ class ConfigDrive:
if dir_path and not self . _tmpfs . exists ( dir_path ) :
self . _tmpfs . makedirs ( dir_path )
self . _tmpfs . settext ( path , content )
if self . _verbose :
print ( " >> " , path )
@ -147,10 +148,10 @@ class ConfigDrive:
" files " : [ ] ,
" hostname " : self . _hostname ,
" name " : self . _hostname . split ( " . " ) [ 0 ] ,
# "meta": {
# "role": "webservers",
# "essential": False,
# }
# "meta": {
# "role": "webservers",
# "essential": False,
# }
" uuid " : str ( uuid . uuid4 ( ) ) ,
}
@ -165,11 +166,15 @@ class ConfigDrive:
meta_data [ " files " ] . append ( { " content_path " : " /content/0000 " , " path " : " /etc/network/interfaces " } )
self . add_text ( " /openstack/content/0000 " , " \n " . join ( self . _interfaces ) )
meta_data [ " files " ] . append ( { " content_path " : " /content/0001 " , " path " : " /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg " } )
meta_data [ " files " ] . append ( { " content_path " : " /content/0001 " ,
" path " : " /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg " } )
self . add_text ( " /openstack/content/0001 " , " network: { config: disabled} " )
#meta_data["files"].append({"content_path": "/content/0002", "path": "/etc/cloud/cloud.cfg.d/10-resolv-conf.cfg"})
#self.add_text("/openstack/content/0002", "cloud_init_modules:\n - resolv-conf\n")
# do not look for datasource on every boot
if self . _clean_metadata :
meta_data [ " files " ] . append ( { " content_path " : " /content/0002 " ,
" path " : " /etc/cloud/cloud.cfg.d/99-manual-cache-clean.cfg " } )
self . add_text ( " /openstack/content/0002 " , " manual_cache_clean: True " )
if self . _pubkeys :
meta_data [ " public_keys " ] = { }
@ -196,18 +201,26 @@ class ConfigDrive:
if " chpasswd " not in self . _user_data :
self . _user_data [ " chpasswd " ] = { }
self . _user_data [ " chpasswd " ] [ " list " ] = " "
# self._user_data["chpasswd"]["list"] = []
# self._user_data["chpasswd"]["list"] = []
# self._user_data["chpasswd"]["list"].append("%s:%s" % (user, password))
# self._user_data["chpasswd"]["list"].append("%s:%s" % (user, password))
self . _user_data [ " chpasswd " ] [ " list " ] + = " %s : %s \n " % ( user , password )
def _write_userdata ( self ) :
self . add_text ( " /openstack/latest/user_data " , " #cloud-config \n " + json . dumps ( self . _user_data , indent = 4 ) )
def write_ iso( self , path ) :
def write_ drive( self , path , fmt ) :
self . _write_metadata ( )
self . _write_userdata ( )
if fmt == " iso " :
self . _write_iso ( path )
elif fmt == " tgz " :
self . _write_tgz ( path )
else :
raise ValueError ( " Unknown format " )
def _write_iso ( self , path ) :
p = subprocess . Popen ( [ self . _genisoimage ,
" -J " , " -r " , " -q " ,
" -V " , " config-2 " ,
@ -220,6 +233,10 @@ class ConfigDrive:
return p . wait ( )
def _write_tgz ( self , path ) :
p = subprocess . Popen ( [ " tar " , " cfz " , path , " -C " , self . _tmpfs . getsyspath ( " " ) , " . " ] )
return p . wait ( )
def close ( self ) :
if self . _tmpfs :
self . _tmpfs . close ( )
@ -232,7 +249,8 @@ class ConfigDrive:
# cfgdrv.add_command("rm -rf /home/debian/; userdel debian; groupdel debian", True)
# cfgdrv.add_command("cloud-init single --name cc_resolv_conf", True)
# cfgdrv.add_command("rm -f /etc/network/interfaces.d/eth*.cfg", True)
# cfgdrv.add_command("sed -rni '/^([^#]|## template)/p' /etc/cloud/templates/sources.list.*.tmpl; rm /etc/apt/sources.list.d/*", True)
# cfgdrv.add_command("sed -rni '/^([^#]|## template)/p' /etc/cloud/templates/sources.list.*.tmpl; "
# "rm /etc/apt/sources.list.d/*", True)
# #cfgdrv.add_command("(whoami; date) > /root/bleep", False)
# cfgdrv.add_pubkey("ssh-rsa bleep foo")
# cfgdrv.set_password("root", "kitteh")
@ -244,16 +262,28 @@ def main():
parser . add_argument ( " -H " , " --hostname " , required = True , help = " Hostname " )
parser . add_argument ( " -o " , " --output " , required = True , help = " Path to write iso to " )
parser . add_argument ( " -n " , " --nameservers " , " --ns " , default = [ " 1.1.1.1 " , " 8.8.8.8 " ] , nargs = " + " , help = " Nameservers " )
parser . add_argument ( " -i " , " --networks " , " --net " , default = [ ] , nargs = " + " , help = " Specify all networks, in format of interface[:address:[gateway]]. Both : and ; can be used as delimiter (but only one per net config). Address MUST be a network in CIDR notation " )
parser . add_argument ( " -i " , " --networks " , " --net " , default = [ ] , nargs = " + " ,
help = " Specify all networks, in format of interface[:address:[gateway]]. "
" Both : and ; can be used as delimiter (but only one per net config). "
" Address MUST be a network in CIDR notation " )
parser . add_argument ( " -u " , " --disable-upgrades " , action = " store_true " , default = False )
parser . add_argument ( " -v " , " --verbose " , action = " store_true " , default = False )
parser . add_argument ( " --no-debian-cleanup " , " --ndc " , action = " store_true " , default = False )
parser . add_argument ( " --set-root-password " , " --srp " , default = None )
parser . add_argument ( " -a " , " --add-user " , default = [ ] , nargs = " + " , help = " Add users, format is username:key?:sudo?:gecos?:password?, sudo is a bool, key is either an ssh key or a path to an ssh key " )
parser . add_argument ( " -a " , " --add-user " , default = [ ] , nargs = " + " ,
help = " Add users, format is username:key?:sudo?:gecos?:password?, "
" sudo is a bool, key is either an ssh key or a path to an ssh key " )
args = parser . parse_args ( )
if not args . format :
if args . output . endswith ( " .tar.gz " ) or args . output . endswith ( " .tgz " ) :
args . format = " tgz "
elif args . output . endswith ( " .iso " ) :
args . format = " iso "
else :
parser . error ( " Could not infer output format from output file extension " )
cfgdrv = None
try :
cfgdrv = ConfigDrive ( verbose = args . verbose )
@ -261,10 +291,7 @@ def main():
cfgdrv . set_hostname ( args . hostname )
for net in args . networks :
if " ; " in net :
net = net . split ( " ; " )
else :
net = net . split ( " : " )
net = net . split ( " : " )
cfgdrv . conf_network ( * net )
if args . nameservers :
@ -275,9 +302,10 @@ def main():
if not args . no_debian_cleanup :
cfgdrv . add_command ( " rm -f /etc/network/interfaces.d/eth* " , True )
cfgdrv . add_command ( " sed -rni ' /^([^#]|## template)/p ' /etc/cloud/templates/sources.list.*.tmpl; rm /etc/apt/sources.list.d/* " , True )
cfgdrv . add_command ( " sed -rni ' /^([^#]|## template)/p ' /etc/resolv.conf /etc/cloud/templates/resolv.conf.tmpl " , True )
cfgdrv . add_command ( " sed -rni ' /^([^#]|## template)/p ' /etc/cloud/templates/sources.list.*.tmpl; "
" rm /etc/apt/sources.list.d/* " , True )
cfgdrv . add_command ( " sed -rni ' /^([^#]|## template)/p ' "
" /etc/resolv.conf /etc/cloud/templates/resolv.conf.tmpl " , True )
if args . set_root_password :
cfgdrv . set_password ( " root " , args . set_root_password )
@ -311,11 +339,11 @@ def main():
cfgdrv . add_user ( user [ 0 ] , keys , sudo = sudo , gecos = gecos , password = password )
if args . output :
ret = cfgdrv . write_iso ( args . output )
sys . exit ( ret )
cfgdrv . write_iso ( args . output )
finally :
if cfgdrv :
cfgdrv . close ( )
if __name__ == ' __main__ ' :
main ( )