1package structs 2 3import ( 4 "encoding/json" 5 "fmt" 6 "net" 7 8 "github.com/hashicorp/consul/api" 9 "github.com/hashicorp/consul/lib" 10) 11 12const ( 13 defaultExposeProtocol = "http" 14) 15 16var allowedExposeProtocols = map[string]bool{"http": true, "http2": true} 17 18type MeshGatewayMode string 19 20const ( 21 // MeshGatewayModeDefault represents no specific mode and should 22 // be used to indicate that a different layer of the configuration 23 // chain should take precedence 24 MeshGatewayModeDefault MeshGatewayMode = "" 25 26 // MeshGatewayModeNone represents that the Upstream Connect connections 27 // should be direct and not flow through a mesh gateway. 28 MeshGatewayModeNone MeshGatewayMode = "none" 29 30 // MeshGatewayModeLocal represents that the Upstream Connect connections 31 // should be made to a mesh gateway in the local datacenter. 32 MeshGatewayModeLocal MeshGatewayMode = "local" 33 34 // MeshGatewayModeRemote represents that the Upstream Connect connections 35 // should be made to a mesh gateway in a remote datacenter. 36 MeshGatewayModeRemote MeshGatewayMode = "remote" 37) 38 39const ( 40 // TODO (freddy) Should we have a TopologySourceMixed when there is a mix of proxy reg and tproxy? 41 // Currently we label as proxy-registration if ANY instance has the explicit upstream definition. 42 // TopologySourceRegistration is used to label upstreams or downstreams from explicit upstream definitions. 43 TopologySourceRegistration = "proxy-registration" 44 45 // TopologySourceSpecificIntention is used to label upstreams or downstreams from specific intentions. 46 TopologySourceSpecificIntention = "specific-intention" 47 48 // TopologySourceWildcardIntention is used to label upstreams or downstreams from wildcard intentions. 49 TopologySourceWildcardIntention = "wildcard-intention" 50 51 // TopologySourceDefaultAllow is used to label upstreams or downstreams from default allow ACL policy. 52 TopologySourceDefaultAllow = "default-allow" 53 54 // TopologySourceRoutingConfig is used to label upstreams that are not backed by a service instance 55 // and are simply used for routing configurations. 56 TopologySourceRoutingConfig = "routing-config" 57) 58 59// MeshGatewayConfig controls how Mesh Gateways are configured and used 60// This is a struct to allow for future additions without having more free-hanging 61// configuration items all over the place 62type MeshGatewayConfig struct { 63 // The Mesh Gateway routing mode 64 Mode MeshGatewayMode `json:",omitempty"` 65} 66 67func (c *MeshGatewayConfig) IsZero() bool { 68 zeroVal := MeshGatewayConfig{} 69 return *c == zeroVal 70} 71 72func (base *MeshGatewayConfig) OverlayWith(overlay MeshGatewayConfig) MeshGatewayConfig { 73 out := *base 74 if overlay.Mode != MeshGatewayModeDefault { 75 out.Mode = overlay.Mode 76 } 77 return out 78} 79 80func ValidateMeshGatewayMode(mode string) (MeshGatewayMode, error) { 81 switch MeshGatewayMode(mode) { 82 case MeshGatewayModeNone: 83 return MeshGatewayModeNone, nil 84 case MeshGatewayModeDefault: 85 return MeshGatewayModeDefault, nil 86 case MeshGatewayModeLocal: 87 return MeshGatewayModeLocal, nil 88 case MeshGatewayModeRemote: 89 return MeshGatewayModeRemote, nil 90 default: 91 return MeshGatewayModeDefault, fmt.Errorf("Invalid Mesh Gateway Mode: %q", mode) 92 } 93} 94 95func (c *MeshGatewayConfig) ToAPI() api.MeshGatewayConfig { 96 return api.MeshGatewayConfig{Mode: api.MeshGatewayMode(c.Mode)} 97} 98 99type ProxyMode string 100 101const ( 102 // ProxyModeDefault represents no specific mode and should 103 // be used to indicate that a different layer of the configuration 104 // chain should take precedence 105 ProxyModeDefault ProxyMode = "" 106 107 // ProxyModeTransparent represents that inbound and outbound application 108 // traffic is being captured and redirected through the proxy. 109 ProxyModeTransparent ProxyMode = "transparent" 110 111 // ProxyModeDirect represents that the proxy's listeners must be dialed directly 112 // by the local application and other proxies. 113 ProxyModeDirect ProxyMode = "direct" 114) 115 116func ValidateProxyMode(mode string) (ProxyMode, error) { 117 switch ProxyMode(mode) { 118 case ProxyModeDefault: 119 return ProxyModeDefault, nil 120 case ProxyModeDirect: 121 return ProxyModeDirect, nil 122 case ProxyModeTransparent: 123 return ProxyModeTransparent, nil 124 default: 125 return ProxyModeDefault, fmt.Errorf("Invalid Proxy Mode: %q", mode) 126 } 127} 128 129type TransparentProxyConfig struct { 130 // The port of the listener where outbound application traffic is being redirected to. 131 OutboundListenerPort int `json:",omitempty" alias:"outbound_listener_port"` 132 133 // DialedDirectly indicates whether transparent proxies can dial this proxy instance directly. 134 // The discovery chain is not considered when dialing a service instance directly. 135 // This setting is useful when addressing stateful services, such as a database cluster with a leader node. 136 DialedDirectly bool `json:",omitempty" alias:"dialed_directly"` 137} 138 139func (c TransparentProxyConfig) ToAPI() *api.TransparentProxyConfig { 140 if c.IsZero() { 141 return nil 142 } 143 return &api.TransparentProxyConfig{ 144 OutboundListenerPort: c.OutboundListenerPort, 145 DialedDirectly: c.DialedDirectly, 146 } 147} 148 149func (c *TransparentProxyConfig) IsZero() bool { 150 zeroVal := TransparentProxyConfig{} 151 return *c == zeroVal 152} 153 154// ConnectProxyConfig describes the configuration needed for any proxy managed 155// or unmanaged. It describes a single logical service's listener and optionally 156// upstreams and sidecar-related config for a single instance. To describe a 157// centralized proxy that routed traffic for multiple services, a different one 158// of these would be needed for each, sharing the same LogicalProxyID. 159type ConnectProxyConfig struct { 160 // DestinationServiceName is required and is the name of the service to accept 161 // traffic for. 162 DestinationServiceName string `json:",omitempty" alias:"destination_service_name"` 163 164 // DestinationServiceID is optional and should only be specified for 165 // "side-car" style proxies where the proxy is in front of just a single 166 // instance of the service. It should be set to the service ID of the instance 167 // being represented which must be registered to the same agent. It's valid to 168 // provide a service ID that does not yet exist to avoid timing issues when 169 // bootstrapping a service with a proxy. 170 DestinationServiceID string `json:",omitempty" alias:"destination_service_id"` 171 172 // LocalServiceAddress is the address of the local service instance. It is 173 // optional and should only be specified for "side-car" style proxies. It will 174 // default to 127.0.0.1 if the proxy is a "side-car" (DestinationServiceID is 175 // set) but otherwise will be ignored. 176 LocalServiceAddress string `json:",omitempty" alias:"local_service_address"` 177 178 // LocalServicePort is the port of the local service instance. It is optional 179 // and should only be specified for "side-car" style proxies. It will default 180 // to the registered port for the instance if the proxy is a "side-car" 181 // (DestinationServiceID is set) but otherwise will be ignored. 182 LocalServicePort int `json:",omitempty" alias:"local_service_port"` 183 184 // LocalServiceSocketPath is the socket of the local service instance. It is optional 185 // and should only be specified for "side-car" style proxies. 186 LocalServiceSocketPath string `json:",omitempty" alias:"local_service_socket_path"` 187 188 // Mode represents how the proxy's inbound and upstream listeners are dialed. 189 Mode ProxyMode 190 191 // Config is the arbitrary configuration data provided with the proxy 192 // registration. 193 Config map[string]interface{} `json:",omitempty" bexpr:"-"` 194 195 // Upstreams describes any upstream dependencies the proxy instance should 196 // setup. 197 Upstreams Upstreams `json:",omitempty"` 198 199 // MeshGateway defines the mesh gateway configuration for this upstream 200 MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` 201 202 // Expose defines whether checks or paths are exposed through the proxy 203 Expose ExposeConfig `json:",omitempty"` 204 205 // TransparentProxy defines configuration for when the proxy is in 206 // transparent mode. 207 TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` 208} 209 210func (t *ConnectProxyConfig) UnmarshalJSON(data []byte) (err error) { 211 type Alias ConnectProxyConfig 212 aux := &struct { 213 DestinationServiceNameSnake string `json:"destination_service_name"` 214 DestinationServiceIDSnake string `json:"destination_service_id"` 215 LocalServiceAddressSnake string `json:"local_service_address"` 216 LocalServicePortSnake int `json:"local_service_port"` 217 LocalServiceSocketPathSnake string `json:"local_service_socket_path"` 218 MeshGatewaySnake MeshGatewayConfig `json:"mesh_gateway"` 219 TransparentProxySnake TransparentProxyConfig `json:"transparent_proxy"` 220 *Alias 221 }{ 222 Alias: (*Alias)(t), 223 } 224 if err = lib.UnmarshalJSON(data, &aux); err != nil { 225 return err 226 } 227 if t.DestinationServiceName == "" { 228 t.DestinationServiceName = aux.DestinationServiceNameSnake 229 } 230 if t.DestinationServiceID == "" { 231 t.DestinationServiceID = aux.DestinationServiceIDSnake 232 } 233 if t.LocalServiceAddress == "" { 234 t.LocalServiceAddress = aux.LocalServiceAddressSnake 235 } 236 if t.LocalServicePort == 0 { 237 t.LocalServicePort = aux.LocalServicePortSnake 238 } 239 if t.LocalServiceSocketPath == "" { 240 t.LocalServiceSocketPath = aux.LocalServiceSocketPathSnake 241 } 242 if t.MeshGateway.Mode == "" { 243 t.MeshGateway.Mode = aux.MeshGatewaySnake.Mode 244 } 245 if t.TransparentProxy.OutboundListenerPort == 0 { 246 t.TransparentProxy.OutboundListenerPort = aux.TransparentProxySnake.OutboundListenerPort 247 } 248 if !t.TransparentProxy.DialedDirectly { 249 t.TransparentProxy.DialedDirectly = aux.TransparentProxySnake.DialedDirectly 250 } 251 252 return nil 253 254} 255 256func (c *ConnectProxyConfig) MarshalJSON() ([]byte, error) { 257 type Alias ConnectProxyConfig 258 out := struct { 259 TransparentProxy *TransparentProxyConfig `json:",omitempty"` 260 Alias 261 }{ 262 Alias: (Alias)(*c), 263 } 264 265 proxyConfig, err := lib.MapWalk(c.Config) 266 if err != nil { 267 return nil, err 268 } 269 out.Alias.Config = proxyConfig 270 271 if !c.TransparentProxy.IsZero() { 272 out.TransparentProxy = &out.Alias.TransparentProxy 273 } 274 275 return json.Marshal(&out) 276} 277 278// ToAPI returns the api struct with the same fields. We have duplicates to 279// avoid the api package depending on this one which imports a ton of Consul's 280// core which you don't want if you are just trying to use our client in your 281// app. 282func (c *ConnectProxyConfig) ToAPI() *api.AgentServiceConnectProxyConfig { 283 return &api.AgentServiceConnectProxyConfig{ 284 DestinationServiceName: c.DestinationServiceName, 285 DestinationServiceID: c.DestinationServiceID, 286 LocalServiceAddress: c.LocalServiceAddress, 287 LocalServicePort: c.LocalServicePort, 288 LocalServiceSocketPath: c.LocalServiceSocketPath, 289 Mode: api.ProxyMode(c.Mode), 290 TransparentProxy: c.TransparentProxy.ToAPI(), 291 Config: c.Config, 292 Upstreams: c.Upstreams.ToAPI(), 293 MeshGateway: c.MeshGateway.ToAPI(), 294 Expose: c.Expose.ToAPI(), 295 } 296} 297 298const ( 299 UpstreamDestTypeService = "service" 300 UpstreamDestTypePreparedQuery = "prepared_query" 301) 302 303// Upstreams is a list of upstreams. Aliased to allow ToAPI method. 304type Upstreams []Upstream 305 306// ToAPI returns the api structs with the same fields. We have duplicates to 307// avoid the api package depending on this one which imports a ton of Consul's 308// core which you don't want if you are just trying to use our client in your 309// app. 310func (us Upstreams) ToAPI() []api.Upstream { 311 a := make([]api.Upstream, len(us)) 312 for i, u := range us { 313 a[i] = u.ToAPI() 314 } 315 return a 316} 317 318func (us Upstreams) ToMap() map[string]*Upstream { 319 upstreamMap := make(map[string]*Upstream) 320 321 for i := range us { 322 upstreamMap[us[i].Identifier()] = &us[i] 323 } 324 return upstreamMap 325} 326 327// UpstreamsFromAPI is a helper for converting api.Upstream to Upstream. 328func UpstreamsFromAPI(us []api.Upstream) Upstreams { 329 a := make([]Upstream, len(us)) 330 for i, u := range us { 331 a[i] = UpstreamFromAPI(u) 332 } 333 return a 334} 335 336// Upstream represents a single upstream dependency for a service or proxy. It 337// describes the mechanism used to discover instances to communicate with (the 338// Target) as well as any potential client configuration that may be useful such 339// as load balancer options, timeouts etc. 340type Upstream struct { 341 // Destination fields are the required ones for determining what this upstream 342 // points to. Depending on DestinationType some other fields below might 343 // further restrict the set of instances allowable. 344 // 345 // DestinationType would be better as an int constant but even with custom 346 // JSON marshallers it causes havoc with all the mapstructure mangling we do 347 // on service definitions in various places. 348 DestinationType string `alias:"destination_type"` 349 DestinationNamespace string `json:",omitempty" alias:"destination_namespace"` 350 DestinationName string `alias:"destination_name"` 351 352 // Datacenter that the service discovery request should be run against. Note 353 // for prepared queries, the actual results might be from a different 354 // datacenter. 355 Datacenter string 356 357 // LocalBindAddress is the ip address a side-car proxy should listen on for 358 // traffic destined for this upstream service. Default if empty is 127.0.0.1. 359 LocalBindAddress string `json:",omitempty" alias:"local_bind_address"` 360 361 // LocalBindPort is the ip address a side-car proxy should listen on for traffic 362 // destined for this upstream service. Required. 363 LocalBindPort int `json:",omitempty" alias:"local_bind_port"` 364 365 // These are exclusive with LocalBindAddress/LocalBindPort 366 LocalBindSocketPath string `json:",omitempty" alias:"local_bind_socket_path"` 367 // This might be represented as an int, but because it's octal outputs can be a bit strange. 368 LocalBindSocketMode string `json:",omitempty" alias:"local_bind_socket_mode"` 369 370 // Config is an opaque config that is specific to the proxy process being run. 371 // It can be used to pass arbitrary configuration for this specific upstream 372 // to the proxy. 373 Config map[string]interface{} `json:",omitempty" bexpr:"-"` 374 375 // MeshGateway is the configuration for mesh gateway usage of this upstream 376 MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` 377 378 // IngressHosts are a list of hosts that should route to this upstream from 379 // an ingress gateway. This cannot and should not be set by a user, it is 380 // used internally to store the association of hosts to an upstream service. 381 IngressHosts []string `json:"-" bexpr:"-"` 382 383 // CentrallyConfigured indicates whether the upstream was defined in a proxy 384 // instance registration or whether it was generated from a config entry. 385 CentrallyConfigured bool `json:",omitempty" bexpr:"-"` 386} 387 388func (t *Upstream) UnmarshalJSON(data []byte) (err error) { 389 type Alias Upstream 390 aux := &struct { 391 DestinationTypeSnake string `json:"destination_type"` 392 DestinationNamespaceSnake string `json:"destination_namespace"` 393 DestinationNameSnake string `json:"destination_name"` 394 395 LocalBindAddressSnake string `json:"local_bind_address"` 396 LocalBindPortSnake int `json:"local_bind_port"` 397 398 LocalBindSocketPathSnake string `json:"local_bind_socket_path"` 399 LocalBindSocketModeSnake string `json:"local_bind_socket_mode"` 400 401 MeshGatewaySnake MeshGatewayConfig `json:"mesh_gateway"` 402 403 *Alias 404 }{ 405 Alias: (*Alias)(t), 406 } 407 if err = lib.UnmarshalJSON(data, &aux); err != nil { 408 return err 409 } 410 if t.DestinationType == "" { 411 t.DestinationType = aux.DestinationTypeSnake 412 } 413 if t.DestinationNamespace == "" { 414 t.DestinationNamespace = aux.DestinationNamespaceSnake 415 } 416 if t.DestinationName == "" { 417 t.DestinationName = aux.DestinationNameSnake 418 } 419 if t.LocalBindAddress == "" { 420 t.LocalBindAddress = aux.LocalBindAddressSnake 421 } 422 if t.LocalBindPort == 0 { 423 t.LocalBindPort = aux.LocalBindPortSnake 424 } 425 if t.LocalBindSocketPath == "" { 426 t.LocalBindSocketPath = aux.LocalBindSocketPathSnake 427 } 428 if t.LocalBindSocketMode == "" { 429 t.LocalBindSocketMode = aux.LocalBindSocketModeSnake 430 } 431 if t.MeshGateway.Mode == "" { 432 t.MeshGateway.Mode = aux.MeshGatewaySnake.Mode 433 } 434 435 return nil 436} 437 438// Validate sanity checks the struct is valid 439func (u *Upstream) Validate() error { 440 switch u.DestinationType { 441 case UpstreamDestTypePreparedQuery: 442 case UpstreamDestTypeService, "": 443 default: 444 return fmt.Errorf("unknown upstream destination type: %q", u.DestinationType) 445 } 446 447 if u.DestinationName == "" { 448 return fmt.Errorf("upstream destination name cannot be empty") 449 } 450 if u.DestinationName == WildcardSpecifier && !u.CentrallyConfigured { 451 return fmt.Errorf("upstream destination name cannot be a wildcard") 452 } 453 454 if u.LocalBindPort == 0 && u.LocalBindSocketPath == "" && !u.CentrallyConfigured { 455 return fmt.Errorf("upstream local bind port or local socket path must be defined and nonzero") 456 } 457 if u.LocalBindPort != 0 && u.LocalBindSocketPath != "" && !u.CentrallyConfigured { 458 return fmt.Errorf("only one of upstream local bind port or local socket path can be defined and nonzero") 459 } 460 461 return nil 462} 463 464// ToAPI returns the api structs with the same fields. We have duplicates to 465// avoid the api package depending on this one which imports a ton of Consul's 466// core which you don't want if you are just trying to use our client in your 467// app. 468func (u *Upstream) ToAPI() api.Upstream { 469 return api.Upstream{ 470 DestinationType: api.UpstreamDestType(u.DestinationType), 471 DestinationNamespace: u.DestinationNamespace, 472 DestinationName: u.DestinationName, 473 Datacenter: u.Datacenter, 474 LocalBindAddress: u.LocalBindAddress, 475 LocalBindPort: u.LocalBindPort, 476 LocalBindSocketPath: u.LocalBindSocketPath, 477 LocalBindSocketMode: u.LocalBindSocketMode, 478 Config: u.Config, 479 MeshGateway: u.MeshGateway.ToAPI(), 480 } 481} 482 483// ToKey returns a value-type representation that uniquely identifies the 484// upstream in a canonical way. Set and unset values are deliberately handled 485// differently. 486// 487// These fields should be user-specificed explicit values and not inferred 488// values. 489func (u *Upstream) ToKey() UpstreamKey { 490 return UpstreamKey{ 491 DestinationType: u.DestinationType, 492 DestinationNamespace: u.DestinationNamespace, 493 DestinationName: u.DestinationName, 494 Datacenter: u.Datacenter, 495 } 496} 497 498func (u Upstream) HasLocalPortOrSocket() bool { 499 return (u.LocalBindPort != 0 || u.LocalBindSocketPath != "") 500} 501 502func (u Upstream) UpstreamIsUnixSocket() bool { 503 return (u.LocalBindPort == 0 && u.LocalBindAddress == "" && u.LocalBindSocketPath != "") 504} 505 506func (u Upstream) UpstreamAddressToString() string { 507 if u.UpstreamIsUnixSocket() { 508 return u.LocalBindSocketPath 509 } 510 511 addr := u.LocalBindAddress 512 if addr == "" { 513 addr = "127.0.0.1" 514 } 515 return net.JoinHostPort(addr, fmt.Sprintf("%d", u.LocalBindPort)) 516} 517 518type UpstreamKey struct { 519 DestinationType string 520 DestinationName string 521 DestinationNamespace string 522 Datacenter string 523} 524 525func (k UpstreamKey) String() string { 526 return fmt.Sprintf( 527 "[type=%q, name=%q, namespace=%q, datacenter=%q]", 528 k.DestinationType, 529 k.DestinationName, 530 k.DestinationNamespace, 531 k.Datacenter, 532 ) 533} 534 535// String implements Stringer by returning the Identifier. 536func (u *Upstream) String() string { 537 return u.Identifier() 538} 539 540// UpstreamFromAPI is a helper for converting api.Upstream to Upstream. 541func UpstreamFromAPI(u api.Upstream) Upstream { 542 return Upstream{ 543 DestinationType: string(u.DestinationType), 544 DestinationNamespace: u.DestinationNamespace, 545 DestinationName: u.DestinationName, 546 Datacenter: u.Datacenter, 547 LocalBindAddress: u.LocalBindAddress, 548 LocalBindPort: u.LocalBindPort, 549 LocalBindSocketPath: u.LocalBindSocketPath, 550 LocalBindSocketMode: u.LocalBindSocketMode, 551 Config: u.Config, 552 } 553} 554 555// ExposeConfig describes HTTP paths to expose through Envoy outside of Connect. 556// Users can expose individual paths and/or all HTTP/GRPC paths for checks. 557type ExposeConfig struct { 558 // Checks defines whether paths associated with Consul checks will be exposed. 559 // This flag triggers exposing all HTTP and GRPC check paths registered for the service. 560 Checks bool `json:",omitempty"` 561 562 // Paths is the list of paths exposed through the proxy. 563 Paths []ExposePath `json:",omitempty"` 564} 565 566func (e ExposeConfig) Clone() ExposeConfig { 567 e2 := e 568 if len(e.Paths) > 0 { 569 e2.Paths = make([]ExposePath, 0, len(e.Paths)) 570 for _, p := range e.Paths { 571 e2.Paths = append(e2.Paths, p) 572 } 573 } 574 return e2 575} 576 577type ExposePath struct { 578 // ListenerPort defines the port of the proxy's listener for exposed paths. 579 ListenerPort int `json:",omitempty" alias:"listener_port"` 580 581 // Path is the path to expose through the proxy, ie. "/metrics." 582 Path string `json:",omitempty"` 583 584 // LocalPathPort is the port that the service is listening on for the given path. 585 LocalPathPort int `json:",omitempty" alias:"local_path_port"` 586 587 // Protocol describes the upstream's service protocol. 588 // Valid values are "http" and "http2", defaults to "http" 589 Protocol string `json:",omitempty"` 590 591 // ParsedFromCheck is set if this path was parsed from a registered check 592 ParsedFromCheck bool `json:",omitempty" alias:"parsed_from_check"` 593} 594 595func (t *ExposePath) UnmarshalJSON(data []byte) (err error) { 596 type Alias ExposePath 597 aux := &struct { 598 ListenerPortSnake int `json:"listener_port"` 599 LocalPathPortSnake int `json:"local_path_port"` 600 ParsedFromCheckSnake bool `json:"parsed_from_check"` 601 602 *Alias 603 }{ 604 Alias: (*Alias)(t), 605 } 606 if err = lib.UnmarshalJSON(data, &aux); err != nil { 607 return err 608 } 609 if t.LocalPathPort == 0 { 610 t.LocalPathPort = aux.LocalPathPortSnake 611 } 612 if t.ListenerPort == 0 { 613 t.ListenerPort = aux.ListenerPortSnake 614 } 615 if aux.ParsedFromCheckSnake { 616 t.ParsedFromCheck = true 617 } 618 619 return nil 620} 621 622func (e *ExposeConfig) ToAPI() api.ExposeConfig { 623 paths := make([]api.ExposePath, 0) 624 for _, p := range e.Paths { 625 paths = append(paths, p.ToAPI()) 626 } 627 if e.Paths == nil { 628 paths = nil 629 } 630 631 return api.ExposeConfig{ 632 Checks: e.Checks, 633 Paths: paths, 634 } 635} 636 637func (p *ExposePath) ToAPI() api.ExposePath { 638 return api.ExposePath{ 639 ListenerPort: p.ListenerPort, 640 Path: p.Path, 641 LocalPathPort: p.LocalPathPort, 642 Protocol: p.Protocol, 643 ParsedFromCheck: p.ParsedFromCheck, 644 } 645} 646 647// Finalize validates ExposeConfig and sets default values 648func (e *ExposeConfig) Finalize() { 649 for i := 0; i < len(e.Paths); i++ { 650 path := &e.Paths[i] 651 652 if path.Protocol == "" { 653 path.Protocol = defaultExposeProtocol 654 } 655 } 656} 657