[Libguestfs] [PATCH v2 2/2] v2v: -i vmx: Enhance VMX support with ability to use ‘-it ssh’ transport.

Richard W.M. Jones rjones at redhat.com
Fri Dec 8 18:24:10 UTC 2017


On Fri, Dec 08, 2017 at 06:04:51PM +0100, Pino Toscano wrote:
> IMHO this new transport could use the standard URI syntax, so
>  root at esxi.example.com/vmfs/volumes/datastore1/guest/guest.vmx
> instead of 
>  root at esxi.example.com:/vmfs/volumes/datastore1/guest/guest.vmx
> 
> This way, Xml.parse_uri can be used to parse it, giving a better code
> for extracting all the parts (including the port, for example).

I tried this, see attached incremental patch, but it has a few
problems:

(1) You must use ssh://... else XmlParseURI cannot parse the URI.

(2) You must replace all spaces with %20 in the path.  This is a bit
of a pain since VMware VM names frequently contain spaces, and the
path will contains x 2 the number of spaces.

On the flip side, adding support for port numbers was possible.

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
Fedora Windows cross-compiler. Compile Windows programs, test, and
build Windows installers. Over 100 libraries supported.
http://fedoraproject.org/wiki/MinGW
-------------- next part --------------
>From 16ce1ed1e63e07a284621a318b820366273b5a4f Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones at redhat.com>
Date: Fri, 8 Dec 2017 18:10:10 +0000
Subject: [PATCH] V2V: USE URI FOR REMOTE VMX PATH

---
 v2v/input_vmx.ml | 104 +++++++++++++++++++++++++++++++++----------------------
 v2v/virt-v2v.pod |  21 +++++++++--
 2 files changed, 81 insertions(+), 44 deletions(-)

diff --git a/v2v/input_vmx.ml b/v2v/input_vmx.ml
index 3032eba96..a8b33f66f 100644
--- a/v2v/input_vmx.ml
+++ b/v2v/input_vmx.ml
@@ -29,52 +29,59 @@ open Utils
 open Name_from_disk
 
 type vmx_source =
