Files
zeekayandHanzo Dev 1ab6e799c1 cd: the module is github.com/hanzoai/cd, where it is actually hosted
go.mod still declared `module github.com/hanzoai/deploy` after the repo was
renamed. A Go module path is not cosmetic — it is the resolvable location. So
`go get github.com/hanzoai/cd` could not work, and anything importing this
module had to spell a name that no longer exists at that address. The build log
of v3.6.0 is where it surfaced: the compiler was still emitting
github.com/hanzoai/deploy/cmd/cd/commands.

3541 import paths across 690 .go, 19 .proto, 4 .yaml, 3 .sh, 2 .mod, Procfile
and Makefile. Plus the 62 ghcr.io/hanzoai/deploy image references, moved to
ghcr.io/hanzoai/cd to match hanzo.yml's build target — a distinct string from
the module path, replaced separately so neither could clobber the other.

go mod tidy clean, CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./... clean.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 21:16:24 -07:00

137 lines
4.1 KiB
Go

package grpc
import (
"context"
"crypto/tls"
"fmt"
"net"
"runtime/debug"
"strings"
"time"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery"
"github.com/sirupsen/logrus"
"golang.org/x/net/proxy"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/status"
"github.com/hanzoai/cd/common"
)
// LoggerRecoveryHandler return a handler for recovering from panics and returning error
func LoggerRecoveryHandler(log *logrus.Entry) recovery.RecoveryHandlerFunc {
return func(p any) (err error) {
log.Errorf("Recovered from panic: %+v\n%s", p, debug.Stack())
return status.Errorf(codes.Internal, "%s", p)
}
}
// BlockingNewClient is a helper method to dial the given address, using optional TLS credentials,
// and blocking until the returned connection is ready. If the given credentials are nil, the
// connection will be insecure (plain-text).
// Lifted from: https://github.com/fullstorydev/grpcurl/blob/master/grpcurl.go
func BlockingNewClient(ctx context.Context, network, address string, creds credentials.TransportCredentials, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
proxyDialer := proxy.FromEnvironment()
rawConn, err := proxyDialer.Dial(network, address)
if err != nil {
return nil, fmt.Errorf("error dial proxy: %w", err)
}
if creds != nil {
rawConn, _, err = creds.ClientHandshake(ctx, address, rawConn)
if err != nil {
return nil, fmt.Errorf("error creating connection: %w", err)
}
}
customDialer := func(_ context.Context, _ string) (net.Conn, error) {
return rawConn, nil
}
opts = append(opts,
grpc.WithContextDialer(customDialer),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: common.GetGRPCKeepAliveTime()}),
)
conn, err := grpc.NewClient("passthrough:"+address, opts...)
if err != nil {
return nil, fmt.Errorf("grpc.NewClient failed: %w", err)
}
conn.Connect()
if err := waitForReady(ctx, conn); err != nil {
return nil, fmt.Errorf("gRPC connection not ready: %w", err)
}
return conn, nil
}
func waitForReady(ctx context.Context, conn *grpc.ClientConn) error {
for {
state := conn.GetState()
if state == connectivity.Ready {
return nil
}
if !conn.WaitForStateChange(ctx, state) {
return ctx.Err() // context timeout or cancellation
}
}
}
type TLSTestResult struct {
TLS bool
InsecureErr error
}
func TestTLS(address string, dialTime time.Duration) (*TLSTestResult, error) {
if parts := strings.Split(address, ":"); len(parts) == 1 {
// If port is unspecified, assume the most likely port
address += ":443"
}
var testResult TLSTestResult
var tlsConfig tls.Config
tlsConfig.InsecureSkipVerify = true
creds := credentials.NewTLS(&tlsConfig)
// Set timeout when dialing to the server
// fix: https://github.com/argoproj/argo-cd/issues/9679
ctx, cancel := context.WithTimeout(context.Background(), dialTime)
defer cancel()
conn, err := BlockingNewClient(ctx, "tcp", address, creds)
if err == nil {
_ = conn.Close()
testResult.TLS = true
creds := credentials.NewTLS(&tls.Config{})
ctx, cancel := context.WithTimeout(context.Background(), dialTime)
defer cancel()
conn, err := BlockingNewClient(ctx, "tcp", address, creds)
if err == nil {
_ = conn.Close()
} else {
// if connection was successful with InsecureSkipVerify true, but unsuccessful with
// InsecureSkipVerify false, it means server is not configured securely
testResult.InsecureErr = err
}
return &testResult, nil
}
// If we get here, we were unable to connect via TLS (even with InsecureSkipVerify: true)
// It may be because server is running without TLS, or because of real issues (e.g. connection
// refused). Test if server accepts plain-text connections
ctx, cancel = context.WithTimeout(context.Background(), dialTime)
defer cancel()
conn, err = BlockingNewClient(ctx, "tcp", address, nil)
if err == nil {
_ = conn.Close()
testResult.TLS = false
return &testResult, nil
}
return nil, err
}