github.com/Jeffail/benthos/v3@v3.65.0/lib/output/writer/amqp_1.go (about) 1 package writer 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "sync" 8 "time" 9 10 "github.com/Azure/go-amqp" 11 "github.com/Jeffail/benthos/v3/internal/metadata" 12 "github.com/Jeffail/benthos/v3/lib/log" 13 "github.com/Jeffail/benthos/v3/lib/metrics" 14 "github.com/Jeffail/benthos/v3/lib/types" 15 "github.com/Jeffail/benthos/v3/lib/util/amqp/sasl" 16 btls "github.com/Jeffail/benthos/v3/lib/util/tls" 17 ) 18 19 //------------------------------------------------------------------------------ 20 21 // AMQP1Config contains configuration fields for the AMQP1 output type. 22 type AMQP1Config struct { 23 URL string `json:"url" yaml:"url"` 24 TargetAddress string `json:"target_address" yaml:"target_address"` 25 MaxInFlight int `json:"max_in_flight" yaml:"max_in_flight"` 26 TLS btls.Config `json:"tls" yaml:"tls"` 27 SASL sasl.Config `json:"sasl" yaml:"sasl"` 28 Metadata metadata.ExcludeFilterConfig `json:"metadata" yaml:"metadata"` 29 } 30 31 // NewAMQP1Config creates a new AMQP1Config with default values. 32 func NewAMQP1Config() AMQP1Config { 33 return AMQP1Config{ 34 URL: "", 35 TargetAddress: "", 36 MaxInFlight: 1, 37 TLS: btls.NewConfig(), 38 SASL: sasl.NewConfig(), 39 Metadata: metadata.NewExcludeFilterConfig(), 40 } 41 } 42 43 //------------------------------------------------------------------------------ 44 45 // AMQP1 is an output type that serves AMQP1 messages. 46 type AMQP1 struct { 47 client *amqp.Client 48 session *amqp.Session 49 sender *amqp.Sender 50 51 metaFilter *metadata.ExcludeFilter 52 53 log log.Modular 54 stats metrics.Type 55 56 conf AMQP1Config 57 tlsConf *tls.Config 58 59 connLock sync.RWMutex 60 } 61 62 // NewAMQP1 creates a new AMQP1 writer type. 63 func NewAMQP1(conf AMQP1Config, log log.Modular, stats metrics.Type) (*AMQP1, error) { 64 a := AMQP1{ 65 log: log, 66 stats: stats, 67 conf: conf, 68 } 69 var err error 70 if conf.TLS.Enabled { 71 if a.tlsConf, err = conf.TLS.Get(); err != nil { 72 return nil, err 73 } 74 } 75 if a.metaFilter, err = conf.Metadata.Filter(); err != nil { 76 return nil, fmt.Errorf("failed to construct metadata filter: %w", err) 77 } 78 return &a, nil 79 } 80 81 //------------------------------------------------------------------------------ 82 83 // Connect establishes a connection to an AMQP1 server. 84 func (a *AMQP1) Connect() error { 85 return a.ConnectWithContext(context.Background()) 86 } 87 88 // ConnectWithContext establishes a connection to an AMQP1 server. 89 func (a *AMQP1) ConnectWithContext(ctx context.Context) error { 90 a.connLock.Lock() 91 defer a.connLock.Unlock() 92 93 if a.client != nil { 94 return nil 95 } 96 97 var ( 98 client *amqp.Client 99 session *amqp.Session 100 sender *amqp.Sender 101 err error 102 ) 103 104 opts, err := a.conf.SASL.ToOptFns() 105 if err != nil { 106 return err 107 } 108 if a.conf.TLS.Enabled { 109 opts = append(opts, amqp.ConnTLS(true), amqp.ConnTLSConfig(a.tlsConf)) 110 } 111 112 // Create client 113 if client, err = amqp.Dial(a.conf.URL, opts...); err != nil { 114 return err 115 } 116 117 // Open a session 118 if session, err = client.NewSession(); err != nil { 119 client.Close() 120 return err 121 } 122 123 // Create a sender 124 if sender, err = session.NewSender( 125 amqp.LinkTargetAddress(a.conf.TargetAddress), 126 ); err != nil { 127 session.Close(context.Background()) 128 client.Close() 129 return err 130 } 131 132 a.client = client 133 a.session = session 134 a.sender = sender 135 136 a.log.Infof("Sending AMQP 1.0 messages to target: %v\n", a.conf.TargetAddress) 137 return nil 138 } 139 140 // disconnect safely closes a connection to an AMQP1 server. 141 func (a *AMQP1) disconnect(ctx context.Context) error { 142 a.connLock.Lock() 143 defer a.connLock.Unlock() 144 145 if a.client == nil { 146 return nil 147 } 148 149 if err := a.sender.Close(ctx); err != nil { 150 a.log.Errorf("Failed to cleanly close sender: %v\n", err) 151 } 152 if err := a.session.Close(ctx); err != nil { 153 a.log.Errorf("Failed to cleanly close session: %v\n", err) 154 } 155 if err := a.client.Close(); err != nil { 156 a.log.Errorf("Failed to cleanly close client: %v\n", err) 157 } 158 a.client = nil 159 a.session = nil 160 a.sender = nil 161 162 return nil 163 } 164 165 //------------------------------------------------------------------------------ 166 167 // Write will attempt to write a message over AMQP1, wait for acknowledgement, 168 // and returns an error if applicable. 169 func (a *AMQP1) Write(msg types.Message) error { 170 return a.WriteWithContext(context.Background(), msg) 171 } 172 173 // WriteWithContext will attempt to write a message over AMQP1, wait for 174 // acknowledgement, and returns an error if applicable. 175 func (a *AMQP1) WriteWithContext(ctx context.Context, msg types.Message) error { 176 var s *amqp.Sender 177 a.connLock.RLock() 178 if a.sender != nil { 179 s = a.sender 180 } 181 a.connLock.RUnlock() 182 183 if s == nil { 184 return types.ErrNotConnected 185 } 186 187 return IterateBatchedSend(msg, func(i int, p types.Part) error { 188 m := amqp.NewMessage(p.Get()) 189 a.metaFilter.Iter(p.Metadata(), func(k, v string) error { 190 if m.Annotations == nil { 191 m.Annotations = amqp.Annotations{} 192 } 193 m.Annotations[k] = v 194 return nil 195 }) 196 err := s.Send(ctx, m) 197 if err != nil { 198 if err == amqp.ErrTimeout { 199 err = types.ErrTimeout 200 } else { 201 if dErr, isDetachError := err.(*amqp.DetachError); isDetachError && dErr.RemoteError != nil { 202 a.log.Errorf("Lost connection due to: %v\n", dErr.RemoteError) 203 } else { 204 a.log.Errorf("Lost connection due to: %v\n", err) 205 } 206 a.disconnect(ctx) 207 err = types.ErrNotConnected 208 } 209 } 210 return err 211 }) 212 } 213 214 // CloseAsync shuts down the AMQP1 output and stops processing messages. 215 func (a *AMQP1) CloseAsync() { 216 a.disconnect(context.Background()) 217 } 218 219 // WaitForClose blocks until the AMQP1 output has closed down. 220 func (a *AMQP1) WaitForClose(timeout time.Duration) error { 221 return nil 222 } 223 224 //------------------------------------------------------------------------------