// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package api import ( "encoding/json" "fmt" "time" ) // DiscoveryChain can be used to query the discovery-chain endpoints type DiscoveryChain struct { c *Client } // DiscoveryChain returns a handle to the discovery-chain endpoints func (c *Client) DiscoveryChain() *DiscoveryChain { return &DiscoveryChain{c} } func (d *DiscoveryChain) Get(name string, opts *DiscoveryChainOptions, q *QueryOptions) (*DiscoveryChainResponse, *QueryMeta, error) { if name == "" { return nil, nil, fmt.Errorf("Name parameter must not be empty") } method := "GET" if opts != nil && opts.requiresPOST() { method = "POST" } r := d.c.newRequest(method, fmt.Sprintf("/v1/discovery-chain/%s", name)) r.setQueryOptions(q) if opts != nil { if opts.EvaluateInDatacenter != "" { r.params.Set("compile-dc", opts.EvaluateInDatacenter) } } if method == "POST" { r.obj = opts } rtt, resp, err := d.c.doRequest(r) if err != nil { return nil, nil, err } defer closeResponseBody(resp) if err := requireOK(resp); err != nil { return nil, nil, err } qm := &QueryMeta{} parseQueryMeta(resp, qm) qm.RequestTime = rtt var out DiscoveryChainResponse if err := decodeBody(resp, &out); err != nil { return nil, nil, err } return &out, qm, nil } type DiscoveryChainOptions struct { EvaluateInDatacenter string `json:"-"` // OverrideMeshGateway allows for the mesh gateway setting to be overridden // for any resolver in the compiled chain. OverrideMeshGateway MeshGatewayConfig `json:",omitempty"` // OverrideProtocol allows for the final protocol for the chain to be // altered. // // - If the chain ordinarily would be TCP and an L7 protocol is passed here // the chain will not include Routers or Splitters. // // - If the chain ordinarily would be L7 and TCP is passed here the chain // will not include Routers or Splitters. OverrideProtocol string `json:",omitempty"` // OverrideConnectTimeout allows for the ConnectTimeout setting to be // overridden for any resolver in the compiled chain. OverrideConnectTimeout time.Duration `json:",omitempty"` } func (o *DiscoveryChainOptions) requiresPOST() bool { if o == nil { return false } return o.OverrideMeshGateway.Mode != "" || o.OverrideProtocol != "" || o.OverrideConnectTimeout != 0 } type DiscoveryChainResponse struct { Chain *CompiledDiscoveryChain } type CompiledDiscoveryChain struct { ServiceName string Namespace string Datacenter string // CustomizationHash is a unique hash of any data that affects the // compilation of the discovery chain other than config entries or the // name/namespace/datacenter evaluation criteria. // // If set, this value should be used to prefix/suffix any generated load // balancer data plane objects to avoid sharing customized and // non-customized versions. CustomizationHash string // Default indicates if this discovery chain is based on no // service-resolver, service-splitter, or service-router config entries. Default bool // Protocol is the overall protocol shared by everything in the chain. Protocol string // ServiceMeta is the metadata from the underlying service-defaults config // entry for the service named ServiceName. ServiceMeta map[string]string // StartNode is the first key into the Nodes map that should be followed // when walking the discovery chain. StartNode string // Nodes contains all nodes available for traversal in the chain keyed by a // unique name. You can walk this by starting with StartNode. // // NOTE: The names should be treated as opaque values and are only // guaranteed to be consistent within a single compilation. Nodes map[string]*DiscoveryGraphNode // Targets is a list of all targets used in this chain. // // NOTE: The names should be treated as opaque values and are only // guaranteed to be consistent within a single compilation. Targets map[string]*DiscoveryTarget } const ( DiscoveryGraphNodeTypeRouter = "router" DiscoveryGraphNodeTypeSplitter = "splitter" DiscoveryGraphNodeTypeResolver = "resolver" ) // DiscoveryGraphNode is a single node in the compiled discovery chain. type DiscoveryGraphNode struct { Type string Name string // this is NOT necessarily a service // fields for Type==router Routes []*DiscoveryRoute // fields for Type==splitter Splits []*DiscoverySplit // fields for Type==resolver Resolver *DiscoveryResolver // shared by Type==resolver || Type==splitter LoadBalancer *LoadBalancer `json:",omitempty"` } // compiled form of ServiceRoute type DiscoveryRoute struct { Definition *ServiceRoute NextNode string } // compiled form of ServiceSplit type DiscoverySplit struct { Weight float32 NextNode string } // compiled form of ServiceResolverConfigEntry type DiscoveryResolver struct { Default bool ConnectTimeout time.Duration Target string Failover *DiscoveryFailover } func (r *DiscoveryResolver) MarshalJSON() ([]byte, error) { type Alias DiscoveryResolver exported := &struct { ConnectTimeout string `json:",omitempty"` *Alias }{ ConnectTimeout: r.ConnectTimeout.String(), Alias: (*Alias)(r), } if r.ConnectTimeout == 0 { exported.ConnectTimeout = "" } return json.Marshal(exported) } func (r *DiscoveryResolver) UnmarshalJSON(data []byte) error { type Alias DiscoveryResolver aux := &struct { ConnectTimeout string *Alias }{ Alias: (*Alias)(r), } if err := json.Unmarshal(data, &aux); err != nil { return err } var err error if aux.ConnectTimeout != "" { if r.ConnectTimeout, err = time.ParseDuration(aux.ConnectTimeout); err != nil { return err } } return nil } // compiled form of ServiceResolverFailover type DiscoveryFailover struct { Targets []string Policy ServiceResolverFailoverPolicy `json:",omitempty"` } // DiscoveryTarget represents all of the inputs necessary to use a resolver // config entry to execute a catalog query to generate a list of service // instances during discovery. type DiscoveryTarget struct { ID string Service string ServiceSubset string Namespace string Datacenter string MeshGateway MeshGatewayConfig Subset ServiceResolverSubset ConnectTimeout time.Duration External bool SNI string Name string } func (t *DiscoveryTarget) MarshalJSON() ([]byte, error) { type Alias DiscoveryTarget exported := &struct { ConnectTimeout string `json:",omitempty"` *Alias }{ ConnectTimeout: t.ConnectTimeout.String(), Alias: (*Alias)(t), } if t.ConnectTimeout == 0 { exported.ConnectTimeout = "" } return json.Marshal(exported) } func (t *DiscoveryTarget) UnmarshalJSON(data []byte) error { type Alias DiscoveryTarget aux := &struct { ConnectTimeout string *Alias }{ Alias: (*Alias)(t), } if err := json.Unmarshal(data, &aux); err != nil { return err } var err error if aux.ConnectTimeout != "" { if t.ConnectTimeout, err = time.ParseDuration(aux.ConnectTimeout); err != nil { return err } } return nil }