@@ -1243,15 +1243,20 @@ def _func(value):
12431243
12441244 return password
12451245
1246+ # Type aliases for config_create parameters
1247+ ConfigCreateDevice = Union ["Disk" , "Volume" , Dict [str , Any ]]
1248+ ConfigCreateDisk = Union ["Disk" , int ]
1249+ ConfigCreateVolume = Union ["Volume" , int ]
1250+
12461251 # create derived objects
12471252 def config_create (
12481253 self ,
12491254 kernel = None ,
12501255 label = None ,
1251- devices = [] ,
1252- disks = [] ,
1253- volumes = [] ,
1254- interfaces = [] ,
1256+ devices = None ,
1257+ disks = None ,
1258+ volumes = None ,
1259+ interfaces = None ,
12551260 ** kwargs ,
12561261 ):
12571262 """
@@ -1265,15 +1270,19 @@ def config_create(
12651270 :param volumes: The volumes, starting after the last disk, to map to this
12661271 config
12671272 :param devices: A list of devices to assign to this config, in device
1268- index order. Values must be of type Disk or Volume. If this is
1269- given, you may not include disks or volumes.
1273+ index order, a raw device mapping dict to pass directly to the API
1274+ (e.g. ``{"sda": {"disk_id": 123}, "sdb": Volume(...)}``), or
1275+ a single Disk or Volume.
1276+ If this is given, you may not include disks or volumes.
12701277 :param **kwargs: Any other arguments accepted by the api.
12711278
12721279 :returns: A new Linode Config
12731280 """
12741281 # needed here to avoid circular imports
12751282 from .volume import Volume # pylint: disable=import-outside-toplevel
12761283
1284+ interfaces = [] if interfaces is None else interfaces
1285+
12771286 hypervisor_prefix = "sd" if self .hypervisor == "kvm" else "xvd"
12781287
12791288 device_limit = int (
@@ -1288,52 +1297,82 @@ def config_create(
12881297 for suffix in generate_device_suffixes (device_limit )
12891298 ]
12901299
1291- device_map = {
1292- device_names [i ]: None for i in range (0 , len (device_names ))
1293- }
1300+ def _flatten_device (device : Optional [Union [Disk , Volume ]]):
1301+ if device is None :
1302+ return None
1303+ elif isinstance (device , Disk ):
1304+ return {"disk_id" : device .id }
1305+ elif isinstance (device , Volume ):
1306+ return {"volume_id" : device .id }
1307+
1308+ raise TypeError ("Disk or Volume expected!" )
1309+
1310+ def _device_entry (device : Union [Disk , Volume , int ], key : str ):
1311+ return (
1312+ {key : device if isinstance (device , int ) else device .id }
1313+ if isinstance (device , int )
1314+ else _flatten_device (device )
1315+ )
1316+
1317+ def _build_devices ():
1318+ # Devices is a dict, pass through
1319+ if isinstance (devices , dict ):
1320+ return {
1321+ k : (
1322+ _flatten_device (v )
1323+ if isinstance (v , (Disk , Volume ))
1324+ else v
1325+ )
1326+ for k , v in devices .items ()
1327+ }
1328+
1329+ device_list = []
1330+
1331+ # "Devices" was provided
1332+ if devices :
1333+ devices_norm = (
1334+ devices if isinstance (devices , (list , tuple )) else [devices ]
1335+ )
1336+
1337+ device_list += [
1338+ _flatten_device (device ) for device in devices_norm
1339+ ]
12941340
1341+ if disks :
1342+ disks_norm = (
1343+ disks if isinstance (disks , (list , tuple )) else [disks ]
1344+ )
1345+
1346+ device_list += [
1347+ _device_entry (disk , "disk_id" ) for disk in disks_norm
1348+ ]
1349+
1350+ if volumes :
1351+ volumes_norm = (
1352+ volumes if isinstance (volumes , (list , tuple )) else [volumes ]
1353+ )
1354+
1355+ device_list += [
1356+ _device_entry (volume , "volume_id" )
1357+ for volume in volumes_norm
1358+ ]
1359+
1360+ return {
1361+ device_names [i ]: device for i , device in enumerate (device_list )
1362+ }
1363+
1364+ # This validation is enforced for backwards compatibility but isn't
1365+ # technically needed anymore
12951366 if devices and (disks or volumes ):
12961367 raise ValueError (
12971368 'You may not call config_create with "devices" and '
12981369 'either of "disks" or "volumes" specified!'
12991370 )
13001371
1301- if not devices :
1302- if not isinstance (disks , list ):
1303- disks = [disks ]
1304- if not isinstance (volumes , list ):
1305- volumes = [volumes ]
1306-
1307- devices = []
1308-
1309- for d in disks :
1310- if d is None :
1311- devices .append (None )
1312- elif isinstance (d , Disk ):
1313- devices .append (d )
1314- else :
1315- devices .append (Disk (self ._client , int (d ), self .id ))
1316-
1317- for v in volumes :
1318- if v is None :
1319- devices .append (None )
1320- elif isinstance (v , Volume ):
1321- devices .append (v )
1322- else :
1323- devices .append (Volume (self ._client , int (v )))
1324-
1325- if not devices :
1326- raise ValueError ("Must include at least one disk or volume!" )
1372+ device_map = _build_devices ()
13271373
1328- for i , d in enumerate (devices ):
1329- if d is None :
1330- pass
1331- elif isinstance (d , Disk ):
1332- device_map [device_names [i ]] = {"disk_id" : d .id }
1333- elif isinstance (d , Volume ):
1334- device_map [device_names [i ]] = {"volume_id" : d .id }
1335- else :
1336- raise TypeError ("Disk or Volume expected!" )
1374+ if len (device_map ) < 1 :
1375+ raise ValueError ("Must include at least one disk or volume!" )
13371376
13381377 param_interfaces = []
13391378 for interface in interfaces :
@@ -1845,8 +1884,8 @@ def clone(
18451884 to_linode = None ,
18461885 region = None ,
18471886 instance_type = None ,
1848- configs = [] ,
1849- disks = [] ,
1887+ configs = None ,
1888+ disks = None ,
18501889 label = None ,
18511890 group = None ,
18521891 with_backups = None ,
@@ -1902,6 +1941,9 @@ def clone(
19021941 'You may only specify one of "to_linode" and "region"'
19031942 )
19041943
1944+ configs = [] if configs is None else configs
1945+ disks = [] if disks is None else disks
1946+
19051947 if region and not type :
19061948 raise ValueError ('Specifying a region requires a "service" as well' )
19071949
0 commit comments