[Libguestfs] [PATCH v12 3/3] New tool: virt-builder-repository
Richard W.M. Jones
rjones at redhat.com
Tue Nov 14 11:53:20 UTC 2017
On Mon, Nov 13, 2017 at 02:56:12PM +0100, Cédric Bosdonnat wrote:
> +let checksums_get_sha512 = function
> + | None -> None
> + | Some csums ->
> + try
> + Some (List.find (
> + function
> + | Checksums.SHA512 _ -> true
> + | _ -> false
> + ) csums)
> + with Not_found -> None
This is still a bit difficult to understand. How about this explicit
loop?
let checksums_get_sha512 = function
| None -> None
| Some csums ->
let rec loop = function
| [] -> None
| Checksums.SHA512 csum :: _ -> Some csum
| _ :: rest -> loop rest
in
loop csums
> +let osinfo_ids = ref None
> +
> +let rec osinfo_get_short_ids () =
> + match !osinfo_ids with
> + | Some ids -> ids
> + | None ->
> + osinfo_ids :=
> + Some (
> + Osinfo.fold (
> + fun set filepath ->
> + let doc = Xml.parse_file filepath in
> + let xpathctx = Xml.xpath_new_context doc in
> + let nodes = xpath_get_nodes xpathctx "/libosinfo/os/short-id" in
> + List.fold_left (
> + fun set node ->
> + let id = Xml.node_as_string node in
> + StringSet.add id set
> + ) set nodes
> + ) StringSet.empty
> + );
> + osinfo_get_short_ids ()
It doesn't really matter for this, but there is a nice way to write a
generic "memoize" higher-order function:
https://stackoverflow.com/questions/14454981/memoization-in-ocaml
(You wouldn't need the recursive variant here)
> +let compress_to file outdir =
> + let outimg = outdir // (Filename.basename file) ^ ".xz" in
Don't need parens around function application.
> + info "Compressing ...%!";
You don't need %! here because the ‘info’ function calls
‘print_newline’ from stdlib and ‘print_newline’ calls ‘flush stdout’:
https://github.com/libguestfs/libguestfs/blob/a88385add653c4fc2592639d72b638f693798091/common/mltools/tools_utils.ml#L96
https://github.com/ocaml/ocaml/blob/c5fe6932b2151d0e4426072b4df3510318bc4edc/stdlib/pervasives.ml#L477
> + let cmd = [ "xz"; "-f"; "--best"; "--block-size=16777216"; "-c"; file ] in
> + let file_flags = [ Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC; ] in
> + let outfd = Unix.openfile outimg file_flags 0o666 in
> + let res = run_command cmd ~stdout_chan:outfd in
I read the code of run_command but I don't think it closes outfd, and
so outfd would be leaked here.
Also it'd be nice to use with_openfile, if it was upstream.
https://www.redhat.com/archives/libguestfs/2017-November/msg00028.html
Pino ^ x 2 ?
> + if res <> 0 then
> + error (f_"‘xz’ command failed");
> + outimg
> +
> +let get_mime_type filepath =
> + let file_cmd = "file --mime-type --brief " ^ (quote filepath) in
Don't need parens.
> +let cmp a b =
> + let string_of_arch = function Index.Arch s -> s | Index.GuessedArch s -> s in
> + (string_of_arch a) = (string_of_arch b)
You don't need parens here.
If you define Index.string_of_arch in the earlier then you can remove
the definition of string_of_arch here as well.
> +
> +let has_entry id arch index =
> + List.exists (
> + fun (item_id, { Index.arch = item_arch }) ->
> + item_id = id && (cmp item_arch arch)
Don't need parens.
> +let process_image acc_entries filename repo tmprepo index interactive
> + compression sigchecker =
> + message (f_"Preparing %s") filename;
> +
> + let filepath = repo // filename in
> + let { format = format; size = size } = get_disk_image_info filepath in
Since commit c7651744da45 you can now write this as:
let { format; size } = get_disk_image_info filepath in
> + let ask ?default ?values message =
> + let default_str = match default with
> + | None -> ""
> + | Some x -> sprintf " [%s] " x in
> + let list_str = match values with
> + | None -> ""
> + | Some x ->
> + sprintf (f_"Choose one from the list below:\n %s\n")
> + (String.concat "\n " x) in
> +
> + printf "%s%s%s%!" message default_str list_str;
Seems quite a long way of writing:
let ask ?default ?values message =
printf "%s" message;
(match default with
| None -> ()
| Some x -> printf " [%s] " x);
(match values with
| None -> ()
| Some x -> printf (f_"Choose ... etc ..));
let value = read_line () in
...
I don't think you need to flush the channel because stdout is line
buffered, and read_line will flush stdout anyway.
> + if not (Str.string_match (Str.regexp "[a-zA-Z0-9-_.]+") id 0) then (
It's not wrong to use Str, but you might want to use PCRE instead (see
common/mlpcre) since Perl regexps are more familiar for most people.
This could be written as:
let re_valid_id = PCRE.compile ~anchored:true "[-a-zA-Z0-9_.]+"
...
if not (PCRE.matches re_valid_id id) then (
etc.
Note that Str regexps are anchored by default but PCRE regexps are not.
> + warning (f_"Allowed characters are letters, digits, - _ and .");
> + ask_id default
> + ) else
> + id in
> +
> + let rec ask_arch guess =
> + let arches = [ "x86_64"; "aarch64"; "armv7l"; "i686"; "ppc64"; "ppc64le"; "s390x" ] in
> + match (ask (s_"Architecture: ") ~default:guess ~values:arches) with
> + | None -> ask_arch guess
> + | Some x ->
> + if x = "" then
> + ask_arch guess
It seems as if the ‘ask’ function returns ‘default’ if the user just
hits return already.
> + let extract_entry_data ?entry () =
> + message (f_"Extracting data from the image...");
> + let g = new Guestfs.guestfs () in
Use open_guestfs from Tools_utils instead of this line.
> + let printable_name =
> + match entry with
> + | Some (_, { Index.printable_name = printable_name }) ->
You can just write this with the same meaning:
| Some (_, { Index.printable_name }) ->
> + (id, { Index.printable_name = printable_name;
> + osinfo = osinfo;
> + file_uri = Filename.basename out_path;
[etc]
This can be written more concisely as:
(id, { Index.printable_name;
osinfo;
file_uri = Filename.basename out_path;
arch;
signature_uri = None;
checksums = Some [checksum];
revision;
[etc]
> + let _, { Index.checksums = checksums } = file_entry in
This can be written as:
let _, { Index.checksums } = file_entry in
> + let images =
> + let is_supported_format file =
> + let extension = last_part_of file '.' in
> + match extension with
> + | Some ext -> List.mem ext [ "qcow2"; "raw"; "img" ]
> + | None ->
> + match (get_mime_type file) with
You don't need parens here.
> + | None -> false
> + | Some mime -> mime = "application/octet-stream" in
> + let is_new file =
> + try
> + let _, { Index.checksums = checksums } =
You can now write this as:
let _, { Index.checksums } =
> + List.find (
> + fun (_, { Index.file_uri = file_uri }) ->
and:
fun (_, { Index.file_uri }) ->
> + List.filter (
> + fun file ->
> + if is_supported_format (cmdline.repo // file) then
> + is_new file
> + else
> + false
How about:
List.filter (
fun file -> is_supported_format (cmdline.repo // file) && is_new file
) files
> + let index_channel = open_out outindex_path in
Be nice to use with_open_out here, if that patch was upstream.
Pino ^ ?
> + (* Filter out entries for newly found images and entries
> + without a corresponding image file or with empty arch *)
> + let index = List.filter (
> + fun (id, { Index.arch = arch;
> + Index.file_uri = file_uri }) ->
You can write:
fun (id, { Index.arch; Index.file_uri }) ->
> + not (has_entry id arch images_entries) && Sys.file_exists file_uri
> + ) index in
> +
> + (* Convert all URIs back to relative ones *)
> + let index = List.map (
> + fun (id, entry) ->
> + let { Index.file_uri = file_uri } = entry in
let { Index.file_uri } = entry in
> + (* Remove the index file since we have the signed version of it *)
> + if not cmdline.keep_unsigned then
> + Sys.remove (tmprepo // "index")
We normally keep the index files even if we have a signed version.
Not sure which is better actually. Maybe we should delete them.
> diff --git a/builder/test-virt-builder-repository.sh b/builder/test-virt-builder-repository.sh
> new file mode 100755
> index 000000000..5ff270bcc
> --- /dev/null
> +++ b/builder/test-virt-builder-repository.sh
> @@ -0,0 +1,98 @@
> +#!/bin/bash -
> +# libguestfs
> +# Copyright (C) 2017 SUSE Inc.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 2 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program; if not, write to the Free Software
> +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
> +
> +set -e
> +
> +$TEST_FUNCTIONS
> +slow_test
> +skip_if_skipped "$script"
> +
> +test_data=repository-testdata
> +rm -rf $test_data
> +mkdir $test_data
> +
> +# Make a copy of the Fedora image
> +cp ../test-data/phony-guests/fedora.img $test_data
> +
> +# Create minimal index file
> +cat > $test_data/index << EOF
> +[fedora]
> +file=fedora.img
> +EOF
> +
> +# Run virt-builder-repository (no compression, interactive)
> +echo 'x86_64
> +Fedora Test Image
> +fedora14
> +/dev/sda1
> +/dev/VG/Root
> +' | virt-builder-repository -v -x --no-compression -i $test_data
> +
> +assert_config () {
> + item=$1
> + regex=$2
> +
> + sed -n -e "/\[$item]/,/^$/p" $test_data/index | grep "$regex"
> +}
> +
> +# Check the generated index file
> +assert_config 'fedora' 'revision=1'
> +assert_config 'fedora' 'arch=x86_64'
> +assert_config 'fedora' 'name=Fedora Test Image'
> +assert_config 'fedora' 'osinfo=fedora14'
> +assert_config 'fedora' 'checksum'
> +assert_config 'fedora' 'format=raw'
> +assert_config 'fedora' '^size='
> +assert_config 'fedora' 'compressed_size='
> +assert_config 'fedora' 'expand=/dev/'
> +
> +
> +# Copy the debian image and add the minimal piece to index
> +cp ../test-data/phony-guests/debian.img $test_data
> +
> +cat >> $test_data/index << EOF
> +
> +[debian]
> +file=debian.img
> +EOF
> +
> +# Run virt-builder-repository again
> +echo 'x86_64
> +Debian Test Image
> +debian9
> +
> +' | virt-builder-repository --no-compression -i $test_data
> +
> +# Check that the new image is complete and the first one hasn't changed
> +assert_config 'fedora' 'revision=1'
> +
> +assert_config 'debian' 'revision=1'
> +assert_config 'debian' 'checksum'
> +
> +# Modify the fedora image
> +export EDITOR='echo newline >>'
> +virt-edit -a $test_data/fedora.img /etc/test3
> +
> +# Rerun the tool (with compression)
> +virt-builder-repository -i $test_data
> +
> +# Check that the revision, file and size have been updated
> +assert_config 'fedora' 'revision=2'
> +assert_config 'fedora' 'file=fedora.img.xz'
> +test -e $test_data/fedora.img.xz
> +! test -e $test_data/fedora.img
The test should ‘rm’ any temporary files it created after a successful
run (but not on error paths).
> --- a/builder/utils.mli
> +++ b/builder/utils.mli
> @@ -32,3 +32,6 @@ val string_of_revision : revision -> string
> val get_image_infos : string -> Yajl.yajl_val
> (** [get_image_infos path] Run qemu-img info on the image pointed at
> path as YAJL tree. *)
> +
> +val increment_revision : revision -> revision
> +(** Add one to the version number *)
s/version/revision/
Looks good in general.
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
More information about the Libguestfs
mailing list