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