Skip to content

Commit

Permalink
feat(notifications): add json template (#1542)
Browse files Browse the repository at this point in the history
  • Loading branch information
piksel committed Jan 29, 2023
1 parent 8464e0d commit 547d033
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 14 deletions.
3 changes: 2 additions & 1 deletion pkg/notifications/common_templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ var commonTemplates = map[string]string{
no containers matched filter
{{- end -}}
{{- end -}}`,
}

`json.v1`: `{{ . | ToJSON }}`,
}
71 changes: 71 additions & 0 deletions pkg/notifications/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package notifications

import (
"encoding/json"

t "github.com/containrrr/watchtower/pkg/types"
)

type jsonMap = map[string]interface{}

// MarshalJSON implements json.Marshaler
func (d Data) MarshalJSON() ([]byte, error) {
var entries = make([]jsonMap, len(d.Entries))
for i, entry := range d.Entries {
entries[i] = jsonMap{
`level`: entry.Level,
`message`: entry.Message,
`data`: entry.Data,
`time`: entry.Time,
}
}

var report jsonMap
if d.Report != nil {
report = jsonMap{
`scanned`: marshalReports(d.Report.Scanned()),
`updated`: marshalReports(d.Report.Updated()),
`failed`: marshalReports(d.Report.Failed()),
`skipped`: marshalReports(d.Report.Skipped()),
`stale`: marshalReports(d.Report.Stale()),
`fresh`: marshalReports(d.Report.Fresh()),
}
}

return json.Marshal(jsonMap{
`report`: report,
`title`: d.Title,
`host`: d.Host,
`entries`: entries,
})
}

func marshalReports(reports []t.ContainerReport) []jsonMap {
jsonReports := make([]jsonMap, len(reports))
for i, report := range reports {
jsonReports[i] = jsonMap{
`id`: report.ID().ShortID(),
`name`: report.Name(),
`currentImageId`: report.CurrentImageID().ShortID(),
`latestImageId`: report.LatestImageID().ShortID(),
`imageName`: report.ImageName(),
`state`: report.State(),
}
if errorMessage := report.Error(); errorMessage != "" {
jsonReports[i][`error`] = errorMessage
}
}
return jsonReports
}

var _ json.Marshaler = &Data{}

func toJSON(v interface{}) string {
var bytes []byte
var err error
if bytes, err = json.MarshalIndent(v, "", " "); err != nil {
LocalLog.Errorf("failed to marshal JSON in notification template: %v", err)
return ""
}
return string(bytes)
}
118 changes: 118 additions & 0 deletions pkg/notifications/json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package notifications

import (
s "github.com/containrrr/watchtower/pkg/session"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("JSON template", func() {
When("using report templates", func() {
When("JSON template is used", func() {
It("should format the messages to the expected format", func() {
expected := `{
"entries": [
{
"data": null,
"level": "info",
"message": "foo Bar",
"time": "0001-01-01T00:00:00Z"
}
],
"host": "Mock",
"report": {
"failed": [
{
"currentImageId": "01d210000000",
"error": "accidentally the whole container",
"id": "c79210000000",
"imageName": "mock/fail1:latest",
"latestImageId": "d0a210000000",
"name": "fail1",
"state": "Failed"
}
],
"fresh": [
{
"currentImageId": "01d310000000",
"id": "c79310000000",
"imageName": "mock/frsh1:latest",
"latestImageId": "01d310000000",
"name": "frsh1",
"state": "Fresh"
}
],
"scanned": [
{
"currentImageId": "01d110000000",
"id": "c79110000000",
"imageName": "mock/updt1:latest",
"latestImageId": "d0a110000000",
"name": "updt1",
"state": "Updated"
},
{
"currentImageId": "01d120000000",
"id": "c79120000000",
"imageName": "mock/updt2:latest",
"latestImageId": "d0a120000000",
"name": "updt2",
"state": "Updated"
},
{
"currentImageId": "01d210000000",
"error": "accidentally the whole container",
"id": "c79210000000",
"imageName": "mock/fail1:latest",
"latestImageId": "d0a210000000",
"name": "fail1",
"state": "Failed"
},
{
"currentImageId": "01d310000000",
"id": "c79310000000",
"imageName": "mock/frsh1:latest",
"latestImageId": "01d310000000",
"name": "frsh1",
"state": "Fresh"
}
],
"skipped": [
{
"currentImageId": "01d410000000",
"error": "unpossible",
"id": "c79410000000",
"imageName": "mock/skip1:latest",
"latestImageId": "01d410000000",
"name": "skip1",
"state": "Skipped"
}
],
"stale": [],
"updated": [
{
"currentImageId": "01d110000000",
"id": "c79110000000",
"imageName": "mock/updt1:latest",
"latestImageId": "d0a110000000",
"name": "updt1",
"state": "Updated"
},
{
"currentImageId": "01d120000000",
"id": "c79120000000",
"imageName": "mock/updt2:latest",
"latestImageId": "d0a120000000",
"name": "updt2",
"state": "Updated"
}
]
},
"title": "Watchtower updates on Mock"
}`
data := mockDataFromStates(s.UpdatedState, s.FreshState, s.FailedState, s.SkippedState, s.UpdatedState)
Expect(getTemplatedResult(`json.v1`, false, data)).To(MatchJSON(expected))
})
})
})
})
19 changes: 19 additions & 0 deletions pkg/notifications/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package notifications

import (
t "github.com/containrrr/watchtower/pkg/types"
log "github.com/sirupsen/logrus"
)

// StaticData is the part of the notification template data model set upon initialization
type StaticData struct {
Title string
Host string
}

// Data is the notification template data model
type Data struct {
StaticData
Entries []*log.Entry
Report t.Report
}
14 changes: 1 addition & 13 deletions pkg/notifications/shoutrrr.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ func getShoutrrrTemplate(tplString string, legacy bool) (tpl *template.Template,
funcs := template.FuncMap{
"ToUpper": strings.ToUpper,
"ToLower": strings.ToLower,
"ToJSON": toJSON,
"Title": cases.Title(language.AmericanEnglish).String,
}
tplBase := template.New("").Funcs(funcs)
Expand Down Expand Up @@ -240,16 +241,3 @@ func getShoutrrrTemplate(tplString string, legacy bool) (tpl *template.Template,

return
}

// StaticData is the part of the notification template data model set upon initialization
type StaticData struct {
Title string
Host string
}

// Data is the notification template data model
type Data struct {
StaticData
Entries []*log.Entry
Report t.Report
}

0 comments on commit 547d033

Please sign in to comment.