From 703788b40ea9a1228008999cd4cf94c38994b2d6 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Tue, 1 Apr 2025 18:06:46 +0100 Subject: [PATCH] serve restic: add serve rc interface --- cmd/serve/restic/restic.go | 55 +++++++++++++++----- cmd/serve/restic/restic_appendonly_test.go | 2 +- cmd/serve/restic/restic_privaterepos_test.go | 2 +- cmd/serve/restic/restic_test.go | 17 ++++-- 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/cmd/serve/restic/restic.go b/cmd/serve/restic/restic.go index 04958fb50..efaff92ee 100644 --- a/cmd/serve/restic/restic.go +++ b/cmd/serve/restic/restic.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "net" "net/http" "os" "path" @@ -19,8 +20,10 @@ import ( cmdserve "github.com/rclone/rclone/cmd/serve" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/accounting" + "github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/flags" "github.com/rclone/rclone/fs/operations" + "github.com/rclone/rclone/fs/rc" "github.com/rclone/rclone/fs/walk" libhttp "github.com/rclone/rclone/lib/http" "github.com/rclone/rclone/lib/http/serve" @@ -73,6 +76,20 @@ func init() { flagSet := Command.Flags() flags.AddFlagsFromOptions(flagSet, "", OptionsInfo) cmdserve.Command.AddCommand(Command) + cmdserve.AddRc("restic", func(ctx context.Context, f fs.Fs, in rc.Params) (cmdserve.Handle, error) { + // Read opts + var opt = Opt // set default opts + err := configstruct.SetAny(in, &opt) + if err != nil { + return nil, err + } + if opt.Stdio { + return nil, errors.New("can't use --stdio via the rc") + } + // Create server + return newServer(ctx, f, &opt) + }) + } // Command definition for cobra @@ -186,17 +203,15 @@ with a path of ` + "`//`" + `. httpSrv := &http2.Server{} opts := &http2.ServeConnOpts{ - Handler: s.Server.Router(), + Handler: s.server.Router(), } httpSrv.ServeConn(conn, opts) return nil } - fs.Logf(s.f, "Serving restic REST API on %s", s.URLs()) + fs.Logf(s.f, "Serving restic REST API on %s", s.server.URLs()) defer systemd.Notify()() - s.Wait() - - return nil + return s.Serve() }) }, } @@ -252,10 +267,10 @@ func checkPrivate(next http.Handler) http.Handler { // server contains everything to run the server type server struct { - *libhttp.Server - f fs.Fs - cache *cache - opt Options + server *libhttp.Server + f fs.Fs + cache *cache + opt Options } func newServer(ctx context.Context, f fs.Fs, opt *Options) (s *server, err error) { @@ -268,19 +283,35 @@ func newServer(ctx context.Context, f fs.Fs, opt *Options) (s *server, err error if opt.Stdio { opt.HTTP.ListenAddr = nil } - s.Server, err = libhttp.NewServer(ctx, + s.server, err = libhttp.NewServer(ctx, libhttp.WithConfig(opt.HTTP), libhttp.WithAuth(opt.Auth), ) if err != nil { return nil, fmt.Errorf("failed to init server: %w", err) } - router := s.Router() + router := s.server.Router() s.Bind(router) - s.Server.Serve() return s, nil } +// Serve restic until the server is shutdown +func (s *server) Serve() error { + s.server.Serve() + s.server.Wait() + return nil +} + +// Return the first address of the server +func (s *server) Addr() net.Addr { + return s.server.Addr() +} + +// Shutdown the server +func (s *server) Shutdown() error { + return s.server.Shutdown() +} + // bind helper for main Bind method func (s *server) bind(router chi.Router) { router.MethodFunc("GET", "/*", func(w http.ResponseWriter, r *http.Request) { diff --git a/cmd/serve/restic/restic_appendonly_test.go b/cmd/serve/restic/restic_appendonly_test.go index 2499481a1..af5e8e680 100644 --- a/cmd/serve/restic/restic_appendonly_test.go +++ b/cmd/serve/restic/restic_appendonly_test.go @@ -119,7 +119,7 @@ func TestResticHandler(t *testing.T) { f := cmd.NewFsSrc([]string{tempdir}) s, err := newServer(ctx, f, &opt) require.NoError(t, err) - router := s.Server.Router() + router := s.server.Router() // create the repo checkRequest(t, router.ServeHTTP, diff --git a/cmd/serve/restic/restic_privaterepos_test.go b/cmd/serve/restic/restic_privaterepos_test.go index 1b89e8681..21e1d68ce 100644 --- a/cmd/serve/restic/restic_privaterepos_test.go +++ b/cmd/serve/restic/restic_privaterepos_test.go @@ -41,7 +41,7 @@ func TestResticPrivateRepositories(t *testing.T) { f := cmd.NewFsSrc([]string{tempdir}) s, err := newServer(ctx, f, &opt) require.NoError(t, err) - router := s.Server.Router() + router := s.server.Router() // Requesting /test/ should allow access reqs := []*http.Request{ diff --git a/cmd/serve/restic/restic_test.go b/cmd/serve/restic/restic_test.go index 9455c61b3..851e542be 100644 --- a/cmd/serve/restic/restic_test.go +++ b/cmd/serve/restic/restic_test.go @@ -14,7 +14,9 @@ import ( _ "github.com/rclone/rclone/backend/all" "github.com/rclone/rclone/cmd" + "github.com/rclone/rclone/cmd/serve/servetest" "github.com/rclone/rclone/fs" + "github.com/rclone/rclone/fs/rc" "github.com/rclone/rclone/fstest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -56,7 +58,10 @@ func TestResticIntegration(t *testing.T) { // Start the server s, err := newServer(ctx, fremote, &opt) require.NoError(t, err) - testURL := s.Server.URLs()[0] + go func() { + require.NoError(t, s.Serve()) + }() + testURL := s.server.URLs()[0] defer func() { _ = s.Shutdown() }() @@ -136,7 +141,7 @@ func TestListErrors(t *testing.T) { f := &listErrorFs{Fs: cmd.NewFsSrc([]string{tempdir})} s, err := newServer(ctx, f, &opt) require.NoError(t, err) - router := s.Server.Router() + router := s.server.Router() req := newRequest(t, "GET", "/test/snapshots/", nil) checkRequest(t, router.ServeHTTP, req, []wantFunc{wantCode(http.StatusInternalServerError)}) @@ -161,7 +166,7 @@ func TestServeErrors(t *testing.T) { f := &newObjectErrorFs{Fs: cmd.NewFsSrc([]string{tempdir})} s, err := newServer(ctx, f, &opt) require.NoError(t, err) - router := s.Server.Router() + router := s.server.Router() f.err = errors.New("oops") req := newRequest(t, "GET", "/test/config", nil) @@ -170,3 +175,9 @@ func TestServeErrors(t *testing.T) { f.err = fs.ErrorObjectNotFound checkRequest(t, router.ServeHTTP, req, []wantFunc{wantCode(http.StatusNotFound)}) } + +func TestRc(t *testing.T) { + servetest.TestRc(t, rc.Params{ + "type": "restic", + }) +}