forked from vmware/govmomi
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
toolbox: add http and exec round trippers
Add guest/toolbox.Client that implements http.RoundTripper and Run method that behaves like exec.Cmd.Run
- Loading branch information
Showing
10 changed files
with
796 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
/* | ||
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 guest | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"flag" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
|
||
"github.com/vmware/govmomi/govc/cli" | ||
"github.com/vmware/govmomi/guest/toolbox" | ||
) | ||
|
||
type run struct { | ||
*GuestFlag | ||
|
||
data string | ||
verbose bool | ||
dir string | ||
vars env | ||
} | ||
|
||
func init() { | ||
cli.Register("guest.run", &run{}) | ||
} | ||
|
||
func (cmd *run) Register(ctx context.Context, f *flag.FlagSet) { | ||
cmd.GuestFlag, ctx = newGuestFlag(ctx) | ||
cmd.GuestFlag.Register(ctx, f) | ||
|
||
f.StringVar(&cmd.data, "d", "", "Input data") | ||
f.BoolVar(&cmd.verbose, "v", false, "Verbose") | ||
f.StringVar(&cmd.dir, "C", "", "The absolute path of the working directory for the program to start") | ||
f.Var(&cmd.vars, "e", "Set environment variable or HTTP header") | ||
} | ||
|
||
func (cmd *run) Process(ctx context.Context) error { | ||
if err := cmd.GuestFlag.Process(ctx); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (cmd *run) Usage() string { | ||
return "NAME [ARG]..." | ||
} | ||
|
||
func (cmd *run) Description() string { | ||
return `Run program NAME in VM and display output. | ||
This command depends on govmomi/toolbox running in the VM guest and does not work with standard VMware tools. | ||
If the program NAME is an HTTP verb, the toolbox's http.RoundTripper will be used as the HTTP transport. | ||
Examples: | ||
govc guest.run -vm $name kubectl get pods | ||
govc guest.run -vm $name -d - kubectl create -f - <svc.json | ||
govc guest.run -vm $name kubectl delete pod,service my-service | ||
govc guest.run -vm $name GET http://localhost:8080/api/v1/nodes | ||
govc guest.run -vm $name -e Content-Type:application/json -d - POST http://localhost:8080/api/v1/namespaces/default/pods <svc.json | ||
govc guest.run -vm $name DELETE http://localhost:8080/api/v1/namespaces/default/services/my-service` | ||
} | ||
|
||
func (cmd *run) do(c *http.Client, req *http.Request) error { | ||
for _, v := range cmd.vars { | ||
h := strings.SplitN(v, ":", 2) | ||
if len(h) != 2 { | ||
return fmt.Errorf("invalid header: %q", v) | ||
} | ||
|
||
req.Header.Set(strings.TrimSpace(h[0]), strings.TrimSpace(h[1])) | ||
} | ||
|
||
res, err := c.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if cmd.verbose { | ||
return res.Write(cmd.Out) | ||
} | ||
|
||
_, err = io.Copy(cmd.Out, res.Body) | ||
|
||
_ = res.Body.Close() | ||
|
||
return err | ||
} | ||
|
||
func (cmd *run) Run(ctx context.Context, f *flag.FlagSet) error { | ||
name := f.Arg(0) | ||
|
||
pm, err := cmd.ProcessManager() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
fm, err := cmd.FileManager() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
tc := &toolbox.Client{ | ||
ProcessManager: pm, | ||
FileManager: fm, | ||
Authentication: cmd.Auth(), | ||
} | ||
|
||
hc := &http.Client{ | ||
Transport: tc, | ||
} | ||
|
||
switch name { | ||
case "HEAD", "GET", "DELETE": | ||
req, err := http.NewRequest(name, f.Arg(1), nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return cmd.do(hc, req) | ||
case "POST": | ||
req, err := http.NewRequest("POST", f.Arg(1), os.Stdin) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return cmd.do(hc, req) | ||
default: | ||
ecmd := &exec.Cmd{ | ||
Path: name, | ||
Args: f.Args()[1:], | ||
Env: cmd.vars, | ||
Dir: cmd.dir, | ||
Stdout: os.Stdout, | ||
Stderr: os.Stderr, | ||
} | ||
|
||
switch cmd.data { | ||
case "": | ||
case "-": | ||
ecmd.Stdin = os.Stdin | ||
default: | ||
ecmd.Stdin = bytes.NewBuffer([]byte(cmd.data)) | ||
} | ||
|
||
return tc.Run(ctx, ecmd) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
/* | ||
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 ( | ||
"bufio" | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os/exec" | ||
"strings" | ||
|
||
"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 | ||
} | ||
|
||
// RoundTrip implements http.RoundTripper over vmx guest RPC. | ||
// This transport depends on govmomi/toolbox running in the VM guest and does not work with standard VMware tools. | ||
// Using this transport makes it is possible to connect to HTTP endpoints that are bound to the VM's loopback address. | ||
// Note that the toolbox's http.RoundTripper only supports the "http" scheme, "https" is not supported. | ||
func (c *Client) RoundTrip(req *http.Request) (*http.Response, error) { | ||
if req.URL.Scheme != "http" { | ||
return nil, fmt.Errorf("%q scheme not supported", req.URL.Scheme) | ||
} | ||
|
||
ctx := req.Context() | ||
|
||
req.Header.Set("Connection", "close") // we need the server to close the connection after 1 request | ||
|
||
spec := types.GuestProgramSpec{ | ||
ProgramPath: "http.RoundTrip", | ||
Arguments: req.URL.Host, | ||
} | ||
|
||
pid, err := c.ProcessManager.StartProgram(ctx, c.Authentication, &spec) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
dst := fmt.Sprintf("/proc/%d/stdin", pid) | ||
src := fmt.Sprintf("/proc/%d/stdout", pid) | ||
|
||
var buf bytes.Buffer | ||
err = req.Write(&buf) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
attr := new(types.GuestPosixFileAttributes) | ||
size := int64(buf.Len()) | ||
|
||
url, err := c.FileManager.InitiateFileTransferToGuest(ctx, c.Authentication, dst, attr, size, true) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
vc := c.ProcessManager.Client() | ||
|
||
u, err := vc.ParseURL(url) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
p := soap.DefaultUpload | ||
p.ContentLength = size | ||
|
||
err = vc.Client.Upload(&buf, u, &p) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
info, err := c.FileManager.InitiateFileTransferFromGuest(ctx, c.Authentication, src) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
u, err = vc.ParseURL(info.Url) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
f, _, err := vc.Client.Download(u, &soap.DefaultDownload) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return http.ReadResponse(bufio.NewReader(f), req) | ||
} | ||
|
||
// Run implements exec.Cmd.Run over vmx guest RPC. | ||
func (c *Client) Run(ctx context.Context, cmd *exec.Cmd) error { | ||
vc := c.ProcessManager.Client() | ||
|
||
spec := types.GuestProgramSpec{ | ||
ProgramPath: cmd.Path, | ||
Arguments: strings.Join(cmd.Args, " "), | ||
EnvVariables: cmd.Env, | ||
WorkingDirectory: cmd.Dir, | ||
} | ||
|
||
pid, serr := c.ProcessManager.StartProgram(ctx, c.Authentication, &spec) | ||
if serr != nil { | ||
return serr | ||
} | ||
|
||
if cmd.Stdin != nil { | ||
dst := fmt.Sprintf("/proc/%d/stdin", pid) | ||
|
||
var buf bytes.Buffer | ||
size, err := io.Copy(&buf, cmd.Stdin) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
attr := new(types.GuestPosixFileAttributes) | ||
|
||
url, err := c.FileManager.InitiateFileTransferToGuest(ctx, c.Authentication, dst, attr, size, true) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
u, err := vc.ParseURL(url) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
p := soap.DefaultUpload | ||
p.ContentLength = size | ||
|
||
err = vc.Client.Upload(&buf, u, &p) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
names := []string{"out", "err"} | ||
|
||
for i, w := range []io.Writer{cmd.Stdout, cmd.Stderr} { | ||
if w == nil { | ||
continue | ||
} | ||
|
||
src := fmt.Sprintf("/proc/%d/std%s", pid, names[i]) | ||
|
||
info, err := c.FileManager.InitiateFileTransferFromGuest(ctx, c.Authentication, src) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
u, err := vc.ParseURL(info.Url) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
f, _, err := vc.Client.Download(u, &soap.DefaultDownload) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = io.Copy(w, f) | ||
_ = f.Close() | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
procs, err := c.ProcessManager.ListProcesses(ctx, c.Authentication, []int64{pid}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if len(procs) == 1 { | ||
rc := procs[0].ExitCode | ||
if rc != 0 { | ||
return fmt.Errorf("%s: exit %d", cmd.Path, rc) | ||
} | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.