diff --git a/.github/workflows/test-zabbix.yml b/.github/workflows/test-zabbix.yml index 2171e55..4649fe9 100644 --- a/.github/workflows/test-zabbix.yml +++ b/.github/workflows/test-zabbix.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - zabbix_version: ["3.0", "3.2", "3.4", "4.0", "4.2", "4.4", "5.0", "5.2"] + zabbix_version: ["3.0", "3.2", "3.4", "4.0", "4.2", "4.4", "5.0", "5.2", "5.4", "6.0", "6.2"] services: mysql-server: diff --git a/application_test.go b/application_test.go index 69232fb..1228b8f 100644 --- a/application_test.go +++ b/application_test.go @@ -26,6 +26,8 @@ func testDeleteApplication(app *zapi.Application, t *testing.T) { } func TestApplications(t *testing.T) { + skipTestIfVersionGreaterThanOrEqual(t, "5.4", "dropped support for Application API") + api := testGetAPI(t) group := testCreateHostGroup(t) diff --git a/base_test.go b/base_test.go index a1bf46f..0d59773 100644 --- a/base_test.go +++ b/base_test.go @@ -10,6 +10,7 @@ import ( "time" zapi "github.com/claranet/go-zabbix-api" + "github.com/hashicorp/go-version" ) var ( @@ -62,6 +63,39 @@ func testGetAPI(t *testing.T) *zapi.API { return _api } +func compVersion(t *testing.T, comparedVersion string) (int, string) { + api := testGetAPI(t) + serverVersion, err := api.Version() + if err != nil { + t.Fatal(err) + } + sVersion, _ := version.NewVersion(serverVersion) + cVersion, _ := version.NewVersion(comparedVersion) + return sVersion.Compare(cVersion), serverVersion +} + +func isVersionLessThan(t *testing.T, comparedVersion string) (bool, string) { + comp, serverVersion := compVersion(t, comparedVersion) + return comp < 0, serverVersion +} + +func isVersionGreaterThanOrEqual(t *testing.T, comparedVersion string) (bool, string) { + comp, serverVersion := compVersion(t, comparedVersion) + return comp >= 0, serverVersion +} + +func skipTestIfVersionGreaterThanOrEqual(t *testing.T, comparedVersion, msg string) { + if compGreaterThanOrEqual, serverVersion := isVersionGreaterThanOrEqual(t, comparedVersion); compGreaterThanOrEqual { + t.Skipf("Zabbix version %s is greater than or equal to %s which %s, skipping test.", serverVersion, comparedVersion, msg) + } +} + +func skipTestIfVersionLessThan(t *testing.T, comparedVersion, msg string) { + if compGreaterThanOrEqual, serverVersion := isVersionLessThan(t, comparedVersion); compGreaterThanOrEqual { + t.Skipf("Zabbix version %s is less than to %s which %s, skipping test.", serverVersion, comparedVersion, msg) + } +} + func TestBadCalls(t *testing.T) { api := testGetAPI(t) res, err := api.Call("", nil) diff --git a/go.mod b/go.mod index 0a0a8c1..ec726bf 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/claranet/go-zabbix-api go 1.18 + +require github.com/hashicorp/go-version v1.6.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..805e323 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= diff --git a/host.go b/host.go index 37916eb..d5bfd41 100644 --- a/host.go +++ b/host.go @@ -145,16 +145,15 @@ func (api *API) HostsDelete(hosts Hosts) (err error) { // HostsDeleteByIds Wrapper for host.delete // https://www.zabbix.com/documentation/3.2/manual/api/reference/host/delete func (api *API) HostsDeleteByIds(ids []string) (err error) { - hostIds := make([]map[string]string, len(ids)) - for i, id := range ids { - hostIds[i] = map[string]string{"hostid": id} - } - - response, err := api.CallWithError("host.delete", hostIds) + response, err := api.CallWithError("host.delete", ids) if err != nil { - // Zabbix 2.4 uses new syntax only if e, ok := err.(*Error); ok && e.Code == -32500 { - response, err = api.CallWithError("host.delete", ids) + // Zabbix 2.0 and older use old syntax only with hostid + hostIds := make([]map[string]string, len(ids)) + for i, id := range ids { + hostIds[i] = map[string]string{"hostid": id} + } + response, err = api.CallWithError("host.delete", hostIds) } } if err != nil { diff --git a/item_prototype_test.go b/item_prototype_test.go index 3841194..e63a7e0 100644 --- a/item_prototype_test.go +++ b/item_prototype_test.go @@ -4,6 +4,7 @@ import ( "testing" dd "github.com/claranet/go-zabbix-api" + zapi "github.com/claranet/go-zabbix-api" ) func testCreateItemPrototype(template *dd.Template, lldRule *dd.LLDRule, t *testing.T) *dd.ItemPrototype { @@ -34,10 +35,27 @@ func testDeleteItemPrototype(item *dd.ItemPrototype, t *testing.T) { func testItemPrototype(t *testing.T) { api := testGetAPI(t) - hostGroup := testCreateHostGroup(t) - defer testDeleteHostGroup(hostGroup, t) + // Zabbix v6.2 introduced Template Groups and requires them for Templates + var groupIds zapi.HostGroupIDs + if compLessThan, _ := isVersionLessThan(t, "6.2"); compLessThan { + hostGroup := testCreateHostGroup(t) + defer testDeleteHostGroup(hostGroup, t) + groupIds = zapi.HostGroupIDs{ + { + GroupID: hostGroup.GroupID, + }, + } + } else { + templateGroup := testCreateTemplateGroup(t) + defer testDeleteTemplateGroup(templateGroup, t) + groupIds = zapi.HostGroupIDs{ + { + GroupID: templateGroup.GroupID, + }, + } + } - template := testCreateTemplate(hostGroup, t) + template := testCreateTemplate(&groupIds, t) defer testDeleteTemplate(template, t) lldRule := testCreateLLDRule(template, t) diff --git a/item_test.go b/item_test.go index b62a41c..a6a2828 100644 --- a/item_test.go +++ b/item_test.go @@ -6,7 +6,22 @@ import ( zapi "github.com/claranet/go-zabbix-api" ) -func testCreateItem(app *zapi.Application, t *testing.T) *zapi.Item { +func testCreateItem(host *zapi.Host, t *testing.T) *zapi.Item { + items := zapi.Items{{ + HostID: host.HostID, + Key: "key.lala.laa", + Name: "name for key", + Type: zapi.ZabbixTrapper, + Delay: "0", + }} + err := testGetAPI(t).ItemsCreate(items) + if err != nil { + t.Fatal(err) + } + return &items[0] +} + +func testCreateItemWithApplication(app *zapi.Application, t *testing.T) *zapi.Item { items := zapi.Items{{ HostID: app.HostID, Key: "key.lala.laa", @@ -38,6 +53,33 @@ func TestItems(t *testing.T) { host := testCreateHost(group, t) defer testDeleteHost(host, t) + item := testCreateItem(host, t) + + _, err := api.ItemGetByID(item.ItemID) + if err != nil { + t.Fatal(err) + } + + item.Name = "another name" + err = api.ItemsUpdate(zapi.Items{*item}) + if err != nil { + t.Error(err) + } + + testDeleteItem(item, t) +} + +func TestItemsWithApplication(t *testing.T) { + skipTestIfVersionGreaterThanOrEqual(t, "5.4", "dropped support for Application API") + + api := testGetAPI(t) + + group := testCreateHostGroup(t) + defer testDeleteHostGroup(group, t) + + host := testCreateHost(group, t) + defer testDeleteHost(host, t) + app := testCreateApplication(host, t) defer testDeleteApplication(app, t) @@ -49,7 +91,7 @@ func TestItems(t *testing.T) { t.Fatal("Found items") } - item := testCreateItem(app, t) + item := testCreateItemWithApplication(app, t) _, err = api.ItemGetByID(item.ItemID) if err != nil { diff --git a/lld_rule_test.go b/lld_rule_test.go index 1676418..e833be6 100644 --- a/lld_rule_test.go +++ b/lld_rule_test.go @@ -4,6 +4,7 @@ import ( "testing" dd "github.com/claranet/go-zabbix-api" + zapi "github.com/claranet/go-zabbix-api" ) func testCreateLLDRule(template *dd.Template, t *testing.T) *dd.LLDRule { @@ -42,10 +43,27 @@ func testDeleteLLDRule(rule *dd.LLDRule, t *testing.T) { func TestLLDRule(t *testing.T) { api := testGetAPI(t) - hostGroup := testCreateHostGroup(t) - defer testDeleteHostGroup(hostGroup, t) + // Zabbix v6.2 introduced Template Groups and requires them for Templates + var groupIds zapi.HostGroupIDs + if compLessThan, _ := isVersionLessThan(t, "6.2"); compLessThan { + hostGroup := testCreateHostGroup(t) + defer testDeleteHostGroup(hostGroup, t) + groupIds = zapi.HostGroupIDs{ + { + GroupID: hostGroup.GroupID, + }, + } + } else { + templateGroup := testCreateTemplateGroup(t) + defer testDeleteTemplateGroup(templateGroup, t) + groupIds = zapi.HostGroupIDs{ + { + GroupID: templateGroup.GroupID, + }, + } + } - template := testCreateTemplate(hostGroup, t) + template := testCreateTemplate(&groupIds, t) defer testDeleteTemplate(template, t) lldRule := testCreateLLDRule(template, t) diff --git a/template.go b/template.go index b941b92..0215920 100644 --- a/template.go +++ b/template.go @@ -3,15 +3,15 @@ package zabbix // Template represent Zabbix Template type returned from Zabbix API // https://www.zabbix.com/documentation/3.2/manual/api/reference/template/object type Template struct { - TemplateID string `json:"templateid,omitempty"` - Host string `json:"host"` - Description string `json:"description,omitempty"` - Name string `json:"name,omitempty"` - Groups HostGroups `json:"groups"` - UserMacros Macros `json:"macros"` - LinkedTemplates Templates `json:"templates,omitempty"` - TemplatesClear Templates `json:"templates_clear,omitempty"` - LinkedHosts Hosts `json:"hosts,omitempty"` + TemplateID string `json:"templateid,omitempty"` + Host string `json:"host"` + Description string `json:"description,omitempty"` + Name string `json:"name,omitempty"` + Groups HostGroupIDs `json:"groups"` + UserMacros Macros `json:"macros,omitempty"` + LinkedTemplates Templates `json:"templates,omitempty"` + TemplatesClear Templates `json:"templates_clear,omitempty"` + LinkedHosts Hosts `json:"hosts,omitempty"` } // Templates is an Array of Template structs. diff --git a/template_group.go b/template_group.go new file mode 100644 index 0000000..341c774 --- /dev/null +++ b/template_group.go @@ -0,0 +1,95 @@ +package zabbix + +// TemplateGroup represent Zabbix template group object, new in v6.2 +// https://www.zabbix.com/documentation/6.2/en/manual/api/reference/templategroup/object +type TemplateGroup struct { + GroupID string `json:"groupid,omitempty"` + Name string `json:"name"` +} + +// TemplateGroups is an array of TemplateGroup +type TemplateGroups []TemplateGroup + +// TemplateGroupsGet Wrapper for templategroup.get +// https://www.zabbix.com/documentation/6.2/en/manual/api/reference/templategroup/get +func (api *API) TemplateGroupsGet(params Params) (res TemplateGroups, err error) { + if _, present := params["output"]; !present { + params["output"] = "extend" + } + err = api.CallWithErrorParse("templategroup.get", params, &res) + return +} + +// TemplateGroupGetByID Gets host group by Id only if there is exactly 1 matching host group. +// https://www.zabbix.com/documentation/6.2/en/manual/api/reference/templategroup/get +func (api *API) TemplateGroupGetByID(id string) (res *TemplateGroup, err error) { + groups, err := api.TemplateGroupsGet(Params{"groupids": id}) + if err != nil { + return + } + + if len(groups) == 1 { + res = &groups[0] + } else { + e := ExpectedOneResult(len(groups)) + err = &e + } + return +} + +// TemplateGroupsCreate Wrapper for templategroup.create +// https://www.zabbix.com/documentation/6.2/en/manual/api/reference/templategroup/create +func (api *API) TemplateGroupsCreate(TemplateGroups TemplateGroups) (err error) { + response, err := api.CallWithError("templategroup.create", TemplateGroups) + if err != nil { + return + } + + result := response.Result.(map[string]interface{}) + groupids := result["groupids"].([]interface{}) + for i, id := range groupids { + TemplateGroups[i].GroupID = id.(string) + } + return +} + +// TemplateGroupsUpdate Wrapper for templategroup.update +// https://www.zabbix.com/documentation/6.2/en/manual/api/reference/templategroup/update +func (api *API) TemplateGroupsUpdate(TemplateGroups TemplateGroups) (err error) { + _, err = api.CallWithError("templategroup.update", TemplateGroups) + return +} + +// TemplateGroupsDelete Wrapper for templategroup.delete +// Cleans GroupId in all TemplateGroups elements if call succeed. +// https://www.zabbix.com/documentation/6.2/en/manual/api/reference/templategroup/delete +func (api *API) TemplateGroupsDelete(TemplateGroups TemplateGroups) (err error) { + ids := make([]string, len(TemplateGroups)) + for i, group := range TemplateGroups { + ids[i] = group.GroupID + } + + err = api.TemplateGroupsDeleteByIds(ids) + if err == nil { + for i := range TemplateGroups { + TemplateGroups[i].GroupID = "" + } + } + return +} + +// TemplateGroupsDeleteByIds Wrapper for templategroup.delete +// https://www.zabbix.com/documentation/6.2/en/manual/api/reference/templategroup/delete +func (api *API) TemplateGroupsDeleteByIds(ids []string) (err error) { + response, err := api.CallWithError("templategroup.delete", ids) + if err != nil { + return + } + + result := response.Result.(map[string]interface{}) + groupids := result["groupids"].([]interface{}) + if len(ids) != len(groupids) { + err = &ExpectedMore{len(ids), len(groupids)} + } + return +} diff --git a/template_group_test.go b/template_group_test.go new file mode 100644 index 0000000..a5a4398 --- /dev/null +++ b/template_group_test.go @@ -0,0 +1,68 @@ +package zabbix_test + +import ( + "fmt" + "math/rand" + "reflect" + "testing" + + zapi "github.com/claranet/go-zabbix-api" +) + +func testCreateTemplateGroup(t *testing.T) *zapi.TemplateGroup { + TemplateGroups := zapi.TemplateGroups{{Name: fmt.Sprintf("zabbix-testing-%d", rand.Int())}} + err := testGetAPI(t).TemplateGroupsCreate(TemplateGroups) + if err != nil { + t.Fatal(err) + } + return &TemplateGroups[0] +} + +func testDeleteTemplateGroup(TemplateGroup *zapi.TemplateGroup, t *testing.T) { + err := testGetAPI(t).TemplateGroupsDelete(zapi.TemplateGroups{*TemplateGroup}) + if err != nil { + t.Fatal(err) + } +} + +func TestTemplateGroups(t *testing.T) { + skipTestIfVersionLessThan(t, "6.2", "introduced support for Template Groups API") + + api := testGetAPI(t) + + groups, err := api.TemplateGroupsGet(zapi.Params{}) + if err != nil { + t.Fatal(err) + } + + TemplateGroup := testCreateTemplateGroup(t) + if TemplateGroup.GroupID == "" || TemplateGroup.Name == "" { + t.Errorf("Something is empty: %#v", TemplateGroup) + } + + TemplateGroup2, err := api.TemplateGroupGetByID(TemplateGroup.GroupID) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(TemplateGroup, TemplateGroup2) { + t.Errorf("Error getting group.\nOld group: %#v\nNew group: %#v", TemplateGroup, TemplateGroup2) + } + + groups2, err := api.TemplateGroupsGet(zapi.Params{}) + if err != nil { + t.Fatal(err) + } + if len(groups2) != len(groups)+1 { + t.Errorf("Error creating group.\nOld groups: %#v\nNew groups: %#v", groups, groups2) + } + + testDeleteTemplateGroup(TemplateGroup, t) + + groups2, err = api.TemplateGroupsGet(zapi.Params{}) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(groups, groups2) { + t.Errorf("Error deleting group.\nOld groups: %#v\nNew groups: %#v", groups, groups2) + } +} diff --git a/template_test.go b/template_test.go index ba72dba..7f91238 100644 --- a/template_test.go +++ b/template_test.go @@ -6,10 +6,10 @@ import ( zapi "github.com/claranet/go-zabbix-api" ) -func testCreateTemplate(hostGroup *zapi.HostGroup, t *testing.T) *zapi.Template { +func testCreateTemplate(hostGroups *zapi.HostGroupIDs, t *testing.T) *zapi.Template { template := zapi.Templates{zapi.Template{ Host: "template name", - Groups: zapi.HostGroups{*hostGroup}, + Groups: *hostGroups, }} err := testGetAPI(t).TemplatesCreate(template) if err != nil { @@ -28,10 +28,27 @@ func testDeleteTemplate(template *zapi.Template, t *testing.T) { func TestTemplates(t *testing.T) { api := testGetAPI(t) - hostGroup := testCreateHostGroup(t) - defer testDeleteHostGroup(hostGroup, t) + // Zabbix v6.2 introduced Template Groups and requires them for Templates + var groupIds zapi.HostGroupIDs + if compLessThan, _ := isVersionLessThan(t, "6.2"); compLessThan { + hostGroup := testCreateHostGroup(t) + defer testDeleteHostGroup(hostGroup, t) + groupIds = zapi.HostGroupIDs{ + { + GroupID: hostGroup.GroupID, + }, + } + } else { + templateGroup := testCreateTemplateGroup(t) + defer testDeleteTemplateGroup(templateGroup, t) + groupIds = zapi.HostGroupIDs{ + { + GroupID: templateGroup.GroupID, + }, + } + } - template := testCreateTemplate(hostGroup, t) + template := testCreateTemplate(&groupIds, t) if template.TemplateID == "" { t.Errorf("Template id is empty %#v", template) } diff --git a/trigger_prototype_test.go b/trigger_prototype_test.go index 0ac26e0..037233c 100644 --- a/trigger_prototype_test.go +++ b/trigger_prototype_test.go @@ -6,6 +6,7 @@ import ( "github.com/claranet/go-zabbix-api" dd "github.com/claranet/go-zabbix-api" + zapi "github.com/claranet/go-zabbix-api" ) func testCreateTriggerPrototype(template *dd.Template, item *dd.ItemPrototype, t *testing.T) *dd.TriggerPrototype { @@ -31,10 +32,27 @@ func testDeleteTriggerPrototype(trigger *dd.TriggerPrototype, t *testing.T) { func testTriggerPrototype(t *testing.T) { api := testGetAPI(t) - hostGroup := testCreateHostGroup(t) - defer testDeleteHostGroup(hostGroup, t) + // Zabbix v6.2 introduced Template Groups and requires them for Templates + var groupIds zapi.HostGroupIDs + if compLessThan, _ := isVersionLessThan(t, "6.2"); compLessThan { + hostGroup := testCreateHostGroup(t) + defer testDeleteHostGroup(hostGroup, t) + groupIds = zapi.HostGroupIDs{ + { + GroupID: hostGroup.GroupID, + }, + } + } else { + templateGroup := testCreateTemplateGroup(t) + defer testDeleteTemplateGroup(templateGroup, t) + groupIds = zapi.HostGroupIDs{ + { + GroupID: templateGroup.GroupID, + }, + } + } - template := testCreateTemplate(hostGroup, t) + template := testCreateTemplate(&groupIds, t) defer testDeleteTemplate(template, t) lldRule := testCreateLLDRule(template, t) diff --git a/trigger_test.go b/trigger_test.go index 27546a7..eb9471a 100644 --- a/trigger_test.go +++ b/trigger_test.go @@ -8,7 +8,13 @@ import ( ) func testCreateTrigger(item *zapi.Item, host *zapi.Host, t *testing.T) *zapi.Trigger { - expression := fmt.Sprintf("{%s:%s.last()}=0", host.Host, item.Key) + var expression string + if compGreaterThanOrEqual, _ := isVersionGreaterThanOrEqual(t, "5.4"); compGreaterThanOrEqual { + expression = fmt.Sprintf("last(/%s/%s)=0", host.Host, item.Key) + } else { + expression = fmt.Sprintf("{%s:%s.last()}=0", host.Host, item.Key) + } + triggers := zapi.Triggers{{ Description: "trigger description", Expression: expression, @@ -36,10 +42,44 @@ func TestTrigger(t *testing.T) { host := testCreateHost(group, t) defer testDeleteHost(host, t) + item := testCreateItem(host, t) + defer testDeleteItem(item, t) + + triggerParam := zapi.Params{"hostids": host.HostID} + res, err := api.TriggersGet(triggerParam) + if err != nil { + t.Fatal(err) + } + if len(res) != 0 { + t.Fatal("Found items") + } + + trigger := testCreateTrigger(item, host, t) + + trigger.Description = "new trigger name" + err = api.TriggersUpdate(zapi.Triggers{*trigger}) + if err != nil { + t.Error(err) + } + + testDeleteTrigger(trigger, t) +} + +func TestTriggerWithApplication(t *testing.T) { + skipTestIfVersionGreaterThanOrEqual(t, "5.4", "dropped support for Application API") + + api := testGetAPI(t) + + group := testCreateHostGroup(t) + defer testDeleteHostGroup(group, t) + + host := testCreateHost(group, t) + defer testDeleteHost(host, t) + app := testCreateApplication(host, t) defer testDeleteApplication(app, t) - item := testCreateItem(app, t) + item := testCreateItemWithApplication(app, t) defer testDeleteItem(item, t) triggerParam := zapi.Params{"hostids": host.HostID}