@@ -396,4 +396,94 @@ public struct Parser {
396396 throw ContainerizationError ( . invalidArgument, message: " mount destination cannot be empty " )
397397 }
398398 }
399+
400+ // Parse --publish-socket arguments into PublishSocket objects
401+ // Format: "host_path:container_path" (e.g., "/tmp/docker.sock:/var/run/docker.sock")
402+ //
403+ // - Parameter rawPublishSockets: Array of socket specifications
404+ // - Returns: Array of PublishSocket objects
405+ // - Throws: ContainerizationError if parsing fails
406+ static func publishSockets( _ rawPublishSockets: [ String ] ) throws -> [ PublishSocket ] {
407+ var sockets : [ PublishSocket ] = [ ]
408+
409+ // Process each raw socket string
410+ for socket in rawPublishSockets {
411+ let parsedSocket = try Parser . publishSocket ( socket)
412+ sockets. append ( parsedSocket)
413+ }
414+ return sockets
415+ }
416+
417+ // Parse a single --publish-socket argument and validate paths
418+ // Format: "host_path:container_path" -> PublishSocket
419+ private static func publishSocket( _ socket: String ) throws -> PublishSocket {
420+ // Split by colon to two parts: [host_path, container_path]
421+ let parts = socket. split ( separator: " : " )
422+
423+ switch parts. count {
424+ case 2 :
425+ // Extract host and container paths
426+ let hostPath = String ( parts [ 0 ] )
427+ let containerPath = String ( parts [ 1 ] )
428+
429+ // Validate paths are not empty
430+ if hostPath. isEmpty {
431+ throw ContainerizationError (
432+ . invalidArgument, message: " host socket path cannot be empty " )
433+ }
434+ if containerPath. isEmpty {
435+ throw ContainerizationError (
436+ . invalidArgument, message: " container socket path cannot be empty " )
437+ }
438+
439+ // Ensure container path must start with /
440+ if !containerPath. hasPrefix ( " / " ) {
441+ throw ContainerizationError (
442+ . invalidArgument,
443+ message: " container socket path must be absolute: \( containerPath) " )
444+ }
445+
446+ // Convert host path to absolute path for consistency
447+ let hostURL = URL ( fileURLWithPath: hostPath)
448+ let absoluteHostPath = hostURL. absoluteURL. path
449+
450+ // Check if host socket already exists and might be in use
451+ if FileManager . default. fileExists ( atPath: absoluteHostPath) {
452+ do {
453+ let attrs = try FileManager . default. attributesOfItem ( atPath: absoluteHostPath)
454+ if let fileType = attrs [ . type] as? FileAttributeType , fileType == . typeSocket {
455+ throw ContainerizationError (
456+ . invalidArgument,
457+ message: " host socket \( absoluteHostPath) already exists and may be in use " )
458+ }
459+ // If it exists but is not a socket, we can remove it and create socket
460+ try FileManager . default. removeItem ( atPath: absoluteHostPath)
461+ } catch let error as ContainerizationError {
462+ throw error
463+ } catch {
464+ // For other file system errors, continue with creation
465+ }
466+ }
467+
468+ // Create host directory if it doesn't exist
469+ let hostDir = hostURL. deletingLastPathComponent ( )
470+ if !FileManager. default. fileExists ( atPath: hostDir. path) {
471+ try FileManager . default. createDirectory (
472+ at: hostDir, withIntermediateDirectories: true )
473+ }
474+
475+ // Create and return PublishSocket object with validated paths
476+ return PublishSocket (
477+ containerPath: URL ( fileURLWithPath: containerPath) ,
478+ hostPath: URL ( fileURLWithPath: absoluteHostPath) ,
479+ permissions: nil
480+ )
481+
482+ default :
483+ throw ContainerizationError (
484+ . invalidArgument,
485+ message:
486+ " invalid publish-socket format \( socket) . Expected: host_path:container_path " )
487+ }
488+ }
399489}
0 commit comments