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