diff --git a/_motor/discovery/vsphere/vm_guest_resolver.go b/_motor/discovery/vsphere/vm_guest_resolver.go deleted file mode 100644 index e4b1502f8e..0000000000 --- a/_motor/discovery/vsphere/vm_guest_resolver.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Mondoo, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package vsphere - -import ( - "context" - "errors" - - "github.com/rs/zerolog/log" - "go.mondoo.com/cnquery/motor/asset" - "go.mondoo.com/cnquery/motor/discovery/common" - "go.mondoo.com/cnquery/motor/providers" - "go.mondoo.com/cnquery/motor/providers/resolver" - "go.mondoo.com/cnquery/motor/providers/vmwareguestapi" - "go.mondoo.com/cnquery/motor/vault" -) - -type VMGuestResolver struct{} - -func (k *VMGuestResolver) Name() string { - return "VMware vSphere VM Guest Resolver" -} - -func (r *VMGuestResolver) AvailableDiscoveryTargets() []string { - return []string{common.DiscoveryAuto} -} - -func (k *VMGuestResolver) Resolve(ctx context.Context, root *asset.Asset, pCfg *providers.Config, credsResolver vault.Resolver, sfn common.QuerySecretFn, userIdDetectors ...providers.PlatformIdDetector) ([]*asset.Asset, error) { - resolved := []*asset.Asset{} - - // we leverage the vpshere provider to establish a connection - m, err := resolver.NewMotorConnection(ctx, pCfg, credsResolver) - if err != nil { - return nil, err - } - defer m.Close() - - trans, ok := m.Provider.(*vmwareguestapi.Provider) - if !ok { - return nil, errors.New("could not initialize vsphere guest provider") - } - - client := trans.Client() - discoveryClient := New(client) - - // resolve vms - vms, err := discoveryClient.ListVirtualMachines(pCfg) - if err != nil { - return nil, err - } - - // add provider config for each vm - for i := range vms { - vm := vms[i] - resolved = append(resolved, vm) - } - - // filter the vms by inventoryPath - inventoryPaths := []string{} - inventoryPathFilter, ok := pCfg.Options["inventoryPath"] - if ok { - inventoryPaths = []string{inventoryPathFilter} - } - - resolved = filter(resolved, func(a *asset.Asset) bool { - inventoryPathLabel := a.Labels["vsphere.vmware.com/inventory-path"] - - return contains(inventoryPaths, inventoryPathLabel) - }) - - if len(resolved) == 1 { - a := resolved[0] - a.Connections = []*providers.Config{pCfg} - - // find the secret reference for the asset - EnrichVsphereToolsConnWithSecrets(a, credsResolver, sfn) - - return []*asset.Asset{a}, nil - } else { - return nil, errors.New("could not resolve vSphere VM") - } -} - -func EnrichVsphereToolsConnWithSecrets(a *asset.Asset, credsResolver vault.Resolver, sfn common.QuerySecretFn) { - // search secret for vm - // NOTE: we do not use `common.EnrichAssetWithSecrets(a, sfn)` here since vmware requires two secrets at the same time - for j := range a.Connections { - conn := a.Connections[j] - - // special handling for vsphere vm config - if conn.Backend == providers.ProviderType_VSPHERE_VM { - var creds *vault.Credential - - secretRefCred, err := sfn(a) - if err == nil && secretRefCred != nil { - creds, err = credsResolver.GetCredential(secretRefCred) - } - - if err == nil && creds != nil { - if conn.Options == nil { - conn.Options = map[string]string{} - } - conn.Options["guestUser"] = creds.User - conn.Options["guestPassword"] = string(creds.Secret) - } - } else { - log.Warn().Str("name", a.Name).Msg("could not determine credentials for asset") - } - } -} diff --git a/_motor/providers/vmwareguestapi/command.go b/_motor/providers/vmwareguestapi/command.go deleted file mode 100644 index 1d0c37f8ff..0000000000 --- a/_motor/providers/vmwareguestapi/command.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Mondoo, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package vmwareguestapi - -import ( - "bytes" - "context" - "errors" - "os/exec" - "time" - - "go.mondoo.com/cnquery/motor/providers/os" - "go.mondoo.com/cnquery/motor/providers/vmwareguestapi/toolbox" -) - -type Command struct { - os.Command - tb *toolbox.Client -} - -func (c *Command) Exec(command string) (*os.Command, error) { - c.Command.Command = command - c.Command.Stats.Duration = time.Since(c.Command.Stats.Start) - - stdoutBuffer := &bytes.Buffer{} - stderrBuffer := &bytes.Buffer{} - - c.Command.Stdout = stdoutBuffer - c.Command.Stderr = stderrBuffer - - if c.tb == nil { - return nil, errors.New("vmware process manager not set") - } - - script := "!/bin/sh\n" + c.Command.Command - - ecmd := &exec.Cmd{ - Path: "", - Args: []string{}, - Env: []string{}, - Dir: "", - Stdin: bytes.NewBuffer([]byte(script)), - Stdout: stdoutBuffer, - Stderr: stderrBuffer, - } - - // start vmware tools call - ctx := context.Background() - // TODO: this is not verify efficient for windows since we call powershell via powershell - // TODO: the toolbox implementation requires bash and we should limit us to /bin/sh - err := c.tb.Run(ctx, ecmd) - c.Command.Stats.Duration = time.Since(c.Command.Stats.Start) - if err != nil { - // TODO extract exit code, since its a private error, we need to parse the string - // match := errors.As(err, &e) - // if match { - // c.Command.ExitStatus = e.ExitStatus() - // } - return &c.Command, err - } - return &c.Command, nil -} diff --git a/_motor/providers/vmwareguestapi/file.go b/_motor/providers/vmwareguestapi/file.go deleted file mode 100644 index 5b00bbadc0..0000000000 --- a/_motor/providers/vmwareguestapi/file.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Mondoo, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package vmwareguestapi - -import ( - "errors" - "io" - "os" -) - -func NewFile(name string, rc io.ReadCloser) *File { - return &File{path: name, rc: rc} -} - -type File struct { - rc io.ReadCloser - path string -} - -func (f *File) Close() error { - return f.rc.Close() -} - -func (f *File) Name() string { - return f.path -} - -func (f *File) Stat() (os.FileInfo, error) { - return nil, errors.New("not implemented") -} - -func (f *File) Sync() error { - return nil -} - -func (f *File) Truncate(size int64) error { - return nil -} - -func (f *File) Read(b []byte) (n int, err error) { - return f.rc.Read(b) -} - -func (f *File) ReadAt(b []byte, off int64) (n int, err error) { - return 0, errors.New("not implemented") -} - -func (f *File) Readdir(count int) (res []os.FileInfo, err error) { - return nil, errors.New("not implemented") -} - -func (f *File) Readdirnames(n int) (names []string, err error) { - return nil, errors.New("not implemented") -} - -func (f *File) Seek(offset int64, whence int) (int64, error) { - return 0, errors.New("not implemented") -} - -func (f *File) Write(b []byte) (n int, err error) { - return 0, errors.New("not implemented") -} - -func (f *File) WriteAt(b []byte, off int64) (n int, err error) { - return 0, errors.New("not implemented") -} - -func (f *File) WriteString(s string) (ret int, err error) { - return 0, errors.New("not implemented") -} diff --git a/_motor/providers/vmwareguestapi/fs.go b/_motor/providers/vmwareguestapi/fs.go deleted file mode 100644 index f7613ec8b3..0000000000 --- a/_motor/providers/vmwareguestapi/fs.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Mondoo, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package vmwareguestapi - -import ( - "context" - "errors" - "os" - "time" - - "github.com/spf13/afero" - "go.mondoo.com/cnquery/motor/providers/os/statutil" - "go.mondoo.com/cnquery/motor/providers/ssh/cat" - "go.mondoo.com/cnquery/motor/providers/vmwareguestapi/toolbox" -) - -// NOTE: this is not useable since simple file transfers like -// /etc/os-release are throwing errors -type VmwareGuestFs struct { - tb *toolbox.Client - commandRunner cat.CommandRunner -} - -var notImplementedError = errors.New("not implemented") - -func (vfs *VmwareGuestFs) Name() string { - return "Vmware GuestFS" -} - -func (vfs *VmwareGuestFs) Create(name string) (afero.File, error) { - return nil, notImplementedError -} - -func (vfs VmwareGuestFs) Mkdir(name string, perm os.FileMode) error { - return notImplementedError -} - -func (vfs *VmwareGuestFs) MkdirAll(path string, perm os.FileMode) error { - return notImplementedError -} - -func (vfs *VmwareGuestFs) Open(name string) (afero.File, error) { - // for now this methods is not reliable for all paths on the os - // https://communities.vmware.com/thread/624928 - ctx := context.Background() - rc, _, err := vfs.tb.Download(ctx, name) - if err != nil { - return nil, err - } - - return NewFile(name, rc), nil -} - -func (vfs *VmwareGuestFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { - return nil, notImplementedError -} - -func (vfs *VmwareGuestFs) Remove(name string) error { - return notImplementedError -} - -func (vfs *VmwareGuestFs) RemoveAll(path string) error { - return notImplementedError -} - -func (VmwareGuestFs) Rename(oldname, newname string) error { - return notImplementedError -} - -// needs to be implemented -func (vfs *VmwareGuestFs) Stat(path string) (os.FileInfo, error) { - return statutil.New(vfs.commandRunner).Stat(path) -} - -func (vfs *VmwareGuestFs) Chmod(name string, mode os.FileMode) error { - return notImplementedError -} - -func (vfs *VmwareGuestFs) Chtimes(name string, atime time.Time, mtime time.Time) error { - return notImplementedError -} diff --git a/_motor/providers/vmwareguestapi/provider.go b/_motor/providers/vmwareguestapi/provider.go deleted file mode 100644 index affb4e731c..0000000000 --- a/_motor/providers/vmwareguestapi/provider.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) Mondoo, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package vmwareguestapi - -// Guest Operations API -// https://docs.vmware.com/en/VMware-Cloud-on-AWS/services/com.vmware.vsphere.vmc-aws-manage-data-center-vms.doc/GUID-FE3B00A4-83F5-45AF-9B16-40008BC39E6F.html -// https://github.com/vmware/vsphere-guest-run/blob/master/vsphere_guest_run/vsphere.py -// -// Install vmware tools -// - [Installing VMware Tools in a Linux virtual machine using Red Hat Package Manager](https://kb.vmware.com/s/article/1018392) -// - [Installing VMware Tools in a Linux virtual machine using a Compiler](https://kb.vmware.com/s/article/1018414) -// - [Installing open-vm-tools](https://docs.vmware.com/en/VMware-Tools/11.0.0/com.vmware.vsphere.vmwaretools.doc/GUID-C48E1F14-240D-4DD1-8D4C-25B6EBE4BB0F.html) -// - [Using Open VM Tools](https://docs.vmware.com/en/VMware-Tools/11.1.0/com.vmware.vsphere.vmwaretools.doc/GUID-8B6EA5B7-453B-48AA-92E5-DB7F061341D1.html) -// -// ```powershell -// Set-PowerCLIConfiguration -InvalidCertificateAction:Ignore -// Connect-VIServer -Server 127.0.0.1 -Port 8990 -User user -Password pass -// $vm = Get-VM example-centos -// ``` - -import ( - "context" - "errors" - - "github.com/rs/zerolog/log" - "github.com/spf13/afero" - "github.com/vmware/govmomi" - "github.com/vmware/govmomi/find" - "github.com/vmware/govmomi/guest" - "github.com/vmware/govmomi/object" - "github.com/vmware/govmomi/vim25/mo" - "github.com/vmware/govmomi/vim25/types" - "go.mondoo.com/cnquery/motor/providers" - "go.mondoo.com/cnquery/motor/providers/os" - "go.mondoo.com/cnquery/motor/providers/ssh/cat" - "go.mondoo.com/cnquery/motor/providers/vmwareguestapi/toolbox" - "go.mondoo.com/cnquery/motor/providers/vsphere" - "go.mondoo.com/cnquery/motor/vault" -) - -var _ providers.Instance = (*Provider)(nil) - -func New(pCfg *providers.Config) (*Provider, error) { - if pCfg.Backend != providers.ProviderType_VSPHERE_VM { - return nil, errors.New("backend is not supported for VMware tools transport") - } - - // search for password secret - c, err := vault.GetPassword(pCfg.Credentials) - if err != nil { - return nil, errors.New("missing password for VMware tools provider") - } - - // derive vsphere connection url from Provider Config - vsphereUrl, err := vsphere.VSphereConnectionURL(pCfg.Host, pCfg.Port, c.User, string(c.Secret)) - if err != nil { - return nil, err - } - - inventoryPath := pCfg.Options["inventoryPath"] - guestuser := pCfg.Options["guestUser"] - guestpassword := pCfg.Options["guestPassword"] - - // establish vsphere connection - ctx := context.Background() - client, err := govmomi.NewClient(ctx, vsphereUrl, true) - if err != nil { - return nil, err - } - - // get vm via inventory path - var vm *object.VirtualMachine - finder := find.NewFinder(client.Client, true) - vm, err = finder.VirtualMachine(context.Background(), inventoryPath) - if err != nil { - return nil, err - } - - // initialize manager for processes and file - o := guest.NewOperationsManager(client.Client, vm.Reference()) - pm, err := o.ProcessManager(ctx) - if err != nil { - return nil, err - } - - fm, err := o.FileManager(ctx) - if err != nil { - return nil, err - } - - // initialize vm authentication via password auth - auth := &types.NamePasswordAuthentication{} - auth.Username = guestuser - auth.Password = guestpassword - - family := "" - var props mo.VirtualMachine - err = vm.Properties(context.Background(), vm.Reference(), []string{"guest.guestFamily"}, &props) - if err != nil { - return nil, err - } - - if props.Guest != nil { - family = props.Guest.GuestFamily - } - - tb := &toolbox.Client{ - ProcessManager: pm, - FileManager: fm, - Authentication: auth, - GuestFamily: types.VirtualMachineGuestOsFamily(family), - } - - return &Provider{ - client: client, - pm: pm, - fm: fm, - tb: tb, - auth: auth, - family: family, - }, nil -} - -type Provider struct { - client *govmomi.Client - pm *guest.ProcessManager - fm *guest.FileManager - tb *toolbox.Client - auth types.BaseGuestAuthentication - family string - fs afero.Fs -} - -func (p *Provider) Client() *govmomi.Client { - return p.client -} - -func (p *Provider) RunCommand(command string) (*os.Command, error) { - log.Debug().Str("command", command).Str("transport", "vmwareguest").Msg("run command") - c := &Command{tb: p.tb} - - cmd, err := c.Exec(command) - log.Debug().Err(err).Int("exit", cmd.ExitStatus).Msg("completed command") - return cmd, err -} - -func (p *Provider) FileInfo(path string) (os.FileInfoDetails, error) { - fs := p.FS() - afs := &afero.Afero{Fs: fs} - stat, err := afs.Stat(path) - if err != nil { - return os.FileInfoDetails{}, err - } - - uid := int64(-1) - gid := int64(-1) - - // if t.Sudo != nil || t.UseScpFilesystem { - // if stat, ok := stat.Sys().(*transports.FileInfo); ok { - // uid = int64(stat.Uid) - // gid = int64(stat.Gid) - // } - // } else { - // if stat, ok := stat.Sys().(*rawsftp.FileStat); ok { - // uid = int64(stat.UID) - // gid = int64(stat.GID) - // } - // } - mode := stat.Mode() - - return os.FileInfoDetails{ - Mode: os.FileModeDetails{mode}, - Size: stat.Size(), - Uid: uid, - Gid: gid, - }, nil -} - -func (p *Provider) FS() afero.Fs { - // if we cached an instance already, return it - if p.fs != nil { - return p.fs - } - - // even with PowerCli this is not working therefore we stick to catfs for now - // Copy-VMGuestFile -VM $vm -GuestToLocal /etc/os-release -GuestUser root -GuestPassword vagrant -Destination os-release - // Copy-VMGuestFile: 11/05/2020 18:38:57 Copy-VMGuestFile A specified parameter was not correct: - // t.fs = &VmwareGuestFs{ - // tb: t.tb, - // commandRunner: t, - // } - p.fs = cat.New(p) - return p.fs -} - -func (p *Provider) Close() {} - -func (p *Provider) Capabilities() providers.Capabilities { - return providers.Capabilities{ - providers.Capability_File, - providers.Capability_RunCommand, - } -} - -func (p *Provider) Kind() providers.Kind { - return providers.Kind_KIND_VIRTUAL_MACHINE -} - -func (p *Provider) Runtime() string { - return providers.RUNTIME_VSPHERE_VM -} - -func (p *Provider) PlatformIdDetectors() []providers.PlatformIdDetector { - return []providers.PlatformIdDetector{ - providers.HostnameDetector, - } -} diff --git a/_motor/providers/vmwareguestapi/provider_test.go b/_motor/providers/vmwareguestapi/provider_test.go deleted file mode 100644 index 9c1b7019d5..0000000000 --- a/_motor/providers/vmwareguestapi/provider_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Mondoo, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package vmwareguestapi - -// import ( -// "io/ioutil" -// "testing" - -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/require" -// "go.mondoo.com/cnquery/motor/transports" -// ) - -// func TestRunCommand(t *testing.T) { -// p, err := New(&transports.TransportConfig{ -// Type: transports.TransportBackend_CONNECTION_VSPHERE_VM, -// Host: "192.168.178.139", -// User: "root", -// Password: "password1!", -// Options: map[string]string{ -// "inventoryPath": "/ha-datacenter/vm/example-centos", -// "guestUser": "root", -// "guestPassword": "vagrant", -// }, -// }) -// require.NoError(t, err) -// cmd, err := trans.RunCommand("uname -s") -// require.NoError(t, err) -// data, err := ioutil.ReadAll(cmd.Stdout) -// require.NoError(t, err) -// assert.Equal(t, "Linux\n", string(data)) - -// cmd, err = trans.RunCommand("cat /etc/os-release") -// require.NoError(t, err) -// data, err = ioutil.ReadAll(cmd.Stdout) -// require.NoError(t, err) -// assert.Equal(t, 393, len(string(data))) - -// f, err := trans.FS().Open("/etc/os-release") -// data, err = ioutil.ReadAll(f) -// require.NoError(t, err) -// assert.Equal(t, 393, len(string(data))) -// } diff --git a/_motor/providers/vmwareguestapi/toolbox/toolbox.go b/_motor/providers/vmwareguestapi/toolbox/toolbox.go deleted file mode 100644 index c8bb99912c..0000000000 --- a/_motor/providers/vmwareguestapi/toolbox/toolbox.go +++ /dev/null @@ -1,304 +0,0 @@ -/* -Copyright (c) 2017 VMware, Inc. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package toolbox - -import ( - "bytes" - "context" - "fmt" - "io" - "log" - "net/url" - "os" - "os/exec" - "strings" - "time" - - "github.com/vmware/govmomi/guest" - "github.com/vmware/govmomi/vim25/soap" - "github.com/vmware/govmomi/vim25/types" -) - -// Client attempts to expose guest.OperationsManager as idiomatic Go interfaces -type Client struct { - ProcessManager *guest.ProcessManager - FileManager *guest.FileManager - Authentication types.BaseGuestAuthentication - GuestFamily types.VirtualMachineGuestOsFamily -} - -func (c *Client) rm(ctx context.Context, path string) { - err := c.FileManager.DeleteFile(ctx, c.Authentication, path) - if err != nil { - log.Printf("rm %q: %s", path, err) - } -} - -func (c *Client) mktemp(ctx context.Context) (string, error) { - return c.FileManager.CreateTemporaryFile(ctx, c.Authentication, "govmomi-", "", "") -} - -type exitError struct { - error - exitCode int -} - -func (e *exitError) ExitCode() int { - return e.exitCode -} - -// Run implements exec.Cmd.Run over vmx guest RPC against standard vmware-tools or toolbox. -func (c *Client) Run(ctx context.Context, cmd *exec.Cmd) error { - if cmd.Stdin != nil { - dst, err := c.mktemp(ctx) - if err != nil { - return err - } - - defer c.rm(ctx, dst) - - var buf bytes.Buffer - size, err := io.Copy(&buf, cmd.Stdin) - if err != nil { - return err - } - - p := soap.DefaultUpload - p.ContentLength = size - attr := new(types.GuestPosixFileAttributes) - - err = c.Upload(ctx, &buf, dst, p, attr, true) - if err != nil { - return err - } - - cmd.Args = append(cmd.Args, "<", dst) - } - - output := []struct { - io.Writer - fd string - path string - }{ - {cmd.Stdout, "1", ""}, - {cmd.Stderr, "2", ""}, - } - - for i, out := range output { - if out.Writer == nil { - continue - } - - dst, err := c.mktemp(ctx) - if err != nil { - return err - } - - defer c.rm(ctx, dst) - - cmd.Args = append(cmd.Args, out.fd+">", dst) - output[i].path = dst - } - - path := cmd.Path - args := cmd.Args - - switch c.GuestFamily { - case types.VirtualMachineGuestOsFamilyWindowsGuest: - // Using 'cmd.exe /c' is required on Windows for i/o redirection - path = "c:\\Windows\\System32\\cmd.exe" - args = append([]string{"/c", cmd.Path}, args...) - default: - if !strings.ContainsAny(cmd.Path, "/") { - // vmware-tools requires an absolute ProgramPath - // Default to 'bash -c' as a convenience - path = "/bin/bash" - if cmd.Path != "" { - arg := "'" + strings.Join(append([]string{cmd.Path}, args...), " ") + "'" - args = []string{"-c", arg} - } - } - } - - spec := types.GuestProgramSpec{ - ProgramPath: path, - Arguments: strings.Join(args, " "), - EnvVariables: cmd.Env, - WorkingDirectory: cmd.Dir, - } - - pid, err := c.ProcessManager.StartProgram(ctx, c.Authentication, &spec) - if err != nil { - return err - } - - rc := 0 - for { - procs, err := c.ProcessManager.ListProcesses(ctx, c.Authentication, []int64{pid}) - if err != nil { - return err - } - - p := procs[0] - if p.EndTime == nil { - <-time.After(time.Second / 2) - continue - } - - rc = int(p.ExitCode) - - break - } - - for _, out := range output { - if out.Writer == nil { - continue - } - - f, _, err := c.Download(ctx, out.path) - if err != nil { - return err - } - - _, err = io.Copy(out.Writer, f) - _ = f.Close() - if err != nil { - return err - } - } - - if rc != 0 { - return &exitError{fmt.Errorf("%s: exit %d", cmd.Path, rc), rc} - } - - return nil -} - -// archiveReader wraps an io.ReadCloser to support streaming download -// of a guest directory, stops reading once it sees the stream trailer. -// This is only useful when guest tools is the Go toolbox. -// The trailer is required since TransferFromGuest requires a Content-Length, -// which toolbox doesn't know ahead of time as the gzip'd tarball never touches the disk. -// We opted to wrap this here for now rather than guest.FileManager so -// DownloadFile can be also be used as-is to handle this use case. -type archiveReader struct { - io.ReadCloser -} - -var ( - gzipHeader = []byte{0x1f, 0x8b, 0x08} // rfc1952 {ID1, ID2, CM} - gzipHeaderLen = len(gzipHeader) -) - -func (r *archiveReader) Read(buf []byte) (int, error) { - nr, err := r.ReadCloser.Read(buf) - - // Stop reading if the last N bytes are the gzipTrailer - if nr >= gzipHeaderLen { - if bytes.Equal(buf[nr-gzipHeaderLen:nr], gzipHeader) { - nr -= gzipHeaderLen - err = io.EOF - } - } - - return nr, err -} - -func isDir(src string) bool { - u, err := url.Parse(src) - if err != nil { - return false - } - - return strings.HasSuffix(u.Path, "/") -} - -// Download initiates a file transfer from the guest -func (c *Client) Download(ctx context.Context, src string) (io.ReadCloser, int64, error) { - vc := c.ProcessManager.Client() - - info, err := c.FileManager.InitiateFileTransferFromGuest(ctx, c.Authentication, src) - if err != nil { - return nil, 0, err - } - - u, err := c.FileManager.TransferURL(ctx, info.Url) - if err != nil { - return nil, 0, err - } - - p := soap.DefaultDownload - - f, n, err := vc.Download(ctx, u, &p) - if err != nil { - return nil, n, err - } - - if strings.HasPrefix(src, "/archive:/") || isDir(src) { - f = &archiveReader{ReadCloser: f} // look for the gzip trailer - } - - return f, n, nil -} - -// Upload transfers a file to the guest -func (c *Client) Upload(ctx context.Context, src io.Reader, dst string, p soap.Upload, attr types.BaseGuestFileAttributes, force bool) error { - vc := c.ProcessManager.Client() - - var err error - - if p.ContentLength == 0 { // Content-Length is required - switch r := src.(type) { - case *bytes.Buffer: - p.ContentLength = int64(r.Len()) - case *bytes.Reader: - p.ContentLength = int64(r.Len()) - case *strings.Reader: - p.ContentLength = int64(r.Len()) - case *os.File: - info, serr := r.Stat() - if serr != nil { - return serr - } - - p.ContentLength = info.Size() - } - - if p.ContentLength == 0 { // os.File for example could be a device (stdin) - buf := new(bytes.Buffer) - - p.ContentLength, err = io.Copy(buf, src) - if err != nil { - return err - } - - src = buf - } - } - - url, err := c.FileManager.InitiateFileTransferToGuest(ctx, c.Authentication, dst, attr, p.ContentLength, force) - if err != nil { - return err - } - - u, err := c.FileManager.TransferURL(ctx, url) - if err != nil { - return err - } - - return vc.Client.Upload(ctx, src, u, &p) -}