mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-21 21:30:48 +02:00
feat(notifications): add json template (#1542)
This commit is contained in:
parent
8464e0dece
commit
547d033460
5 changed files with 211 additions and 14 deletions
|
@ -35,5 +35,6 @@ var commonTemplates = map[string]string{
|
||||||
no containers matched filter
|
no containers matched filter
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end -}}`,
|
{{- end -}}`,
|
||||||
}
|
|
||||||
|
|
||||||
|
`json.v1`: `{{ . | ToJSON }}`,
|
||||||
|
}
|
||||||
|
|
71
pkg/notifications/json.go
Normal file
71
pkg/notifications/json.go
Normal file
|
@ -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
pkg/notifications/json_test.go
Normal file
118
pkg/notifications/json_test.go
Normal file
|
@ -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
pkg/notifications/model.go
Normal file
19
pkg/notifications/model.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -210,6 +210,7 @@ func getShoutrrrTemplate(tplString string, legacy bool) (tpl *template.Template,
|
||||||
funcs := template.FuncMap{
|
funcs := template.FuncMap{
|
||||||
"ToUpper": strings.ToUpper,
|
"ToUpper": strings.ToUpper,
|
||||||
"ToLower": strings.ToLower,
|
"ToLower": strings.ToLower,
|
||||||
|
"ToJSON": toJSON,
|
||||||
"Title": cases.Title(language.AmericanEnglish).String,
|
"Title": cases.Title(language.AmericanEnglish).String,
|
||||||
}
|
}
|
||||||
tplBase := template.New("").Funcs(funcs)
|
tplBase := template.New("").Funcs(funcs)
|
||||||
|
@ -240,16 +241,3 @@ func getShoutrrrTemplate(tplString string, legacy bool) (tpl *template.Template,
|
||||||
|
|
||||||
return
|
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
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue