1package structs
2
3import (
4	"crypto/sha1"
5	"fmt"
6	"io"
7	"net/url"
8	"reflect"
9	"regexp"
10	"sort"
11	"strings"
12	"time"
13
14	"github.com/hashicorp/consul/api"
15	multierror "github.com/hashicorp/go-multierror"
16	"github.com/hashicorp/nomad/helper"
17	"github.com/hashicorp/nomad/helper/args"
18	"github.com/mitchellh/copystructure"
19)
20
21const (
22	EnvoyBootstrapPath = "${NOMAD_SECRETS_DIR}/envoy_bootstrap.json"
23
24	ServiceCheckHTTP   = "http"
25	ServiceCheckTCP    = "tcp"
26	ServiceCheckScript = "script"
27	ServiceCheckGRPC   = "grpc"
28
29	// minCheckInterval is the minimum check interval permitted.  Consul
30	// currently has its MinInterval set to 1s.  Mirror that here for
31	// consistency.
32	minCheckInterval = 1 * time.Second
33
34	// minCheckTimeout is the minimum check timeout permitted for Consul
35	// script TTL checks.
36	minCheckTimeout = 1 * time.Second
37)
38
39// ServiceCheck represents the Consul health check.
40type ServiceCheck struct {
41	Name          string              // Name of the check, defaults to id
42	Type          string              // Type of the check - tcp, http, docker and script
43	Command       string              // Command is the command to run for script checks
44	Args          []string            // Args is a list of arguments for script checks
45	Path          string              // path of the health check url for http type check
46	Protocol      string              // Protocol to use if check is http, defaults to http
47	PortLabel     string              // The port to use for tcp/http checks
48	AddressMode   string              // 'host' to use host ip:port or 'driver' to use driver's
49	Interval      time.Duration       // Interval of the check
50	Timeout       time.Duration       // Timeout of the response from the check before consul fails the check
51	InitialStatus string              // Initial status of the check
52	TLSSkipVerify bool                // Skip TLS verification when Protocol=https
53	Method        string              // HTTP Method to use (GET by default)
54	Header        map[string][]string // HTTP Headers for Consul to set when making HTTP checks
55	CheckRestart  *CheckRestart       // If and when a task should be restarted based on checks
56	GRPCService   string              // Service for GRPC checks
57	GRPCUseTLS    bool                // Whether or not to use TLS for GRPC checks
58	TaskName      string              // What task to execute this check in
59}
60
61// Copy the stanza recursively. Returns nil if nil.
62func (sc *ServiceCheck) Copy() *ServiceCheck {
63	if sc == nil {
64		return nil
65	}
66	nsc := new(ServiceCheck)
67	*nsc = *sc
68	nsc.Args = helper.CopySliceString(sc.Args)
69	nsc.Header = helper.CopyMapStringSliceString(sc.Header)
70	nsc.CheckRestart = sc.CheckRestart.Copy()
71	return nsc
72}
73
74// Equals returns true if the structs are recursively equal.
75func (sc *ServiceCheck) Equals(o *ServiceCheck) bool {
76	if sc == nil || o == nil {
77		return sc == o
78	}
79
80	if sc.Name != o.Name {
81		return false
82	}
83
84	if sc.AddressMode != o.AddressMode {
85		return false
86	}
87
88	if !helper.CompareSliceSetString(sc.Args, o.Args) {
89		return false
90	}
91
92	if !sc.CheckRestart.Equals(o.CheckRestart) {
93		return false
94	}
95
96	if sc.TaskName != o.TaskName {
97		return false
98	}
99
100	if sc.Command != o.Command {
101		return false
102	}
103
104	if sc.GRPCService != o.GRPCService {
105		return false
106	}
107
108	if sc.GRPCUseTLS != o.GRPCUseTLS {
109		return false
110	}
111
112	// Use DeepEqual here as order of slice values could matter
113	if !reflect.DeepEqual(sc.Header, o.Header) {
114		return false
115	}
116
117	if sc.InitialStatus != o.InitialStatus {
118		return false
119	}
120
121	if sc.Interval != o.Interval {
122		return false
123	}
124
125	if sc.Method != o.Method {
126		return false
127	}
128
129	if sc.Path != o.Path {
130		return false
131	}
132
133	if sc.PortLabel != o.Path {
134		return false
135	}
136
137	if sc.Protocol != o.Protocol {
138		return false
139	}
140
141	if sc.TLSSkipVerify != o.TLSSkipVerify {
142		return false
143	}
144
145	if sc.Timeout != o.Timeout {
146		return false
147	}
148
149	if sc.Type != o.Type {
150		return false
151	}
152
153	return true
154}
155
156func (sc *ServiceCheck) Canonicalize(serviceName string) {
157	// Ensure empty maps/slices are treated as null to avoid scheduling
158	// issues when using DeepEquals.
159	if len(sc.Args) == 0 {
160		sc.Args = nil
161	}
162
163	if len(sc.Header) == 0 {
164		sc.Header = nil
165	} else {
166		for k, v := range sc.Header {
167			if len(v) == 0 {
168				sc.Header[k] = nil
169			}
170		}
171	}
172
173	if sc.Name == "" {
174		sc.Name = fmt.Sprintf("service: %q check", serviceName)
175	}
176}
177
178// validate a Service's ServiceCheck
179func (sc *ServiceCheck) validate() error {
180	// Validate Type
181	switch strings.ToLower(sc.Type) {
182	case ServiceCheckGRPC:
183	case ServiceCheckTCP:
184	case ServiceCheckHTTP:
185		if sc.Path == "" {
186			return fmt.Errorf("http type must have a valid http path")
187		}
188		url, err := url.Parse(sc.Path)
189		if err != nil {
190			return fmt.Errorf("http type must have a valid http path")
191		}
192		if url.IsAbs() {
193			return fmt.Errorf("http type must have a relative http path")
194		}
195
196	case ServiceCheckScript:
197		if sc.Command == "" {
198			return fmt.Errorf("script type must have a valid script path")
199		}
200
201	default:
202		return fmt.Errorf(`invalid type (%+q), must be one of "http", "tcp", or "script" type`, sc.Type)
203	}
204
205	// Validate interval and timeout
206	if sc.Interval == 0 {
207		return fmt.Errorf("missing required value interval. Interval cannot be less than %v", minCheckInterval)
208	} else if sc.Interval < minCheckInterval {
209		return fmt.Errorf("interval (%v) cannot be lower than %v", sc.Interval, minCheckInterval)
210	}
211
212	if sc.Timeout == 0 {
213		return fmt.Errorf("missing required value timeout. Timeout cannot be less than %v", minCheckInterval)
214	} else if sc.Timeout < minCheckTimeout {
215		return fmt.Errorf("timeout (%v) is lower than required minimum timeout %v", sc.Timeout, minCheckInterval)
216	}
217
218	// Validate InitialStatus
219	switch sc.InitialStatus {
220	case "":
221	case api.HealthPassing:
222	case api.HealthWarning:
223	case api.HealthCritical:
224	default:
225		return fmt.Errorf(`invalid initial check state (%s), must be one of %q, %q, %q or empty`, sc.InitialStatus, api.HealthPassing, api.HealthWarning, api.HealthCritical)
226
227	}
228
229	// Validate AddressMode
230	switch sc.AddressMode {
231	case "", AddressModeHost, AddressModeDriver:
232		// Ok
233	case AddressModeAuto:
234		return fmt.Errorf("invalid address_mode %q - %s only valid for services", sc.AddressMode, AddressModeAuto)
235	default:
236		return fmt.Errorf("invalid address_mode %q", sc.AddressMode)
237	}
238
239	return sc.CheckRestart.Validate()
240}
241
242// RequiresPort returns whether the service check requires the task has a port.
243func (sc *ServiceCheck) RequiresPort() bool {
244	switch sc.Type {
245	case ServiceCheckGRPC, ServiceCheckHTTP, ServiceCheckTCP:
246		return true
247	default:
248		return false
249	}
250}
251
252// TriggersRestarts returns true if this check should be watched and trigger a restart
253// on failure.
254func (sc *ServiceCheck) TriggersRestarts() bool {
255	return sc.CheckRestart != nil && sc.CheckRestart.Limit > 0
256}
257
258// Hash all ServiceCheck fields and the check's corresponding service ID to
259// create an identifier. The identifier is not guaranteed to be unique as if
260// the PortLabel is blank, the Service's PortLabel will be used after Hash is
261// called.
262func (sc *ServiceCheck) Hash(serviceID string) string {
263	h := sha1.New()
264	io.WriteString(h, serviceID)
265	io.WriteString(h, sc.Name)
266	io.WriteString(h, sc.Type)
267	io.WriteString(h, sc.Command)
268	io.WriteString(h, strings.Join(sc.Args, ""))
269	io.WriteString(h, sc.Path)
270	io.WriteString(h, sc.Protocol)
271	io.WriteString(h, sc.PortLabel)
272	io.WriteString(h, sc.Interval.String())
273	io.WriteString(h, sc.Timeout.String())
274	io.WriteString(h, sc.Method)
275	// Only include TLSSkipVerify if set to maintain ID stability with Nomad <0.6
276	if sc.TLSSkipVerify {
277		io.WriteString(h, "true")
278	}
279
280	// Since map iteration order isn't stable we need to write k/v pairs to
281	// a slice and sort it before hashing.
282	if len(sc.Header) > 0 {
283		headers := make([]string, 0, len(sc.Header))
284		for k, v := range sc.Header {
285			headers = append(headers, k+strings.Join(v, ""))
286		}
287		sort.Strings(headers)
288		io.WriteString(h, strings.Join(headers, ""))
289	}
290
291	// Only include AddressMode if set to maintain ID stability with Nomad <0.7.1
292	if len(sc.AddressMode) > 0 {
293		io.WriteString(h, sc.AddressMode)
294	}
295
296	// Only include GRPC if set to maintain ID stability with Nomad <0.8.4
297	if sc.GRPCService != "" {
298		io.WriteString(h, sc.GRPCService)
299	}
300	if sc.GRPCUseTLS {
301		io.WriteString(h, "true")
302	}
303
304	return fmt.Sprintf("%x", h.Sum(nil))
305}
306
307const (
308	AddressModeAuto   = "auto"
309	AddressModeHost   = "host"
310	AddressModeDriver = "driver"
311)
312
313// Service represents a Consul service definition
314type Service struct {
315	// Name of the service registered with Consul. Consul defaults the
316	// Name to ServiceID if not specified.  The Name if specified is used
317	// as one of the seed values when generating a Consul ServiceID.
318	Name string
319
320	// PortLabel is either the numeric port number or the `host:port`.
321	// To specify the port number using the host's Consul Advertise
322	// address, specify an empty host in the PortLabel (e.g. `:port`).
323	PortLabel string
324
325	// AddressMode specifies whether or not to use the host ip:port for
326	// this service.
327	AddressMode string
328
329	Tags       []string          // List of tags for the service
330	CanaryTags []string          // List of tags for the service when it is a canary
331	Checks     []*ServiceCheck   // List of checks associated with the service
332	Connect    *ConsulConnect    // Consul Connect configuration
333	Meta       map[string]string // Consul service meta
334}
335
336// Copy the stanza recursively. Returns nil if nil.
337func (s *Service) Copy() *Service {
338	if s == nil {
339		return nil
340	}
341	ns := new(Service)
342	*ns = *s
343	ns.Tags = helper.CopySliceString(ns.Tags)
344	ns.CanaryTags = helper.CopySliceString(ns.CanaryTags)
345
346	if s.Checks != nil {
347		checks := make([]*ServiceCheck, len(ns.Checks))
348		for i, c := range ns.Checks {
349			checks[i] = c.Copy()
350		}
351		ns.Checks = checks
352	}
353
354	ns.Connect = s.Connect.Copy()
355
356	ns.Meta = helper.CopyMapStringString(s.Meta)
357
358	return ns
359}
360
361// Canonicalize interpolates values of Job, Task Group and Task in the Service
362// Name. This also generates check names, service id and check ids.
363func (s *Service) Canonicalize(job string, taskGroup string, task string) {
364	// Ensure empty lists are treated as null to avoid scheduler issues when
365	// using DeepEquals
366	if len(s.Tags) == 0 {
367		s.Tags = nil
368	}
369	if len(s.CanaryTags) == 0 {
370		s.CanaryTags = nil
371	}
372	if len(s.Checks) == 0 {
373		s.Checks = nil
374	}
375
376	s.Name = args.ReplaceEnv(s.Name, map[string]string{
377		"JOB":       job,
378		"TASKGROUP": taskGroup,
379		"TASK":      task,
380		"BASE":      fmt.Sprintf("%s-%s-%s", job, taskGroup, task),
381	},
382	)
383
384	for _, check := range s.Checks {
385		check.Canonicalize(s.Name)
386	}
387}
388
389// Validate checks if the Check definition is valid
390func (s *Service) Validate() error {
391	var mErr multierror.Error
392
393	// Ensure the service name is valid per the below RFCs but make an exception
394	// for our interpolation syntax by first stripping any environment variables from the name
395
396	serviceNameStripped := args.ReplaceEnvWithPlaceHolder(s.Name, "ENV-VAR")
397
398	if err := s.ValidateName(serviceNameStripped); err != nil {
399		mErr.Errors = append(mErr.Errors, fmt.Errorf("Service name must be valid per RFC 1123 and can contain only alphanumeric characters or dashes: %q", s.Name))
400	}
401
402	switch s.AddressMode {
403	case "", AddressModeAuto, AddressModeHost, AddressModeDriver:
404		// OK
405	default:
406		mErr.Errors = append(mErr.Errors, fmt.Errorf("Service address_mode must be %q, %q, or %q; not %q", AddressModeAuto, AddressModeHost, AddressModeDriver, s.AddressMode))
407	}
408
409	for _, c := range s.Checks {
410		if s.PortLabel == "" && c.PortLabel == "" && c.RequiresPort() {
411			mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %s invalid: check requires a port but neither check nor service %+q have a port", c.Name, s.Name))
412			continue
413		}
414
415		// TCP checks against a Consul Connect enabled service are not supported
416		// due to the service being bound to the loopback interface inside the
417		// network namespace
418		if c.Type == ServiceCheckTCP && s.Connect != nil && s.Connect.SidecarService != nil {
419			mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %s invalid: tcp checks are not valid for Connect enabled services", c.Name))
420			continue
421		}
422
423		if err := c.validate(); err != nil {
424			mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %s invalid: %v", c.Name, err))
425		}
426	}
427
428	if s.Connect != nil {
429		if err := s.Connect.Validate(); err != nil {
430			mErr.Errors = append(mErr.Errors, err)
431		}
432	}
433
434	return mErr.ErrorOrNil()
435}
436
437// ValidateName checks if the services Name is valid and should be called after
438// the name has been interpolated
439func (s *Service) ValidateName(name string) error {
440	// Ensure the service name is valid per RFC-952 §1
441	// (https://tools.ietf.org/html/rfc952), RFC-1123 §2.1
442	// (https://tools.ietf.org/html/rfc1123), and RFC-2782
443	// (https://tools.ietf.org/html/rfc2782).
444	re := regexp.MustCompile(`^(?i:[a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])$`)
445	if !re.MatchString(name) {
446		return fmt.Errorf("Service name must be valid per RFC 1123 and can contain only alphanumeric characters or dashes and must be no longer than 63 characters: %q", name)
447	}
448	return nil
449}
450
451// Hash returns a base32 encoded hash of a Service's contents excluding checks
452// as they're hashed independently.
453func (s *Service) Hash(allocID, taskName string, canary bool) string {
454	h := sha1.New()
455	io.WriteString(h, allocID)
456	io.WriteString(h, taskName)
457	io.WriteString(h, s.Name)
458	io.WriteString(h, s.PortLabel)
459	io.WriteString(h, s.AddressMode)
460	for _, tag := range s.Tags {
461		io.WriteString(h, tag)
462	}
463	for _, tag := range s.CanaryTags {
464		io.WriteString(h, tag)
465	}
466	if len(s.Meta) > 0 {
467		fmt.Fprintf(h, "%v", s.Meta)
468	}
469
470	// Vary ID on whether or not CanaryTags will be used
471	if canary {
472		h.Write([]byte("Canary"))
473	}
474
475	// Base32 is used for encoding the hash as sha1 hashes can always be
476	// encoded without padding, only 4 bytes larger than base64, and saves
477	// 8 bytes vs hex. Since these hashes are used in Consul URLs it's nice
478	// to have a reasonably compact URL-safe representation.
479	return b32.EncodeToString(h.Sum(nil))
480}
481
482// Equals returns true if the structs are recursively equal.
483func (s *Service) Equals(o *Service) bool {
484	if s == nil || o == nil {
485		return s == o
486	}
487
488	if s.AddressMode != o.AddressMode {
489		return false
490	}
491
492	if !helper.CompareSliceSetString(s.CanaryTags, o.CanaryTags) {
493		return false
494	}
495
496	if len(s.Checks) != len(o.Checks) {
497		return false
498	}
499
500OUTER:
501	for i := range s.Checks {
502		for ii := range o.Checks {
503			if s.Checks[i].Equals(o.Checks[ii]) {
504				// Found match; continue with next check
505				continue OUTER
506			}
507		}
508
509		// No match
510		return false
511	}
512
513	if !s.Connect.Equals(o.Connect) {
514		return false
515	}
516
517	if s.Name != o.Name {
518		return false
519	}
520
521	if s.PortLabel != o.PortLabel {
522		return false
523	}
524
525	if !reflect.DeepEqual(s.Meta, o.Meta) {
526		return false
527	}
528
529	if !helper.CompareSliceSetString(s.Tags, o.Tags) {
530		return false
531	}
532
533	return true
534}
535
536// ConsulConnect represents a Consul Connect jobspec stanza.
537type ConsulConnect struct {
538	// Native is true if a service implements Connect directly and does not
539	// need a sidecar.
540	Native bool
541
542	// SidecarService is non-nil if a service requires a sidecar.
543	SidecarService *ConsulSidecarService
544
545	// SidecarTask is non-nil if sidecar overrides are set
546	SidecarTask *SidecarTask
547}
548
549// Copy the stanza recursively. Returns nil if nil.
550func (c *ConsulConnect) Copy() *ConsulConnect {
551	if c == nil {
552		return nil
553	}
554
555	return &ConsulConnect{
556		Native:         c.Native,
557		SidecarService: c.SidecarService.Copy(),
558		SidecarTask:    c.SidecarTask.Copy(),
559	}
560}
561
562// Equals returns true if the structs are recursively equal.
563func (c *ConsulConnect) Equals(o *ConsulConnect) bool {
564	if c == nil || o == nil {
565		return c == o
566	}
567
568	if c.Native != o.Native {
569		return false
570	}
571
572	return c.SidecarService.Equals(o.SidecarService)
573}
574
575// HasSidecar checks if a sidecar task is needed
576func (c *ConsulConnect) HasSidecar() bool {
577	return c != nil && c.SidecarService != nil
578}
579
580// Validate that the Connect stanza has exactly one of Native or sidecar.
581func (c *ConsulConnect) Validate() error {
582	if c == nil {
583		return nil
584	}
585
586	if c.Native && c.SidecarService != nil {
587		return fmt.Errorf("Consul Connect must be native or use a sidecar service; not both")
588	}
589
590	if !c.Native && c.SidecarService == nil {
591		return fmt.Errorf("Consul Connect must be native or use a sidecar service")
592	}
593
594	return nil
595}
596
597// ConsulSidecarService represents a Consul Connect SidecarService jobspec
598// stanza.
599type ConsulSidecarService struct {
600	// Tags are optional service tags that get registered with the sidecar service
601	// in Consul. If unset, the sidecar service inherits the parent service tags.
602	Tags []string
603
604	// Port is the service's port that the sidecar will connect to. May be
605	// a port label or a literal port number.
606	Port string
607
608	// Proxy stanza defining the sidecar proxy configuration.
609	Proxy *ConsulProxy
610}
611
612// HasUpstreams checks if the sidecar service has any upstreams configured
613func (s *ConsulSidecarService) HasUpstreams() bool {
614	return s != nil && s.Proxy != nil && len(s.Proxy.Upstreams) > 0
615}
616
617// Copy the stanza recursively. Returns nil if nil.
618func (s *ConsulSidecarService) Copy() *ConsulSidecarService {
619	return &ConsulSidecarService{
620		Tags:  helper.CopySliceString(s.Tags),
621		Port:  s.Port,
622		Proxy: s.Proxy.Copy(),
623	}
624}
625
626// Equals returns true if the structs are recursively equal.
627func (s *ConsulSidecarService) Equals(o *ConsulSidecarService) bool {
628	if s == nil || o == nil {
629		return s == o
630	}
631
632	if s.Port != o.Port {
633		return false
634	}
635
636	if !helper.CompareSliceSetString(s.Tags, o.Tags) {
637		return false
638	}
639
640	return s.Proxy.Equals(o.Proxy)
641}
642
643// SidecarTask represents a subset of Task fields that are able to be overridden
644// from the sidecar_task stanza
645type SidecarTask struct {
646	// Name of the task
647	Name string
648
649	// Driver is used to control which driver is used
650	Driver string
651
652	// User is used to determine which user will run the task. It defaults to
653	// the same user the Nomad client is being run as.
654	User string
655
656	// Config is provided to the driver to initialize
657	Config map[string]interface{}
658
659	// Map of environment variables to be used by the driver
660	Env map[string]string
661
662	// Resources is the resources needed by this task
663	Resources *Resources
664
665	// Meta is used to associate arbitrary metadata with this
666	// task. This is opaque to Nomad.
667	Meta map[string]string
668
669	// KillTimeout is the time between signaling a task that it will be
670	// killed and killing it.
671	KillTimeout *time.Duration
672
673	// LogConfig provides configuration for log rotation
674	LogConfig *LogConfig
675
676	// ShutdownDelay is the duration of the delay between deregistering a
677	// task from Consul and sending it a signal to shutdown. See #2441
678	ShutdownDelay *time.Duration
679
680	// KillSignal is the kill signal to use for the task. This is an optional
681	// specification and defaults to SIGINT
682	KillSignal string
683}
684
685func (t *SidecarTask) Copy() *SidecarTask {
686	if t == nil {
687		return nil
688	}
689	nt := new(SidecarTask)
690	*nt = *t
691	nt.Env = helper.CopyMapStringString(nt.Env)
692
693	nt.Resources = nt.Resources.Copy()
694	nt.LogConfig = nt.LogConfig.Copy()
695	nt.Meta = helper.CopyMapStringString(nt.Meta)
696
697	if i, err := copystructure.Copy(nt.Config); err != nil {
698		panic(err.Error())
699	} else {
700		nt.Config = i.(map[string]interface{})
701	}
702
703	if t.KillTimeout != nil {
704		nt.KillTimeout = helper.TimeToPtr(*t.KillTimeout)
705	}
706
707	if t.ShutdownDelay != nil {
708		nt.ShutdownDelay = helper.TimeToPtr(*t.ShutdownDelay)
709	}
710
711	return nt
712}
713
714// MergeIntoTask merges the SidecarTask fields over the given task
715func (t *SidecarTask) MergeIntoTask(task *Task) {
716	if t.Name != "" {
717		task.Name = t.Name
718	}
719
720	// If the driver changes then the driver config can be overwritten.
721	// Otherwise we'll merge the driver config together
722	if t.Driver != "" && t.Driver != task.Driver {
723		task.Driver = t.Driver
724		task.Config = t.Config
725	} else {
726		for k, v := range t.Config {
727			task.Config[k] = v
728		}
729	}
730
731	if t.User != "" {
732		task.User = t.User
733	}
734
735	if t.Env != nil {
736		if task.Env == nil {
737			task.Env = t.Env
738		} else {
739			for k, v := range t.Env {
740				task.Env[k] = v
741			}
742		}
743	}
744
745	if t.Resources != nil {
746		task.Resources.Merge(t.Resources)
747	}
748
749	if t.Meta != nil {
750		if task.Meta == nil {
751			task.Meta = t.Meta
752		} else {
753			for k, v := range t.Meta {
754				task.Meta[k] = v
755			}
756		}
757	}
758
759	if t.KillTimeout != nil {
760		task.KillTimeout = *t.KillTimeout
761	}
762
763	if t.LogConfig != nil {
764		if task.LogConfig == nil {
765			task.LogConfig = t.LogConfig
766		} else {
767			if t.LogConfig.MaxFiles > 0 {
768				task.LogConfig.MaxFiles = t.LogConfig.MaxFiles
769			}
770			if t.LogConfig.MaxFileSizeMB > 0 {
771				task.LogConfig.MaxFileSizeMB = t.LogConfig.MaxFileSizeMB
772			}
773		}
774	}
775
776	if t.ShutdownDelay != nil {
777		task.ShutdownDelay = *t.ShutdownDelay
778	}
779
780	if t.KillSignal != "" {
781		task.KillSignal = t.KillSignal
782	}
783}
784
785// ConsulProxy represents a Consul Connect sidecar proxy jobspec stanza.
786type ConsulProxy struct {
787
788	// LocalServiceAddress is the address the local service binds to.
789	// Usually 127.0.0.1 it is useful to customize in clusters with mixed
790	// Connect and non-Connect services.
791	LocalServiceAddress string
792
793	// LocalServicePort is the port the local service binds to. Usually
794	// the same as the parent service's port, it is useful to customize
795	// in clusters with mixed Connect and non-Connect services
796	LocalServicePort int
797
798	// Upstreams configures the upstream services this service intends to
799	// connect to.
800	Upstreams []ConsulUpstream
801
802	// Config is a proxy configuration. It is opaque to Nomad and passed
803	// directly to Consul.
804	Config map[string]interface{}
805}
806
807// Copy the stanza recursively. Returns nil if nil.
808func (p *ConsulProxy) Copy() *ConsulProxy {
809	if p == nil {
810		return nil
811	}
812
813	newP := ConsulProxy{}
814	newP.LocalServiceAddress = p.LocalServiceAddress
815	newP.LocalServicePort = p.LocalServicePort
816
817	if n := len(p.Upstreams); n > 0 {
818		newP.Upstreams = make([]ConsulUpstream, n)
819
820		for i := range p.Upstreams {
821			newP.Upstreams[i] = *p.Upstreams[i].Copy()
822		}
823	}
824
825	if n := len(p.Config); n > 0 {
826		newP.Config = make(map[string]interface{}, n)
827
828		for k, v := range p.Config {
829			newP.Config[k] = v
830		}
831	}
832
833	return &newP
834}
835
836// Equals returns true if the structs are recursively equal.
837func (p *ConsulProxy) Equals(o *ConsulProxy) bool {
838	if p == nil || o == nil {
839		return p == o
840	}
841
842	if p.LocalServiceAddress != o.LocalServiceAddress {
843		return false
844	}
845	if p.LocalServicePort != o.LocalServicePort {
846		return false
847	}
848	if len(p.Upstreams) != len(o.Upstreams) {
849		return false
850	}
851
852	// Order doesn't matter
853OUTER:
854	for _, up := range p.Upstreams {
855		for _, innerUp := range o.Upstreams {
856			if up.Equals(&innerUp) {
857				// Match; find next upstream
858				continue OUTER
859			}
860		}
861
862		// No match
863		return false
864	}
865
866	// Avoid nil vs {} differences
867	if len(p.Config) != 0 && len(o.Config) != 0 {
868		if !reflect.DeepEqual(p.Config, o.Config) {
869			return false
870		}
871	}
872
873	return true
874}
875
876// ConsulUpstream represents a Consul Connect upstream jobspec stanza.
877type ConsulUpstream struct {
878	// DestinationName is the name of the upstream service.
879	DestinationName string
880
881	// LocalBindPort is the port the proxy will receive connections for the
882	// upstream on.
883	LocalBindPort int
884}
885
886// Copy the stanza recursively. Returns nil if nil.
887func (u *ConsulUpstream) Copy() *ConsulUpstream {
888	if u == nil {
889		return nil
890	}
891
892	return &ConsulUpstream{
893		DestinationName: u.DestinationName,
894		LocalBindPort:   u.LocalBindPort,
895	}
896}
897
898// Equals returns true if the structs are recursively equal.
899func (u *ConsulUpstream) Equals(o *ConsulUpstream) bool {
900	if u == nil || o == nil {
901		return u == o
902	}
903
904	return (*u) == (*o)
905}
906