github.com/hashicorp/nomad/api@v0.0.0-20240306165712-3193ac204f65/services.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package api 5 6 import ( 7 "fmt" 8 "net/url" 9 "time" 10 ) 11 12 // ServiceRegistration is an instance of a single allocation advertising itself 13 // as a named service with a specific address. Each registration is constructed 14 // from the job specification Service block. Whether the service is registered 15 // within Nomad, and therefore generates a ServiceRegistration is controlled by 16 // the Service.Provider parameter. 17 type ServiceRegistration struct { 18 19 // ID is the unique identifier for this registration. It currently follows 20 // the Consul service registration format to provide consistency between 21 // the two solutions. 22 ID string 23 24 // ServiceName is the human friendly identifier for this service 25 // registration. 26 ServiceName string 27 28 // Namespace represents the namespace within which this service is 29 // registered. 30 Namespace string 31 32 // NodeID is Node.ID on which this service registration is currently 33 // running. 34 NodeID string 35 36 // Datacenter is the DC identifier of the node as identified by 37 // Node.Datacenter. 38 Datacenter string 39 40 // JobID is Job.ID and represents the job which contained the service block 41 // which resulted in this service registration. 42 JobID string 43 44 // AllocID is Allocation.ID and represents the allocation within which this 45 // service is running. 46 AllocID string 47 48 // Tags are determined from either Service.Tags or Service.CanaryTags and 49 // help identify this service. Tags can also be used to perform lookups of 50 // services depending on their state and role. 51 Tags []string 52 53 // Address is the IP address of this service registration. This information 54 // comes from the client and is not guaranteed to be routable; this depends 55 // on cluster network topology. 56 Address string 57 58 // Port is the port number on which this service registration is bound. It 59 // is determined by a combination of factors on the client. 60 Port int 61 62 CreateIndex uint64 63 ModifyIndex uint64 64 } 65 66 // ServiceRegistrationListStub represents all service registrations held within a 67 // single namespace. 68 type ServiceRegistrationListStub struct { 69 70 // Namespace details the namespace in which these services have been 71 // registered. 72 Namespace string 73 74 // Services is a list of services found within the namespace. 75 Services []*ServiceRegistrationStub 76 } 77 78 // ServiceRegistrationStub is the stub object describing an individual 79 // namespaced service. The object is built in a manner which would allow us to 80 // add additional fields in the future, if we wanted. 81 type ServiceRegistrationStub struct { 82 83 // ServiceName is the human friendly name for this service as specified 84 // within Service.Name. 85 ServiceName string 86 87 // Tags is a list of unique tags found for this service. The list is 88 // de-duplicated automatically by Nomad. 89 Tags []string 90 } 91 92 // Services is used to query the service endpoints. 93 type Services struct { 94 client *Client 95 } 96 97 // Services returns a new handle on the services endpoints. 98 func (c *Client) Services() *Services { 99 return &Services{client: c} 100 } 101 102 // List can be used to list all service registrations currently stored within 103 // the target namespace. It returns a stub response object. 104 func (s *Services) List(q *QueryOptions) ([]*ServiceRegistrationListStub, *QueryMeta, error) { 105 var resp []*ServiceRegistrationListStub 106 qm, err := s.client.query("/v1/services", &resp, q) 107 if err != nil { 108 return nil, qm, err 109 } 110 return resp, qm, nil 111 } 112 113 // Get is used to return a list of service registrations whose name matches the 114 // specified parameter. 115 func (s *Services) Get(serviceName string, q *QueryOptions) ([]*ServiceRegistration, *QueryMeta, error) { 116 var resp []*ServiceRegistration 117 qm, err := s.client.query("/v1/service/"+url.PathEscape(serviceName), &resp, q) 118 if err != nil { 119 return nil, qm, err 120 } 121 return resp, qm, nil 122 } 123 124 // Delete can be used to delete an individual service registration as defined 125 // by its service name and service ID. 126 func (s *Services) Delete(serviceName, serviceID string, q *WriteOptions) (*WriteMeta, error) { 127 path := fmt.Sprintf("/v1/service/%s/%s", url.PathEscape(serviceName), url.PathEscape(serviceID)) 128 wm, err := s.client.delete(path, nil, nil, q) 129 if err != nil { 130 return nil, err 131 } 132 return wm, nil 133 } 134 135 // CheckRestart describes if and when a task should be restarted based on 136 // failing health checks. 137 type CheckRestart struct { 138 Limit int `mapstructure:"limit" hcl:"limit,optional"` 139 Grace *time.Duration `mapstructure:"grace" hcl:"grace,optional"` 140 IgnoreWarnings bool `mapstructure:"ignore_warnings" hcl:"ignore_warnings,optional"` 141 } 142 143 // Canonicalize CheckRestart fields if not nil. 144 func (c *CheckRestart) Canonicalize() { 145 if c == nil { 146 return 147 } 148 149 if c.Grace == nil { 150 c.Grace = pointerOf(1 * time.Second) 151 } 152 } 153 154 // Copy returns a copy of CheckRestart or nil if unset. 155 func (c *CheckRestart) Copy() *CheckRestart { 156 if c == nil { 157 return nil 158 } 159 160 nc := new(CheckRestart) 161 nc.Limit = c.Limit 162 if c.Grace != nil { 163 g := *c.Grace 164 nc.Grace = &g 165 } 166 nc.IgnoreWarnings = c.IgnoreWarnings 167 return nc 168 } 169 170 // Merge values from other CheckRestart over default values on this 171 // CheckRestart and return merged copy. 172 func (c *CheckRestart) Merge(o *CheckRestart) *CheckRestart { 173 if c == nil { 174 // Just return other 175 return o 176 } 177 178 nc := c.Copy() 179 180 if o == nil { 181 // Nothing to merge 182 return nc 183 } 184 185 if o.Limit > 0 { 186 nc.Limit = o.Limit 187 } 188 189 if o.Grace != nil { 190 nc.Grace = o.Grace 191 } 192 193 if o.IgnoreWarnings { 194 nc.IgnoreWarnings = o.IgnoreWarnings 195 } 196 197 return nc 198 } 199 200 // ServiceCheck represents a Nomad job-submitters view of a Consul service health check. 201 type ServiceCheck struct { 202 Name string `hcl:"name,optional"` 203 Type string `hcl:"type,optional"` 204 Command string `hcl:"command,optional"` 205 Args []string `hcl:"args,optional"` 206 Path string `hcl:"path,optional"` 207 Protocol string `hcl:"protocol,optional"` 208 PortLabel string `mapstructure:"port" hcl:"port,optional"` 209 Expose bool `hcl:"expose,optional"` 210 AddressMode string `mapstructure:"address_mode" hcl:"address_mode,optional"` 211 Advertise string `hcl:"advertise,optional"` 212 Interval time.Duration `hcl:"interval,optional"` 213 Timeout time.Duration `hcl:"timeout,optional"` 214 InitialStatus string `mapstructure:"initial_status" hcl:"initial_status,optional"` 215 TLSServerName string `mapstructure:"tls_server_name" hcl:"tls_server_name,optional"` 216 TLSSkipVerify bool `mapstructure:"tls_skip_verify" hcl:"tls_skip_verify,optional"` 217 Header map[string][]string `hcl:"header,block"` 218 Method string `hcl:"method,optional"` 219 CheckRestart *CheckRestart `mapstructure:"check_restart" hcl:"check_restart,block"` 220 GRPCService string `mapstructure:"grpc_service" hcl:"grpc_service,optional"` 221 GRPCUseTLS bool `mapstructure:"grpc_use_tls" hcl:"grpc_use_tls,optional"` 222 TaskName string `mapstructure:"task" hcl:"task,optional"` 223 SuccessBeforePassing int `mapstructure:"success_before_passing" hcl:"success_before_passing,optional"` 224 FailuresBeforeCritical int `mapstructure:"failures_before_critical" hcl:"failures_before_critical,optional"` 225 FailuresBeforeWarning int `mapstructure:"failures_before_warning" hcl:"failures_before_warning,optional"` 226 Body string `hcl:"body,optional"` 227 OnUpdate string `mapstructure:"on_update" hcl:"on_update,optional"` 228 } 229 230 // Service represents a Nomad job-submitters view of a Consul or Nomad service. 231 type Service struct { 232 Name string `hcl:"name,optional"` 233 Tags []string `hcl:"tags,optional"` 234 CanaryTags []string `mapstructure:"canary_tags" hcl:"canary_tags,optional"` 235 EnableTagOverride bool `mapstructure:"enable_tag_override" hcl:"enable_tag_override,optional"` 236 PortLabel string `mapstructure:"port" hcl:"port,optional"` 237 AddressMode string `mapstructure:"address_mode" hcl:"address_mode,optional"` 238 Address string `hcl:"address,optional"` 239 Checks []ServiceCheck `hcl:"check,block"` 240 CheckRestart *CheckRestart `mapstructure:"check_restart" hcl:"check_restart,block"` 241 Connect *ConsulConnect `hcl:"connect,block"` 242 Meta map[string]string `hcl:"meta,block"` 243 CanaryMeta map[string]string `hcl:"canary_meta,block"` 244 TaggedAddresses map[string]string `hcl:"tagged_addresses,block"` 245 TaskName string `mapstructure:"task" hcl:"task,optional"` 246 OnUpdate string `mapstructure:"on_update" hcl:"on_update,optional"` 247 Identity *WorkloadIdentity `hcl:"identity,block"` 248 249 // Provider defines which backend system provides the service registration, 250 // either "consul" (default) or "nomad". 251 Provider string `hcl:"provider,optional"` 252 253 // Cluster is valid only for Nomad Enterprise with provider: consul 254 Cluster string `hcl:"cluster,optional"` 255 } 256 257 const ( 258 OnUpdateRequireHealthy = "require_healthy" 259 OnUpdateIgnoreWarn = "ignore_warnings" 260 OnUpdateIgnore = "ignore" 261 262 // ServiceProviderConsul is the default provider for services when no 263 // parameter is set. 264 ServiceProviderConsul = "consul" 265 ) 266 267 // Canonicalize the Service by ensuring its name and address mode are set. Task 268 // will be nil for group services. 269 func (s *Service) Canonicalize(t *Task, tg *TaskGroup, job *Job) { 270 if s.Name == "" { 271 if t != nil { 272 s.Name = fmt.Sprintf("%s-%s-%s", *job.Name, *tg.Name, t.Name) 273 } else { 274 s.Name = fmt.Sprintf("%s-%s", *job.Name, *tg.Name) 275 } 276 } 277 278 // Default to AddressModeAuto 279 if s.AddressMode == "" { 280 s.AddressMode = "auto" 281 } 282 283 // Default to OnUpdateRequireHealthy 284 if s.OnUpdate == "" { 285 s.OnUpdate = OnUpdateRequireHealthy 286 } 287 288 // Default the service provider. 289 if s.Provider == "" { 290 s.Provider = ServiceProviderConsul 291 } 292 if s.Cluster == "" { 293 s.Cluster = "default" 294 } 295 296 if len(s.Meta) == 0 { 297 s.Meta = nil 298 } 299 300 if len(s.CanaryMeta) == 0 { 301 s.CanaryMeta = nil 302 } 303 304 if len(s.TaggedAddresses) == 0 { 305 s.TaggedAddresses = nil 306 } 307 308 s.Connect.Canonicalize() 309 310 // Canonicalize CheckRestart on Checks and merge Service.CheckRestart 311 // into each check. 312 for i, check := range s.Checks { 313 s.Checks[i].CheckRestart = s.CheckRestart.Merge(check.CheckRestart) 314 s.Checks[i].CheckRestart.Canonicalize() 315 316 if s.Checks[i].SuccessBeforePassing < 0 { 317 s.Checks[i].SuccessBeforePassing = 0 318 } 319 320 if s.Checks[i].FailuresBeforeCritical < 0 { 321 s.Checks[i].FailuresBeforeCritical = 0 322 } 323 324 if s.Checks[i].FailuresBeforeWarning < 0 { 325 s.Checks[i].FailuresBeforeWarning = 0 326 } 327 328 // Inhert Service 329 if s.Checks[i].OnUpdate == "" { 330 s.Checks[i].OnUpdate = s.OnUpdate 331 } 332 } 333 }