diff --git a/app/config/variant.go b/app/config/variant.go new file mode 100644 index 0000000..bb007b3 --- /dev/null +++ b/app/config/variant.go @@ -0,0 +1,10 @@ +package config + +const ( + confdir = "/home/ziggi/Projects/mproxy/etc/mproxy" + rundir = "/home/ziggi/Projects/mproxy/tmp/run" + logdir = "/home/ziggi/Projects/mproxy/tmp/log" + datadir = "/home/ziggi/Projects/mproxy/tmp/data" + version = "0.0.1" + srvname = "mproxyd" +) diff --git a/app/handler/connect.go b/app/handler/connect.go index 24e5070..e9d5ff6 100644 --- a/app/handler/connect.go +++ b/app/handler/connect.go @@ -4,66 +4,66 @@ package handler import ( + "io" + "net" "net/http" - "sync" - "io" - "net" + "sync" "mproxy/app/router" ) func (hand *Handler) ConnectTo(rctx *router.Context) { hostaddr := rctx.GetHost() - hand.logg.Debugf("Hostaddr: [%s]", hostaddr) + hand.logg.Debugf("Hostaddr: [%s]", hostaddr) - destConn, err := net.Dial("tcp", hostaddr) - if err != nil { + destConn, err := net.Dial("tcp", hostaddr) + if err != nil { hand.logg.Errorf("ConnectTo error: %v", err) rctx.SetStatus(http.StatusInternalServerError) return - } + } if err != nil { hand.logg.Errorf("ConnectTo error: %v", err) rctx.SetStatus(http.StatusInternalServerError) return } - defer destConn.Close() - hijacker, hijackerOk := rctx.Writer.(http.Hijacker) - if !hijackerOk { - hand.logg.Errorf("Hijacking not OK") + defer destConn.Close() + hijacker, hijackerOk := rctx.Writer.(http.Hijacker) + if !hijackerOk { + hand.logg.Errorf("Hijacking not OK") rctx.SetStatus(http.StatusInternalServerError) return - } + } rctx.SetStatus(http.StatusOK) - clientConn, _, err := hijacker.Hijack() - if err != nil { - hand.logg.Errorf("Hijacking error: %v", err) + clientConn, _, err := hijacker.Hijack() + if err != nil { + hand.logg.Errorf("Hijacking error: %v", err) rctx.SetStatus(http.StatusInternalServerError) return - } - var wg sync.WaitGroup - copyTo := func() { - defer wg.Done() - _, err = io.Copy(clientConn, destConn) - if err != nil { - hand.logg.Errorf("CopyTo error: %v", err) - return - } - } - copyFrom := func() { - defer wg.Done() - _, err = io.Copy(destConn, clientConn) - if err != nil { - hand.logg.Errorf("CopyFrom error: %v", err) - return - } - } - wg.Add(1) - go copyTo() - wg.Add(1) - go copyFrom() - wg.Wait() + } + var wg sync.WaitGroup + copyTo := func() { + defer wg.Done() + _, err = io.Copy(clientConn, destConn) + if err != nil { + hand.logg.Errorf("CopyTo error: %v", err) + return + } + } + copyFrom := func() { + defer wg.Done() + _, err = io.Copy(destConn, clientConn) + if err != nil { + hand.logg.Errorf("CopyFrom error: %v", err) + return + } + } + wg.Add(1) + go copyTo() + wg.Add(1) + go copyFrom() + wg.Wait() return } diff --git a/app/handler/plaincall.go b/app/handler/plaincall.go index aa4e1b8..bc7f239 100644 --- a/app/handler/plaincall.go +++ b/app/handler/plaincall.go @@ -4,56 +4,54 @@ package handler import ( + "context" + "io" "net/http" - "io" - "context" - "time" + "time" "mproxy/app/router" ) func (hand *Handler) PlainCall(rctx *router.Context) { hostaddr := rctx.GetHost() - hand.logg.Debugf("Hostaddr: [%s]", hostaddr) - + hand.logg.Debugf("Hostaddr: [%s]", hostaddr) ctx := rctx.GetContext() - ctx, _ = context.WithTimeout(ctx, 5*time.Second) - reqMethod := rctx.Request.Method - reqUrl := rctx.URL().String() - reqBody := rctx.Request.Body + ctx, _ = context.WithTimeout(ctx, 5*time.Second) + reqMethod := rctx.Request.Method + reqUrl := rctx.URL().String() + reqBody := rctx.Request.Body + req, err := http.NewRequestWithContext(ctx, reqMethod, reqUrl, reqBody) + if err != nil { + hand.logg.Errorf("Create request error: %v", err) + rctx.SetStatus(http.StatusInternalServerError) + return + } + for key := range rctx.Request.Header { + val := rctx.Request.Header.Get(key) + req.Header.Add(key, val) + } - req, err := http.NewRequestWithContext(ctx, reqMethod, reqUrl, reqBody) - if err != nil { - hand.logg.Errorf("Create request error: %v", err) - rctx.SetStatus(http.StatusInternalServerError) - return - } - for key := range rctx.Request.Header { - val := rctx.Request.Header.Get(key) - req.Header.Add(key, val) - } - - httpClient := &http.Client{} - resp, err := httpClient.Do(req) - if err != nil { - hand.logg.Errorf("Call request error: %v", err) - rctx.SetStatus(http.StatusInternalServerError) - return - } - // Copy headers from remote side - for key := range resp.Header { - val := resp.Header.Get(key) - rctx.Writer.Header().Set(key, val) - } - // Copy status code - rctx.SetStatus(resp.StatusCode) - // Copy body - _, err = io.Copy(rctx.Writer, resp.Body) - if err != nil { - hand.logg.Errorf("Copy resp body error: %v", err) - return - } + httpClient := &http.Client{} + resp, err := httpClient.Do(req) + if err != nil { + hand.logg.Errorf("Call request error: %v", err) + rctx.SetStatus(http.StatusInternalServerError) + return + } + // Copy headers from remote side + for key := range resp.Header { + val := resp.Header.Get(key) + rctx.Writer.Header().Set(key, val) + } + // Copy status code + rctx.SetStatus(resp.StatusCode) + // Copy body + _, err = io.Copy(rctx.Writer, resp.Body) + if err != nil { + hand.logg.Errorf("Copy resp body error: %v", err) + return + } return } diff --git a/app/handler/service.go b/app/handler/service.go index 7998aa8..7a98324 100644 --- a/app/handler/service.go +++ b/app/handler/service.go @@ -8,8 +8,8 @@ import ( "mproxy/app/servoper" ) -func (hand *Handler) SendHello(rctx *router.Context) { - params := &servoper.SendHelloParams{} - res, _ := hand.seop.SendHello(params) +func (hand *Handler) GetHello(rctx *router.Context) { + params := &servoper.GetHelloParams{} + res, _ := hand.seop.GetHello(params) hand.SendResult(rctx, res) } diff --git a/app/router/router.go b/app/router/router.go index e12f759..1adecc9 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -158,9 +158,9 @@ func (route Route) Match(req *http.Request) bool { if req.Method != route.Method { return false } - if req.Method == http.MethodConnect { + if req.Method == http.MethodConnect { return true - } + } match := route.Regexp.MatchString(req.URL.Path) if match { return true diff --git a/app/service/service.go b/app/service/service.go index 0ce5ca9..1871dfb 100644 --- a/app/service/service.go +++ b/app/service/service.go @@ -63,7 +63,7 @@ func (svc *Service) Build() error { svc.rout.Use(router.NewCorsMiddleware()) svc.rout.Use(svc.hand.AuthMiddleware) - svc.rout.Get(`/v3/api/service/hello`, svc.hand.SendHello) + svc.rout.Get(`/v3/api/service/hello`, svc.hand.GetHello) svc.rout.Connect(``, svc.hand.ConnectTo) diff --git a/app/servoper/service.go b/app/servoper/service.go index b688430..4721cbd 100644 --- a/app/servoper/service.go +++ b/app/servoper/service.go @@ -3,16 +3,16 @@ */ package servoper -type SendHelloParams struct{} +type GetHelloParams struct{} -type SendHelloResult struct { +type GetHelloResult struct { Message string `json:"message"` Alive bool `json:"alive"` } -func (oper *Operator) SendHello(param *SendHelloParams) (*SendHelloResult, error) { +func (oper *Operator) GetHello(param *GetHelloParams) (*GetHelloResult, error) { var err error - res := &SendHelloResult{ + res := &GetHelloResult{ Alive: true, Message: "hello", } diff --git a/cmd/mproxyctl/servcmd/command.go b/cmd/mproxyctl/servcmd/command.go new file mode 100644 index 0000000..ebbeedf --- /dev/null +++ b/cmd/mproxyctl/servcmd/command.go @@ -0,0 +1,64 @@ +/* + * Copyright 2026 Oleg Borodin + */ +package servcmd + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const ( + uuidRegex = `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$` + defaultHostname = "localhost:1025" +) + +func NewServiceUtil() *ServiceUtil { + return &ServiceUtil{} +} + +type ServiceUtil struct { + getHelloParams GetHelloParams + commonServiceParams CommonServiceParams +} + +type CommonServiceParams struct { + Username string + Password string + Hostname string + Timeout uint64 + SkipTLSVerify bool +} + +func (util *ServiceUtil) Makeservcmds() *cobra.Command { + var subCmd = &cobra.Command{ + Use: "service", + Short: "Service operations", + } + const defaultTimeout uint64 = 10 + + subCmd.PersistentFlags().StringVarP(&util.commonServiceParams.Username, "user", "U", util.commonServiceParams.Username, "Username") + subCmd.PersistentFlags().StringVarP(&util.commonServiceParams.Password, "pass", "P", util.commonServiceParams.Password, "Password") + subCmd.PersistentFlags().StringVarP(&util.commonServiceParams.Hostname, "host", "X", defaultHostname, "Hostname") + subCmd.PersistentFlags().Uint64VarP(&util.commonServiceParams.Timeout, "timeout", "T", defaultTimeout, "Operation timeout") + subCmd.PersistentFlags().BoolVarP(&util.commonServiceParams.SkipTLSVerify, "skipVerify", "S", true, "Skip server certificate verify") + subCmd.MarkFlagsRequiredTogether("user", "pass") + + vi := viper.New() + vi.SetEnvPrefix("mproxy") + vi.BindEnv("user") + vi.BindEnv("pass") + util.commonServiceParams.Username = vi.GetString("user") + util.commonServiceParams.Password = vi.GetString("pass") + + // GetService + var getservcmd = &cobra.Command{ + Use: "get [user:pass@]hostname[:port]", + Short: "Get service hello", + Args: cobra.ExactArgs(1), + Run: util.GetHello, + } + subCmd.AddCommand(getservcmd) + + return subCmd +} diff --git a/cmd/mproxyctl/servcmd/gethello.go b/cmd/mproxyctl/servcmd/gethello.go new file mode 100644 index 0000000..7ea8506 --- /dev/null +++ b/cmd/mproxyctl/servcmd/gethello.go @@ -0,0 +1,46 @@ +/* + * Copyright 2026 Oleg Borodin + */ +package servcmd + +import ( + "context" + "time" + + "github.com/spf13/cobra" + + "mproxy/pkg/servcli" +) + +// GetHello +type GetHelloParams struct { + Address string +} + +func (util *ServiceUtil) GetHello(cmd *cobra.Command, args []string) { + util.getHelloParams.Address = args[0] + res, err := util.getHello(&util.commonServiceParams, &util.getHelloParams) + printResponse(res, err) +} + +type GetHelloResult struct{} + +func (util *ServiceUtil) getHello(common *CommonServiceParams, params *GetHelloParams) (*GetHelloResult, error) { + var err error + res := &GetHelloResult{} + + timeout := time.Duration(common.Timeout) * time.Second + ctx, _ := context.WithTimeout(context.Background(), timeout) + ref, err := servcli.ParsePath(params.Address) + if err != nil { + return res, err + } + ref.SetUserinfo(common.Username, common.Password) + mw := servcli.NewBasicAuthMiddleware(ref.Userinfo()) + cli := servcli.NewClient(nil, mw) + err = cli.GetHello(ctx, ref.Raw()) + if err != nil { + return res, err + } + return res, err +} diff --git a/cmd/mproxyctl/servcmd/printres.go b/cmd/mproxyctl/servcmd/printres.go new file mode 100644 index 0000000..8866044 --- /dev/null +++ b/cmd/mproxyctl/servcmd/printres.go @@ -0,0 +1,27 @@ +/* + * Copyright 2026 Oleg Borodin + */ +package servcmd + +import ( + "fmt" + + "sigs.k8s.io/yaml" +) + +func printResponse(res any, err error) { + type Response struct { + Error bool `json:"error" yaml:"error"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` + Result any `json:"result,omitempty" yaml:"result,omitempty"` + } + resp := Response{} + if err != nil { + resp.Error = true + resp.Message = err.Error() + } else { + resp.Result = res + } + respBytes, _ := yaml.Marshal(resp) + fmt.Printf("---\n%s\n", string(respBytes)) +} diff --git a/cmd/mproxyctl/util/util.go b/cmd/mproxyctl/util/util.go index b553a4b..16ac9b5 100644 --- a/cmd/mproxyctl/util/util.go +++ b/cmd/mproxyctl/util/util.go @@ -8,6 +8,8 @@ import ( "os" "path/filepath" + "mproxy/cmd/mproxyctl/servcmd" + "github.com/spf13/cobra" ) @@ -28,12 +30,15 @@ func (util *Util) Build() error { execName := filepath.Base(os.Args[0]) rootCmd := &cobra.Command{ Use: execName, - Short: "\nOperation with artefacts: files, images, service accounts and grants", + Short: "\nGet hello from service", SilenceUsage: true, } rootCmd.CompletionOptions.DisableDefaultCmd = true util.rootCmd = rootCmd + ServiceUtil := servcmd.NewServiceUtil() + rootCmd.AddCommand(ServiceUtil.MakeServiceCmds()) + return err } diff --git a/go.mod b/go.mod index 1afcc59..6bac3b6 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,30 @@ go 1.26.2 require ( github.com/google/uuid v1.6.0 github.com/spf13/cobra v1.10.2 + github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 go.yaml.in/yaml/v4 v4.0.0-rc.4 + sigs.k8s.io/yaml v1.6.0 ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/spf13/pflag v1.0.9 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.28.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 75a8e70..09dc8d9 100644 --- a/go.sum +++ b/go.sum @@ -2,25 +2,63 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/mans/Makefile.am b/mans/Makefile.am index 12281d5..2b721b3 100644 --- a/mans/Makefile.am +++ b/mans/Makefile.am @@ -2,20 +2,7 @@ AUTOMAKE_OPTIONS = foreign no-dependencies no-installinfo dist_man1_MANS = \ - mproxyctl.1 \ - mproxyctl-accounts.1 \ - mproxyctl-accounts-create.1 \ - mproxyctl-accounts-delete.1 \ - mproxyctl-accounts-get.1 \ - mproxyctl-accounts-list.1 \ - mproxyctl-accounts-update.1 \ - mproxyctl-grants.1 \ - mproxyctl-grants-create.1 \ - mproxyctl-grants-delete.1 \ - mproxyctl-grants-get.1 \ - mproxyctl-grants-list.1 \ - mproxyctl-grants-update.1 - + mproxyctl.1 dist_man8_MANS = \ mproxyd.8 diff --git a/mans/Makefile.in b/mans/Makefile.in index 5ebe271..a0bb686 100644 --- a/mans/Makefile.in +++ b/mans/Makefile.in @@ -267,19 +267,7 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AUTOMAKE_OPTIONS = foreign no-dependencies no-installinfo dist_man1_MANS = \ - mproxyctl.1 \ - mproxyctl-accounts.1 \ - mproxyctl-accounts-create.1 \ - mproxyctl-accounts-delete.1 \ - mproxyctl-accounts-get.1 \ - mproxyctl-accounts-list.1 \ - mproxyctl-accounts-update.1 \ - mproxyctl-grants.1 \ - mproxyctl-grants-create.1 \ - mproxyctl-grants-delete.1 \ - mproxyctl-grants-get.1 \ - mproxyctl-grants-list.1 \ - mproxyctl-grants-update.1 + mproxyctl.1 dist_man8_MANS = \ mproxyd.8 diff --git a/pkg/servcli/authmw.go b/pkg/servcli/authmw.go new file mode 100644 index 0000000..44e17b0 --- /dev/null +++ b/pkg/servcli/authmw.go @@ -0,0 +1,58 @@ +package servcli + +import ( + "encoding/base64" + "net/http" +) + +// BasicAuthMiddleware +func NewBasicAuthMiddleware(user, pass string) MiddlewareFunc { + return func(next http.RoundTripper) http.RoundTripper { + return newBasicAuthMW(next, user, pass) + } +} + +type BasicAuthMW struct { + user, pass string + next http.RoundTripper +} + +func newBasicAuthMW(next http.RoundTripper, user, pass string) *BasicAuthMW { + return &BasicAuthMW{ + user: user, + pass: pass, + next: next, + } +} + +func (tran BasicAuthMW) RoundTrip(req *http.Request) (*http.Response, error) { + if tran.user != "" && tran.pass != "" { + pair := base64.StdEncoding.EncodeToString([]byte(tran.user + ":" + tran.pass)) + req.Header.Set("Authorization", "Basic "+pair) + } + return tran.next.RoundTrip(req) +} + +// BearerAuthMiddleware +func NewBearerAuthMiddleware(token string) MiddlewareFunc { + return func(next http.RoundTripper) http.RoundTripper { + return newBearerAuthMW(next, token) + } +} + +type BearerAuthMW struct { + token string + next http.RoundTripper +} + +func newBearerAuthMW(next http.RoundTripper, token string) *BearerAuthMW { + return &BearerAuthMW{ + token: token, + next: next, + } +} + +func (tran BearerAuthMW) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set("Authorization", "Bearer "+tran.token) + return tran.next.RoundTrip(req) +} diff --git a/pkg/servcli/client.go b/pkg/servcli/client.go index 9960bcd..8b1a57a 100644 --- a/pkg/servcli/client.go +++ b/pkg/servcli/client.go @@ -1,11 +1,11 @@ package servcli import ( - "crypto/tls" - "encoding/base64" "net/http" ) +type MiddlewareFunc func(next http.RoundTripper) http.RoundTripper + type Client struct { httpClient *http.Client userAgent string @@ -21,10 +21,9 @@ func NewClient(transport http.RoundTripper, mwFuncs ...MiddlewareFunc) *Client { httpClient := &http.Client{ Transport: transport, } - return &Client{ httpClient: httpClient, - userAgent: "ociClient/1.0", + userAgent: "proxyClient/1.0", } } @@ -32,100 +31,6 @@ func (cli *Client) SetTransport(transport http.RoundTripper) { cli.httpClient.Transport = transport } -type MiddlewareFunc func(next http.RoundTripper) http.RoundTripper - func (cli *Client) UseMiddleware(mwFunc MiddlewareFunc) { cli.httpClient.Transport = mwFunc(cli.httpClient.Transport) } - -// BasicAuthMiddleware -func NewBasicAuthMiddleware(user, pass string) MiddlewareFunc { - return func(next http.RoundTripper) http.RoundTripper { - return newBasicAuthMW(next, user, pass) - } -} - -type BasicAuthMW struct { - user, pass string - next http.RoundTripper -} - -func newBasicAuthMW(next http.RoundTripper, user, pass string) *BasicAuthMW { - return &BasicAuthMW{ - user: user, - pass: pass, - next: next, - } -} - -func (tran BasicAuthMW) RoundTrip(req *http.Request) (*http.Response, error) { - if tran.user != "" && tran.pass != "" { - pair := base64.StdEncoding.EncodeToString([]byte(tran.user + ":" + tran.pass)) - req.Header.Set("Authorization", "Basic "+pair) - } - return tran.next.RoundTrip(req) -} - -// BearerAuthMiddleware -func NewBearerAuthMiddleware(token string) MiddlewareFunc { - return func(next http.RoundTripper) http.RoundTripper { - return newBearerAuthMW(next, token) - } -} - -type BearerAuthMW struct { - token string - next http.RoundTripper -} - -func newBearerAuthMW(next http.RoundTripper, token string) *BearerAuthMW { - return &BearerAuthMW{ - token: token, - next: next, - } -} - -func (tran BearerAuthMW) RoundTrip(req *http.Request) (*http.Response, error) { - req.Header.Set("Authorization", "Bearer "+tran.token) - return tran.next.RoundTrip(req) -} - -// DefaultTransport -type DefaultTransport struct { - transport http.RoundTripper -} - -func NewDefaultTransport() *DefaultTransport { - return &DefaultTransport{ - transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - } -} - -func (wrap *DefaultTransport) RoundTrip(req *http.Request) (*http.Response, error) { - return wrap.transport.RoundTrip(req) -} - -// ExampleMiddleware -func NewExampleMiddleware() MiddlewareFunc { - return func(next http.RoundTripper) http.RoundTripper { - return newExampleTransport(next) - } -} - -type ExampleTransport struct { - next http.RoundTripper -} - -func newExampleTransport(next http.RoundTripper) *ExampleTransport { - return &ExampleTransport{ - next: next, - } -} - -func (tran ExampleTransport) RoundTrip(req *http.Request) (*http.Response, error) { - return tran.next.RoundTrip(req) -} diff --git a/pkg/servcli/copyctx.go b/pkg/servcli/copyctx.go new file mode 100644 index 0000000..57b7e1b --- /dev/null +++ b/pkg/servcli/copyctx.go @@ -0,0 +1,42 @@ +/* + * Copyright 2026 Oleg Borodin + */ +package servcli + +import ( + "context" + "errors" + "io" +) + +func Copy(ctx context.Context, writer io.Writer, reader io.Reader) (int64, error) { + var err error + var size int64 + var halt bool + buffer := make([]byte, 1024*4) + for { + select { + case <-ctx.Done(): + err = errors.New("Break copy by context") + break + default: + } + rsize, err := reader.Read(buffer) + if err == io.EOF { + err = nil + halt = true + } + if err != nil { + return size, err + } + wsize, err := writer.Write(buffer[0:rsize]) + size += int64(wsize) + if err != nil { + return size, err + } + if halt { + break + } + } + return size, err +} diff --git a/pkg/servcli/gethello.go b/pkg/servcli/gethello.go new file mode 100644 index 0000000..a238094 --- /dev/null +++ b/pkg/servcli/gethello.go @@ -0,0 +1,77 @@ +/* + * Copyright 2026 Oleg Borodin + */ +package servcli + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + + "mproxy/app/servoper" +) + +func (cli *Client) GetHello(ctx context.Context, rawpath string) error { + var err error + req := servoper.GetHelloParams{} + reqdata, err := json.Marshal(req) + if err != nil { + return err + } + _, err = cli.DoCall(ctx, rawpath, reqdata) + if err != nil { + return err + } + return err +} + +func (cli *Client) DoCall(ctx context.Context, rawpath string, reqdata []byte) ([]byte, error) { + var err error + res := make([]byte, 0) + + ref, err := ParsePath(rawpath) + if err != nil { + return res, err + } + uri := ref.HelloEP() + reader := bytes.NewReader(reqdata) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, reader) + if err != nil { + return res, err + } + req.Header.Set("User-Agent", cli.userAgent) + req.Header.Set("Accept", "*/*") + resp, err := cli.httpClient.Do(req) + if err != nil { + return res, err + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return res, err + } + if resp.StatusCode != http.StatusOK { + err := fmt.Errorf("Unexpected response code %s", resp.Status) + return res, err + } + contentLength := resp.Header.Get("Content-Length") + if contentLength == "" { + err := fmt.Errorf("Content-Length header is missing") + return res, err + } + blobSize, err := strconv.ParseInt(contentLength, 10, 64) + if err != nil { + return res, err + } + buffer := bytes.NewBuffer(nil) + recSize, err := Copy(ctx, buffer, resp.Body) + if blobSize != recSize { + err := fmt.Errorf("Mismatch declared and actual body size: %d and %d", blobSize, recSize) + return res, err + } + res = buffer.Bytes() + return res, err +} diff --git a/pkg/servcli/httpcall.go b/pkg/servcli/httpcall.go deleted file mode 100644 index 5cf5784..0000000 --- a/pkg/servcli/httpcall.go +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2026 Oleg Borodin - */ -package servcli - -import ( - "bytes" - "context" - "fmt" - "io" - "net/http" - "strconv" -) - -func (cli *Client) doHTTPCall(ctx context.Context, host, obj, oper string, req []byte) ([]byte, error) { - var err error - var res []byte - - reader := bytes.NewReader(req) - ref, err := NewReferer(host, obj, oper) - if err != nil { - return res, err - } - httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, ref.Point(), reader) - if err != nil { - return res, err - } - httpReq.Header.Set("User-Agent", cli.userAgent) - httpReq.Header.Set("Accept", "*/*") - httpResp, err := cli.httpClient.Do(httpReq) - if err != nil { - return res, err - } - defer httpResp.Body.Close() - if httpResp.StatusCode != http.StatusOK { - err := fmt.Errorf("Unexpected response code: %s", httpResp.Status) - return res, err - } - contentLength := httpResp.Header.Get("Content-Length") - if contentLength == "" { - err := fmt.Errorf("Content-Length header is missing") - return res, err - } - blobSize, err := strconv.ParseInt(contentLength, 10, 64) - if err != nil { - return res, err - } - buffer := bytes.NewBuffer(nil) - recSize, err := io.Copy(buffer, httpResp.Body) - if blobSize != recSize { - err := fmt.Errorf("Mismatch declared and actual body size, %d and %d", blobSize, recSize) - return res, err - } - res = buffer.Bytes() - return res, err -} diff --git a/pkg/servcli/referer.go b/pkg/servcli/referer.go index ad4fc6e..1073bd6 100644 --- a/pkg/servcli/referer.go +++ b/pkg/servcli/referer.go @@ -1,26 +1,36 @@ +/* + * Copyright 2026 Oleg Borodin + */ package servcli import ( "net/url" "path" + "strconv" "strings" ) +const ( + PathTypeIdentic = "identic" + PathTypePrefix = "prefix" + PathTypeRegexp = "regexp" +) + type Referer struct { urlobj *url.URL user, pass string - obj, oper string + resource string + values url.Values } -func NewReferer(hostname, object, operation string) (*Referer, error) { +func ParsePath(rawpath string) (*Referer, error) { ref := &Referer{ - obj: object, - oper: operation, + values: url.Values{}, } - if !strings.Contains(hostname, "://") { - hostname = "https://" + hostname + if !strings.Contains(rawpath, "://") { + rawpath = "https://" + rawpath } - urlobj, err := url.Parse(hostname) + urlobj, err := url.Parse(rawpath) if err != nil { return ref, err } @@ -29,40 +39,40 @@ func NewReferer(hostname, object, operation string) (*Referer, error) { ref.pass, _ = urlobj.User.Password() urlobj.User = nil } + ref.resource = path.Join("/", urlobj.Path) urlobj.Path = "/" ref.urlobj = urlobj + ref.values = urlobj.Query() return ref, err } -func ParseHostinfo(hostname string) (*Referer, error) { - ref := &Referer{} - if !strings.Contains(hostname, "://") { - hostname = "https://" + hostname - } - urlobj, err := url.Parse(hostname) - if err != nil { - return ref, err - } - if urlobj.User != nil { - ref.user = urlobj.User.Username() - ref.pass, _ = urlobj.User.Password() - urlobj.User = nil - } - urlobj.Path = "/" - ref.urlobj = urlobj - return ref, err -} - -func (ref *Referer) Host() string { - return ref.urlobj.Host -} - func (ref *Referer) Raw() string { - return path.Join(ref.urlobj.Host, "/v3/api/", ref.obj, ref.oper) + res := path.Join(ref.urlobj.Host, ref.resource) + query := ref.values.Encode() + if query != "" { + return res + "?" + query + } + return res } -func (ref *Referer) Point() string { - curl := ref.urlobj.JoinPath("/v3/api/", ref.obj, ref.oper) +func (ref *Referer) SetResource(resource string) { + ref.resource = path.Join("/", resource) +} + +func (ref *Referer) JoinResource(resource string) { + ref.resource = path.Join("/", ref.resource, resource) +} + +func (ref *Referer) PathType(typ string) { + ref.values.Set("pathType", typ) +} + +func (ref *Referer) DryRun(yesno bool) { + ref.values.Set("dryRun", strconv.FormatBool(yesno)) +} + +func (ref *Referer) HelloEP() string { + curl := ref.urlobj.JoinPath("/v3/api/service/hello", ref.resource) return curl.String() } diff --git a/pkg/servcli/servhello.go b/pkg/servcli/servhello.go deleted file mode 100644 index 51d5d35..0000000 --- a/pkg/servcli/servhello.go +++ /dev/null @@ -1,36 +0,0 @@ -package servcli - -import ( - "context" - "encoding/json" - "errors" - - "mproxy/app/handler" - "mproxy/app/servoper" -) - -func (cli *Client) ServiceHello(ctx context.Context, host string) (bool, error) { - var res bool - var err error - - params := servoper.SendHelloParams{} - reqdata, err := json.Marshal(params) - if err != nil { - return res, err - } - resdata, err := cli.doHTTPCall(ctx, host, "service", "hello", reqdata) - if err != nil { - return res, err - } - response := handler.Response[servoper.SendHelloResult]{} - err = json.Unmarshal(resdata, &response) - if err != nil { - return res, err - } - if response.Error { - err = errors.New(response.Message) - return res, err - } - res = response.Result.Alive - return res, err -} diff --git a/pkg/servcli/transp.go b/pkg/servcli/transp.go new file mode 100644 index 0000000..4b4fcd3 --- /dev/null +++ b/pkg/servcli/transp.go @@ -0,0 +1,25 @@ +package servcli + +import ( + "crypto/tls" + "net/http" +) + +// DefaultTransport +type DefaultTransport struct { + transport http.RoundTripper +} + +func NewDefaultTransport() *DefaultTransport { + return &DefaultTransport{ + transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + }, + } +} + +func (wrap *DefaultTransport) RoundTrip(req *http.Request) (*http.Response, error) { + return wrap.transport.RoundTrip(req) +}