"tart exec" support (--run-rpc) and component groups (e.g. --run-daemon) (#10)
* "tart exec" support (--run-rpc) and component groups (e.g. --run-daemon) * CI: check for lacking "buf generate" invocation * CI: a task to run "buf push" * defer ptmx.Close() * Use getpeername(2) to get AF_VSOCK peer's identity * Only call ptmx.Close() when pty.StartWithSize() succeeded
This commit is contained in:
parent
9f799bbfda
commit
9fac15bc9a
24
.cirrus.yml
24
.cirrus.yml
|
|
@ -35,6 +35,30 @@ task:
|
|||
prepare_script: brew install go
|
||||
test_script: go test -v ./...
|
||||
|
||||
task:
|
||||
name: Check for lacking "buf generate" invocation
|
||||
|
||||
container:
|
||||
image: golang:latest
|
||||
|
||||
install_buf_script: go install github.com/bufbuild/buf/cmd/buf@v1.50.0
|
||||
generate_script: buf generate
|
||||
check_script: git diff --exit-code
|
||||
|
||||
task:
|
||||
only_if: $CIRRUS_BRANCH != ''
|
||||
name: buf push
|
||||
|
||||
container:
|
||||
image: bufbuild/buf
|
||||
|
||||
login_script: echo $BUF_TOKEN | buf registry login --username $BUF_LOGIN --token-stdin
|
||||
push_script: buf push --git-metadata
|
||||
|
||||
env:
|
||||
BUF_LOGIN: fkorotkov
|
||||
BUF_TOKEN: ENCRYPTED[!8ee7eb2504cc84b08d4a7c0dacbe103640b1feaa26d06f0df010784e872d39e65a0cdea3fc7c09b065a917a77113b96b!]
|
||||
|
||||
task:
|
||||
name: Release (Dry Run)
|
||||
only_if: $CIRRUS_TAG == ''
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -6,7 +6,17 @@ Currently implemented features:
|
|||
|
||||
* Automatic disk resizing for macOS VMs with recovery partition removed (`--resize-disk`)
|
||||
* needs to be invoked as a launchd [global daemon](https://launchd.info/)
|
||||
* example usage: [`tart-guest-daemon.plist`](https://github.com/cirruslabs/macos-image-templates/blob/main/data/tart-guest-daemon.plist)
|
||||
* Clipboard sharing for macOS VMs using our in-house SPICE vdagent implementation (`--run-vdagent`)
|
||||
* needs to be invoked as a launchd [global agent](https://launchd.info/)
|
||||
* `tart exec` support (`--run-rpc`)
|
||||
* it's recommended to invoke it as a launchd [global agent](https://launchd.info/) because fewer privileges will be available to commands started via `tart exec`
|
||||
* however, you can also invoke it as a launchd [global daemon](https://launchd.info/) if running commands started via `tart exec` as `root` is desired
|
||||
|
||||
To run all features appropriate for a given context, use component groups:
|
||||
|
||||
* `--run-daemon`
|
||||
* implies `--resize-disk`
|
||||
* example usage: [`tart-guest-daemon.plist`](https://github.com/cirruslabs/macos-image-templates/blob/main/data/tart-guest-daemon.plist)
|
||||
* `--run-agent`
|
||||
* implies `--run-vdagent --run-rpc`
|
||||
* example usage: [`tart-guest-agent.plist`](https://github.com/cirruslabs/macos-image-templates/blob/main/data/tart-guest-agent.plist)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
version: v2
|
||||
plugins:
|
||||
- remote: buf.build/protocolbuffers/go:v1.36.6
|
||||
out: internal/
|
||||
opt: paths=source_relative
|
||||
- remote: buf.build/grpc/go:v1.5.1
|
||||
out: internal/
|
||||
opt: paths=source_relative
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
version: v2
|
||||
modules:
|
||||
- path: proto/
|
||||
name: buf.build/cirruslabs/tart-guest-agent
|
||||
lint:
|
||||
use:
|
||||
- STANDARD
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
7
go.mod
7
go.mod
|
|
@ -3,6 +3,7 @@ module github.com/cirruslabs/tart-guest-agent
|
|||
go 1.23.7
|
||||
|
||||
require (
|
||||
github.com/creack/pty v1.1.24
|
||||
github.com/hashicorp/go-version v1.7.0
|
||||
github.com/samber/lo v1.49.1
|
||||
github.com/spf13/cobra v1.9.1
|
||||
|
|
@ -10,6 +11,8 @@ require (
|
|||
go.uber.org/zap v1.27.0
|
||||
golang.design/x/clipboard v0.7.0
|
||||
golang.org/x/sys v0.32.0
|
||||
google.golang.org/grpc v1.72.1
|
||||
google.golang.org/protobuf v1.36.6
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
|
||||
|
|
@ -22,6 +25,8 @@ require (
|
|||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect
|
||||
golang.org/x/image v0.6.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
36
go.sum
36
go.sum
|
|
@ -1,7 +1,19 @@
|
|||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
|
|
@ -23,6 +35,18 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
|
|
@ -51,6 +75,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
@ -71,14 +97,20 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
|||
|
|
@ -2,11 +2,14 @@ package command
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/cirruslabs/tart-guest-agent/internal/diskresizer"
|
||||
"github.com/cirruslabs/tart-guest-agent/internal/logginglevel"
|
||||
"github.com/cirruslabs/tart-guest-agent/internal/rpc"
|
||||
"github.com/cirruslabs/tart-guest-agent/internal/spice/vdagent"
|
||||
"github.com/cirruslabs/tart-guest-agent/internal/tart"
|
||||
"github.com/cirruslabs/tart-guest-agent/internal/version"
|
||||
"github.com/cirruslabs/tart-guest-agent/internal/vsock"
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
|
@ -17,6 +20,11 @@ import (
|
|||
|
||||
var resizeDisk bool
|
||||
var runVdagent bool
|
||||
var runRPC bool
|
||||
|
||||
var runDaemon bool
|
||||
var runAgent bool
|
||||
|
||||
var debug bool
|
||||
|
||||
func NewRootCommand() *cobra.Command {
|
||||
|
|
@ -36,8 +44,17 @@ func NewRootCommand() *cobra.Command {
|
|||
RunE: run,
|
||||
}
|
||||
|
||||
// Individual components
|
||||
cmd.Flags().BoolVar(&resizeDisk, "resize-disk", false, "resize disk")
|
||||
cmd.Flags().BoolVar(&runVdagent, "run-vdagent", false, "run vdagent")
|
||||
cmd.Flags().BoolVar(&runRPC, "run-rpc", false, "run RPC service (currently required "+
|
||||
"to support \"tart exec\" functionality)")
|
||||
|
||||
// Component groups
|
||||
cmd.Flags().BoolVar(&runDaemon, "run-daemon", false, "identical to running the agent"+
|
||||
"with \"--resize-disk\" command-line argument")
|
||||
cmd.Flags().BoolVar(&runAgent, "run-agent", false, "identical to running the agent "+
|
||||
"with \"--run-vdagent\" and \"--run-rpc\" command-line arguments")
|
||||
|
||||
cmd.Flags().BoolVar(&debug, "debug", false, "enable debug logging")
|
||||
|
||||
|
|
@ -45,6 +62,16 @@ func NewRootCommand() *cobra.Command {
|
|||
}
|
||||
|
||||
func run(cmd *cobra.Command, args []string) error {
|
||||
// Component groups automatically enable certain individual components
|
||||
if runDaemon {
|
||||
resizeDisk = true
|
||||
}
|
||||
|
||||
if runAgent {
|
||||
runVdagent = true
|
||||
runRPC = true
|
||||
}
|
||||
|
||||
// Terminate to prevent corruption on systems with disk layouts other than Tart's
|
||||
communicationPoint, ok := tart.LocateCommunicationPoint()
|
||||
if !ok {
|
||||
|
|
@ -56,6 +83,8 @@ func run(cmd *cobra.Command, args []string) error {
|
|||
|
||||
// Perform disk resizing
|
||||
if resizeDisk {
|
||||
zap.S().Info("attempting to resize disk...")
|
||||
|
||||
if err := diskresizer.Resize(); err != nil {
|
||||
if errors.Is(err, diskresizer.ErrUnsupported) || errors.Is(err, diskresizer.ErrAlreadyResized) {
|
||||
zap.S().Infof("skipping disk resizing: %v", err)
|
||||
|
|
@ -68,14 +97,38 @@ func run(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
if runVdagent {
|
||||
zap.S().Infof("running vdagent...")
|
||||
|
||||
vdAgent, err := vdagent.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := vdAgent.Run(cmd.Context()); err != nil {
|
||||
zap.S().Fatalf("vdagent failed: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if runRPC {
|
||||
listener, err := vsock.Listen(8080)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on AF_VSOCK port 8080: %v", err)
|
||||
}
|
||||
|
||||
zap.S().Info("running RPC server on AF_VSOCK port 8080...")
|
||||
|
||||
rpcServer, err := rpc.New(listener)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := rpcServer.Run(); err != nil {
|
||||
zap.S().Fatalf("RPC server failed: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait indefinitely
|
||||
|
|
|
|||
|
|
@ -0,0 +1,538 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc (unknown)
|
||||
// source: rpc/agent.proto
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
_ "google.golang.org/protobuf/types/known/emptypb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type ExecRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Type:
|
||||
//
|
||||
// *ExecRequest_Command_
|
||||
// *ExecRequest_StandardInput
|
||||
// *ExecRequest_TerminalResize
|
||||
Type isExecRequest_Type `protobuf_oneof:"type"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ExecRequest) Reset() {
|
||||
*x = ExecRequest{}
|
||||
mi := &file_rpc_agent_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ExecRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ExecRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ExecRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_agent_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ExecRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ExecRequest) Descriptor() ([]byte, []int) {
|
||||
return file_rpc_agent_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ExecRequest) GetType() isExecRequest_Type {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExecRequest) GetCommand() *ExecRequest_Command {
|
||||
if x != nil {
|
||||
if x, ok := x.Type.(*ExecRequest_Command_); ok {
|
||||
return x.Command
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExecRequest) GetStandardInput() *IOChunk {
|
||||
if x != nil {
|
||||
if x, ok := x.Type.(*ExecRequest_StandardInput); ok {
|
||||
return x.StandardInput
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExecRequest) GetTerminalResize() *TerminalSize {
|
||||
if x != nil {
|
||||
if x, ok := x.Type.(*ExecRequest_TerminalResize); ok {
|
||||
return x.TerminalResize
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isExecRequest_Type interface {
|
||||
isExecRequest_Type()
|
||||
}
|
||||
|
||||
type ExecRequest_Command_ struct {
|
||||
Command *ExecRequest_Command `protobuf:"bytes,1,opt,name=command,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ExecRequest_StandardInput struct {
|
||||
StandardInput *IOChunk `protobuf:"bytes,2,opt,name=standard_input,json=standardInput,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ExecRequest_TerminalResize struct {
|
||||
TerminalResize *TerminalSize `protobuf:"bytes,3,opt,name=terminal_resize,json=terminalResize,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*ExecRequest_Command_) isExecRequest_Type() {}
|
||||
|
||||
func (*ExecRequest_StandardInput) isExecRequest_Type() {}
|
||||
|
||||
func (*ExecRequest_TerminalResize) isExecRequest_Type() {}
|
||||
|
||||
type ExecResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Type:
|
||||
//
|
||||
// *ExecResponse_Exit_
|
||||
// *ExecResponse_StandardOutput
|
||||
// *ExecResponse_StandardError
|
||||
Type isExecResponse_Type `protobuf_oneof:"type"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ExecResponse) Reset() {
|
||||
*x = ExecResponse{}
|
||||
mi := &file_rpc_agent_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ExecResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ExecResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ExecResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_agent_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ExecResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ExecResponse) Descriptor() ([]byte, []int) {
|
||||
return file_rpc_agent_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ExecResponse) GetType() isExecResponse_Type {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExecResponse) GetExit() *ExecResponse_Exit {
|
||||
if x != nil {
|
||||
if x, ok := x.Type.(*ExecResponse_Exit_); ok {
|
||||
return x.Exit
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExecResponse) GetStandardOutput() *IOChunk {
|
||||
if x != nil {
|
||||
if x, ok := x.Type.(*ExecResponse_StandardOutput); ok {
|
||||
return x.StandardOutput
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExecResponse) GetStandardError() *IOChunk {
|
||||
if x != nil {
|
||||
if x, ok := x.Type.(*ExecResponse_StandardError); ok {
|
||||
return x.StandardError
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isExecResponse_Type interface {
|
||||
isExecResponse_Type()
|
||||
}
|
||||
|
||||
type ExecResponse_Exit_ struct {
|
||||
Exit *ExecResponse_Exit `protobuf:"bytes,1,opt,name=exit,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ExecResponse_StandardOutput struct {
|
||||
StandardOutput *IOChunk `protobuf:"bytes,2,opt,name=standard_output,json=standardOutput,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ExecResponse_StandardError struct {
|
||||
StandardError *IOChunk `protobuf:"bytes,3,opt,name=standard_error,json=standardError,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*ExecResponse_Exit_) isExecResponse_Type() {}
|
||||
|
||||
func (*ExecResponse_StandardOutput) isExecResponse_Type() {}
|
||||
|
||||
func (*ExecResponse_StandardError) isExecResponse_Type() {}
|
||||
|
||||
type TerminalSize struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Rows uint32 `protobuf:"varint,1,opt,name=rows,proto3" json:"rows,omitempty"`
|
||||
Cols uint32 `protobuf:"varint,2,opt,name=cols,proto3" json:"cols,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *TerminalSize) Reset() {
|
||||
*x = TerminalSize{}
|
||||
mi := &file_rpc_agent_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *TerminalSize) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*TerminalSize) ProtoMessage() {}
|
||||
|
||||
func (x *TerminalSize) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_agent_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use TerminalSize.ProtoReflect.Descriptor instead.
|
||||
func (*TerminalSize) Descriptor() ([]byte, []int) {
|
||||
return file_rpc_agent_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *TerminalSize) GetRows() uint32 {
|
||||
if x != nil {
|
||||
return x.Rows
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *TerminalSize) GetCols() uint32 {
|
||||
if x != nil {
|
||||
return x.Cols
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type IOChunk struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *IOChunk) Reset() {
|
||||
*x = IOChunk{}
|
||||
mi := &file_rpc_agent_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *IOChunk) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*IOChunk) ProtoMessage() {}
|
||||
|
||||
func (x *IOChunk) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_agent_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use IOChunk.ProtoReflect.Descriptor instead.
|
||||
func (*IOChunk) Descriptor() ([]byte, []int) {
|
||||
return file_rpc_agent_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *IOChunk) GetData() []byte {
|
||||
if x != nil {
|
||||
return x.Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ExecRequest_Command struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Args []string `protobuf:"bytes,2,rep,name=args,proto3" json:"args,omitempty"`
|
||||
Interactive bool `protobuf:"varint,3,opt,name=interactive,proto3" json:"interactive,omitempty"`
|
||||
Tty bool `protobuf:"varint,4,opt,name=tty,proto3" json:"tty,omitempty"`
|
||||
TerminalSize *TerminalSize `protobuf:"bytes,5,opt,name=terminal_size,json=terminalSize,proto3" json:"terminal_size,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ExecRequest_Command) Reset() {
|
||||
*x = ExecRequest_Command{}
|
||||
mi := &file_rpc_agent_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ExecRequest_Command) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ExecRequest_Command) ProtoMessage() {}
|
||||
|
||||
func (x *ExecRequest_Command) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_agent_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ExecRequest_Command.ProtoReflect.Descriptor instead.
|
||||
func (*ExecRequest_Command) Descriptor() ([]byte, []int) {
|
||||
return file_rpc_agent_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *ExecRequest_Command) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ExecRequest_Command) GetArgs() []string {
|
||||
if x != nil {
|
||||
return x.Args
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExecRequest_Command) GetInteractive() bool {
|
||||
if x != nil {
|
||||
return x.Interactive
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ExecRequest_Command) GetTty() bool {
|
||||
if x != nil {
|
||||
return x.Tty
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *ExecRequest_Command) GetTerminalSize() *TerminalSize {
|
||||
if x != nil {
|
||||
return x.TerminalSize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ExecResponse_Exit struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ExecResponse_Exit) Reset() {
|
||||
*x = ExecResponse_Exit{}
|
||||
mi := &file_rpc_agent_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ExecResponse_Exit) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ExecResponse_Exit) ProtoMessage() {}
|
||||
|
||||
func (x *ExecResponse_Exit) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_rpc_agent_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ExecResponse_Exit.ProtoReflect.Descriptor instead.
|
||||
func (*ExecResponse_Exit) Descriptor() ([]byte, []int) {
|
||||
return file_rpc_agent_proto_rawDescGZIP(), []int{1, 0}
|
||||
}
|
||||
|
||||
func (x *ExecResponse_Exit) GetCode() int32 {
|
||||
if x != nil {
|
||||
return x.Code
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_rpc_agent_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_rpc_agent_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x0frpc/agent.proto\x1a\x1bgoogle/protobuf/empty.proto\"\xd0\x02\n" +
|
||||
"\vExecRequest\x120\n" +
|
||||
"\acommand\x18\x01 \x01(\v2\x14.ExecRequest.CommandH\x00R\acommand\x121\n" +
|
||||
"\x0estandard_input\x18\x02 \x01(\v2\b.IOChunkH\x00R\rstandardInput\x128\n" +
|
||||
"\x0fterminal_resize\x18\x03 \x01(\v2\r.TerminalSizeH\x00R\x0eterminalResize\x1a\x99\x01\n" +
|
||||
"\aCommand\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" +
|
||||
"\x04args\x18\x02 \x03(\tR\x04args\x12 \n" +
|
||||
"\vinteractive\x18\x03 \x01(\bR\vinteractive\x12\x10\n" +
|
||||
"\x03tty\x18\x04 \x01(\bR\x03tty\x122\n" +
|
||||
"\rterminal_size\x18\x05 \x01(\v2\r.TerminalSizeR\fterminalSizeB\x06\n" +
|
||||
"\x04type\"\xc4\x01\n" +
|
||||
"\fExecResponse\x12(\n" +
|
||||
"\x04exit\x18\x01 \x01(\v2\x12.ExecResponse.ExitH\x00R\x04exit\x123\n" +
|
||||
"\x0fstandard_output\x18\x02 \x01(\v2\b.IOChunkH\x00R\x0estandardOutput\x121\n" +
|
||||
"\x0estandard_error\x18\x03 \x01(\v2\b.IOChunkH\x00R\rstandardError\x1a\x1a\n" +
|
||||
"\x04Exit\x12\x12\n" +
|
||||
"\x04code\x18\x01 \x01(\x05R\x04codeB\x06\n" +
|
||||
"\x04type\"6\n" +
|
||||
"\fTerminalSize\x12\x12\n" +
|
||||
"\x04rows\x18\x01 \x01(\rR\x04rows\x12\x12\n" +
|
||||
"\x04cols\x18\x02 \x01(\rR\x04cols\"\x1d\n" +
|
||||
"\aIOChunk\x12\x12\n" +
|
||||
"\x04data\x18\x01 \x01(\fR\x04data20\n" +
|
||||
"\x05Agent\x12'\n" +
|
||||
"\x04Exec\x12\f.ExecRequest\x1a\r.ExecResponse(\x010\x01B5Z3github.com/cirruslabs/tart-guest-agent/internal/rpcb\x06proto3"
|
||||
|
||||
var (
|
||||
file_rpc_agent_proto_rawDescOnce sync.Once
|
||||
file_rpc_agent_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_rpc_agent_proto_rawDescGZIP() []byte {
|
||||
file_rpc_agent_proto_rawDescOnce.Do(func() {
|
||||
file_rpc_agent_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_rpc_agent_proto_rawDesc), len(file_rpc_agent_proto_rawDesc)))
|
||||
})
|
||||
return file_rpc_agent_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_rpc_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||
var file_rpc_agent_proto_goTypes = []any{
|
||||
(*ExecRequest)(nil), // 0: ExecRequest
|
||||
(*ExecResponse)(nil), // 1: ExecResponse
|
||||
(*TerminalSize)(nil), // 2: TerminalSize
|
||||
(*IOChunk)(nil), // 3: IOChunk
|
||||
(*ExecRequest_Command)(nil), // 4: ExecRequest.Command
|
||||
(*ExecResponse_Exit)(nil), // 5: ExecResponse.Exit
|
||||
}
|
||||
var file_rpc_agent_proto_depIdxs = []int32{
|
||||
4, // 0: ExecRequest.command:type_name -> ExecRequest.Command
|
||||
3, // 1: ExecRequest.standard_input:type_name -> IOChunk
|
||||
2, // 2: ExecRequest.terminal_resize:type_name -> TerminalSize
|
||||
5, // 3: ExecResponse.exit:type_name -> ExecResponse.Exit
|
||||
3, // 4: ExecResponse.standard_output:type_name -> IOChunk
|
||||
3, // 5: ExecResponse.standard_error:type_name -> IOChunk
|
||||
2, // 6: ExecRequest.Command.terminal_size:type_name -> TerminalSize
|
||||
0, // 7: Agent.Exec:input_type -> ExecRequest
|
||||
1, // 8: Agent.Exec:output_type -> ExecResponse
|
||||
8, // [8:9] is the sub-list for method output_type
|
||||
7, // [7:8] is the sub-list for method input_type
|
||||
7, // [7:7] is the sub-list for extension type_name
|
||||
7, // [7:7] is the sub-list for extension extendee
|
||||
0, // [0:7] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_rpc_agent_proto_init() }
|
||||
func file_rpc_agent_proto_init() {
|
||||
if File_rpc_agent_proto != nil {
|
||||
return
|
||||
}
|
||||
file_rpc_agent_proto_msgTypes[0].OneofWrappers = []any{
|
||||
(*ExecRequest_Command_)(nil),
|
||||
(*ExecRequest_StandardInput)(nil),
|
||||
(*ExecRequest_TerminalResize)(nil),
|
||||
}
|
||||
file_rpc_agent_proto_msgTypes[1].OneofWrappers = []any{
|
||||
(*ExecResponse_Exit_)(nil),
|
||||
(*ExecResponse_StandardOutput)(nil),
|
||||
(*ExecResponse_StandardError)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_rpc_agent_proto_rawDesc), len(file_rpc_agent_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 6,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_rpc_agent_proto_goTypes,
|
||||
DependencyIndexes: file_rpc_agent_proto_depIdxs,
|
||||
MessageInfos: file_rpc_agent_proto_msgTypes,
|
||||
}.Build()
|
||||
File_rpc_agent_proto = out.File
|
||||
file_rpc_agent_proto_goTypes = nil
|
||||
file_rpc_agent_proto_depIdxs = nil
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc (unknown)
|
||||
// source: rpc/agent.proto
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
Agent_Exec_FullMethodName = "/Agent/Exec"
|
||||
)
|
||||
|
||||
// AgentClient is the client API for Agent service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type AgentClient interface {
|
||||
Exec(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ExecRequest, ExecResponse], error)
|
||||
}
|
||||
|
||||
type agentClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewAgentClient(cc grpc.ClientConnInterface) AgentClient {
|
||||
return &agentClient{cc}
|
||||
}
|
||||
|
||||
func (c *agentClient) Exec(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[ExecRequest, ExecResponse], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &Agent_ServiceDesc.Streams[0], Agent_Exec_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[ExecRequest, ExecResponse]{ClientStream: stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type Agent_ExecClient = grpc.BidiStreamingClient[ExecRequest, ExecResponse]
|
||||
|
||||
// AgentServer is the server API for Agent service.
|
||||
// All implementations must embed UnimplementedAgentServer
|
||||
// for forward compatibility.
|
||||
type AgentServer interface {
|
||||
Exec(grpc.BidiStreamingServer[ExecRequest, ExecResponse]) error
|
||||
mustEmbedUnimplementedAgentServer()
|
||||
}
|
||||
|
||||
// UnimplementedAgentServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedAgentServer struct{}
|
||||
|
||||
func (UnimplementedAgentServer) Exec(grpc.BidiStreamingServer[ExecRequest, ExecResponse]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Exec not implemented")
|
||||
}
|
||||
func (UnimplementedAgentServer) mustEmbedUnimplementedAgentServer() {}
|
||||
func (UnimplementedAgentServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeAgentServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to AgentServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeAgentServer interface {
|
||||
mustEmbedUnimplementedAgentServer()
|
||||
}
|
||||
|
||||
func RegisterAgentServer(s grpc.ServiceRegistrar, srv AgentServer) {
|
||||
// If the following call pancis, it indicates UnimplementedAgentServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&Agent_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Agent_Exec_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(AgentServer).Exec(&grpc.GenericServerStream[ExecRequest, ExecResponse]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type Agent_ExecServer = grpc.BidiStreamingServer[ExecRequest, ExecResponse]
|
||||
|
||||
// Agent_ServiceDesc is the grpc.ServiceDesc for Agent service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Agent_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "Agent",
|
||||
HandlerType: (*AgentServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Exec",
|
||||
Handler: _Agent_Exec_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "rpc/agent.proto",
|
||||
}
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/creack/pty"
|
||||
"github.com/samber/lo"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
standardStreamsBufferSize = 4096
|
||||
|
||||
eofChar = 0x04
|
||||
)
|
||||
|
||||
type standardStreamOutput struct {
|
||||
Data []byte
|
||||
Err error
|
||||
}
|
||||
|
||||
func (rpc *RPC) Exec(stream grpc.BidiStreamingServer[ExecRequest, ExecResponse]) error {
|
||||
// Read the first exec request, it should describe a command to execute
|
||||
firstExecRequest, err := stream.Recv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
firstExecRequestCommand, ok := firstExecRequest.Type.(*ExecRequest_Command_)
|
||||
if !ok {
|
||||
return fmt.Errorf("first exec request should describe a command to execute")
|
||||
}
|
||||
|
||||
zap.S().Infof("executing %s", formatCommandAndArgs(firstExecRequestCommand.Command.Name,
|
||||
firstExecRequestCommand.Command.Args))
|
||||
|
||||
// Execute the command
|
||||
cmd := exec.CommandContext(stream.Context(), firstExecRequestCommand.Command.Name,
|
||||
firstExecRequestCommand.Command.Args...)
|
||||
|
||||
var stdin io.WriteCloser
|
||||
var stdout, stderr io.ReadCloser
|
||||
var ptmx *os.File
|
||||
|
||||
if firstExecRequestCommand.Command.Tty {
|
||||
ptmx, err = pty.StartWithSize(cmd, &pty.Winsize{
|
||||
Rows: uint16(firstExecRequestCommand.Command.GetTerminalSize().GetRows()),
|
||||
Cols: uint16(firstExecRequestCommand.Command.GetTerminalSize().GetCols()),
|
||||
})
|
||||
|
||||
if firstExecRequestCommand.Command.Interactive {
|
||||
stdin = ptmx
|
||||
}
|
||||
stdout = ptmx
|
||||
stderr = ptmx
|
||||
} else {
|
||||
if firstExecRequestCommand.Command.Interactive {
|
||||
stdin, err = cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
stdout, err = cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stderr, err = cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ptmx != nil {
|
||||
defer ptmx.Close()
|
||||
}
|
||||
|
||||
// Handle standard input and terminal resize events from the client
|
||||
fromClientErrCh := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
request, err := stream.Recv()
|
||||
if err != nil {
|
||||
if !errors.Is(err, context.Canceled) {
|
||||
fromClientErrCh <- err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
switch typedAction := request.Type.(type) {
|
||||
case *ExecRequest_StandardInput:
|
||||
if !firstExecRequestCommand.Command.Interactive {
|
||||
// Ignore standard input from the client
|
||||
// as non-interactive command is running
|
||||
continue
|
||||
}
|
||||
|
||||
dataToWrite := typedAction.StandardInput.Data
|
||||
|
||||
// Check if the remote client has received EOF on their standard input
|
||||
if len(typedAction.StandardInput.Data) == 0 {
|
||||
if firstExecRequestCommand.Command.Tty {
|
||||
// When using pseudo-terminal, we can't simply close the
|
||||
// standard input, as the file descriptor is shared for
|
||||
// standard output and standard error too, so we send
|
||||
// an EOF character instead
|
||||
dataToWrite = []byte{eofChar}
|
||||
} else {
|
||||
// Close the standard input
|
||||
if err := stdin.Close(); err != nil {
|
||||
fromClientErrCh <- err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := stdin.Write(dataToWrite); err != nil {
|
||||
fromClientErrCh <- err
|
||||
|
||||
return
|
||||
}
|
||||
case *ExecRequest_TerminalResize:
|
||||
// Ignore terminal resize requests
|
||||
// when pseudo terminal is disabled
|
||||
if !firstExecRequestCommand.Command.Tty {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := pty.Setsize(ptmx, &pty.Winsize{
|
||||
Rows: uint16(typedAction.TerminalResize.GetRows()),
|
||||
Cols: uint16(typedAction.TerminalResize.GetCols()),
|
||||
}); err != nil {
|
||||
fromClientErrCh <- err
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Handle standard output from the command
|
||||
stdoutOutputCh := make(chan *standardStreamOutput, 1)
|
||||
|
||||
go streamStandardStream(stdout, stdoutOutputCh)
|
||||
|
||||
// Handle standard error from the command
|
||||
//
|
||||
// Note that it makes no sense to handle standard error when TTY is requested
|
||||
// because in this case stdout and stderr will point to the same file descriptor
|
||||
stderrOutputCh := make(chan *standardStreamOutput, 1)
|
||||
|
||||
if !firstExecRequestCommand.Command.Tty {
|
||||
go streamStandardStream(stderr, stderrOutputCh)
|
||||
}
|
||||
|
||||
// Wait for the command to finish
|
||||
commandErrCh := make(chan error, 1)
|
||||
go func() {
|
||||
commandErrCh <- cmd.Wait()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case stdoutOutput := <-stdoutOutputCh:
|
||||
if err := stdoutOutput.Err; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := stream.Send(&ExecResponse{
|
||||
Type: &ExecResponse_StandardOutput{
|
||||
StandardOutput: &IOChunk{
|
||||
Data: stdoutOutput.Data,
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
case stderrOutput := <-stderrOutputCh:
|
||||
if err := stderrOutput.Err; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := stream.Send(&ExecResponse{
|
||||
Type: &ExecResponse_StandardError{
|
||||
StandardError: &IOChunk{
|
||||
Data: stderrOutput.Data,
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
case commandErr := <-commandErrCh:
|
||||
exitCode := 0
|
||||
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(commandErr, &exitError) {
|
||||
exitCode = exitError.ExitCode()
|
||||
}
|
||||
|
||||
return stream.Send(&ExecResponse{
|
||||
Type: &ExecResponse_Exit_{
|
||||
Exit: &ExecResponse_Exit{
|
||||
Code: int32(exitCode),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatCommandAndArgs(name string, args []string) string {
|
||||
var all []string
|
||||
|
||||
all = append(all, name)
|
||||
all = append(all, args...)
|
||||
|
||||
all = lo.Map(all, func(item string, _ int) string {
|
||||
return fmt.Sprintf("%q", item)
|
||||
})
|
||||
|
||||
return fmt.Sprintf("[%s]", strings.Join(all, ", "))
|
||||
}
|
||||
|
||||
func streamStandardStream(standardStream io.Reader, outputCh chan *standardStreamOutput) {
|
||||
buf := make([]byte, standardStreamsBufferSize)
|
||||
|
||||
for {
|
||||
n, err := standardStream.Read(buf)
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
outputCh <- &standardStreamOutput{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
outputCh <- &standardStreamOutput{
|
||||
Data: slices.Clone(buf[:n]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
"net"
|
||||
)
|
||||
|
||||
type RPC struct {
|
||||
grpcServer *grpc.Server
|
||||
listener net.Listener
|
||||
|
||||
UnimplementedAgentServer
|
||||
}
|
||||
|
||||
func New(listener net.Listener) (*RPC, error) {
|
||||
rpc := &RPC{
|
||||
grpcServer: grpc.NewServer(),
|
||||
listener: listener,
|
||||
}
|
||||
|
||||
RegisterAgentServer(rpc.grpcServer, rpc)
|
||||
|
||||
return rpc, nil
|
||||
}
|
||||
|
||||
func (rpc *RPC) Run() error {
|
||||
return rpc.grpcServer.Serve(rpc.listener)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package vsock
|
||||
|
||||
import "fmt"
|
||||
|
||||
type addr struct {
|
||||
port uint32
|
||||
}
|
||||
|
||||
func (addr *addr) Network() string {
|
||||
return "vsock"
|
||||
}
|
||||
|
||||
func (addr *addr) String() string {
|
||||
return fmt.Sprintf("%d", addr.port)
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package vsock
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type conn struct {
|
||||
file *os.File
|
||||
localPort uint32
|
||||
remotePort uint32
|
||||
}
|
||||
|
||||
func (conn *conn) Read(b []byte) (n int, err error) {
|
||||
return conn.file.Read(b)
|
||||
}
|
||||
|
||||
func (conn *conn) Write(b []byte) (n int, err error) {
|
||||
return conn.file.Write(b)
|
||||
}
|
||||
|
||||
func (conn *conn) SetDeadline(t time.Time) error {
|
||||
return conn.file.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (conn *conn) SetReadDeadline(t time.Time) error {
|
||||
return conn.file.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (conn *conn) SetWriteDeadline(t time.Time) error {
|
||||
return conn.file.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (conn *conn) LocalAddr() net.Addr {
|
||||
return &addr{port: conn.localPort}
|
||||
}
|
||||
|
||||
func (conn *conn) RemoteAddr() net.Addr {
|
||||
return &addr{port: conn.remotePort}
|
||||
}
|
||||
|
||||
func (conn *conn) Close() error {
|
||||
return conn.file.Close()
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package vsock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/sys/unix"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
type listener struct {
|
||||
file *os.File
|
||||
port uint32
|
||||
}
|
||||
|
||||
func Listen(port uint32) (net.Listener, error) {
|
||||
fd, err := unix.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file := os.NewFile(uintptr(fd), "vsock")
|
||||
|
||||
if err := unix.Bind(int(file.Fd()), &unix.SockaddrVM{
|
||||
CID: unix.VMADDR_CID_ANY,
|
||||
Port: port,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := unix.Listen(int(file.Fd()), unix.SOMAXCONN); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listener{
|
||||
file: file,
|
||||
port: port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (listener *listener) Accept() (net.Conn, error) {
|
||||
fd, _, err := unix.Accept(int(listener.file.Fd()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file := os.NewFile(uintptr(fd), "vsock")
|
||||
|
||||
peerName, err := unix.Getpeername(int(file.Fd()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get a peer name for an AF_VSOCK connection %w", err)
|
||||
}
|
||||
|
||||
peerNameVM, ok := peerName.(*unix.SockaddrVM)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("accepted a non-AF_VSOCK connection on an AF_VSOCK socket")
|
||||
}
|
||||
|
||||
return &conn{
|
||||
file: file,
|
||||
localPort: listener.port,
|
||||
remotePort: peerNameVM.Port,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (listener *listener) Addr() net.Addr {
|
||||
return &addr{port: listener.port}
|
||||
}
|
||||
|
||||
func (listener *listener) Close() error {
|
||||
return listener.file.Close()
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
option go_package = "github.com/cirruslabs/tart-guest-agent/internal/rpc";
|
||||
|
||||
service Agent {
|
||||
rpc Exec(stream ExecRequest) returns (stream ExecResponse);
|
||||
}
|
||||
|
||||
message ExecRequest {
|
||||
message Command {
|
||||
string name = 1;
|
||||
repeated string args = 2;
|
||||
bool interactive = 3;
|
||||
bool tty = 4;
|
||||
TerminalSize terminal_size = 5;
|
||||
}
|
||||
|
||||
oneof type {
|
||||
Command command = 1;
|
||||
IOChunk standard_input = 2;
|
||||
TerminalSize terminal_resize = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message ExecResponse {
|
||||
message Exit {
|
||||
int32 code = 1;
|
||||
}
|
||||
|
||||
oneof type {
|
||||
Exit exit = 1;
|
||||
IOChunk standard_output = 2;
|
||||
IOChunk standard_error = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message TerminalSize {
|
||||
uint32 rows = 1;
|
||||
uint32 cols = 2;
|
||||
}
|
||||
|
||||
message IOChunk {
|
||||
bytes data = 1;
|
||||
}
|
||||
Loading…
Reference in New Issue