From 870c9f4a7ab80745ae1b8f63093db13cb575af48 Mon Sep 17 00:00:00 2001 From: Thomas Hallgren Date: Mon, 3 Feb 2025 10:41:43 +0100 Subject: [PATCH] Add build flag to link the go-fuseftp implementation. This build-flag is not enabled by default, but can be used when there is a desire to build a telepresence client that links the fuseftp implementation instead of using a remote process via gRPC. Signed-off-by: Thomas Hallgren --- DEPENDENCIES.md | 3 + build-aux/main.mk | 42 +++++---- go.mod | 3 + go.sum | 6 ++ pkg/client/remotefs/fuseftp.go | 2 +- pkg/client/remotefs/fuseftp_docker.go | 15 ++-- .../{fuseftpembed.go => fuseftp_embedded.go} | 2 +- .../{fuseftpextern.go => fuseftp_external.go} | 2 +- .../remotefs/{ftp.go => fuseftp_grpc.go} | 2 + pkg/client/remotefs/fuseftp_linked.go | 87 +++++++++++++++++++ pkg/client/userd/trafficmgr/mount.go | 3 +- pkg/grpc/server/server.go | 2 +- 12 files changed, 141 insertions(+), 28 deletions(-) rename pkg/client/remotefs/{fuseftpembed.go => fuseftp_embedded.go} (90%) rename pkg/client/remotefs/{fuseftpextern.go => fuseftp_external.go} (83%) rename pkg/client/remotefs/{ftp.go => fuseftp_grpc.go} (98%) create mode 100644 pkg/client/remotefs/fuseftp_linked.go diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index b60fd4cc74..ada6ffbe61 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -79,6 +79,7 @@ following Free and Open Source software: github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb MIT license github.com/huandu/xstrings v1.5.0 MIT license github.com/inconshreveable/mousetrap v1.1.0 Apache License 2.0 + github.com/jlaffaye/ftp v0.2.0 ISC license github.com/jmoiron/sqlx v1.4.0 MIT license github.com/josharian/intern v1.0.1-0.20211109044230-42b52b674af5 MIT license github.com/json-iterator/go v1.1.12 MIT license @@ -128,10 +129,12 @@ following Free and Open Source software: github.com/spf13/cobra v1.8.1 Apache License 2.0 github.com/spf13/pflag v1.0.6 3-clause BSD license github.com/stretchr/testify v1.10.0 MIT license + github.com/telepresenceio/go-fuseftp v0.6.1 Apache License 2.0 github.com/telepresenceio/go-fuseftp/rpc v0.6.1 Apache License 2.0 github.com/telepresenceio/telepresence/rpc/v2 (modified) Apache License 2.0 github.com/vishvananda/netlink v1.3.0 Apache License 2.0 github.com/vishvananda/netns v0.0.5 Apache License 2.0 + github.com/winfsp/cgofuse v1.6.0 MIT license github.com/x448/float16 v0.8.4 MIT license github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb Apache License 2.0 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 Apache License 2.0 diff --git a/build-aux/main.mk b/build-aux/main.mk index 3b058bd36c..76db832263 100644 --- a/build-aux/main.mk +++ b/build-aux/main.mk @@ -34,6 +34,9 @@ export DOCKER_BUILDKIT := 1 .PHONY: FORCE FORCE: +EXTERNAL_FUSEFTP=0 +LINKED_FUSEFTP=0 + # Build with CGO_ENABLED=0 on all platforms to ensure that the binary is as # portable as possible, but we must make an exception for darwin, because # the Go implementation of the DNS resolver doesn't work properly there unless @@ -41,8 +44,13 @@ FORCE: ifeq ($(GOOS),darwin) CGO_ENABLED=1 else +ifeq ($(GOOS),linux) +# The winfsp module requires CGO on Linux. +CGO_ENABLED=$(LINKED_FUSEFTP) +else CGO_ENABLED=0 endif +endif ifeq ($(GOOS),windows) BEXE=.exe @@ -52,8 +60,6 @@ BEXE= BZIP= endif -EMBED_FUSEFTP=1 - # Generate: artifacts that get checked in to Git # ============================================== @@ -161,10 +167,19 @@ else sdkroot= endif +BUILD_TAGS= +build-deps: + ifeq ($(DOCKER_BUILD),1) +BUILD_TAGS=-tags docker +else +ifeq ($(EXTERNAL_FUSEFTP),1) +BUILD_TAGS=-tags external_fuseftp build-deps: else -ifeq ($(EMBED_FUSEFTP),1) +ifeq ($(LINKED_FUSEFTP),1) +BUILD_TAGS=-tags linked_fuseftp +else FUSEFTP_VERSION=$(shell go list -m -f {{.Version}} github.com/telepresenceio/go-fuseftp/rpc) $(BUILDDIR)/fuseftp-$(GOOS)-$(GOARCH)$(BEXE): go.mod @@ -175,8 +190,7 @@ pkg/client/remotefs/fuseftp.bits: $(BUILDDIR)/fuseftp-$(GOOS)-$(GOARCH)$(BEXE) F cp $< $@ build-deps: pkg/client/remotefs/fuseftp.bits -else -build-deps: +endif endif endif @@ -208,14 +222,10 @@ $(TELEPRESENCE): build-deps $(BINDIR)/wintun.dll FORCE endif mkdir -p $(@D) ifeq ($(DOCKER_BUILD),1) - CGO_ENABLED=$(CGO_ENABLED) $(sdkroot) go build -tags docker -trimpath -ldflags=-X=$(PKG_VERSION).Version=$(TELEPRESENCE_VERSION) -o $@ ./cmd/telepresence + CGO_ENABLED=$(CGO_ENABLED) $(sdkroot) go build $(BUILD_TAGS) -trimpath -ldflags=-X=$(PKG_VERSION).Version=$(TELEPRESENCE_VERSION) -o $@ ./cmd/telepresence else # -buildmode=pie addresses https://github.com/datawire/telepresence2-proprietary/issues/315 -ifeq ($(EMBED_FUSEFTP),1) - CGO_ENABLED=$(CGO_ENABLED) $(sdkroot) go build -tags embed_fuseftp -buildmode=pie -trimpath -ldflags=-X=$(PKG_VERSION).Version=$(TELEPRESENCE_VERSION) -o $@ ./cmd/telepresence -else - CGO_ENABLED=$(CGO_ENABLED) $(sdkroot) go build -buildmode=pie -trimpath -ldflags=-X=$(PKG_VERSION).Version=$(TELEPRESENCE_VERSION) -o $@ ./cmd/telepresence -endif + CGO_ENABLED=$(CGO_ENABLED) $(sdkroot) go build $(BUILD_TAGS) -buildmode=pie -trimpath -ldflags=-X=$(PKG_VERSION).Version=$(TELEPRESENCE_VERSION) -o $@ ./cmd/telepresence endif ifeq ($(GOOS),windows) @@ -375,7 +385,7 @@ shellscripts += ./packaging/windows-package.sh lint: lint-rpc lint-go -GOLANGCI_VERSION:=v1.62.2 +GOLANGCI_VERSION:=v1.63.4 lint-go: lint-deps ## (QA) Run the golangci-lint $(eval badimports = $(shell find cmd integration_test pkg -name '*.go' | grep -v '/mocks/' | xargs $(tools/gosimports) --local github.com/datawire/,github.com/telepresenceio/ -l)) @@ -402,7 +412,7 @@ ifeq ($(GOHOSTOS),windows) run --timeout 8m --fix ./cmd/telepresence/... ./integration_test/... ./pkg/... else docker run -e GOOS=$(GOOS) --rm -v $$(pwd):/app -v ~/.cache/golangci-lint/$(GOLANGCI_VERSION):/root/.cache -w /app golangci/golangci-lint:$(GOLANGCI_VERSION) golangci-lint \ - run --timeout 8m --fix ./... + run --timeout 8m --fix ./cmd/telepresence/... ./cmd/manager/cmd/... ./cmd/agent/cmd/... ./integration_test/... ./pkg/... endif $(tools/protolint) lint --fix rpc || true @@ -431,11 +441,7 @@ endif # is only used for extensions. Therefore, we want to validate that our tests, and # telepresence, run without requiring any outside dependencies. set -o pipefail -ifeq ($(EMBED_FUSEFTP),1) - TELEPRESENCE_MAX_LOGFILES=300 TELEPRESENCE_LOGIN_DOMAIN=127.0.0.1 CGO_ENABLED=$(CGO_ENABLED) go test -tags embed_fuseftp -failfast -json -timeout=80m ./integration_test/... | $(tools/test-report) -else - TELEPRESENCE_MAX_LOGFILES=300 TELEPRESENCE_LOGIN_DOMAIN=127.0.0.1 CGO_ENABLED=$(CGO_ENABLED) go test -failfast -json -timeout=80m ./integration_test/... | $(tools/test-report) -endif + TELEPRESENCE_MAX_LOGFILES=300 TELEPRESENCE_LOGIN_DOMAIN=127.0.0.1 CGO_ENABLED=$(CGO_ENABLED) go test $(BUILD_TAGS) -failfast -json -timeout=80m ./integration_test/... | $(tools/test-report) .PHONY: _login _login: diff --git a/go.mod b/go.mod index fbb89a5b78..0486ca53a1 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 + github.com/telepresenceio/go-fuseftp v0.6.1 github.com/telepresenceio/go-fuseftp/rpc v0.6.1 github.com/telepresenceio/telepresence/rpc/v2 v2.21.1 github.com/vishvananda/netlink v1.3.0 @@ -115,6 +116,7 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jlaffaye/ftp v0.2.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/josharian/intern v1.0.1-0.20211109044230-42b52b674af5 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -154,6 +156,7 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/spf13/cast v1.7.1 // indirect github.com/vishvananda/netns v0.0.5 // indirect + github.com/winfsp/cgofuse v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect diff --git a/go.sum b/go.sum index af0bb3dbba..2162924305 100644 --- a/go.sum +++ b/go.sum @@ -222,6 +222,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg= +github.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.1-0.20211109044230-42b52b674af5 h1:f8m7k2T128wwQej7ewBVgUfHNgCu3uXod6wopWGDvE4= @@ -397,6 +399,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 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/telepresenceio/go-fuseftp v0.6.1 h1:v3Hqwl2rDxep3kHfEOwsXijOOCXT3blRGN0cs45uR9A= +github.com/telepresenceio/go-fuseftp v0.6.1/go.mod h1:EXH+oL5GalxZ/pZV5MCHFYW3aLW1SRxV5wSedWoSS2g= github.com/telepresenceio/go-fuseftp/rpc v0.6.1 h1:y/RA6OiE6qM47SiBeSaez+a+PVdIUEl68M7Wl+SqJcc= github.com/telepresenceio/go-fuseftp/rpc v0.6.1/go.mod h1:jLvPHOWARcSRV5b1zxRYbv4Sa5VlMZxpnyTpCKrlPzA= github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= @@ -404,6 +408,8 @@ github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/winfsp/cgofuse v1.6.0 h1:re3W+HTd0hj4fISPBqfsrwyvPFpzqhDu8doJ9nOPDB0= +github.com/winfsp/cgofuse v1.6.0/go.mod h1:uxjoF2jEYT3+x+vC2KJddEGdk/LU8pRowXmyVMHSV5I= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= diff --git a/pkg/client/remotefs/fuseftp.go b/pkg/client/remotefs/fuseftp.go index a77ee48577..5e95a60737 100644 --- a/pkg/client/remotefs/fuseftp.go +++ b/pkg/client/remotefs/fuseftp.go @@ -1,4 +1,4 @@ -//go:build !docker +//go:build !docker && !linked_fuseftp package remotefs diff --git a/pkg/client/remotefs/fuseftp_docker.go b/pkg/client/remotefs/fuseftp_docker.go index f1833a8529..2b96c0e08d 100644 --- a/pkg/client/remotefs/fuseftp_docker.go +++ b/pkg/client/remotefs/fuseftp_docker.go @@ -1,30 +1,35 @@ //go:build docker -// +build docker package remotefs import ( "context" "errors" + "sync" "github.com/telepresenceio/go-fuseftp/rpc" ) +// NewFTPMounter returns nil. It's here to satisfy the linker. +func NewFTPMounter(rpc.FuseFTPClient, *sync.WaitGroup) Mounter { + return nil +} + type fuseFtpMgr struct{} type FuseFTPManager interface { - DeferInit(ctx context.Context) error - GetFuseFTPClient(ctx context.Context) rpc.FuseFTPClient + DeferInit(context.Context) error + GetFuseFTPClient(context.Context) rpc.FuseFTPClient } func NewFuseFTPManager() FuseFTPManager { return &fuseFtpMgr{} } -func (s *fuseFtpMgr) DeferInit(ctx context.Context) error { +func (s *fuseFtpMgr) DeferInit(context.Context) error { return errors.New("fuseftp client is not available") } -func (s *fuseFtpMgr) GetFuseFTPClient(ctx context.Context) rpc.FuseFTPClient { +func (s *fuseFtpMgr) GetFuseFTPClient(context.Context) rpc.FuseFTPClient { return nil } diff --git a/pkg/client/remotefs/fuseftpembed.go b/pkg/client/remotefs/fuseftp_embedded.go similarity index 90% rename from pkg/client/remotefs/fuseftpembed.go rename to pkg/client/remotefs/fuseftp_embedded.go index 3045aea491..caf23fcf5d 100644 --- a/pkg/client/remotefs/fuseftpembed.go +++ b/pkg/client/remotefs/fuseftp_embedded.go @@ -1,4 +1,4 @@ -//go:build embed_fuseftp && !docker +//go:build !(docker || external_fuseftp || linked_fuseftp) package remotefs diff --git a/pkg/client/remotefs/fuseftpextern.go b/pkg/client/remotefs/fuseftp_external.go similarity index 83% rename from pkg/client/remotefs/fuseftpextern.go rename to pkg/client/remotefs/fuseftp_external.go index 997cffd411..24cba4f6b4 100644 --- a/pkg/client/remotefs/fuseftpextern.go +++ b/pkg/client/remotefs/fuseftp_external.go @@ -1,4 +1,4 @@ -//go:build !embed_fuseftp && !docker +//go:build external_fuseftp && !docker package remotefs diff --git a/pkg/client/remotefs/ftp.go b/pkg/client/remotefs/fuseftp_grpc.go similarity index 98% rename from pkg/client/remotefs/ftp.go rename to pkg/client/remotefs/fuseftp_grpc.go index afc42380d1..43072da6de 100644 --- a/pkg/client/remotefs/ftp.go +++ b/pkg/client/remotefs/fuseftp_grpc.go @@ -1,3 +1,5 @@ +//go:build !(linked_fuseftp || docker) + package remotefs import ( diff --git a/pkg/client/remotefs/fuseftp_linked.go b/pkg/client/remotefs/fuseftp_linked.go new file mode 100644 index 0000000000..f446d597f1 --- /dev/null +++ b/pkg/client/remotefs/fuseftp_linked.go @@ -0,0 +1,87 @@ +//go:build linked_fuseftp && !docker + +package remotefs + +import ( + "context" + "net/netip" + "strings" + "sync" + "time" + + "github.com/sirupsen/logrus" + + "github.com/datawire/dlib/dlog" + "github.com/telepresenceio/go-fuseftp/pkg/fs" + "github.com/telepresenceio/go-fuseftp/rpc" + "github.com/telepresenceio/telepresence/v2/pkg/agentconfig" + "github.com/telepresenceio/telepresence/v2/pkg/client" +) + +type ftpMounter struct { + mountPoint string + cancel context.CancelFunc + ftpClient fs.FTPClient + iceptWG *sync.WaitGroup +} + +type fuseFtpMgr struct{} + +type FuseFTPManager interface { + DeferInit(ctx context.Context) error + GetFuseFTPClient(ctx context.Context) rpc.FuseFTPClient +} + +func NewFuseFTPManager() FuseFTPManager { + return &fuseFtpMgr{} +} + +func (s *fuseFtpMgr) DeferInit(_ context.Context) error { + return nil +} + +func (s *fuseFtpMgr) GetFuseFTPClient(_ context.Context) rpc.FuseFTPClient { + return rpc.NewFuseFTPClient(nil) +} + +func NewFTPMounter(_ rpc.FuseFTPClient, iceptWG *sync.WaitGroup) Mounter { + // The FTPClient uses the global logrus logger. It's very verbose on DebugLevel. + logrus.SetLevel(logrus.InfoLevel) + return &ftpMounter{iceptWG: iceptWG} +} + +func (m *ftpMounter) Start(ctx context.Context, workload, container, clientMountPoint, mountPoint string, podAddrPort netip.AddrPort, ro bool) error { + roTxt := "" + if ro { + roTxt = " read-only" + } + if m.ftpClient == nil { + cfg := client.GetConfig(ctx) + dlog.Infof(ctx, "Mounting FTP file system for container %s[%s] (address %s)%s at %q", workload, container, podAddrPort, roTxt, clientMountPoint) + // FTPs remote mount is already relative to the agentconfig.ExportsMountPoint + rmp := strings.TrimPrefix(mountPoint, agentconfig.ExportsMountPoint) + ftpClient, err := fs.NewFTPClient(ctx, podAddrPort, rmp, ro, cfg.Timeouts().Get(client.TimeoutFtpReadWrite)) + if err != nil { + return err + } + host := fs.NewHost(ftpClient, clientMountPoint) + if err = host.Start(ctx, 5*time.Second); err != nil { + return err + } + + m.ftpClient = ftpClient + // Ensure unmount when intercept context is cancelled + m.iceptWG.Add(1) + go func() { + defer m.iceptWG.Done() + <-ctx.Done() + dlog.Debugf(ctx, "Unmounting FTP file system for container %s[%s] (address %s) at %q", workload, container, podAddrPort, clientMountPoint) + }() + dlog.Infof(ctx, "File system for container %s[%s] (address %s) successfully mounted%s at %q", workload, container, podAddrPort, roTxt, clientMountPoint) + return nil + } + + // Assign a new address to the FTP client. This kills any open connections but leaves the FUSE driver intact + dlog.Infof(ctx, "Switching remote address to %s for FTP file system for workload container %s[%s] at %q", podAddrPort, workload, container, clientMountPoint) + return m.ftpClient.SetAddress(podAddrPort) +} diff --git a/pkg/client/userd/trafficmgr/mount.go b/pkg/client/userd/trafficmgr/mount.go index daeb6c89e4..71e5792428 100644 --- a/pkg/client/userd/trafficmgr/mount.go +++ b/pkg/client/userd/trafficmgr/mount.go @@ -44,7 +44,8 @@ func (pa *podAccess) startMount(ctx context.Context, iceptWG, podWG *sync.WaitGr } // The FTP mounter survives multiple starts for the same intercept. It just resets the address mountCtx = pa.ctx - if fuseftp = userd.GetService(ctx).FuseFTPMgr().GetFuseFTPClient(ctx); fuseftp == nil { + fuseftp = userd.GetService(ctx).FuseFTPMgr().GetFuseFTPClient(ctx) + if fuseftp == nil { dlog.Errorf(ctx, "Client is configured to perform remote mounts using FTP, but the fuseftp server was unable to start") return } diff --git a/pkg/grpc/server/server.go b/pkg/grpc/server/server.go index 83e8a9c00a..3964100a83 100644 --- a/pkg/grpc/server/server.go +++ b/pkg/grpc/server/server.go @@ -115,9 +115,9 @@ func Stop(ctx context.Context, svc *grpc.Server, maxTime time.Duration) { // if the context has soft-cancel enabled. The server's Stop function will be called if no soft-cancel is enabled or // when the GracefulStop doesn't finish until the Done channel of the hard context closed. func Wait(ctx context.Context, svc *grpc.Server) { + <-ctx.Done() hardCtx := dcontext.HardContext(ctx) if hardCtx != ctx { - <-ctx.Done() dead := make(chan struct{}) go func() { dlog.Debug(ctx, "Initiating soft shutdown")