package consul import ( "fmt" "net" "strconv" "github.com/hashicorp/consul/api" "github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/nomad/structs" ) // newConnect creates a new Consul AgentServiceConnect struct based on a Nomad // Connect struct. If the nomad Connect struct is nil, nil will be returned to // disable Connect for this service. func newConnect(serviceId string, serviceName string, nc *structs.ConsulConnect, networks structs.Networks, ports structs.AllocatedPorts) (*api.AgentServiceConnect, error) { switch { case nc == nil: // no connect stanza means there is no connect service to register return nil, nil case nc.IsGateway(): // gateway settings are configured on the service block on the consul side return nil, nil case nc.IsNative(): // the service is connect native return &api.AgentServiceConnect{Native: true}, nil case nc.HasSidecar(): // must register the sidecar for this service if nc.SidecarService.Port == "" { nc.SidecarService.Port = fmt.Sprintf("%s-%s", structs.ConnectProxyPrefix, serviceName) } sidecarReg, err := connectSidecarRegistration(serviceId, nc.SidecarService, networks, ports) if err != nil { return nil, err } return &api.AgentServiceConnect{SidecarService: sidecarReg}, nil default: // a non-nil but empty connect block makes no sense return nil, fmt.Errorf("Connect configuration empty for service %s", serviceName) } } // newConnectGateway creates a new Consul AgentServiceConnectProxyConfig struct based on // a Nomad Connect struct. If the Nomad Connect struct does not contain a gateway, nil // will be returned as this service is not a gateway. func newConnectGateway(serviceName string, connect *structs.ConsulConnect) *api.AgentServiceConnectProxyConfig { if !connect.IsGateway() { return nil } var envoyConfig map[string]interface{} // Populate the envoy configuration from the gateway.proxy stanza, if // such configuration is provided. if proxy := connect.Gateway.Proxy; proxy != nil { envoyConfig = make(map[string]interface{}) if len(proxy.EnvoyGatewayBindAddresses) > 0 { envoyConfig["envoy_gateway_bind_addresses"] = proxy.EnvoyGatewayBindAddresses } if proxy.EnvoyGatewayNoDefaultBind { envoyConfig["envoy_gateway_no_default_bind"] = true } if proxy.EnvoyGatewayBindTaggedAddresses { envoyConfig["envoy_gateway_bind_tagged_addresses"] = true } if proxy.EnvoyDNSDiscoveryType != "" { envoyConfig["envoy_dns_discovery_type"] = proxy.EnvoyDNSDiscoveryType } if proxy.ConnectTimeout != nil { envoyConfig["connect_timeout_ms"] = proxy.ConnectTimeout.Milliseconds() } if len(proxy.Config) > 0 { for k, v := range proxy.Config { envoyConfig[k] = v } } } return &api.AgentServiceConnectProxyConfig{Config: envoyConfig} } func connectSidecarRegistration(serviceId string, css *structs.ConsulSidecarService, networks structs.Networks, ports structs.AllocatedPorts) (*api.AgentServiceRegistration, error) { if css == nil { // no sidecar stanza means there is no sidecar service to register return nil, nil } cMapping, err := connectPort(css.Port, networks, ports) if err != nil { return nil, err } proxy, err := connectSidecarProxy(css.Proxy, cMapping.To, networks) if err != nil { return nil, err } return &api.AgentServiceRegistration{ Tags: helper.CopySliceString(css.Tags), Port: cMapping.Value, Address: cMapping.HostIP, Proxy: proxy, Checks: api.AgentServiceChecks{ { Name: "Connect Sidecar Listening", TCP: net.JoinHostPort(cMapping.HostIP, strconv.Itoa(cMapping.Value)), Interval: "10s", }, { Name: "Connect Sidecar Aliasing " + serviceId, AliasService: serviceId, }, }, }, nil } func connectSidecarProxy(proxy *structs.ConsulProxy, cPort int, networks structs.Networks) (*api.AgentServiceConnectProxyConfig, error) { if proxy == nil { proxy = new(structs.ConsulProxy) } expose, err := connectProxyExpose(proxy.Expose, networks) if err != nil { return nil, err } return &api.AgentServiceConnectProxyConfig{ LocalServiceAddress: proxy.LocalServiceAddress, LocalServicePort: proxy.LocalServicePort, Config: connectProxyConfig(proxy.Config, cPort), Upstreams: connectUpstreams(proxy.Upstreams), Expose: expose, }, nil } func connectProxyExpose(expose *structs.ConsulExposeConfig, networks structs.Networks) (api.ExposeConfig, error) { if expose == nil { return api.ExposeConfig{}, nil } paths, err := connectProxyExposePaths(expose.Paths, networks) if err != nil { return api.ExposeConfig{}, err } return api.ExposeConfig{ Checks: false, Paths: paths, }, nil } func connectProxyExposePaths(in []structs.ConsulExposePath, networks structs.Networks) ([]api.ExposePath, error) { if len(in) == 0 { return nil, nil } paths := make([]api.ExposePath, len(in)) for i, path := range in { if _, exposedPort, err := connectExposePathPort(path.ListenerPort, networks); err != nil { return nil, err } else { paths[i] = api.ExposePath{ ListenerPort: exposedPort, Path: path.Path, LocalPathPort: path.LocalPathPort, Protocol: path.Protocol, ParsedFromCheck: false, } } } return paths, nil } func connectUpstreams(in []structs.ConsulUpstream) []api.Upstream { if len(in) == 0 { return nil } upstreams := make([]api.Upstream, len(in)) for i, upstream := range in { upstreams[i] = api.Upstream{ DestinationName: upstream.DestinationName, LocalBindPort: upstream.LocalBindPort, Datacenter: upstream.Datacenter, } } return upstreams } func connectProxyConfig(cfg map[string]interface{}, port int) map[string]interface{} { if cfg == nil { cfg = make(map[string]interface{}) } cfg["bind_address"] = "0.0.0.0" cfg["bind_port"] = port return cfg } func connectNetworkInvariants(networks structs.Networks) error { if n := len(networks); n != 1 { return fmt.Errorf("Connect only supported with exactly 1 network (found %d)", n) } return nil } // connectPort returns the network and port for the Connect proxy sidecar // defined for this service. An error is returned if the network and port // cannot be determined. func connectPort(portLabel string, networks structs.Networks, ports structs.AllocatedPorts) (structs.AllocatedPortMapping, error) { if err := connectNetworkInvariants(networks); err != nil { return structs.AllocatedPortMapping{}, err } mapping, ok := ports.Get(portLabel) if !ok { mapping = networks.Port(portLabel) if mapping.Value > 0 { return mapping, nil } return structs.AllocatedPortMapping{}, fmt.Errorf("No port of label %q defined", portLabel) } return mapping, nil } // connectExposePathPort returns the port for the exposed path for the exposed // proxy path. func connectExposePathPort(portLabel string, networks structs.Networks) (string, int, error) { if err := connectNetworkInvariants(networks); err != nil { return "", 0, err } mapping := networks.Port(portLabel) if mapping.Value == 0 { return "", 0, fmt.Errorf("No port of label %q defined", portLabel) } return mapping.HostIP, mapping.Value, nil }