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, H2PING. Script,
16// HTTP, Docker, TCP, GRPC, and H2PING 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 or H2PING/Interval.
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	H2PING                 string
36	Header                 map[string][]string
37	Method                 string
38	Body                   string
39	TCP                    string
40	Interval               time.Duration
41	AliasNode              string
42	AliasService           string
43	DockerContainerID      string
44	Shell                  string
45	GRPC                   string
46	GRPCUseTLS             bool
47	TLSServerName          string
48	TLSSkipVerify          bool
49	Timeout                time.Duration
50	TTL                    time.Duration
51	SuccessBeforePassing   int
52	FailuresBeforeCritical int
53
54	// Definition fields used when exposing checks through a proxy
55	ProxyHTTP string
56	ProxyGRPC string
57
58	// DeregisterCriticalServiceAfter, if >0, will cause the associated
59	// service, if any, to be deregistered if this check is critical for
60	// longer than this duration.
61	DeregisterCriticalServiceAfter time.Duration
62	OutputMaxSize                  int
63}
64
65func (t *CheckType) UnmarshalJSON(data []byte) (err error) {
66	type Alias CheckType
67	aux := &struct {
68		Interval                       interface{}
69		Timeout                        interface{}
70		TTL                            interface{}
71		DeregisterCriticalServiceAfter interface{}
72
73		// Translate fields
74
75		// "args" -> ScriptArgs
76		Args                                []string    `json:"args"`
77		ScriptArgsSnake                     []string    `json:"script_args"`
78		DeregisterCriticalServiceAfterSnake interface{} `json:"deregister_critical_service_after"`
79		DockerContainerIDSnake              string      `json:"docker_container_id"`
80		TLSServerNameSnake                  string      `json:"tls_server_name"`
81		TLSSkipVerifySnake                  bool        `json:"tls_skip_verify"`
82		GRPCUseTLSSnake                     bool        `json:"grpc_use_tls"`
83
84		// These are going to be ignored but since we are disallowing unknown fields
85		// during parsing we have to be explicit about parsing but not using these.
86		ServiceID      string `json:"ServiceID"`
87		ServiceIDSnake string `json:"service_id"`
88
89		*Alias
90	}{
91		Alias: (*Alias)(t),
92	}
93	if err = lib.UnmarshalJSON(data, aux); err != nil {
94		return err
95	}
96	if aux.DeregisterCriticalServiceAfter == nil {
97		aux.DeregisterCriticalServiceAfter = aux.DeregisterCriticalServiceAfterSnake
98	}
99	if len(t.ScriptArgs) == 0 {
100		t.ScriptArgs = aux.Args
101	}
102	if len(t.ScriptArgs) == 0 {
103		t.ScriptArgs = aux.ScriptArgsSnake
104	}
105	if t.DockerContainerID == "" {
106		t.DockerContainerID = aux.DockerContainerIDSnake
107	}
108	if t.TLSServerName == "" {
109		t.TLSServerName = aux.TLSServerNameSnake
110	}
111	if aux.TLSSkipVerifySnake {
112		t.TLSSkipVerify = aux.TLSSkipVerifySnake
113	}
114	if aux.GRPCUseTLSSnake {
115		t.GRPCUseTLS = aux.GRPCUseTLSSnake
116	}
117
118	if aux.Interval != nil {
119		switch v := aux.Interval.(type) {
120		case string:
121			if t.Interval, err = time.ParseDuration(v); err != nil {
122				return err
123			}
124		case float64:
125			t.Interval = time.Duration(v)
126		}
127	}
128	if aux.Timeout != nil {
129		switch v := aux.Timeout.(type) {
130		case string:
131			if t.Timeout, err = time.ParseDuration(v); err != nil {
132				return err
133			}
134		case float64:
135			t.Timeout = time.Duration(v)
136		}
137	}
138	if aux.TTL != nil {
139		switch v := aux.TTL.(type) {
140		case string:
141			if t.TTL, err = time.ParseDuration(v); err != nil {
142				return err
143			}
144		case float64:
145			t.TTL = time.Duration(v)
146		}
147	}
148	if aux.DeregisterCriticalServiceAfter != nil {
149		switch v := aux.DeregisterCriticalServiceAfter.(type) {
150		case string:
151			if t.DeregisterCriticalServiceAfter, err = time.ParseDuration(v); err != nil {
152				return err
153			}
154		case float64:
155			t.DeregisterCriticalServiceAfter = time.Duration(v)
156		}
157	}
158
159	return nil
160
161}
162
163// Validate returns an error message if the check is invalid
164func (c *CheckType) Validate() error {
165	intervalCheck := c.IsScript() || c.HTTP != "" || c.TCP != "" || c.GRPC != "" || c.H2PING != ""
166
167	if c.Interval > 0 && c.TTL > 0 {
168		return fmt.Errorf("Interval and TTL cannot both be specified")
169	}
170	if intervalCheck && c.Interval <= 0 {
171		return fmt.Errorf("Interval must be > 0 for Script, HTTP, H2PING, or TCP checks")
172	}
173	if intervalCheck && c.IsAlias() {
174		return fmt.Errorf("Interval cannot be set for Alias checks")
175	}
176	if c.IsAlias() && c.TTL > 0 {
177		return fmt.Errorf("TTL must be not be set for Alias checks")
178	}
179	if !intervalCheck && !c.IsAlias() && c.TTL <= 0 {
180		return fmt.Errorf("TTL must be > 0 for TTL checks")
181	}
182	if c.OutputMaxSize < 0 {
183		return fmt.Errorf("MaxOutputMaxSize must be positive")
184	}
185	return nil
186}
187
188// Empty checks if the CheckType has no fields defined. Empty checks parsed from json configs are filtered out
189func (c *CheckType) Empty() bool {
190	return reflect.DeepEqual(c, &CheckType{})
191}
192
193// IsAlias checks if this is an alias check.
194func (c *CheckType) IsAlias() bool {
195	return c.AliasNode != "" || c.AliasService != ""
196}
197
198// IsScript checks if this is a check that execs some kind of script.
199func (c *CheckType) IsScript() bool {
200	return len(c.ScriptArgs) > 0
201}
202
203// IsTTL checks if this is a TTL type
204func (c *CheckType) IsTTL() bool {
205	return c.TTL > 0
206}
207
208// IsMonitor checks if this is a Monitor type
209func (c *CheckType) IsMonitor() bool {
210	return c.IsScript() && c.DockerContainerID == "" && c.Interval > 0
211}
212
213// IsHTTP checks if this is a HTTP type
214func (c *CheckType) IsHTTP() bool {
215	return c.HTTP != "" && c.Interval > 0
216}
217
218// IsTCP checks if this is a TCP type
219func (c *CheckType) IsTCP() bool {
220	return c.TCP != "" && c.Interval > 0
221}
222
223// IsDocker returns true when checking a docker container.
224func (c *CheckType) IsDocker() bool {
225	return c.IsScript() && c.DockerContainerID != "" && c.Interval > 0
226}
227
228// IsGRPC checks if this is a GRPC type
229func (c *CheckType) IsGRPC() bool {
230	return c.GRPC != "" && c.Interval > 0
231}
232
233// IsH2PING checks if this is a H2PING type
234func (c *CheckType) IsH2PING() bool {
235	return c.H2PING != "" && c.Interval > 0
236}
237
238func (c *CheckType) Type() string {
239	switch {
240	case c.IsGRPC():
241		return "grpc"
242	case c.IsHTTP():
243		return "http"
244	case c.IsTTL():
245		return "ttl"
246	case c.IsTCP():
247		return "tcp"
248	case c.IsAlias():
249		return "alias"
250	case c.IsDocker():
251		return "docker"
252	case c.IsScript():
253		return "script"
254	case c.IsH2PING():
255		return "h2ping"
256	default:
257		return ""
258	}
259}
260