github.com/rudderlabs/rudder-go-kit@v0.30.0/kafkaclient/client.go (about) 1 package client 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "time" 8 9 "github.com/segmentio/kafka-go" 10 "golang.org/x/crypto/ssh" 11 ) 12 13 // Logger specifies a logger used to report internal changes within the consumer 14 type Logger interface { 15 Printf(format string, args ...interface{}) 16 } 17 18 // MessageHeader is a key/value pair type representing headers set on records 19 type MessageHeader struct { 20 Key string 21 Value []byte 22 } 23 24 // Message is a data structure representing a Kafka message 25 type Message struct { 26 Key, Value []byte 27 Topic string 28 Partition int32 29 Offset int64 30 Headers []MessageHeader 31 Timestamp time.Time 32 } 33 34 type Client struct { 35 network string 36 addresses []string 37 dialer *kafka.Dialer 38 config *Config 39 } 40 41 // New returns a new Kafka client 42 func New(network string, addresses []string, conf Config) (*Client, error) { 43 conf.defaults() 44 45 dialer := kafka.Dialer{ 46 DualStack: true, 47 Timeout: conf.DialTimeout, 48 } 49 50 if conf.ClientID != "" { 51 dialer.ClientID = conf.ClientID 52 } 53 if conf.TLS != nil { 54 var err error 55 dialer.TLS, err = conf.TLS.build() 56 if err != nil { 57 return nil, err 58 } 59 } 60 61 if conf.SASL != nil { 62 var err error 63 dialer.SASLMechanism, err = conf.SASL.build() 64 if err != nil { 65 return nil, err 66 } 67 } 68 69 if conf.SSHConfig != nil { 70 signer, err := ssh.ParsePrivateKey([]byte(conf.SSHConfig.PrivateKey)) 71 if err != nil { 72 return nil, fmt.Errorf("cannot parse SSH private key: %w", err) 73 } 74 75 sshConfig := &ssh.ClientConfig{ 76 User: conf.SSHConfig.User, 77 Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, 78 Timeout: conf.DialTimeout, 79 HostKeyCallback: ssh.InsecureIgnoreHostKey(), // skipcq: GSC-G106 80 } 81 82 dialer.DialFunc = func(ctx context.Context, network, address string) (net.Conn, error) { 83 sshClient, err := ssh.Dial("tcp", conf.SSHConfig.Host, sshConfig) 84 if err != nil { 85 return nil, fmt.Errorf("cannot dial SSH host %q: %w", conf.SSHConfig.Host, err) 86 } 87 88 conn, err := sshClient.Dial(network, address) 89 if err != nil { 90 return nil, fmt.Errorf( 91 "cannot dial address %q over SSH (host %q): %w", address, conf.SSHConfig.Host, err, 92 ) 93 } 94 return conn, nil 95 } 96 } 97 98 return &Client{ 99 network: network, 100 addresses: addresses, 101 dialer: &dialer, 102 config: &conf, 103 }, nil 104 } 105 106 // NewConfluentCloud returns a Kafka client pre-configured to connect to Confluent Cloud 107 func NewConfluentCloud(addresses []string, key, secret string, conf Config) (*Client, error) { 108 conf.SASL = &SASL{ 109 ScramHashGen: ScramPlainText, 110 Username: key, 111 Password: secret, 112 } 113 conf.TLS = &TLS{ 114 WithSystemCertPool: true, 115 } 116 return New("tcp", addresses, conf) 117 } 118 119 // NewAzureEventHubs returns a Kafka client pre-configured to connect to Azure Event Hubs 120 // addresses should be in the form of "host:port" where the port is usually 9093 on Azure Event Hubs 121 // Also make sure to select at least the Standard tier since the Basic tier does not support Kafka 122 func NewAzureEventHubs(addresses []string, connectionString string, conf Config) (*Client, error) { 123 conf.SASL = &SASL{ 124 ScramHashGen: ScramPlainText, 125 Username: "$ConnectionString", 126 Password: connectionString, 127 } 128 conf.TLS = &TLS{ 129 WithSystemCertPool: true, 130 } 131 return New("tcp", addresses, conf) 132 } 133 134 // Ping is used to check the connectivity only, then it discards the connection 135 // Ping ensures that at least one of the provided addresses is reachable. 136 func (c *Client) Ping(ctx context.Context) error { 137 var lastErr error 138 for _, addr := range c.addresses { 139 conn, err := c.dialer.DialContext(ctx, c.network, kafka.TCP(addr).String()) 140 if err == nil { 141 go func() { _ = conn.Close() }() 142 // we can connect to at least one address, no need to check all of them 143 return nil 144 } 145 lastErr = err 146 } 147 148 return fmt.Errorf( 149 "could not dial any of the addresses %s/%s: %w", c.network, kafka.TCP(c.addresses...).String(), lastErr, 150 ) 151 }