Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a new CLI option to specify the issue reopen window #43

Merged
merged 4 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ OPTIONS:
--github-app-private-key value GitHub App private key (command line argument is not recommended) [$ATG_GITHUB_APP_PRIVATE_KEY]
--github-token value GitHub API token (command line argument is not recommended) [$ATG_GITHUB_TOKEN]
--auto-close-resolved-issues Should issues be automatically closed when resolved (default: true) [$ATG_AUTO_CLOSE_RESOLVED_ISSUES]
--reopen-window value Alerts will create a new issue instead of reopening closed issues if the specified duration has passed [$ATG_REOPEN_WINDOW]
--help, -h show help
```

Expand All @@ -99,6 +100,7 @@ Issue title and body are rendered from [Go template](https://golang.org/pkg/text

- Variables
- `.Payload`: Webhook payload incoming to this receiver. For more information, see `WebhookPayload` in [pkg/types/payload.go](https://github.com/pfnet-research/alertmanager-to-github/blob/master/pkg/types/payload.go)
- `.PreviousIssue`: The previous issue with the same alert ID, or `nil` if there is no such issue. For more information, see `Issue` in [github.com/google/go-github/v54/github](https://pkg.go.dev/github.com/google/go-github/[email protected]/github#Issue). Useful when `--reopen-window` is specified.
- Functions
- `urlQueryEscape`: Escape a string as a URL query
- `json`: Marshal an object to JSON string
Expand Down
36 changes: 35 additions & 1 deletion pkg/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http"
"os"
"strings"
"time"

"github.com/bradleyfalzon/ghinstallation/v2"
"github.com/google/go-github/v54/github"
Expand All @@ -34,6 +35,8 @@ const flagAlertIDTemplate = "alert-id-template"
const flagTemplateFile = "template-file"
const flagPayloadFile = "payload-file"
const flagAutoCloseResolvedIssues = "auto-close-resolved-issues"
const flagReopenWindow = "reopen-window"
const flagNoPreviousIssue = "no-previous-issue"

const defaultPayload = `{
"version": "4",
Expand Down Expand Up @@ -97,6 +100,9 @@ const defaultPayload = `{
]
}`

//go:embed samples/issue.json
var sampleIssue string

//go:embed templates/*.tmpl
var templates embed.FS

Expand Down Expand Up @@ -178,6 +184,14 @@ func App() *cli.App {
Usage: "Should issues be automatically closed when resolved",
EnvVars: []string{"ATG_AUTO_CLOSE_RESOLVED_ISSUES"},
},
&noDefaultDurationFlag{
cli.DurationFlag{
Name: flagReopenWindow,
Required: false,
Usage: "Alerts will create a new issue instead of reopening closed issues if the specified duration has passed",
EnvVars: []string{"ATG_REOPEN_WINDOW"},
},
},
},
},
{
Expand All @@ -193,6 +207,10 @@ func App() *cli.App {
Name: flagPayloadFile,
Usage: "Payload data file",
},
&cli.BoolFlag{
Name: flagNoPreviousIssue,
Usage: "Set `.PreviousIssue` to nil",
},
},
Action: func(c *cli.Context) error {
if err := actionTestTemplate(c); err != nil {
Expand Down Expand Up @@ -310,6 +328,12 @@ func actionStart(c *cli.Context) error {
return err
}

var reopenWindow *time.Duration
if c.IsSet(flagReopenWindow) {
d := c.Duration(flagReopenWindow)
reopenWindow = &d
}

nt, err := notifier.NewGitHub()
if err != nil {
return err
Expand All @@ -323,6 +347,7 @@ func actionStart(c *cli.Context) error {
nt.TitleTemplate = titleTemplate
nt.AlertIDTemplate = alertIDTemplate
nt.AutoCloseResolvedIssues = c.Bool(flagAutoCloseResolvedIssues)
nt.ReopenWindow = reopenWindow

router := server.New(nt).Router()
if err := router.Run(c.String(flagListen)); err != nil {
Expand Down Expand Up @@ -355,7 +380,16 @@ func actionTestTemplate(c *cli.Context) error {
return err
}

s, err := t.Execute(payload)
var previousIssue *github.Issue
if !c.Bool(flagNoPreviousIssue) {
previousIssue = &github.Issue{}
err = json.NewDecoder(strings.NewReader(sampleIssue)).Decode(previousIssue)
if err != nil {
return err
}
}

s, err := t.Execute(payload, previousIssue)
if err != nil {
return err
}
Expand Down
18 changes: 18 additions & 0 deletions pkg/cli/flag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package cli

import (
"github.com/urfave/cli/v2"
)

// cli.DurationFlag without the default value "0s"
type noDefaultDurationFlag struct {
cli.DurationFlag
}

func (f *noDefaultDurationFlag) GetDefaultText() string {
return ""
}

func (f *noDefaultDurationFlag) String() string {
return cli.FlagStringer(f)
}
32 changes: 32 additions & 0 deletions pkg/cli/flag_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cli

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
)

func TestNoDefaultDurationFlag(t *testing.T) {
writer := bytes.Buffer{}
app := &cli.App{
Name: "app",
Usage: "app usage",
Flags: []cli.Flag{
&noDefaultDurationFlag{
cli.DurationFlag{
Name: "no-default-flag",
Usage: "no default flag usage",
},
},
},
Writer: &writer,
}

err := app.Run([]string{"help"})

assert.NoError(t, err)
assert.Contains(t, writer.String(), "--no-default-flag value no default flag usage\n")
assert.NotContains(t, writer.String(), "default: 0s")
}
117 changes: 117 additions & 0 deletions pkg/cli/samples/issue.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{
"id": 1,
"node_id": "ABCDEFGHIJKL",
"url": "https://api.github.com/repos/pfnet-research/alertmanager-to-github/issues/1",
"repository_url": "https://api.github.com/repos/pfnet-research/alertmanager-to-github",
"labels_url": "https://api.github.com/repos/pfnet-research/alertmanager-to-github/issues/1/labels{/name}",
"comments_url": "https://api.github.com/repos/pfnet-research/alertmanager-to-github/issues/1/comments",
"events_url": "https://api.github.com/repos/pfnet-research/alertmanager-to-github/issues/1/events",
"html_url": "https://github.com/pfnet-research/alertmanager-to-github/issues/1",
"number": 1,
"state": "closed",
"title": "[ALERT] SampleAlert",
"body": "Alert firing.",
"user": {
"login": "octocat",
"id": 1,
"node_id": "abcdefghijkl",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"labels": [
{
"id": 123456789,
"node_id": "abcdefghijklmnopqrstuvwxyz",
"url": "https://api.github.com/repos/pfnet-research/alertmanager-to-github/labels/label",
"name": "label",
"color": "ffffff",
"default": true,
"description": "Sample Label"
}
],
"assignee": {
"login": "octocat",
"id": 1,
"node_id": "abcdefghijkl",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"assignees": [
{
"login": "octocat",
"id": 1,
"node_id": "abcdefghijkl",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
}
],
"milestone": null,
"locked": false,
"active_lock_reason": null,
"comments": 0,
"pull_request": null,
"closed_at": "2020-01-02T03:04:06Z",
"created_at": "2020-01-02T03:04:05Z",
"updated_at": "2020-01-02T03:04:06Z",
"closed_by": {
"login": "octocat",
"id": 1,
"node_id": "abcdefghijkl",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"author_association": "OWNER",
"state_reason": null
}
5 changes: 5 additions & 0 deletions pkg/cli/templates/body.tmpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{{- $payload := .Payload -}}
{{- $previousIssue := .PreviousIssue -}}
(Updated at {{timeNow}})
{{- if $previousIssue }}

Previous Issue: {{ $previousIssue.HTMLURL }}
{{- end }}

## Common Labels

Expand Down
Loading