// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package ova import ( "bytes" "crypto/sha256" "fmt" "io" "io/ioutil" "os" "path/filepath" "runtime" "strings" "text/template" "github.com/talos-systems/talos/cmd/installer/pkg" "github.com/talos-systems/talos/cmd/installer/pkg/qemuimg" "github.com/talos-systems/talos/pkg/cmd" ) const mfTpl = `SHA256({{ .VMDK }})= {{ .VMDKSHA }} SHA256({{ .OVF }})= {{ .OVFSHA }} ` // OVF format reference: https://www.dmtf.org/standards/ovf. //nolint: lll const ovfTpl = ` Virtual disk information The list of logical networks The VM Network network A virtual machine talos The kind of installed guest operating system Virtual hardware requirements Virtual Hardware Family 0 talos vmx-13 hertz * 10^6 Number of Virtual CPUs 2 virtual CPU(s) 1 3 2 byte * 2^20 Memory Size 2048MB of memory 2 4 2048 0 SCSI Controller scsiController0 3 VirtualSCSI 6 0 disk0 ovf:/disk/vmdisk1 4 3 17 2 true VM Network VmxNet3 ethernet adapter on "VM Network" ethernet0 5 VmxNet3 10 false video 6 24 false vmci 7 vmware.vmci 1 ` // CreateOVAFromRAW creates an OVA from a RAW disk. //nolint: gocyclo func CreateOVAFromRAW(name, src, out string) (err error) { dir, err := ioutil.TempDir("/tmp", "talos") if err != nil { return err } dest := filepath.Join(dir, name+".vmdk") if err = qemuimg.Convert("raw", "vmdk", "compat6,subformat=streamOptimized,adapter_type=lsilogic", src, dest); err != nil { return err } f, err := os.Stat(dest) if err != nil { return err } size := f.Size() ovf, err := renderOVF(name, size, pkg.RAWDiskSize) if err != nil { return err } input, err := os.Open(dest) if err != nil { return err } vmdkSHA25Sum, err := sha256sum(input) if err != nil { return err } ovfSHA25Sum, err := sha256sum(strings.NewReader(ovf)) if err != nil { return err } mf, err := renderMF(name, vmdkSHA25Sum, ovfSHA25Sum) if err != nil { return err } // nolint: errcheck defer os.RemoveAll(dir) if err = ioutil.WriteFile(filepath.Join(dir, name+".mf"), []byte(mf), 0o666); err != nil { return err } if err = ioutil.WriteFile(filepath.Join(dir, name+".ovf"), []byte(ovf), 0o666); err != nil { return err } if _, err = cmd.Run("tar", "-cvf", filepath.Join(out, fmt.Sprintf("vmware-%s.ova", runtime.GOARCH)), "-C", dir, name+".ovf", name+".mf", name+".vmdk"); err != nil { return err } return nil } func sha256sum(input io.Reader) (string, error) { hash := sha256.New() if _, err := io.Copy(hash, input); err != nil { return "", err } sum := hash.Sum(nil) return fmt.Sprintf("%x", sum), nil } func renderMF(name, vmdkSHA25Sum, ovfSHA25Sum string) (string, error) { cfg := struct { VMDK string VMDKSHA string OVF string OVFSHA string }{ VMDK: name + ".vmdk", VMDKSHA: vmdkSHA25Sum, OVF: name + ".ovf", OVFSHA: ovfSHA25Sum, } templ := template.Must(template.New("mf").Parse(mfTpl)) var buf bytes.Buffer if err := templ.Execute(&buf, cfg); err != nil { return "", err } return buf.String(), nil } func renderOVF(name string, size, capacity int64) (string, error) { cfg := struct { VMDK string Size int64 Capacity int64 }{ VMDK: name + ".vmdk", Size: size, Capacity: capacity, } templ := template.Must(template.New("ovf").Parse(ovfTpl)) var buf bytes.Buffer if err := templ.Execute(&buf, cfg); err != nil { return "", err } return buf.String(), nil }