@@ -321,13 +321,28 @@ def _perform_flash_operation(
321321 """
322322 with self ._busybox () as console :
323323 manifest = self .manifest
324- target = partition or self .call ("get_default_target" ) or manifest .spec .default_target
324+ # Check if partition is a valid target device name in the manifest
325+ # If not, treat it as a partition label and use the default target
326+ partition_label = None
327+ if partition and partition in manifest .spec .targets :
328+ target = partition
329+ else :
330+ # partition is a partition label, not a target device
331+ partition_label = partition
332+ target = self .call ("get_default_target" ) or manifest .spec .default_target
325333 if not target :
326334 raise ArgumentError ("No partition or default target specified" )
327335
328336 target_device = self ._get_target_device (target , manifest , console )
329337
330- self .logger .info (f"Using target block device: { target_device } " )
338+ # If a partition label is provided, construct the partition path
339+ flash_target = target_device
340+ if partition_label :
341+ flash_target = f"/dev/disk/by-partlabel/{ partition_label } "
342+ self .logger .info (f"Using target partition: { partition_label } ({ flash_target } )" )
343+ else :
344+ self .logger .info (f"Using target block device: { target_device } " )
345+
331346 console .sendline (f"export dhcp_addr={ self ._dhcp_details .ip_address } " )
332347 console .expect (manifest .spec .login .prompt , timeout = EXPECT_TIMEOUT_DEFAULT )
333348 console .sendline (f"export gw_addr={ self ._dhcp_details .gateway } " )
@@ -372,7 +387,7 @@ def _perform_flash_operation(
372387 manifest ,
373388 path ,
374389 image_url ,
375- target_device ,
390+ flash_target ,
376391 insecure_tls ,
377392 stored_cacert ,
378393 header_args ,
@@ -385,7 +400,7 @@ def _perform_flash_operation(
385400 manifest ,
386401 path ,
387402 image_url ,
388- target_device ,
403+ flash_target ,
389404 insecure_tls ,
390405 stored_cacert ,
391406 header_args ,
@@ -1162,15 +1177,82 @@ def _validate_bearer_token(self, token: str | None) -> str | None:
11621177
11631178 return token
11641179
1180+ def _resolve_flash_parameters (
1181+ self , file : str | None , partitions : tuple [str , ...] | None , block_device : str | None
1182+ ) -> list [tuple [str , str | None , str | None ]]:
1183+ """Resolve and validate flash parameters from CLI options.
1184+
1185+ Supports multiple modes:
1186+ 1. Single file to default block device: flash image.img
1187+ 2. Single file to specific block device: flash image.img --target usd
1188+ 3. Multiple file-partition pairs: flash -t rootfs:rootfs.img -t boot:boot.img
1189+ 4. Multiple file-partition pairs to specific block device: flash --target emmc -t rootfs:rootfs.img -t boot:boot.img
1190+
1191+ Args:
1192+ file: The image file argument (positional, optional). When provided alone,
1193+ flashes to the default block device.
1194+ partitions: The -t options in 'partition:file' format (repeatable).
1195+ Use this to specify partition and file together.
1196+ block_device: The --target option (block device name like 'usd', 'emmc').
1197+ Can be used with both file and -t options.
1198+
1199+ Returns:
1200+ list[tuple]: List of (image_file, target_partition, block_device) tuples
1201+
1202+ Raises:
1203+ click.UsageError: If parameters are invalid or conflicting
1204+ """
1205+ flash_ops : list [tuple [str , str | None , str | None ]] = []
1206+
1207+ # Mode 1 & 2: Single file with optional block device
1208+ if file :
1209+ if partitions :
1210+ raise click .UsageError (
1211+ "Cannot specify FILE argument with -t options. "
1212+ "Use either 'flash image.img' or 'flash -t partition:file'"
1213+ )
1214+ flash_ops .append ((file , None , block_device ))
1215+
1216+ # Mode 3 & 4: Multiple file-partition pairs with optional block device
1217+ elif partitions :
1218+ for spec in partitions :
1219+ if ':' not in spec :
1220+ raise click .UsageError (
1221+ f"Invalid flash spec format: '{ spec } '. "
1222+ "Expected 'partition:filename'"
1223+ )
1224+ partition_label , filename = spec .split (':' , 1 )
1225+ if not partition_label or not filename :
1226+ raise click .UsageError (
1227+ f"Invalid flash spec format: '{ spec } '. "
1228+ "Both partition label and filename are required"
1229+ )
1230+ flash_ops .append ((filename , partition_label , block_device ))
1231+
1232+ # No input provided
1233+ else :
1234+ raise click .UsageError (
1235+ "Must provide either FILE argument or -t options. "
1236+ "Use 'jumpstarter-cli flash --help' for usage examples"
1237+ )
1238+
1239+ return flash_ops
1240+
11651241 def cli (self ):
11661242 @driver_click_group (self )
11671243 def base ():
11681244 """Software-defined flasher interface"""
11691245 pass
11701246
11711247 @base .command ()
1172- @click .argument ("file" )
1173- @click .option ("--target" , type = str )
1248+ @click .argument ("file" , required = False )
1249+ @click .option ("--target" , type = str , help = "Block device to flash to (e.g., 'usd', 'emmc'). If not provided, uses default target." )
1250+ @click .option (
1251+ "-t" ,
1252+ "partitions" ,
1253+ multiple = True ,
1254+ help = "Flash file to partition: 'partition:filename'. Can be repeated for multiple partitions." ,
1255+ )
11741256 @click .option ("--os-image-checksum" , help = "SHA256 checksum of OS image (direct value)" )
11751257 @click .option (
11761258 "--os-image-checksum-file" ,
@@ -1219,6 +1301,7 @@ def base():
12191301 def flash (
12201302 file ,
12211303 target ,
1304+ partitions ,
12221305 os_image_checksum ,
12231306 os_image_checksum_file ,
12241307 console_debug ,
@@ -1233,31 +1316,60 @@ def flash(
12331316 fls_version ,
12341317 fls_binary_url ,
12351318 ):
1236- """Flash image to DUT from file"""
1237- if os_image_checksum_file and os .path .exists (os_image_checksum_file ):
1238- with open (os_image_checksum_file ) as f :
1239- os_image_checksum = f .read ().strip ().split ()[0 ]
1240- self .logger .info (f"Read checksum from file: { os_image_checksum } " )
1319+ """Flash image(s) to DUT
12411320
1242- self .set_console_debug (console_debug )
1321+ Usage examples:
1322+ # Flash to default block device and target
1323+ jumpstarter-cli flash image.img
12431324
1244- headers = self ._parse_headers (header ) if header else None
1245-
1246- self .flash (
1247- file ,
1248- partition = target ,
1249- force_exporter_http = force_exporter_http ,
1250- force_flash_bundle = force_flash_bundle ,
1251- cacert_file = cacert ,
1252- insecure_tls = insecure_tls ,
1253- headers = headers ,
1254- bearer_token = bearer ,
1255- retries = retries ,
1256- method = method ,
1257- fls_version = fls_version ,
1258- fls_binary_url = fls_binary_url ,
1325+ # Flash to specific block device (e.g., 'emmc')
1326+ jumpstarter-cli flash image.img --target emmc
1327+
1328+ # Flash to partition(s) on default block device
1329+ jumpstarter-cli flash -t rootfs:rootfs.img
1330+
1331+ # Flash to partition(s) on specific block device
1332+ jumpstarter-cli flash --target emmc -t rootfs:rootfs.img -t boot:boot.img
1333+ """
1334+ # Validate and resolve flash parameters
1335+ flash_operations = self ._resolve_flash_parameters (
1336+ file , partitions , target
12591337 )
12601338
1339+ # Setup common options
1340+ self .set_console_debug (console_debug )
1341+ headers_dict = self ._parse_headers (header ) if header else None
1342+
1343+ # Load checksum from file if provided (used for all operations)
1344+ checksum = os_image_checksum
1345+ if os_image_checksum_file and os .path .exists (os_image_checksum_file ):
1346+ with open (os_image_checksum_file ) as f :
1347+ checksum = f .read ().strip ().split ()[0 ]
1348+ self .logger .info (f"Read checksum from file: { checksum } " )
1349+
1350+ # Execute each flash operation
1351+ for idx , (image_file , target_partition , block_device ) in enumerate (flash_operations ):
1352+ op_num = f"{ idx + 1 } /{ len (flash_operations )} " if len (flash_operations ) > 1 else ""
1353+ op_desc = f"partition '{ target_partition } '" if target_partition else "default target"
1354+
1355+ self .logger .info (f"Flashing { op_num } { op_desc } with '{ image_file } '" .strip ())
1356+
1357+ # Perform the flash operation
1358+ self .flash (
1359+ image_file ,
1360+ partition = target_partition ,
1361+ force_exporter_http = force_exporter_http ,
1362+ force_flash_bundle = force_flash_bundle ,
1363+ cacert_file = cacert ,
1364+ insecure_tls = insecure_tls ,
1365+ headers = headers_dict ,
1366+ bearer_token = bearer ,
1367+ retries = retries ,
1368+ method = method ,
1369+ fls_version = fls_version ,
1370+ fls_binary_url = fls_binary_url ,
1371+ )
1372+
12611373 @base .command ()
12621374 @debug_console_option
12631375 def bootloader_shell (console_debug ):
0 commit comments