From 89e23571c916f43c290d2a39c3260fa05ee3a74a Mon Sep 17 00:00:00 2001 From: Francis Guimond Date: Mon, 3 Apr 2023 16:26:18 -0400 Subject: [PATCH 1/5] Add `--details-format` option It's used to be able to specify if the event details should be sent as a string or a JSON document --- CHANGELOG.md | 7 +++- README.md | 18 ++++++--- main.go | 54 ++++++++++++++++++++++---- main_test.go | 107 +++++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 160 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f93c23..233018f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,12 @@ Versioning](http://semver.org/spec/v2.0.0.html). ## Unreleased -## 2.3.0 – 2022-05-06 +## 2.4.0 - 2023-04-03 + +### +- Add `--details-format` option to be able to specify if the event details should be sent as a string or a JSON document + +## 2.3.0 – 2022-05-06 ### Changed diff --git a/README.md b/README.md index c16b99c..c8b63b6 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Available Commands: Flags: --contact-routing Enable contact routing -k, --dedup-key-template string The PagerDuty V2 API deduplication key template, can be set with PAGERDUTY_DEDUP_KEY_TEMPLATE (default "{{.Entity.Name}}-{{.Check.Name}}") + --details-format string The format of the details output ('string' or 'json'), can be set with PAGERDUTY_DETAILS_FORMAT (default "string") -d, --details-template string The template for the alert details, can be set with PAGERDUTY_DETAILS_TEMPLATE (default full event JSON) -h, --help help for sensu-pagerduty-handler -s, --status-map string The status map used to translate a Sensu check status to a PagerDuty severity, can be set with PAGERDUTY_STATUS_MAP @@ -132,6 +133,7 @@ spec: --status-map "{\"info\":[0],\"warning\": [1],\"critical\": [2],\"error\": [3,127]}" --summary-template "[{{.Entity.Namespace}}] {{.Entity.Name}}/{{.Check.Name}}: {{.Check.State}}" --details-template "{{.Check.Output}}\n\n{{.Check}}" + --details-format string timeout: 10 runtime_assets: - sensu/sensu-pagerduty-handler @@ -148,12 +150,16 @@ Most arguments for this handler are available to be set via environment variables. However, any arguments specified directly on the command line override the corresponding environment variable. -|Argument |Environment Variable | -|--------------------|----------------------------| -|--token |PAGERDUTY_TOKEN | -|--summary-template |PAGERDUTY_SUMMARY_TEMPLATE | -|--dedup-key-template|PAGERDUTY_DEDUP_KEY_TEMPLATE| -|--status-map |PAGERDUTY_STATUS_MAP | +| Argument | Environment Variable | +|----------------------|------------------------------| +| --dedup-key-template | PAGERDUTY_DEDUP_KEY_TEMPLATE | +| --details-template | PAGERDUTY_DETAILS_TEMPLATE | +| --details-format | PAGERDUTY_DETAILS_FORMAT | +| --status-map | PAGERDUTY_STATUS_MAP | +| --summary-template | PAGERDUTY_SUMMARY_TEMPLATE | +| --team | PAGERDUTY_TEAM | +| --team-suffix | PAGERDUTY_TEAM_SUFFIX | +| --token | PAGERDUTY_TOKEN | **Security Note:** Care should be taken to not expose the auth token for this handler by specifying it on the command line or by directly setting the diff --git a/main.go b/main.go index 04ddb6e..a608524 100644 --- a/main.go +++ b/main.go @@ -25,12 +25,32 @@ type HandlerConfig struct { teamName string teamSuffix string detailsTemplate string + detailsFormat string contactRouting bool contacts []string } type eventStatusMap map[string][]uint32 +type detailsFormat string + +const ( + stringDetailsFormat detailsFormat = "string" + jsonDetailsFormat detailsFormat = "json" +) + +func (df detailsFormat) IsValid() bool { + switch df { + case stringDetailsFormat, jsonDetailsFormat: + return true + } + return false +} + +func (df detailsFormat) String() string { + return string(df) +} + var ( config = HandlerConfig{ PluginConfig: sensu.PluginConfig{ @@ -103,6 +123,15 @@ var ( Value: &config.detailsTemplate, Default: "", }, + { + Path: "details-format", + Env: "PAGERDUTY_DETAILS_FORMAT", + Argument: "details-format", + Shorthand: "", + Usage: "The format of the details output ('string' or 'json'), can be set with PAGERDUTY_DETAILS_FORMAT", + Value: &config.detailsFormat, + Default: "string", + }, { Path: "", Env: "", @@ -182,6 +211,10 @@ func checkArgs(event *corev2.Event) error { } } + if !detailsFormat(config.detailsFormat).IsValid() { + return fmt.Errorf("invalid details format: %s", config.detailsFormat) + } + return nil } @@ -395,7 +428,7 @@ func parseStatusMap(statusMapJSON string) (map[uint32]string, error) { return nil, fmt.Errorf("invalid pagerduty severity: %s", severity) } for i := range statuses { - statusToSeverityMap[uint32(statuses[i])] = severity + statusToSeverityMap[statuses[i]] = severity } } @@ -415,17 +448,22 @@ func getSummary(event *corev2.Event) (string, error) { return summary, nil } -func getDetails(event *corev2.Event) (interface{}, error) { - var ( - details interface{} - err error - ) - +func getDetails(event *corev2.Event) (details interface{}, err error) { if len(config.detailsTemplate) > 0 { - details, err = templates.EvalTemplate("details", config.detailsTemplate, event) + detailsStr, err := templates.EvalTemplate("details", config.detailsTemplate, event) if err != nil { return "", fmt.Errorf("failed to evaluate template %s: %v", config.detailsTemplate, err) } + + details = detailsStr + if config.detailsFormat == jsonDetailsFormat.String() { + var msgMap interface{} + err = json.Unmarshal([]byte(detailsStr), &msgMap) + if err != nil { + return "", fmt.Errorf("failed to unmarshal json details: %v", err) + } + details = msgMap + } } else { details = event } diff --git a/main_test.go b/main_test.go index 6672b38..953884c 100644 --- a/main_test.go +++ b/main_test.go @@ -19,9 +19,9 @@ var ( ) func Test_ParseStatusMap_Success(t *testing.T) { - json := "{\"info\":[130,10],\"error\":[4]}" + statusJSON := "{\"info\":[130,10],\"error\":[4]}" - statusMap, err := parseStatusMap(json) + statusMap, err := parseStatusMap(statusJSON) assert.Nil(t, err) assert.Equal(t, 3, len(statusMap)) assert.Equal(t, "info", statusMap[130]) @@ -30,9 +30,9 @@ func Test_ParseStatusMap_Success(t *testing.T) { } func Test_ParseStatusMap_EmptyStatus(t *testing.T) { - json := "{\"info\":[130,10],\"error\":[]}" + statusJSON := "{\"info\":[130,10],\"error\":[]}" - statusMap, err := parseStatusMap(json) + statusMap, err := parseStatusMap(statusJSON) assert.Nil(t, err) assert.Equal(t, 2, len(statusMap)) assert.Equal(t, "info", statusMap[130]) @@ -41,18 +41,18 @@ func Test_ParseStatusMap_EmptyStatus(t *testing.T) { } func Test_ParseStatusMap_InvalidJson(t *testing.T) { - json := "{\"info\":[130,10],\"error:[]}" + statusJSON := "{\"info\":[130,10],\"error:[]}" - statusMap, err := parseStatusMap(json) + statusMap, err := parseStatusMap(statusJSON) assert.NotNil(t, err) assert.EqualError(t, err, "unexpected end of JSON input") assert.Nil(t, statusMap) } func Test_ParseStatusMap_InvalidSeverity(t *testing.T) { - json := "{\"info\":[130,10],\"invalid\":[4]}" + statusJSON := "{\"info\":[130,10],\"invalid\":[4]}" - statusMap, err := parseStatusMap(json) + statusMap, err := parseStatusMap(statusJSON) assert.NotNil(t, err) assert.EqualError(t, err, "invalid pagerduty severity: invalid") assert.Nil(t, statusMap) @@ -111,7 +111,7 @@ func Test_GetPagerDutyDedupKey(t *testing.T) { func Test_PagerTeamToken(t *testing.T) { config.teamName = "test_team" config.teamSuffix = "_test_suffix" - os.Setenv("test_team_test_suffix", "token_value") + _ = os.Setenv("test_team_test_suffix", "token_value") teamToken, err := getTeamToken() assert.Nil(t, err) assert.NotNil(t, teamToken) @@ -121,7 +121,7 @@ func Test_PagerTeamToken(t *testing.T) { func Test_PagerIllegalTeamToken(t *testing.T) { config.teamName = "test-team" config.teamSuffix = "_test-a-suffix" - os.Setenv("test_team_test_a_suffix", "token_value") + _ = os.Setenv("test_team_test_a_suffix", "token_value") teamToken, err := getTeamToken() assert.Nil(t, err) assert.NotNil(t, teamToken) @@ -131,7 +131,7 @@ func Test_PagerIllegalTeamToken(t *testing.T) { func Test_PagerTeamNoSuffix(t *testing.T) { config.teamName = "test-team" config.teamSuffix = "" - os.Setenv("test_team", "token_value") + _ = os.Setenv("test_team", "token_value") teamToken, err := getTeamToken() assert.Nil(t, err) assert.NotNil(t, teamToken) @@ -171,6 +171,46 @@ func Test_GetDetailsTemplate(t *testing.T) { assert.Equal(t, "foo-bar", details) } +func Test_GetDetailsObj(t *testing.T) { + tests := []struct { + name string + detailsTemplate string + expectError bool + }{ + { + name: "valid-json", + detailsTemplate: `{"entity": "{{.Entity.Name}}", "check":"{{.Check.Name}}", "namespace":"{{.Namespace}}", "id":"{{.GetUUID.String}}"}`, + expectError: false, + }, + { + name: "invalid-json", + detailsTemplate: `{"entity": "{{.Entity.Name}}"WHAT?}`, + expectError: true, + }, + } + event := corev2.FixtureEvent("entity-name", "check-name") + config.detailsFormat = "json" + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + config.detailsTemplate = test.detailsTemplate + details, err := getDetails(event) + if test.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, details) + detailMap, ok := details.(map[string]interface{}) + assert.True(t, ok) + assert.Equal(t, "entity-name", detailMap["entity"]) + assert.Equal(t, "check-name", detailMap["check"]) + assert.Equal(t, "default", detailMap["namespace"]) + assert.Equal(t, event.GetUUID().String(), detailMap["id"]) + } + }) + } +} + func Test_checkArgs(t *testing.T) { originalConfig := config type args struct { @@ -210,6 +250,51 @@ func Test_checkArgs(t *testing.T) { wantErr: true, wantErrMsg: "invalid contact syntax: invalid-contact", }, + { + name: "no error with json details format", + config: HandlerConfig{ + detailsFormat: "json", + authToken: "aaa", + }, + args: args{ + event: func() *corev2.Event { + event := corev2.FixtureEvent("foo", "bar") + return event + }(), + }, + wantErr: false, + wantErrMsg: "", + }, + { + name: "no error with string details format", + config: HandlerConfig{ + detailsFormat: "string", + authToken: "aaa", + }, + args: args{ + event: func() *corev2.Event { + event := corev2.FixtureEvent("foo", "bar") + return event + }(), + }, + wantErr: false, + wantErrMsg: "", + }, + { + name: "no error with string details format", + config: HandlerConfig{ + detailsFormat: "invalidformat", + authToken: "aaa", + }, + args: args{ + event: func() *corev2.Event { + event := corev2.FixtureEvent("foo", "bar") + return event + }(), + }, + wantErr: true, + wantErrMsg: "invalid details format: invalidformat", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From fd866a34b6f9682ad0623b7b9cb20ea188eea6b3 Mon Sep 17 00:00:00 2001 From: Francis Guimond Date: Mon, 3 Apr 2023 17:34:27 -0400 Subject: [PATCH 2/5] Try to fix staticcheck error Update golang.org/x/x libraries to remove vulnerabilities Update to GO 1.20.x --- .github/workflows/release.yml | 2 +- .github/workflows/static-check.yml | 4 ++-- .github/workflows/test.yml | 2 +- go.mod | 8 ++++---- go.sum | 10 ++++++---- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3c1179d..3253602 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v1 with: - go-version: 1.18.x + go-version: 1.20.x - name: Run GoReleaser uses: goreleaser/goreleaser-action@v1 with: diff --git a/.github/workflows/static-check.yml b/.github/workflows/static-check.yml index db86351..e3d038d 100644 --- a/.github/workflows/static-check.yml +++ b/.github/workflows/static-check.yml @@ -12,6 +12,6 @@ jobs: fetch-depth: 1 - uses: dominikh/staticcheck-action@v1.2.0 with: - version: "2022.1" + version: "2023.1.3" env: - GO_VERSION: 1.18.1 + GO_VERSION: 1.20.x diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f1c7764..61b419c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Go 1.18 uses: actions/setup-go@v1 with: - go-version: 1.18.x + go-version: 1.20.x id: go - name: Test run: go test -v ./... diff --git a/go.mod b/go.mod index 75706be..4f0fd76 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/sensu/sensu-pagerduty-handler -go 1.18 +go 1.20 require ( github.com/PagerDuty/go-pagerduty v1.3.0 @@ -40,9 +40,9 @@ require ( github.com/spf13/viper v1.7.1 // indirect github.com/subosito/gotenv v1.2.0 // indirect go.etcd.io/etcd/api/v3 v3.5.0 // indirect - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect - golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect - golang.org/x/text v0.3.5 // indirect + golang.org/x/net v0.0.0-20210610124326-52da8fb2a613 // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/text v0.3.8 // indirect google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect google.golang.org/grpc v1.38.0 // indirect google.golang.org/protobuf v1.26.0 // indirect diff --git a/go.sum b/go.sum index a8d631c..5dcc008 100644 --- a/go.sum +++ b/go.sum @@ -345,8 +345,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210610124326-52da8fb2a613 h1:SqvqnUCcwFhyyRueFOEFTBaWeXYwK+CL/767809IlbQ= +golang.org/x/net v0.0.0-20210610124326-52da8fb2a613/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -380,15 +381,16 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 1c0d55cb2f01bd3666e6b341e8606efa0660dcb1 Mon Sep 17 00:00:00 2001 From: Francis Guimond Date: Mon, 3 Apr 2023 17:37:51 -0400 Subject: [PATCH 3/5] Try to fix staticcheck error Bump static-check to version 1.3.0 --- .github/workflows/static-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static-check.yml b/.github/workflows/static-check.yml index e3d038d..f0a5edc 100644 --- a/.github/workflows/static-check.yml +++ b/.github/workflows/static-check.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v1 with: fetch-depth: 1 - - uses: dominikh/staticcheck-action@v1.2.0 + - uses: dominikh/staticcheck-action@v1.3.0 with: version: "2023.1.3" env: From 5df77449933b92d117b9a0a3887b87b96713b301 Mon Sep 17 00:00:00 2001 From: Francis Guimond Date: Mon, 3 Apr 2023 17:45:11 -0400 Subject: [PATCH 4/5] Try to fix staticcheck error Fix captions --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3253602..1a649da 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: uses: actions/checkout@v2 - name: Unshallow run: git fetch --prune --unshallow - - name: Set up Go + - name: Set up Go 1.20 uses: actions/setup-go@v1 with: go-version: 1.20.x diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 61b419c..e1db9a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Go 1.18 + - name: Set up Go 1.20 uses: actions/setup-go@v1 with: go-version: 1.20.x From 6298cea537a8687f8d31c63b24ca376e6ce1e13b Mon Sep 17 00:00:00 2001 From: Francis Guimond Date: Mon, 3 Apr 2023 17:45:33 -0400 Subject: [PATCH 5/5] Update release date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 233018f..2bea43c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ Versioning](http://semver.org/spec/v2.0.0.html). ## Unreleased -## 2.4.0 - 2023-04-03 +## 2.4.0 - 2023-04-04 ### - Add `--details-format` option to be able to specify if the event details should be sent as a string or a JSON document