-  | File of string                         (* local file or NFS *)
-  | SSH of string option * string * string (* SSH username, server, path *)
+  | File of string              (* local file or NFS *)
+  | SSH of Xml.uri              (* SSH URI *)
 
 (* The single filename on the command line is intepreted either as
- * a local file or a remote SSH path (only if ?-it ssh?).
+ * a local file or a remote SSH URI (only if ?-it ssh?).
  *)
 let vmx_source_of_arg input_transport arg =
   match input_transport, arg with
   | None, arg -> File arg
   | Some `SSH, arg ->
-     let arg1, path = String.split ":" arg in
-     if path = "" then
-       error (f_"expecting [user@]server:path with ?-it ssh?");
-     let user, server = match String.split "@" arg1 with
-       | server, "" -> None, server
-       | user, server -> Some user, server in
-     SSH (user, server, path)
+     let uri =
+       try Xml.parse_uri arg
+       with Invalid_argument _ ->
+         error (f_"remote vmx ?%s? could not be parsed as a URI") arg in
+     if uri.Xml.uri_scheme <> None && uri.Xml.uri_scheme <> Some "ssh" then
+       error (f_"vmx URI start with ?ssh://...?");
+     if uri.Xml.uri_server = None then
+       error (f_"vmx URI remote server name omitted");
+     if uri.Xml.uri_path = None || uri.Xml.uri_path = Some "/" then
+       error (f_"vmx URI path component looks incorrect");
+     SSH uri
+
+(* Return various fields from the URI.  The checks in vmx_source_of_arg
+ * should ensure that none of these assertions fail.
+ *)
+let port_of_uri { Xml.uri_port } =
+  match uri_port with i when i <= 0 -> None | i -> Some i
+let server_of_uri { Xml.uri_server } =
+  match uri_server with None -> assert false | Some s -> s
+let path_of_uri { Xml.uri_path } =
+  match uri_path with None -> assert false | Some p -> p
 
 (* 'scp' a remote file into a temporary local file, returning the path
  * of the temporary local file.
  *)
-let memo_tmpdir = ref None
-let scp_from_remote_to_temporary user server path filename =
-  let tmpdir =
-    match !memo_tmpdir with
-    | None ->
-       let base_dir = (open_guestfs ())#get_cachedir () in
-       let t = Mkdtemp.temp_dir ~base_dir "vmx." in
-       rmdir_on_exit t;
-       memo_tmpdir := Some t;
-       t
-    | Some tmpdir -> tmpdir in
-
+let scp_from_remote_to_temporary uri tmpdir filename =
   let localfile = tmpdir // filename in
 
-  (* XXX Assumes default port number. *)
   let cmd =
-    sprintf "scp%s %s%s:%s %s"
+    sprintf "scp%s%s %s%s:%s %s"
             (if verbose () then "" else " -q")
-            (match user with None -> "" | Some user -> quote user ^ "@")
-            (quote server)
+            (match port_of_uri uri with
+             | None -> ""
+             | Some port -> sprintf " -P %d" port)
+            (match uri.Xml.uri_user with
+             | None -> ""
+             | Some user -> quote user ^ "@")
+            (quote (server_of_uri uri))
             (* The double quoting of the path is counter-intuitive
              * but correct, see:
              * https://stackoverflow.com/questions/19858176/how-to-escape-spaces-in-path-during-scp-copy-in-linux
              *)
-            (quote (quote path))
+            (quote (quote (path_of_uri uri)))
             (quote localfile) in
   if verbose () then
     eprintf "%s\n%!" cmd;
@@ -83,12 +90,16 @@ let scp_from_remote_to_temporary user server path filename =
   localfile
 
 (* Test if [path] exists on the remote server. *)
-let remote_file_exists user server path =
-  (* XXX Assumes default port number. *)
+let remote_file_exists uri path =
   let cmd =
-    sprintf "ssh %s%s test -f %s"
-            (match user with None -> "" | Some user -> quote user ^ "@")
-            (quote server)
+    sprintf "ssh%s %s%s test -f %s"
+            (match port_of_uri uri with
+             | None -> ""
+             | Some port -> sprintf " -p %d" port)
+            (match uri.Xml.uri_user with
+             | None -> ""
+             | Some user -> quote user ^ "@")
+            (quote (server_of_uri uri))
             (* Double quoting is necessary here, see above. *)
             (quote (quote path)) in
   if verbose () then
@@ -204,7 +215,8 @@ and qemu_uri_of_filename vmx_source filename =
       *)
      absolute_path_from_other_file vmx_filename filename, "vmdk"
 
-  | SSH (user, server, vmx_path) ->
+  | SSH uri ->
+     let vmx_path = path_of_uri uri in
      let abs_path = absolute_path_from_other_file vmx_path filename in
      let format = "vmdk" in
 
@@ -215,20 +227,25 @@ and qemu_uri_of_filename vmx_source filename =
      let abs_path, format =
        let flat_vmdk =
          PCRE.replace (PCRE.compile "\\.vmdk$") "-flat.vmdk" abs_path in
-       if remote_file_exists user server flat_vmdk then (flat_vmdk, "raw")
+       if remote_file_exists uri flat_vmdk then (flat_vmdk, "raw")
        else (abs_path, format) in
 
      let json_params = [
        "file.driver", JSON.String "ssh";
        "file.path", JSON.String abs_path;
-       "file.host", JSON.String server;
+       "file.host", JSON.String (server_of_uri uri);
        "file.host_key_check", JSON.String "no";
      ] in
      let json_params =
-       match user with
+       match uri.Xml.uri_user with
        | None -> json_params
        | Some user ->
           ("file.user", JSON.String user) :: json_params in
+     let json_params =
+       match port_of_uri uri with
+       | None -> json_params
+       | Some port ->
+          ("file.port", JSON.Int port) :: json_params in
 
      "json:" ^ JSON.string_of_doc json_params, format
 
@@ -367,7 +384,13 @@ and find_nics vmx =
   let nics = List.map (fun (_, source) -> source) nics in
   nics
 
-class input_vmx input_transport arg = object
+class input_vmx input_transport arg =
+  let tmpdir =
+    let base_dir = (open_guestfs ())#get_cachedir () in
+    let t = Mkdtemp.temp_dir ~base_dir "vmx." in
+    rmdir_on_exit t;
+    t in
+object
   inherit input
 
   method as_options = "-i vmx " ^ arg
@@ -389,9 +412,8 @@ class input_vmx input_transport arg = object
     let vmx =
       match vmx_source with
       | File filename -> Parse_vmx.parse_file filename
-      | SSH (user, server, path) ->
-         let filename =
-           scp_from_remote_to_temporary user server path "source.vmx" in
+      | SSH uri ->
+         let filename = scp_from_remote_to_temporary uri tmpdir "source.vmx" in
          Parse_vmx.parse_file filename in
 
     let name =
@@ -401,7 +423,7 @@ class input_vmx input_transport arg = object
          warning (f_"no displayName key found in VMX file");
          match vmx_source with
          | File filename -> name_from_disk filename
-         | SSH (_, _, path) -> name_from_disk path in
+         | SSH uri -> name_from_disk (path_of_uri uri) in
 
     let memory_mb =
       match Parse_vmx.get_int64 vmx ["memSize"] with
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
index 79269a9da..cc4e90dff 100644
--- a/v2v/virt-v2v.pod
+++ b/v2v/virt-v2v.pod
@@ -126,7 +126,7 @@ a local file.
 
  virt-v2v \
    -i vmx -it ssh \
-   "root at esxi.example.com:/vmfs/volumes/datastore1/guest/guest.vmx" \
+   "root at esxi.example.com/vmfs/volumes/datastore1/guest/guest.vmx" \
    -o local -os /var/tmp
 
 The guest must not be running.  Virt-v2v would I<not> need to be run
@@ -1433,6 +1433,21 @@ Note that password-interactive and Kerberos access are B<not>
 supported.  You B<have> to set up ssh access using ssh-agent and
 authorized_keys.
 
+=head3 VMX: Construct the SSH URI
+
+When using the SSH input transport you must specify a remote
+C<ssh://...> URI pointing to the VMX file.  A typical URI looks like:
+
+ ssh://root@esxi.example.com/vmfs/volumes/datastore1/my%20guest/my%20guest.vmx
+
+Any space must be escaped with C<%20> and other non-ASCII characters
+may also need to be URI-escaped.
+
+The username is not required if it is the same as your local username.
+
+You may optionally supply a port number after the hostname if the SSH
+server is not listening on the default port (22).
+
 =head2 VMX: IMPORTING A GUEST
 
 To import a vmx file from a local file or NFS, do:
@@ -1440,11 +1455,11 @@ To import a vmx file from a local file or NFS, do:
  $ virt-v2v -i vmx guest.vmx -o local -os /var/tmp
 
 To import a vmx file over SSH, add I<-it ssh> to select the SSH
-transport and supply a remote C<server:/path> with optional username:
+transport and supply a remote SSH URI:
 
  $ virt-v2v \
      -i vmx -it ssh \
-     "root at esxi.example.com:/vmfs/volumes/datastore1/guest/guest.vmx" \
+     "ssh://root@esxi.example.com/vmfs/volumes/datastore1/guest/guest.vmx" \
      -o local -os /var/tmp
 
 Virt-v2v processes the vmx file and uses it to find the location of
-- 
2.13.2



More information about the Libguestfs mailing list