1package structs
2
3import (
4	"fmt"
5	"reflect"
6	"time"
7
8	"github.com/hashicorp/consul/lib"
9	"github.com/hashicorp/consul/types"
10)
11
12type CheckTypes []*CheckType
13
14// CheckType is used to create either the CheckMonitor or the CheckTTL.
15// The following types are supported: Script, HTTP, TCP, Docker, TTL, GRPC, Alias. Script,
16// HTTP, Docker, TCP and GRPC all require Interval. Only one of the types may
17// to be provided: TTL or Script/Interval or HTTP/Interval or TCP/Interval or
18// Docker/Interval or GRPC/Interval or AliasService.
19// Since types like CheckHTTP and CheckGRPC derive from CheckType, there are
20// helper conversion methods that do the reverse conversion. ie. checkHTTP.CheckType()
21type CheckType struct {
22	// fields already embedded in CheckDefinition
23	// Note: CheckType.CheckID == CheckDefinition.ID
24
25	CheckID types.CheckID
26	Name    string
27	Status  string
28	Notes   string
29
30	// fields copied to CheckDefinition
31	// Update CheckDefinition when adding fields here
32
33	ScriptArgs             []string
34	HTTP                   string
35	Header                 map[string][]string
36	Method                 string
37	Body                   string
38	TCP                    string
39	Interval               time.Duration
40	AliasNode              string
41	AliasService           string
42	DockerContainerID      string
43	Shell                  string
44	GRPC                   string
45	GRPCUseTLS             bool
46	TLSSkipVerify          bool
47	Timeout                time.Duration
48	TTL                    time.Duration
49	SuccessBeforePassing   int
50	FailuresBeforeCritical int
51
52	// Definition fields used when exposing checks through a proxy
53	ProxyHTTP string
54	ProxyGRPC string
55
56	// DeregisterCriticalServiceAfter, if >0, will cause the associated
57	// service, if any, to be deregistered if this check is critical for
58	// longer than this duration.
59	DeregisterCriticalServiceAfter time.Duration
60	OutputMaxSize                  int
61}
62
63func (t *CheckType) UnmarshalJSON(data []byte) (err error) {
64	type Alias CheckType
65	aux := &struct {
66		Interval                       interface{}
67		Timeout                        interface{}
68		TTL                            interface{}
69		DeregisterCriticalServiceAfter interface{}
70
71		// Translate fields
72
73		// "args" -> ScriptArgs
74		Args                                []string    `json:"args"`
75		ScriptArgsSnake                     []string    `json:"script_args"`
76		DeregisterCriticalServiceAfterSnake interface{} `json:"deregister_critical_service_after"`
77		DockerContainerIDSnake              string      `json:"docker_container_id"`
78		TLSSkipVerifySnake                  bool        `json:"tls_skip_verify"`
79
80		// These are going to be ignored but since we are disallowing unknown fields
81		// during parsing we have to be explicit about parsing but not using these.
82		ServiceID      string `json:"ServiceID"`
83		ServiceIDSnake string `json:"service_id"`
84
85		*Alias
86	}{
87		Alias: (*Alias)(t),
88	}
89	if err = lib.UnmarshalJSON(data, aux); err != nil {
90		return err
91	}
92	if aux.DeregisterCriticalServiceAfter == nil {
93		aux.DeregisterCriticalServiceAfter = aux.DeregisterCriticalServiceAfterSnake
94	}
95	if len(t.ScriptArgs) == 0 {
96		t.ScriptArgs = aux.Args
97	}
98	if len(t.ScriptArgs) == 0 {
99		t.ScriptArgs = aux.ScriptArgsSnake
100	}
101	if t.DockerContainerID == "" {
102		t.DockerContainerID = aux.DockerContainerIDSnake
103	}
104	if aux.TLSSkipVerifySnake {
105		t.TLSSkipVerify = aux.TLSSkipVerifySnake
106	}
107
108	if aux.Interval != nil {
109		switch v := aux.Interval.(type) {
110		case string:
111			if t.Interval, err = time.ParseDuration(v); err != nil {
112				return err
113			}
114		case float64:
115			t.Interval = time.Duration(v)
116		}
117	}
118	if aux.Timeout != nil {
119		switch v := aux.Timeout.(type) {
120		case string:
121			if t.Timeout, err = time.ParseDuration(v); err != nil {
122				return err
123			}
124		case float64:
125			t.Timeout = time.Duration(v)
126		}
127	}
128	if aux.TTL != nil {
129		switch v := aux.TTL.(type) {
130		case string:
131			if t.TTL, err = time.ParseDuration(v); err != nil {
132				return err
133			}
134		case float64:
135			t.TTL = time.Duration(v)
136		}
137	}
138	if aux.DeregisterCriticalServiceAfter != nil {
139		switch v := aux.DeregisterCriticalServiceAfter.(type) {
140		case string:
141			if t.DeregisterCriticalServiceAfter, err = time.ParseDuration(v); err != nil {
142				return err
143			}
144		case float64:
145			t.DeregisterCriticalServiceAfter = time.Duration(v)
146		}
147	}
148
149	return nil
150
151}
152
153// Validate returns an error message if the check is invalid
154func (c *CheckType) Validate() error {
155	intervalCheck := c.IsScript() || c.HTTP != "" || c.TCP != "" || c.GRPC != ""
156
157	if c.Interval > 0 && c.TTL > 0 {
158		return fmt.Errorf("Interval and TTL cannot both be specified")
159	}
160	if intervalCheck && c.Interval <= 0 {
161		return fmt.Errorf("Interval must be > 0 for Script, HTTP, or TCP checks")
162	}
163	if intervalCheck && c.IsAlias() {
164		return fmt.Errorf("Interval cannot be set for Alias checks")
165	}
166	if c.IsAlias() && c.TTL > 0 {
167		return fmt.Errorf("TTL must be not be set for Alias checks")
168	}
169	if !intervalCheck && !c.IsAlias() && c.TTL <= 0 {
170		return fmt.Errorf("TTL must be > 0 for TTL checks")
171	}
172	if c.OutputMaxSize < 0 {
173		return fmt.Errorf("MaxOutputMaxSize must be positive")
174	}
175	return nil
176}
177
178// Empty checks if the CheckType has no fields defined. Empty checks parsed from json configs are filtered out
179func (c *CheckType) Empty() bool {
180	return reflect.DeepEqual(c, &CheckType{})
181}
182
183// IsAlias checks if this is an alias check.
184func (c *CheckType) IsAlias() bool {
185	return c.AliasNode != "" || c.AliasService != ""
186}
187
188// IsScript checks if this is a check that execs some kind of script.
189func (c *CheckType) IsScript() bool {
190	return len(c.ScriptArgs) > 0
191}
192
193// IsTTL checks if this is a TTL type
194func (c *CheckType) IsTTL() bool {
195	return c.TTL > 0
196}
197
198// IsMonitor checks if this is a Monitor type
199func (c *CheckType) IsMonitor() bool {
200	return c.IsScript() && c.DockerContainerID == "" && c.Interval > 0
201}
202
203// IsHTTP checks if this is a HTTP type
204func (c *CheckType) IsHTTP() bool {
205	return c.HTTP != "" && c.Interval > 0
206}
207
208// IsTCP checks if this is a TCP type
209func (c *CheckType) IsTCP() bool {
210	return c.TCP != "" && c.Interval > 0
211}
212
213// IsDocker returns true when checking a docker container.
214func (c *CheckType) IsDocker() bool {
215	return c.IsScript() && c.DockerContainerID != "" && c.Interval > 0
216}
217
218// IsGRPC checks if this is a GRPC type
219func (c *CheckType) IsGRPC() bool {
220	return c.GRPC != "" && c.Interval > 0
221}
222
223func (c *CheckType) Type() string {
224	switch {
225	case c.IsGRPC():
226		return "grpc"
227	case c.IsHTTP():
228		return "http"
229	case c.IsTTL():
230		return "ttl"
231	case c.IsTCP():
232		return "tcp"
233	case c.IsAlias():
234		return "alias"
235	case c.IsDocker():
236		return "docker"
237	case c.IsScript():
238		return "script"
239	default:
240		return ""
241	}
242}
243