go.uber.org/yarpc@v1.72.1/yarpcconfig/builder.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package yarpcconfig 22 23 import ( 24 "fmt" 25 "reflect" 26 27 "github.com/uber-go/mapdecode" 28 "go.uber.org/multierr" 29 "go.uber.org/yarpc" 30 "go.uber.org/yarpc/api/transport" 31 yarpctls "go.uber.org/yarpc/api/transport/tls" 32 "go.uber.org/yarpc/internal/config" 33 ) 34 35 type buildableOutbounds struct { 36 Service string 37 Unary *buildableOutbound 38 Oneway *buildableOutbound 39 Stream *buildableOutbound 40 } 41 42 type buildableInbound struct { 43 Transport string 44 Value *buildable 45 } 46 47 type buildableOutbound struct { 48 TransportSpec *compiledTransportSpec 49 Value *buildable 50 } 51 52 type builder struct { 53 Name string 54 kit *Kit 55 56 // Transports that we actually need and their specs. We need a transport 57 // only if we have at least one inbound or outbound using it. 58 needTransports map[string]*compiledTransportSpec 59 60 transports map[string]*buildable 61 inbounds []buildableInbound 62 clients map[string]*buildableOutbounds 63 } 64 65 func newBuilder(name string, kit *Kit) *builder { 66 return &builder{ 67 Name: name, 68 kit: kit, 69 needTransports: make(map[string]*compiledTransportSpec), 70 transports: make(map[string]*buildable), 71 clients: make(map[string]*buildableOutbounds), 72 } 73 } 74 75 func (b *builder) Build() (yarpc.Config, error) { 76 var ( 77 transports = make(map[string]transport.Transport) 78 cfg = yarpc.Config{Name: b.Name} 79 errs error 80 ) 81 82 for name, spec := range b.needTransports { 83 cv, ok := b.transports[name] 84 85 var err error 86 if !ok { 87 // No configuration provided for the transport. Use an empty map. 88 cv, err = spec.Transport.Decode(config.AttributeMap{}, config.InterpolateWith(b.kit.resolver)) 89 if err != nil { 90 return yarpc.Config{}, err 91 } 92 } 93 94 transports[name], err = buildTransport(cv, b.kit) 95 if err != nil { 96 return yarpc.Config{}, err 97 } 98 } 99 100 for _, i := range b.inbounds { 101 ib, err := buildInbound(i.Value, transports[i.Transport], b.kit) 102 if err != nil { 103 errs = multierr.Append(errs, err) 104 continue 105 } 106 cfg.Inbounds = append(cfg.Inbounds, ib) 107 } 108 109 outbounds := make(yarpc.Outbounds, len(b.clients)) 110 for ccname, c := range b.clients { 111 var err error 112 113 var ob transport.Outbounds 114 if c.Service != ccname { 115 ob.ServiceName = c.Service 116 } 117 118 kit := b.kit.withOutboundName(c.Service) 119 if o := c.Unary; o != nil { 120 ob.Unary, err = buildUnaryOutbound(o, transports[o.TransportSpec.Name], kit) 121 if err != nil { 122 errs = multierr.Append(errs, fmt.Errorf(`failed to configure unary outbound for %q: %v`, ccname, err)) 123 continue 124 } 125 } 126 if o := c.Oneway; o != nil { 127 ob.Oneway, err = buildOnewayOutbound(o, transports[o.TransportSpec.Name], kit) 128 if err != nil { 129 errs = multierr.Append(errs, fmt.Errorf(`failed to configure oneway outbound for %q: %v`, ccname, err)) 130 continue 131 } 132 } 133 if o := c.Stream; o != nil { 134 ob.Stream, err = buildStreamOutbound(o, transports[o.TransportSpec.Name], kit) 135 if err != nil { 136 errs = multierr.Append(errs, fmt.Errorf(`failed to configure stream outbound for %q: %v`, ccname, err)) 137 continue 138 } 139 } 140 141 outbounds[ccname] = ob 142 } 143 if len(outbounds) > 0 { 144 cfg.Outbounds = outbounds 145 } 146 147 return cfg, errs 148 } 149 150 // buildTransport builds a Transport from the given value. This will panic if 151 // the output type is not a Transport. 152 func buildTransport(cv *buildable, k *Kit) (transport.Transport, error) { 153 result, err := cv.Build(k) 154 if err != nil { 155 return nil, err 156 } 157 return result.(transport.Transport), nil 158 } 159 160 // buildInbound builds an Inbound from the given value. This will panic if the 161 // output type for this is not transport.Inbound. 162 func buildInbound(cv *buildable, t transport.Transport, k *Kit) (transport.Inbound, error) { 163 result, err := cv.Build(t, k) 164 if err != nil { 165 return nil, err 166 } 167 return result.(transport.Inbound), nil 168 } 169 170 // buildUnaryOutbound builds an UnaryOutbound from the given value. This will panic 171 // if the output type for this is not transport.UnaryOutbound. 172 func buildUnaryOutbound(o *buildableOutbound, t transport.Transport, k *Kit) (transport.UnaryOutbound, error) { 173 result, err := o.Value.Build(t, k.withTransportSpec(o.TransportSpec)) 174 if err != nil { 175 return nil, err 176 } 177 return result.(transport.UnaryOutbound), nil 178 } 179 180 // buildOnewayOutbound builds an OnewayOutbound from the given value. This will 181 // panic if the output type for this is not transport.OnewayOutbound. 182 func buildOnewayOutbound(o *buildableOutbound, t transport.Transport, k *Kit) (transport.OnewayOutbound, error) { 183 result, err := o.Value.Build(t, k.withTransportSpec(o.TransportSpec)) 184 if err != nil { 185 return nil, err 186 } 187 return result.(transport.OnewayOutbound), nil 188 } 189 190 // buildStreamOutbound builds an StreamOutbound from the given value. This will 191 // panic if the output type for this is not transport.StreamOutbound. 192 func buildStreamOutbound(o *buildableOutbound, t transport.Transport, k *Kit) (transport.StreamOutbound, error) { 193 result, err := o.Value.Build(t, k.withTransportSpec(o.TransportSpec)) 194 if err != nil { 195 return nil, err 196 } 197 return result.(transport.StreamOutbound), nil 198 } 199 200 func (b *builder) AddTransportConfig(spec *compiledTransportSpec, attrs config.AttributeMap) error { 201 cv, err := spec.Transport.Decode(attrs, config.InterpolateWith(b.kit.resolver)) 202 if err != nil { 203 return fmt.Errorf("failed to decode transport configuration: %v", err) 204 } 205 206 b.transports[spec.Name] = cv 207 return nil 208 } 209 210 func (b *builder) AddInboundConfig(spec *compiledTransportSpec, attrs config.AttributeMap) error { 211 if spec.Inbound == nil { 212 return fmt.Errorf("transport %q does not support inbound requests", spec.Name) 213 } 214 215 b.needTransport(spec) 216 cv, err := spec.Inbound.Decode(attrs, config.InterpolateWith(b.kit.resolver), mapdecode.DecodeHook(tlsModeDecodeHook)) 217 if err != nil { 218 return fmt.Errorf("failed to decode inbound configuration: %v", err) 219 } 220 221 b.inbounds = append(b.inbounds, buildableInbound{ 222 Transport: spec.Name, 223 Value: cv, 224 }) 225 return nil 226 } 227 228 func (b *builder) AddImplicitOutbound( 229 spec *compiledTransportSpec, outboundKey, service string, attrs config.AttributeMap, 230 ) error { 231 var errs error 232 supportsOutbound := false 233 234 if spec.SupportsUnaryOutbound() { 235 supportsOutbound = true 236 if err := b.AddUnaryOutbound(spec, outboundKey, service, attrs); err != nil { 237 errs = multierr.Append(errs, err) 238 } 239 } 240 241 if spec.SupportsOnewayOutbound() { 242 supportsOutbound = true 243 if err := b.AddOnewayOutbound(spec, outboundKey, service, attrs); err != nil { 244 errs = multierr.Append(errs, err) 245 } 246 } 247 if spec.SupportsStreamOutbound() { 248 supportsOutbound = true 249 if err := b.AddStreamOutbound(spec, outboundKey, service, attrs); err != nil { 250 errs = multierr.Append(errs, err) 251 } 252 } 253 254 if !supportsOutbound { 255 return fmt.Errorf("transport %q does not support outbound requests", spec.Name) 256 } 257 258 return errs 259 } 260 261 func (b *builder) AddUnaryOutbound( 262 spec *compiledTransportSpec, outboundKey, service string, attrs config.AttributeMap, 263 ) error { 264 if spec.UnaryOutbound == nil { 265 return fmt.Errorf("transport %q does not support unary outbound requests", spec.Name) 266 } 267 268 b.needTransport(spec) 269 cv, err := spec.UnaryOutbound.Decode(attrs, config.InterpolateWith(b.kit.resolver), mapdecode.DecodeHook(tlsModeDecodeHook)) 270 if err != nil { 271 return fmt.Errorf("failed to decode unary outbound configuration: %v", err) 272 } 273 274 cc, ok := b.clients[outboundKey] 275 if !ok { 276 cc = &buildableOutbounds{Service: service} 277 b.clients[outboundKey] = cc 278 } 279 280 cc.Unary = &buildableOutbound{TransportSpec: spec, Value: cv} 281 return nil 282 } 283 284 func (b *builder) AddOnewayOutbound( 285 spec *compiledTransportSpec, outboundKey, service string, attrs config.AttributeMap, 286 ) error { 287 if spec.OnewayOutbound == nil { 288 return fmt.Errorf("transport %q does not support oneway outbound requests", spec.Name) 289 } 290 291 b.needTransport(spec) 292 cv, err := spec.OnewayOutbound.Decode(attrs, config.InterpolateWith(b.kit.resolver), mapdecode.DecodeHook(tlsModeDecodeHook)) 293 if err != nil { 294 return fmt.Errorf("failed to decode oneway outbound configuration: %v", err) 295 } 296 297 cc, ok := b.clients[outboundKey] 298 if !ok { 299 cc = &buildableOutbounds{Service: service} 300 b.clients[outboundKey] = cc 301 } 302 303 cc.Oneway = &buildableOutbound{TransportSpec: spec, Value: cv} 304 return nil 305 } 306 307 func (b *builder) AddStreamOutbound( 308 spec *compiledTransportSpec, outboundKey, service string, attrs config.AttributeMap, 309 ) error { 310 if spec.StreamOutbound == nil { 311 return fmt.Errorf("transport %q does not support stream outbound requests", spec.Name) 312 } 313 314 b.needTransport(spec) 315 cv, err := spec.StreamOutbound.Decode(attrs, config.InterpolateWith(b.kit.resolver), mapdecode.DecodeHook(tlsModeDecodeHook)) 316 if err != nil { 317 return fmt.Errorf("failed to decode stream outbound configuration: %v", err) 318 } 319 320 cc, ok := b.clients[outboundKey] 321 if !ok { 322 cc = &buildableOutbounds{Service: service} 323 b.clients[outboundKey] = cc 324 } 325 326 cc.Stream = &buildableOutbound{TransportSpec: spec, Value: cv} 327 return nil 328 } 329 330 func (b *builder) needTransport(spec *compiledTransportSpec) { 331 b.needTransports[spec.Name] = spec 332 } 333 334 func tlsModeDecodeHook(from, to reflect.Type, data reflect.Value) (reflect.Value, error) { 335 var mode yarpctls.Mode 336 if from.Kind() != reflect.String || to != reflect.TypeOf(mode) { 337 return data, nil 338 } 339 340 err := mode.UnmarshalText([]byte(data.String())) 341 return reflect.ValueOf(mode), err 342 }