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