2016-02-01 12:39:02 +01:00
|
|
|
package datadog
|
|
|
|
|
|
|
|
import (
|
2016-04-19 02:28:46 +02:00
|
|
|
"encoding/json"
|
2016-02-01 12:39:02 +01:00
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
|
|
"github.com/zorkian/go-datadog-api"
|
|
|
|
)
|
|
|
|
|
|
|
|
func resourceDatadogMonitor() *schema.Resource {
|
|
|
|
return &schema.Resource{
|
|
|
|
Create: resourceDatadogMonitorCreate,
|
|
|
|
Read: resourceDatadogMonitorRead,
|
|
|
|
Update: resourceDatadogMonitorUpdate,
|
|
|
|
Delete: resourceDatadogMonitorDelete,
|
|
|
|
Exists: resourceDatadogMonitorExists,
|
2016-08-22 05:55:26 +02:00
|
|
|
Importer: &schema.ResourceImporter{
|
2016-12-05 14:16:47 +01:00
|
|
|
State: resourceDatadogMonitorImport,
|
2016-08-22 05:55:26 +02:00
|
|
|
},
|
2016-02-01 12:39:02 +01:00
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
2017-02-13 15:31:00 +01:00
|
|
|
"name": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"message": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
2016-03-22 19:47:08 +01:00
|
|
|
StateFunc: func(val interface{}) string {
|
|
|
|
return strings.TrimSpace(val.(string))
|
|
|
|
},
|
2016-02-01 12:39:02 +01:00
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"escalation_message": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2016-03-22 19:47:08 +01:00
|
|
|
StateFunc: func(val interface{}) string {
|
|
|
|
return strings.TrimSpace(val.(string))
|
|
|
|
},
|
2016-02-01 12:39:02 +01:00
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"query": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
2016-03-22 19:47:08 +01:00
|
|
|
StateFunc: func(val interface{}) string {
|
|
|
|
return strings.TrimSpace(val.(string))
|
|
|
|
},
|
2016-02-01 12:39:02 +01:00
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"type": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
// Options
|
2017-02-13 15:31:00 +01:00
|
|
|
"thresholds": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeMap,
|
2016-12-05 10:52:59 +01:00
|
|
|
Optional: true,
|
2016-02-01 12:39:02 +01:00
|
|
|
Elem: &schema.Resource{
|
|
|
|
Schema: map[string]*schema.Schema{
|
2017-02-13 15:31:00 +01:00
|
|
|
"ok": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeFloat,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"warning": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeFloat,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"critical": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeFloat,
|
2016-12-05 10:52:59 +01:00
|
|
|
Optional: true,
|
2016-02-01 12:39:02 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2016-12-05 14:16:47 +01:00
|
|
|
DiffSuppressFunc: suppressDataDogFloatIntDiff,
|
2016-02-01 12:39:02 +01:00
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"notify_no_data": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
2017-02-13 15:31:00 +01:00
|
|
|
Default: false,
|
2016-02-01 12:39:02 +01:00
|
|
|
},
|
2017-02-17 16:08:31 +01:00
|
|
|
"new_host_delay": {
|
|
|
|
Type: schema.TypeInt,
|
|
|
|
Computed: true,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"no_data_timeframe": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"renotify_interval": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"notify_audit": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"timeout_h": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeInt,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"require_full_window": {
|
2016-05-19 10:29:23 +02:00
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"locked": {
|
2016-05-19 10:29:23 +02:00
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2016-02-01 12:39:02 +01:00
|
|
|
// TODO should actually be map[string]int
|
2017-02-13 15:31:00 +01:00
|
|
|
"silenced": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeMap,
|
|
|
|
Optional: true,
|
|
|
|
Elem: &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Elem: &schema.Schema{
|
|
|
|
Type: schema.TypeInt},
|
|
|
|
},
|
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"include_tags": {
|
2016-02-01 12:39:02 +01:00
|
|
|
Type: schema.TypeBool,
|
|
|
|
Optional: true,
|
|
|
|
},
|
2017-02-13 15:31:00 +01:00
|
|
|
"tags": {
|
2016-12-07 12:05:57 +01:00
|
|
|
Type: schema.TypeList,
|
2016-08-18 17:54:44 +02:00
|
|
|
Optional: true,
|
2016-12-07 12:05:57 +01:00
|
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
2016-08-18 17:54:44 +02:00
|
|
|
},
|
2016-02-01 12:39:02 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func buildMonitorStruct(d *schema.ResourceData) *datadog.Monitor {
|
|
|
|
|
|
|
|
var thresholds datadog.ThresholdCount
|
|
|
|
|
|
|
|
if r, ok := d.GetOk("thresholds.ok"); ok {
|
|
|
|
thresholds.Ok = json.Number(r.(string))
|
|
|
|
}
|
|
|
|
if r, ok := d.GetOk("thresholds.warning"); ok {
|
|
|
|
thresholds.Warning = json.Number(r.(string))
|
|
|
|
}
|
|
|
|
if r, ok := d.GetOk("thresholds.critical"); ok {
|
|
|
|
thresholds.Critical = json.Number(r.(string))
|
|
|
|
}
|
|
|
|
|
|
|
|
o := datadog.Options{
|
2017-02-13 15:31:00 +01:00
|
|
|
Thresholds: thresholds,
|
|
|
|
NotifyNoData: d.Get("notify_no_data").(bool),
|
2016-02-01 12:39:02 +01:00
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("silenced"); ok {
|
|
|
|
s := make(map[string]int)
|
|
|
|
// TODO: this is not very defensive, test if we can fail on non int input
|
|
|
|
for k, v := range attr.(map[string]interface{}) {
|
|
|
|
s[k], _ = strconv.Atoi(v.(string))
|
|
|
|
}
|
|
|
|
o.Silenced = s
|
|
|
|
}
|
2017-02-17 16:08:31 +01:00
|
|
|
if attr, ok := d.GetOk("notify_no_data"); ok {
|
|
|
|
o.NotifyNoData = attr.(bool)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("new_host_delay"); ok {
|
|
|
|
o.NewHostDelay = datadog.Int(attr.(int))
|
|
|
|
}
|
2016-02-01 12:39:02 +01:00
|
|
|
if attr, ok := d.GetOk("no_data_timeframe"); ok {
|
2016-08-29 22:30:31 +02:00
|
|
|
o.NoDataTimeframe = datadog.NoDataTimeframe(attr.(int))
|
2016-02-01 12:39:02 +01:00
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("renotify_interval"); ok {
|
|
|
|
o.RenotifyInterval = attr.(int)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("notify_audit"); ok {
|
|
|
|
o.NotifyAudit = attr.(bool)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("timeout_h"); ok {
|
|
|
|
o.TimeoutH = attr.(int)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("escalation_message"); ok {
|
|
|
|
o.EscalationMessage = attr.(string)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("include_tags"); ok {
|
|
|
|
o.IncludeTags = attr.(bool)
|
|
|
|
}
|
2016-05-19 10:29:23 +02:00
|
|
|
if attr, ok := d.GetOk("require_full_window"); ok {
|
|
|
|
o.RequireFullWindow = attr.(bool)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("locked"); ok {
|
|
|
|
o.Locked = attr.(bool)
|
|
|
|
}
|
2016-02-01 12:39:02 +01:00
|
|
|
|
|
|
|
m := datadog.Monitor{
|
|
|
|
Type: d.Get("type").(string),
|
|
|
|
Query: d.Get("query").(string),
|
|
|
|
Name: d.Get("name").(string),
|
|
|
|
Message: d.Get("message").(string),
|
|
|
|
Options: o,
|
|
|
|
}
|
|
|
|
|
2016-08-18 17:54:44 +02:00
|
|
|
if attr, ok := d.GetOk("tags"); ok {
|
2016-12-07 12:05:57 +01:00
|
|
|
tags := []string{}
|
|
|
|
for _, s := range attr.([]interface{}) {
|
|
|
|
tags = append(tags, s.(string))
|
2016-08-18 17:54:44 +02:00
|
|
|
}
|
2016-12-07 12:05:57 +01:00
|
|
|
m.Tags = tags
|
2016-08-18 17:54:44 +02:00
|
|
|
}
|
|
|
|
|
2016-02-01 12:39:02 +01:00
|
|
|
return &m
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceDatadogMonitorExists(d *schema.ResourceData, meta interface{}) (b bool, e error) {
|
|
|
|
// Exists - This is called to verify a resource still exists. It is called prior to Read,
|
|
|
|
// and lowers the burden of Read to be able to assume the resource exists.
|
|
|
|
client := meta.(*datadog.Client)
|
|
|
|
|
|
|
|
i, err := strconv.Atoi(d.Id())
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = client.GetMonitor(i); err != nil {
|
|
|
|
if strings.Contains(err.Error(), "404 Not Found") {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceDatadogMonitorCreate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
|
|
|
|
client := meta.(*datadog.Client)
|
|
|
|
|
|
|
|
m := buildMonitorStruct(d)
|
|
|
|
m, err := client.CreateMonitor(m)
|
|
|
|
if err != nil {
|
2017-02-15 11:41:37 +01:00
|
|
|
return fmt.Errorf("error updating monitor: %s", err.Error())
|
2016-02-01 12:39:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
d.SetId(strconv.Itoa(m.Id))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceDatadogMonitorRead(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*datadog.Client)
|
|
|
|
|
|
|
|
i, err := strconv.Atoi(d.Id())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
m, err := client.GetMonitor(i)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-08-22 05:55:26 +02:00
|
|
|
thresholds := make(map[string]string)
|
|
|
|
for k, v := range map[string]json.Number{
|
|
|
|
"ok": m.Options.Thresholds.Ok,
|
|
|
|
"warning": m.Options.Thresholds.Warning,
|
|
|
|
"critical": m.Options.Thresholds.Critical,
|
|
|
|
} {
|
|
|
|
s := v.String()
|
|
|
|
if s != "" {
|
|
|
|
thresholds[k] = s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-07 12:05:57 +01:00
|
|
|
tags := []string{}
|
2016-08-22 05:55:26 +02:00
|
|
|
for _, s := range m.Tags {
|
2016-12-07 12:05:57 +01:00
|
|
|
tags = append(tags, s)
|
2016-08-22 05:55:26 +02:00
|
|
|
}
|
|
|
|
|
2016-02-01 12:39:02 +01:00
|
|
|
log.Printf("[DEBUG] monitor: %v", m)
|
|
|
|
d.Set("name", m.Name)
|
|
|
|
d.Set("message", m.Message)
|
|
|
|
d.Set("query", m.Query)
|
|
|
|
d.Set("type", m.Type)
|
2016-08-22 05:55:26 +02:00
|
|
|
d.Set("thresholds", thresholds)
|
2017-02-17 16:08:31 +01:00
|
|
|
|
|
|
|
d.Set("new_host_delay", m.Options.NewHostDelay)
|
2016-02-01 12:39:02 +01:00
|
|
|
d.Set("notify_no_data", m.Options.NotifyNoData)
|
2016-05-06 01:49:35 +02:00
|
|
|
d.Set("no_data_timeframe", m.Options.NoDataTimeframe)
|
2016-02-01 12:39:02 +01:00
|
|
|
d.Set("renotify_interval", m.Options.RenotifyInterval)
|
|
|
|
d.Set("notify_audit", m.Options.NotifyAudit)
|
|
|
|
d.Set("timeout_h", m.Options.TimeoutH)
|
|
|
|
d.Set("escalation_message", m.Options.EscalationMessage)
|
|
|
|
d.Set("silenced", m.Options.Silenced)
|
|
|
|
d.Set("include_tags", m.Options.IncludeTags)
|
2016-08-22 05:55:26 +02:00
|
|
|
d.Set("tags", tags)
|
2016-05-19 10:29:23 +02:00
|
|
|
d.Set("require_full_window", m.Options.RequireFullWindow)
|
|
|
|
d.Set("locked", m.Options.Locked)
|
2016-02-01 12:39:02 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceDatadogMonitorUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*datadog.Client)
|
|
|
|
|
|
|
|
m := &datadog.Monitor{}
|
|
|
|
|
|
|
|
i, err := strconv.Atoi(d.Id())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.Id = i
|
|
|
|
if attr, ok := d.GetOk("name"); ok {
|
|
|
|
m.Name = attr.(string)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("message"); ok {
|
|
|
|
m.Message = attr.(string)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("query"); ok {
|
|
|
|
m.Query = attr.(string)
|
|
|
|
}
|
|
|
|
|
2016-08-18 17:54:44 +02:00
|
|
|
if attr, ok := d.GetOk("tags"); ok {
|
|
|
|
s := make([]string, 0)
|
2016-12-07 12:05:57 +01:00
|
|
|
for _, v := range attr.([]interface{}) {
|
|
|
|
s = append(s, v.(string))
|
2016-08-18 17:54:44 +02:00
|
|
|
}
|
|
|
|
m.Tags = s
|
|
|
|
}
|
|
|
|
|
2017-02-13 15:31:00 +01:00
|
|
|
o := datadog.Options{
|
|
|
|
NotifyNoData: d.Get("notify_no_data").(bool),
|
|
|
|
}
|
2016-02-01 12:39:02 +01:00
|
|
|
if attr, ok := d.GetOk("thresholds"); ok {
|
|
|
|
thresholds := attr.(map[string]interface{})
|
|
|
|
if thresholds["ok"] != nil {
|
|
|
|
o.Thresholds.Ok = json.Number(thresholds["ok"].(string))
|
|
|
|
}
|
|
|
|
if thresholds["warning"] != nil {
|
|
|
|
o.Thresholds.Warning = json.Number(thresholds["warning"].(string))
|
|
|
|
}
|
|
|
|
if thresholds["critical"] != nil {
|
|
|
|
o.Thresholds.Critical = json.Number(thresholds["critical"].(string))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-17 16:08:31 +01:00
|
|
|
if attr, ok := d.GetOk("notify_no_data"); ok {
|
|
|
|
o.NotifyNoData = attr.(bool)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("new_host_delay"); ok {
|
|
|
|
o.NewHostDelay = datadog.Int(attr.(int))
|
|
|
|
}
|
2016-05-06 01:49:35 +02:00
|
|
|
if attr, ok := d.GetOk("no_data_timeframe"); ok {
|
2016-08-29 22:30:31 +02:00
|
|
|
o.NoDataTimeframe = datadog.NoDataTimeframe(attr.(int))
|
2016-02-01 12:39:02 +01:00
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("renotify_interval"); ok {
|
|
|
|
o.RenotifyInterval = attr.(int)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("notify_audit"); ok {
|
|
|
|
o.NotifyAudit = attr.(bool)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("timeout_h"); ok {
|
|
|
|
o.TimeoutH = attr.(int)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("escalation_message"); ok {
|
|
|
|
o.EscalationMessage = attr.(string)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("silenced"); ok {
|
|
|
|
// TODO: this is not very defensive, test if we can fail non int input
|
|
|
|
s := make(map[string]int)
|
|
|
|
for k, v := range attr.(map[string]interface{}) {
|
|
|
|
s[k], _ = strconv.Atoi(v.(string))
|
|
|
|
}
|
|
|
|
o.Silenced = s
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("include_tags"); ok {
|
|
|
|
o.IncludeTags = attr.(bool)
|
|
|
|
}
|
2016-05-19 10:29:23 +02:00
|
|
|
if attr, ok := d.GetOk("require_full_window"); ok {
|
|
|
|
o.RequireFullWindow = attr.(bool)
|
|
|
|
}
|
|
|
|
if attr, ok := d.GetOk("locked"); ok {
|
|
|
|
o.Locked = attr.(bool)
|
|
|
|
}
|
2016-02-01 12:39:02 +01:00
|
|
|
|
|
|
|
m.Options = o
|
|
|
|
|
|
|
|
if err = client.UpdateMonitor(m); err != nil {
|
2016-03-22 19:47:08 +01:00
|
|
|
return fmt.Errorf("error updating monitor: %s", err.Error())
|
2016-02-01 12:39:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return resourceDatadogMonitorRead(d, meta)
|
|
|
|
}
|
|
|
|
|
|
|
|
func resourceDatadogMonitorDelete(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
client := meta.(*datadog.Client)
|
|
|
|
|
|
|
|
i, err := strconv.Atoi(d.Id())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = client.DeleteMonitor(i); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2016-08-22 05:55:26 +02:00
|
|
|
|
2016-12-05 14:16:47 +01:00
|
|
|
func resourceDatadogMonitorImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
2016-08-22 05:55:26 +02:00
|
|
|
if err := resourceDatadogMonitorRead(d, meta); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return []*schema.ResourceData{d}, nil
|
|
|
|
}
|
2016-10-20 02:31:12 +02:00
|
|
|
|
|
|
|
// Ignore any diff that results from the mix of ints or floats returned from the
|
|
|
|
// DataDog API.
|
2016-12-05 14:16:47 +01:00
|
|
|
func suppressDataDogFloatIntDiff(k, old, new string, d *schema.ResourceData) bool {
|
2016-10-20 02:31:12 +02:00
|
|
|
oF, err := strconv.ParseFloat(old, 64)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error parsing float of old value (%s): %s", old, err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
nF, err := strconv.ParseFloat(new, 64)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error parsing float of new value (%s): %s", new, err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the float values of these attributes are equivalent, ignore this
|
|
|
|
// diff
|
|
|
|
if oF == nF {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|