// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package api import ( "fmt" "net/url" "time" ) // ServiceRegistration is an instance of a single allocation advertising itself // as a named service with a specific address. Each registration is constructed // from the job specification Service block. Whether the service is registered // within Nomad, and therefore generates a ServiceRegistration is controlled by // the Service.Provider parameter. type ServiceRegistration struct { // ID is the unique identifier for this registration. It currently follows // the Consul service registration format to provide consistency between // the two solutions. ID string // ServiceName is the human friendly identifier for this service // registration. ServiceName string // Namespace represents the namespace within which this service is // registered. Namespace string // NodeID is Node.ID on which this service registration is currently // running. NodeID string // Datacenter is the DC identifier of the node as identified by // Node.Datacenter. Datacenter string // JobID is Job.ID and represents the job which contained the service block // which resulted in this service registration. JobID string // AllocID is Allocation.ID and represents the allocation within which this // service is running. AllocID string // Tags are determined from either Service.Tags or Service.CanaryTags and // help identify this service. Tags can also be used to perform lookups of // services depending on their state and role. Tags []string // Address is the IP address of this service registration. This information // comes from the client and is not guaranteed to be routable; this depends // on cluster network topology. Address string // Port is the port number on which this service registration is bound. It // is determined by a combination of factors on the client. Port int CreateIndex uint64 ModifyIndex uint64 } // ServiceRegistrationListStub represents all service registrations held within a // single namespace. type ServiceRegistrationListStub struct { // Namespace details the namespace in which these services have been // registered. Namespace string // Services is a list of services found within the namespace. Services []*ServiceRegistrationStub } // ServiceRegistrationStub is the stub object describing an individual // namespaced service. The object is built in a manner which would allow us to // add additional fields in the future, if we wanted. type ServiceRegistrationStub struct { // ServiceName is the human friendly name for this service as specified // within Service.Name. ServiceName string // Tags is a list of unique tags found for this service. The list is // de-duplicated automatically by Nomad. Tags []string } // Services is used to query the service endpoints. type Services struct { client *Client } // Services returns a new handle on the services endpoints. func (c *Client) Services() *Services { return &Services{client: c} } // List can be used to list all service registrations currently stored within // the target namespace. It returns a stub response object. func (s *Services) List(q *QueryOptions) ([]*ServiceRegistrationListStub, *QueryMeta, error) { var resp []*ServiceRegistrationListStub qm, err := s.client.query("/v1/services", &resp, q) if err != nil { return nil, qm, err } return resp, qm, nil } // Get is used to return a list of service registrations whose name matches the // specified parameter. func (s *Services) Get(serviceName string, q *QueryOptions) ([]*ServiceRegistration, *QueryMeta, error) { var resp []*ServiceRegistration qm, err := s.client.query("/v1/service/"+url.PathEscape(serviceName), &resp, q) if err != nil { return nil, qm, err } return resp, qm, nil } // Delete can be used to delete an individual service registration as defined // by its service name and service ID. func (s *Services) Delete(serviceName, serviceID string, q *WriteOptions) (*WriteMeta, error) { path := fmt.Sprintf("/v1/service/%s/%s", url.PathEscape(serviceName), url.PathEscape(serviceID)) wm, err := s.client.delete(path, nil, nil, q) if err != nil { return nil, err } return wm, nil } // CheckRestart describes if and when a task should be restarted based on // failing health checks. type CheckRestart struct { Limit int `mapstructure:"limit" hcl:"limit,optional"` Grace *time.Duration `mapstructure:"grace" hcl:"grace,optional"` IgnoreWarnings bool `mapstructure:"ignore_warnings" hcl:"ignore_warnings,optional"` } // Canonicalize CheckRestart fields if not nil. func (c *CheckRestart) Canonicalize() { if c == nil { return } if c.Grace == nil { c.Grace = pointerOf(1 * time.Second) } } // Copy returns a copy of CheckRestart or nil if unset. func (c *CheckRestart) Copy() *CheckRestart { if c == nil { return nil } nc := new(CheckRestart) nc.Limit = c.Limit if c.Grace != nil { g := *c.Grace nc.Grace = &g } nc.IgnoreWarnings = c.IgnoreWarnings return nc } // Merge values from other CheckRestart over default values on this // CheckRestart and return merged copy. func (c *CheckRestart) Merge(o *CheckRestart) *CheckRestart { if c == nil { // Just return other return o } nc := c.Copy() if o == nil { // Nothing to merge return nc } if o.Limit > 0 { nc.Limit = o.Limit } if o.Grace != nil { nc.Grace = o.Grace } if o.IgnoreWarnings { nc.IgnoreWarnings = o.IgnoreWarnings } return nc } // ServiceCheck represents a Nomad job-submitters view of a Consul service health check. type ServiceCheck struct { Name string `hcl:"name,optional"` Type string `hcl:"type,optional"` Command string `hcl:"command,optional"` Args []string `hcl:"args,optional"` Path string `hcl:"path,optional"` Protocol string `hcl:"protocol,optional"` PortLabel string `mapstructure:"port" hcl:"port,optional"` Expose bool `hcl:"expose,optional"` AddressMode string `mapstructure:"address_mode" hcl:"address_mode,optional"` Advertise string `hcl:"advertise,optional"` Interval time.Duration `hcl:"interval,optional"` Timeout time.Duration `hcl:"timeout,optional"` InitialStatus string `mapstructure:"initial_status" hcl:"initial_status,optional"` TLSServerName string `mapstructure:"tls_server_name" hcl:"tls_server_name,optional"` TLSSkipVerify bool `mapstructure:"tls_skip_verify" hcl:"tls_skip_verify,optional"` Header map[string][]string `hcl:"header,block"` Method string `hcl:"method,optional"` CheckRestart *CheckRestart `mapstructure:"check_restart" hcl:"check_restart,block"` GRPCService string `mapstructure:"grpc_service" hcl:"grpc_service,optional"` GRPCUseTLS bool `mapstructure:"grpc_use_tls" hcl:"grpc_use_tls,optional"` TaskName string `mapstructure:"task" hcl:"task,optional"` SuccessBeforePassing int `mapstructure:"success_before_passing" hcl:"success_before_passing,optional"` FailuresBeforeCritical int `mapstructure:"failures_before_critical" hcl:"failures_before_critical,optional"` Body string `hcl:"body,optional"` OnUpdate string `mapstructure:"on_update" hcl:"on_update,optional"` } // Service represents a Nomad job-submitters view of a Consul or Nomad service. type Service struct { Name string `hcl:"name,optional"` Tags []string `hcl:"tags,optional"` CanaryTags []string `mapstructure:"canary_tags" hcl:"canary_tags,optional"` EnableTagOverride bool `mapstructure:"enable_tag_override" hcl:"enable_tag_override,optional"` PortLabel string `mapstructure:"port" hcl:"port,optional"` AddressMode string `mapstructure:"address_mode" hcl:"address_mode,optional"` Address string `hcl:"address,optional"` Checks []ServiceCheck `hcl:"check,block"` CheckRestart *CheckRestart `mapstructure:"check_restart" hcl:"check_restart,block"` Connect *ConsulConnect `hcl:"connect,block"` Meta map[string]string `hcl:"meta,block"` CanaryMeta map[string]string `hcl:"canary_meta,block"` TaggedAddresses map[string]string `hcl:"tagged_addresses,block"` TaskName string `mapstructure:"task" hcl:"task,optional"` OnUpdate string `mapstructure:"on_update" hcl:"on_update,optional"` // Provider defines which backend system provides the service registration, // either "consul" (default) or "nomad". Provider string `hcl:"provider,optional"` } const ( OnUpdateRequireHealthy = "require_healthy" OnUpdateIgnoreWarn = "ignore_warnings" OnUpdateIgnore = "ignore" // ServiceProviderConsul is the default provider for services when no // parameter is set. ServiceProviderConsul = "consul" ) // Canonicalize the Service by ensuring its name and address mode are set. Task // will be nil for group services. func (s *Service) Canonicalize(t *Task, tg *TaskGroup, job *Job) { if s.Name == "" { if t != nil { s.Name = fmt.Sprintf("%s-%s-%s", *job.Name, *tg.Name, t.Name) } else { s.Name = fmt.Sprintf("%s-%s", *job.Name, *tg.Name) } } // Default to AddressModeAuto if s.AddressMode == "" { s.AddressMode = "auto" } // Default to OnUpdateRequireHealthy if s.OnUpdate == "" { s.OnUpdate = OnUpdateRequireHealthy } // Default the service provider. if s.Provider == "" { s.Provider = ServiceProviderConsul } if len(s.Meta) == 0 { s.Meta = nil } if len(s.CanaryMeta) == 0 { s.CanaryMeta = nil } if len(s.TaggedAddresses) == 0 { s.TaggedAddresses = nil } s.Connect.Canonicalize() // Canonicalize CheckRestart on Checks and merge Service.CheckRestart // into each check. for i, check := range s.Checks { s.Checks[i].CheckRestart = s.CheckRestart.Merge(check.CheckRestart) s.Checks[i].CheckRestart.Canonicalize() if s.Checks[i].SuccessBeforePassing < 0 { s.Checks[i].SuccessBeforePassing = 0 } if s.Checks[i].FailuresBeforeCritical < 0 { s.Checks[i].FailuresBeforeCritical = 0 } // Inhert Service if s.Checks[i].OnUpdate == "" { s.Checks[i].OnUpdate = s.OnUpdate } } }