This is an automated email from the ASF dual-hosted git repository.

zhongxjian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git


The following commit(s) were added to refs/heads/master by this push:
     new 31b96a65 [dubboctl] add docker cerdential logic (#582)
31b96a65 is described below

commit 31b96a65c5e9ec53e2a7716c09c094bc5ca46bb3
Author: Jian Zhong <[email protected]>
AuthorDate: Thu Feb 6 14:59:27 2025 +0800

    [dubboctl] add docker cerdential logic (#582)
---
 dubboctl/cmd/root.go                               |  26 ++-
 .../{cred/cred.go => credentials/credentials.go}   |  26 +--
 dubboctl/pkg/hub/credentials/prompt/prompt.go      | 132 +++++++++++
 dubboctl/pkg/hub/pusher.go                         |  32 ---
 dubboctl/pkg/hub/pusher/pusher.go                  | 242 +++++++++++++++++++++
 dubboctl/pkg/hub/pusher/transport.go               | 184 ++++++++++++++++
 dubboctl/pkg/sdk/client.go                         |   6 +
 7 files changed, 602 insertions(+), 46 deletions(-)

diff --git a/dubboctl/cmd/root.go b/dubboctl/cmd/root.go
index 2d90a3bd..dc02f154 100644
--- a/dubboctl/cmd/root.go
+++ b/dubboctl/cmd/root.go
@@ -19,13 +19,18 @@ import (
        "flag"
        "github.com/apache/dubbo-kubernetes/dubboctl/pkg/cli"
        "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/builder/pack"
+       // "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/credentials"
+       // 
"github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/credentials/prompt"
        "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/deployer"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/pusher"
        "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk"
        "github.com/apache/dubbo-kubernetes/dubboctl/pkg/util"
        "github.com/apache/dubbo-kubernetes/dubboctl/pkg/validate"
        "github.com/apache/dubbo-kubernetes/dubboctl/pkg/version"
        "github.com/apache/dubbo-kubernetes/operator/cmd/cluster"
        "github.com/spf13/cobra"
+       "net/http"
+       // "os"
 )
 
 type staticClient struct {
@@ -36,11 +41,16 @@ type ClientFactory func(...sdk.Option) (*sdk.Client, func())
 
 func NewClientFactory(options ...sdk.Option) (*sdk.Client, func()) {
        var (
+               t = newTransport(false)
+               // c = newCredentialsProvider(config.Dir(), t)
                d = newDubboDeployer()
                o = []sdk.Option{
                        sdk.WithRepositoriesPath(util.RepositoriesPath()),
-                       sdk.WithDeployer(d),
                        sdk.WithBuilder(pack.NewBuilder()),
+                       sdk.WithPusher(pusher.NewPusher(
+                               // pusher.WithCredentialsProvider(c),
+                               pusher.WithTransport(t))),
+                       sdk.WithDeployer(d),
                }
        )
        client := sdk.New(append(o, options...)...)
@@ -49,6 +59,20 @@ func NewClientFactory(options ...sdk.Option) (*sdk.Client, 
func()) {
        return client, cleanup
 }
 
+func newTransport(insecureSkipVerify bool) pusher.RoundTripCloser {
+       return 
pusher.NewRoundTripper(pusher.WithInsecureSkipVerify(insecureSkipVerify))
+}
+
+func newCredentialsProvider(configPath string, t http.RoundTripper) 
pusher.CredentialsProvider {
+       // options := []credentials.Opt{
+       //      
credentials.WithPromptForCredentials(prompt.NewPromptForCredentials(os.Stdin, 
os.Stdout, os.Stderr)),
+       //      
credentials.WithPromptForCredentialStore(prompt.NewPromptForCredentialStore()),
+       //      credentials.WithTransport(t),
+       // }
+       // TODO
+       return nil
+}
+
 func newDubboDeployer() sdk.Deployer {
        var options []deployer.DeployerOption
 
diff --git a/dubboctl/pkg/hub/cred/cred.go 
b/dubboctl/pkg/hub/credentials/credentials.go
similarity index 64%
rename from dubboctl/pkg/hub/cred/cred.go
rename to dubboctl/pkg/hub/credentials/credentials.go
index 7c72b634..37d666bb 100644
--- a/dubboctl/pkg/hub/cred/cred.go
+++ b/dubboctl/pkg/hub/credentials/credentials.go
@@ -1,10 +1,10 @@
-package cred
+package credentials
 
 import (
        "context"
        "errors"
        "fmt"
-       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/pusher"
        "github.com/google/go-containerregistry/pkg/authn"
        "github.com/google/go-containerregistry/pkg/name"
        "github.com/google/go-containerregistry/pkg/v1/remote"
@@ -17,17 +17,17 @@ type keyChain struct {
        pwd  string
 }
 
-type verifyCredentialsCallback func(ctx context.Context, image string, 
credentials hub.Credentials) error
+type VerifyCredentialsCallback func(ctx context.Context, image string, 
credentials pusher.Credentials) error
 
-type credentialsCallback func(registry string) (hub.Credentials, error)
+type CredentialsCallback func(registry string) (pusher.Credentials, error)
 
-type chooseCredentialHelperCallback func(available []string) (string, error)
+type ChooseCredentialHelperCallback func(available []string) (string, error)
 
 type credentialsProvider struct {
-       promptForCredentials     credentialsCallback
-       verifyCredentials        verifyCredentialsCallback
-       promptForCredentialStore chooseCredentialHelperCallback
-       credentialLoaders        []credentialsCallback
+       promptForCredentials     CredentialsCallback
+       verifyCredentials        VerifyCredentialsCallback
+       promptForCredentialStore ChooseCredentialHelperCallback
+       credentialLoaders        []CredentialsCallback
        authFilePath             string
        transport                http.RoundTripper
 }
@@ -39,7 +39,7 @@ func (k keyChain) Resolve(resource authn.Resource) 
(authn.Authenticator, error)
        }, nil
 }
 
-func checkAuth(ctx context.Context, image string, credentials hub.Credentials, 
trans http.RoundTripper) error {
+func checkAuth(ctx context.Context, image string, credentials 
pusher.Credentials, trans http.RoundTripper) error {
        ref, err := name.ParseReference(image)
        if err != nil {
                return fmt.Errorf("cannot parse image reference: %w", err)
@@ -64,19 +64,19 @@ func checkAuth(ctx context.Context, image string, 
credentials hub.Credentials, t
 
 type Opt func(opts *credentialsProvider)
 
-func WithPromptForCredentials(cbk credentialsCallback) Opt {
+func WithPromptForCredentials(cbk CredentialsCallback) Opt {
        return func(opts *credentialsProvider) {
                opts.promptForCredentials = cbk
        }
 }
 
-func WithVerifyCredentials(cbk verifyCredentialsCallback) Opt {
+func WithVerifyCredentials(cbk VerifyCredentialsCallback) Opt {
        return func(opts *credentialsProvider) {
                opts.verifyCredentials = cbk
        }
 }
 
-func WithPromptForCredentialStore(cbk chooseCredentialHelperCallback) Opt {
+func WithPromptForCredentialStore(cbk ChooseCredentialHelperCallback) Opt {
        return func(opts *credentialsProvider) {
                opts.promptForCredentialStore = cbk
        }
diff --git a/dubboctl/pkg/hub/credentials/prompt/prompt.go 
b/dubboctl/pkg/hub/credentials/prompt/prompt.go
new file mode 100644
index 00000000..e7ae68fb
--- /dev/null
+++ b/dubboctl/pkg/hub/credentials/prompt/prompt.go
@@ -0,0 +1,132 @@
+package prompt
+
+import (
+       "bufio"
+       "fmt"
+       "github.com/AlecAivazis/survey/v2"
+       "github.com/AlecAivazis/survey/v2/terminal"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/credentials"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub/pusher"
+       "golang.org/x/term"
+       "io"
+       "os"
+       "strings"
+)
+
+func NewPromptForCredentials(in io.Reader, out, errOut io.Writer) 
func(registry string) (pusher.Credentials, error) {
+       firstTime := true
+       return func(registry string) (pusher.Credentials, error) {
+               var result pusher.Credentials
+
+               if firstTime {
+                       firstTime = false
+                       fmt.Fprintf(out, "Please provide credentials for image 
registry (%s).\n", registry)
+               } else {
+                       fmt.Fprintln(out, "Incorrect credentials, please try 
again.")
+               }
+
+               qs := []*survey.Question{
+                       {
+                               Name: "username",
+                               Prompt: &survey.Input{
+                                       Message: "Username:",
+                               },
+                               Validate: survey.Required,
+                       },
+                       {
+                               Name: "password",
+                               Prompt: &survey.Password{
+                                       Message: "Password:",
+                               },
+                               Validate: survey.Required,
+                       },
+               }
+
+               var (
+                       fr terminal.FileReader
+                       ok bool
+               )
+
+               isTerm := false
+               if fr, ok = in.(terminal.FileReader); ok {
+                       isTerm = term.IsTerminal(int(fr.Fd()))
+               }
+
+               if isTerm {
+                       err := survey.Ask(qs, &result, survey.WithStdio(fr, 
out.(terminal.FileWriter), errOut))
+                       if err != nil {
+                               return pusher.Credentials{}, err
+                       }
+               } else {
+                       reader := bufio.NewReader(in)
+
+                       fmt.Fprint(out, "Username: ")
+                       u, err := reader.ReadString('\n')
+                       if err != nil {
+                               return pusher.Credentials{}, err
+                       }
+                       u = strings.Trim(u, "\r\n")
+
+                       fmt.Fprint(out, "Password: ")
+                       p, err := reader.ReadString('\n')
+                       if err != nil {
+                               return pusher.Credentials{}, err
+                       }
+                       p = strings.Trim(p, "\r\n")
+
+                       result = pusher.Credentials{Username: u, Password: p}
+               }
+
+               return result, nil
+       }
+}
+
+func NewPromptForCredentialStore() credentials.ChooseCredentialHelperCallback {
+       return func(availableHelpers []string) (string, error) {
+               if len(availableHelpers) < 1 {
+                       fmt.Fprintf(os.Stderr, `Credentials will not be saved.
+If you would like to save your credentials in the future,
+you can install docker credential helper 
https://github.com/docker/docker-credential-helpers.
+`)
+                       return "", nil
+               }
+
+               isTerm := term.IsTerminal(int(os.Stdin.Fd()))
+
+               var resp string
+
+               if isTerm {
+                       err := survey.AskOne(&survey.Select{
+                               Message: "Choose credentials helper",
+                               Options: append(availableHelpers, "None"),
+                       }, &resp, survey.WithValidator(survey.Required))
+                       if err != nil {
+                               return "", err
+                       }
+                       if resp == "None" {
+                               fmt.Fprintf(os.Stderr, "No helper selected. 
Credentials will not be saved.\n")
+                               return "", nil
+                       }
+               } else {
+                       fmt.Fprintf(os.Stderr, "Available credential 
helpers:\n")
+                       for _, helper := range availableHelpers {
+                               fmt.Fprintf(os.Stderr, "%s\n", helper)
+                       }
+                       fmt.Fprintf(os.Stderr, "Choose credentials helper: ")
+
+                       reader := bufio.NewReader(os.Stdin)
+
+                       var err error
+                       resp, err = reader.ReadString('\n')
+                       if err != nil {
+                               return "", err
+                       }
+                       resp = strings.Trim(resp, "\r\n")
+                       if resp == "" {
+                               fmt.Fprintf(os.Stderr, "No helper selected. 
Credentials will not be saved.\n")
+                       }
+               }
+
+               return resp, nil
+       }
+}
diff --git a/dubboctl/pkg/hub/pusher.go b/dubboctl/pkg/hub/pusher.go
deleted file mode 100644
index adfafc34..00000000
--- a/dubboctl/pkg/hub/pusher.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package hub
-
-import (
-       "context"
-       "github.com/docker/docker/api/types"
-       "github.com/google/go-containerregistry/pkg/v1/daemon"
-       "io"
-       "net/http"
-)
-
-type Opt func(*Pusher)
-
-type Credentials struct {
-       Username string
-       Password string
-}
-
-type CredentialsProvider func(ctx context.Context, image string) (Credentials, 
error)
-
-type PusherDockerClientFactory func() (PusherDockerClient, error)
-
-type Pusher struct {
-       credentialsProvider CredentialsProvider
-       transport           http.RoundTripper
-       dockerClientFactory PusherDockerClientFactory
-}
-
-type PusherDockerClient interface {
-       daemon.Client
-       ImagePush(ctx context.Context, ref string, options 
types.ImagePushOptions) (io.ReadCloser, error)
-       Close() error
-}
diff --git a/dubboctl/pkg/hub/pusher/pusher.go 
b/dubboctl/pkg/hub/pusher/pusher.go
new file mode 100644
index 00000000..60a36f13
--- /dev/null
+++ b/dubboctl/pkg/hub/pusher/pusher.go
@@ -0,0 +1,242 @@
+package pusher
+
+import (
+       "bytes"
+       "context"
+       "encoding/base64"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/hub"
+       "github.com/apache/dubbo-kubernetes/dubboctl/pkg/sdk/dubbo"
+       "github.com/docker/docker/api/types"
+       "github.com/docker/docker/client"
+       "github.com/docker/docker/pkg/jsonmessage"
+       "github.com/google/go-containerregistry/pkg/authn"
+       "github.com/google/go-containerregistry/pkg/name"
+       "github.com/google/go-containerregistry/pkg/v1"
+       "github.com/google/go-containerregistry/pkg/v1/daemon"
+       "github.com/google/go-containerregistry/pkg/v1/remote"
+       "golang.org/x/term"
+       "io"
+       "net"
+       "net/http"
+       "os"
+       "regexp"
+)
+
+type Opt func(*Pusher)
+
+type Credentials struct {
+       Username string
+       Password string
+}
+
+type CredentialsProvider func(ctx context.Context, image string) (Credentials, 
error)
+
+type PusherDockerClientFactory func() (PusherDockerClient, error)
+
+type Pusher struct {
+       credentialsProvider CredentialsProvider
+       transport           http.RoundTripper
+       dockerClientFactory PusherDockerClientFactory
+}
+
+type AuthConfig struct {
+       Username      string `json:"username,omitempty"`
+       Password      string `json:"password,omitempty"`
+       Auth          string `json:"auth,omitempty"`
+       Email         string `json:"email,omitempty"`
+       ServerAddress string `json:"serveraddress,omitempty"`
+       IdentityToken string `json:"identitytoken,omitempty"`
+       RegistryToken string `json:"registrytoken,omitempty"`
+}
+
+func NewPusher(opts ...Opt) *Pusher {
+       result := &Pusher{
+               credentialsProvider: EmptyCredentialsProvider,
+               transport:           http.DefaultTransport,
+               dockerClientFactory: func() (PusherDockerClient, error) {
+                       c, _, err := hub.NewClient(client.DefaultDockerHost)
+                       return c, err
+               },
+       }
+       for _, opt := range opts {
+               opt(result)
+       }
+
+       return result
+}
+
+func (p *Pusher) Push(ctx context.Context, dc *dubbo.DubboConfig) (digest 
string, err error) {
+       var output io.Writer
+
+       output = os.Stderr
+
+       if dc.Image == "" {
+               return "", errors.New("Function has no associated image.  Has 
it been built?")
+       }
+
+       registry, err := getRegistry(dc.Image)
+       if err != nil {
+               return "", err
+       }
+
+       credentials, err := p.credentialsProvider(ctx, dc.Image)
+       if err != nil {
+               return "", fmt.Errorf("failed to get credentials: %w", err)
+       }
+       fmt.Fprintf(os.Stderr, "Pushing function image to the registry %q using 
the %q user credentials\n", registry, credentials.Username)
+
+       if _, err = net.DefaultResolver.LookupHost(ctx, registry); err == nil {
+               return p.daemon(ctx, dc, credentials, output)
+       }
+
+       return p.push(ctx, dc, credentials, output)
+}
+
+func getRegistry(img string) (string, error) {
+       ref, err := name.ParseReference(img, name.WeakValidation)
+       if err != nil {
+               return "", err
+       }
+       registry := ref.Context().RegistryStr()
+       return registry, nil
+}
+
+func (p *Pusher) daemon(ctx context.Context, dc *dubbo.DubboConfig, 
credentials Credentials, output io.Writer) (digest string, err error) {
+       cli, err := p.dockerClientFactory()
+       if err != nil {
+               return "", fmt.Errorf("failed to create docker api client: %w", 
err)
+       }
+       defer cli.Close()
+
+       authConfig := AuthConfig{
+               Username: credentials.Username,
+               Password: credentials.Password,
+       }
+
+       b, err := json.Marshal(&authConfig)
+       if err != nil {
+               return "", err
+       }
+
+       opts := types.ImagePushOptions{RegistryAuth: 
base64.StdEncoding.EncodeToString(b)}
+
+       r, err := cli.ImagePush(ctx, dc.Image, opts)
+       if err != nil {
+               return "", fmt.Errorf("failed to push the image: %w", err)
+       }
+       defer r.Close()
+
+       var outBuff bytes.Buffer
+       output = io.MultiWriter(&outBuff, output)
+
+       var isTerminal bool
+       var fd uintptr
+       if outF, ok := output.(*os.File); ok {
+               fd = outF.Fd()
+               isTerminal = term.IsTerminal(int(outF.Fd()))
+       }
+
+       err = jsonmessage.DisplayJSONMessagesStream(r, output, fd, isTerminal, 
nil)
+       if err != nil {
+               return "", err
+       }
+
+       return parseDigest(outBuff.String()), nil
+}
+
+var digestRE = regexp.MustCompile(`digest:\s+(sha256:\w{64})`)
+
+func parseDigest(output string) string {
+       match := digestRE.FindStringSubmatch(output)
+       if len(match) >= 2 {
+               return match[1]
+       }
+       return ""
+}
+
+func (n *Pusher) push(ctx context.Context, dc *dubbo.DubboConfig, credentials 
Credentials, output io.Writer) (digest string, err error) {
+       auth := &authn.Basic{
+               Username: credentials.Username,
+               Password: credentials.Password,
+       }
+
+       ref, err := name.ParseReference(dc.Image)
+       if err != nil {
+               return "", err
+       }
+
+       dockerClient, err := n.dockerClientFactory()
+       if err != nil {
+               return "", fmt.Errorf("failed to create docker api client: %w", 
err)
+       }
+       defer dockerClient.Close()
+
+       img, err := daemon.Image(ref,
+               daemon.WithContext(ctx),
+               daemon.WithClient(dockerClient))
+       if err != nil {
+               return "", err
+       }
+
+       progressChannel := make(chan v1.Update, 1024)
+       errChan := make(chan error)
+       go func() {
+               defer fmt.Fprint(output, "\n")
+
+               for progress := range progressChannel {
+                       if progress.Error != nil {
+                               errChan <- progress.Error
+                               return
+                       }
+                       fmt.Fprintf(output, "\rprogress: %d%%", 
progress.Complete*100/progress.Total)
+               }
+
+               errChan <- nil
+       }()
+
+       err = remote.Write(ref, img,
+               remote.WithAuth(auth),
+               remote.WithProgress(progressChannel),
+               remote.WithTransport(n.transport),
+               remote.WithJobs(1),
+               remote.WithContext(ctx))
+       if err != nil {
+               return "", err
+       }
+       err = <-errChan
+       if err != nil {
+               return "", err
+       }
+
+       hash, err := img.Digest()
+       if err != nil {
+               return "", err
+       }
+
+       return hash.String(), nil
+}
+
+type PusherDockerClient interface {
+       daemon.Client
+       ImagePush(ctx context.Context, ref string, options 
types.ImagePushOptions) (io.ReadCloser, error)
+       Close() error
+}
+
+func WithTransport(transport http.RoundTripper) Opt {
+       return func(pusher *Pusher) {
+               pusher.transport = transport
+       }
+}
+
+func WithCredentialsProvider(cp CredentialsProvider) Opt {
+       return func(p *Pusher) {
+               p.credentialsProvider = cp
+       }
+}
+
+func EmptyCredentialsProvider(ctx context.Context, registry string) 
(Credentials, error) {
+       return Credentials{}, nil
+}
diff --git a/dubboctl/pkg/hub/pusher/transport.go 
b/dubboctl/pkg/hub/pusher/transport.go
new file mode 100644
index 00000000..01d60924
--- /dev/null
+++ b/dubboctl/pkg/hub/pusher/transport.go
@@ -0,0 +1,184 @@
+package pusher
+
+import (
+       "context"
+       "crypto/tls"
+       "crypto/x509"
+       "errors"
+       "fmt"
+       "io"
+       "net"
+       "net/http"
+       "time"
+)
+
+type ContextDialer interface {
+       DialContext(ctx context.Context, network string, addr string) 
(net.Conn, error)
+       Close() error
+}
+
+type RoundTripCloser interface {
+       http.RoundTripper
+       io.Closer
+}
+
+func NewRoundTripper(opts ...Option) RoundTripCloser {
+       o := options{
+               insecureSkipVerify: false,
+       }
+       for _, option := range opts {
+               option(&o)
+       }
+
+       httpTransport := newHTTPTransport()
+
+       primaryDialer := dialContextFn(httpTransport.DialContext)
+       secondaryDialer := o.inClusterDialer
+
+       combinedDialer := newDialerWithFallback(primaryDialer, secondaryDialer)
+
+       httpTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: 
o.insecureSkipVerify}
+
+       httpTransport.DialContext = combinedDialer.DialContext
+
+       httpTransport.DialTLSContext = newDialTLSContext(combinedDialer, 
httpTransport.TLSClientConfig, o.selectCA)
+
+       return &roundTripCloser{
+               Transport: httpTransport,
+               dialer:    combinedDialer,
+       }
+}
+
+func newDialTLSContext(dialer ContextDialer, config *tls.Config, selectCA 
func(ctx context.Context, serverName string) (*x509.Certificate, error)) 
func(ctx context.Context, network, addr string) (net.Conn, error) {
+       if selectCA == nil {
+               return nil
+       }
+       return func(ctx context.Context, network, addr string) (net.Conn, 
error) {
+               conn, err := dialer.DialContext(ctx, network, addr)
+               if err != nil {
+                       return nil, err
+               }
+
+               var cfg *tls.Config
+               if config != nil {
+                       cfg = config.Clone()
+               } else {
+                       cfg = &tls.Config{}
+               }
+
+               serverName, _, err := net.SplitHostPort(addr)
+               if err != nil {
+                       return nil, err
+               }
+
+               if cfg.ServerName == "" {
+                       cfg.ServerName = serverName
+               }
+
+               if ca, err := selectCA(ctx, serverName); ca != nil && err == 
nil {
+                       caPool := x509.NewCertPool()
+                       caPool.AddCert(ca)
+                       cfg.RootCAs = caPool
+               }
+
+               tlsConn := tls.Client(conn, cfg)
+               return tlsConn, nil
+       }
+}
+
+type roundTripCloser struct {
+       *http.Transport
+       dialer ContextDialer
+}
+
+func (d *dialerWithFallback) DialContext(ctx context.Context, network, address 
string) (net.Conn, error) {
+       conn, err := d.primaryDialer.DialContext(ctx, network, address)
+       if err == nil {
+               return conn, nil
+       }
+
+       var dnsErr *net.DNSError
+       if !errors.As(err, &dnsErr) {
+               return nil, err
+       }
+
+       return d.fallbackDialer.DialContext(ctx, network, address)
+}
+
+func (d *dialerWithFallback) Close() error {
+       var err error
+       errs := make([]error, 0, 2)
+
+       err = d.primaryDialer.Close()
+       if err != nil {
+               errs = append(errs, err)
+       }
+
+       err = d.fallbackDialer.Close()
+       if err != nil {
+               errs = append(errs, err)
+       }
+
+       if len(errs) > 0 {
+               return fmt.Errorf("failed to Close(): %v", errs)
+       }
+
+       return nil
+}
+
+func (r *roundTripCloser) Close() error {
+       return r.dialer.Close()
+}
+
+func newHTTPTransport() *http.Transport {
+       if dt, ok := http.DefaultTransport.(*http.Transport); ok {
+               return dt.Clone()
+       } else {
+               return &http.Transport{
+                       Proxy: http.ProxyFromEnvironment,
+                       DialContext: (&net.Dialer{
+                               Timeout:   time.Minute,
+                               KeepAlive: time.Minute,
+                       }).DialContext,
+                       ForceAttemptHTTP2:     true,
+                       MaxIdleConns:          100,
+                       IdleConnTimeout:       90 * time.Second,
+                       TLSHandshakeTimeout:   10 * time.Second,
+                       ExpectContinueTimeout: 1 * time.Second,
+               }
+       }
+}
+
+type dialerWithFallback struct {
+       primaryDialer  ContextDialer
+       fallbackDialer ContextDialer
+}
+
+func newDialerWithFallback(primaryDialer ContextDialer, fallbackDialer 
ContextDialer) *dialerWithFallback {
+       return &dialerWithFallback{
+               primaryDialer:  primaryDialer,
+               fallbackDialer: fallbackDialer,
+       }
+}
+
+type dialContextFn func(ctx context.Context, network string, addr string) 
(net.Conn, error)
+
+func (d dialContextFn) DialContext(ctx context.Context, network string, addr 
string) (net.Conn, error) {
+       return d(ctx, network, addr)
+}
+
+func (d dialContextFn) Close() error { return nil }
+
+type options struct {
+       selectCA           func(ctx context.Context, serverName string) 
(*x509.Certificate, error)
+       inClusterDialer    ContextDialer
+       insecureSkipVerify bool
+}
+
+type Option func(*options)
+
+func WithInsecureSkipVerify(insecureSkipVerify bool) Option {
+       return func(o *options) {
+               o.insecureSkipVerify = insecureSkipVerify
+       }
+}
diff --git a/dubboctl/pkg/sdk/client.go b/dubboctl/pkg/sdk/client.go
index 35d9b1bb..35ac1ec1 100644
--- a/dubboctl/pkg/sdk/client.go
+++ b/dubboctl/pkg/sdk/client.go
@@ -333,6 +333,12 @@ func WithBuilder(b Builder) Option {
        }
 }
 
+func WithPusher(pusher Pusher) Option {
+       return func(c *Client) {
+               c.pusher = pusher
+       }
+}
+
 func WithDeployer(d Deployer) Option {
        return func(c *Client) {
                c.deployer = d

Reply via email to