Skip to content

Commit 4755eb5

Browse files
Alerting: Support template UID in template service (grafana#92164)
* add uid to template and populate it * update delete method to support both uid and name * update UpdateTemplate to support search by UID and fallback to name + support renaming of the template * update upsert to exit if template not found and uid is specified * update Get method to address by name or uid --------- Co-authored-by: Matthew Jacobson <[email protected]>
1 parent 354aee9 commit 4755eb5

File tree

4 files changed

+364
-128
lines changed

4 files changed

+364
-128
lines changed

pkg/services/ngalert/api/api_provisioning.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ type ContactPointService interface {
4545

4646
type TemplateService interface {
4747
GetTemplates(ctx context.Context, orgID int64) ([]definitions.NotificationTemplate, error)
48-
GetTemplate(ctx context.Context, orgID int64, name string) (definitions.NotificationTemplate, error)
48+
GetTemplate(ctx context.Context, orgID int64, nameOrUid string) (definitions.NotificationTemplate, error)
4949
UpsertTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error)
50-
DeleteTemplate(ctx context.Context, orgID int64, name string, provenance definitions.Provenance, version string) error
50+
DeleteTemplate(ctx context.Context, orgID int64, nameOrUid string, provenance definitions.Provenance, version string) error
5151
}
5252

5353
type NotificationPolicyService interface {
@@ -229,9 +229,9 @@ func (srv *ProvisioningSrv) RoutePutTemplate(c *contextmodel.ReqContext, body de
229229
return response.JSON(http.StatusAccepted, modified)
230230
}
231231

232-
func (srv *ProvisioningSrv) RouteDeleteTemplate(c *contextmodel.ReqContext, name string) response.Response {
232+
func (srv *ProvisioningSrv) RouteDeleteTemplate(c *contextmodel.ReqContext, nameOrUid string) response.Response {
233233
version := c.Query("version")
234-
err := srv.templates.DeleteTemplate(c.Req.Context(), c.SignedInUser.GetOrgID(), name, determineProvenance(c), version)
234+
err := srv.templates.DeleteTemplate(c.Req.Context(), c.SignedInUser.GetOrgID(), nameOrUid, determineProvenance(c), version)
235235
if err != nil {
236236
return response.ErrOrFallback(http.StatusInternalServerError, "", err)
237237
}

pkg/services/ngalert/api/tooling/definitions/provisioning_templates.go

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type RouteDeleteTemplateParam struct {
5555

5656
// swagger:model
5757
type NotificationTemplate struct {
58+
UID string `json:"-" yaml:"-"`
5859
Name string `json:"name"`
5960
Template string `json:"template"`
6061
Provenance Provenance `json:"provenance,omitempty"`

pkg/services/ngalert/provisioning/templates.go

+75-27
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func (t *TemplateService) GetTemplates(ctx context.Context, orgID int64) ([]defi
5050
templates := make([]definitions.NotificationTemplate, 0, len(revision.Config.TemplateFiles))
5151
for name, tmpl := range revision.Config.TemplateFiles {
5252
tmpl := definitions.NotificationTemplate{
53+
UID: legacy_storage.NameToUid(name),
5354
Name: name,
5455
Template: tmpl,
5556
ResourceVersion: calculateTemplateFingerprint(tmpl),
@@ -65,30 +66,34 @@ func (t *TemplateService) GetTemplates(ctx context.Context, orgID int64) ([]defi
6566
return templates, nil
6667
}
6768

68-
func (t *TemplateService) GetTemplate(ctx context.Context, orgID int64, name string) (definitions.NotificationTemplate, error) {
69+
func (t *TemplateService) GetTemplate(ctx context.Context, orgID int64, nameOrUid string) (definitions.NotificationTemplate, error) {
6970
revision, err := t.configStore.Get(ctx, orgID)
7071
if err != nil {
7172
return definitions.NotificationTemplate{}, err
7273
}
7374

74-
for tmplName, tmpl := range revision.Config.TemplateFiles {
75-
if tmplName != name {
76-
continue
77-
}
78-
tmpl := definitions.NotificationTemplate{
79-
Name: name,
80-
Template: tmpl,
81-
ResourceVersion: calculateTemplateFingerprint(tmpl),
82-
}
75+
existingName := nameOrUid
76+
existingContent, ok := revision.Config.TemplateFiles[nameOrUid]
77+
if !ok {
78+
existingName, existingContent, ok = getTemplateByUid(revision.Config.TemplateFiles, nameOrUid)
79+
}
80+
if !ok {
81+
return definitions.NotificationTemplate{}, ErrTemplateNotFound.Errorf("")
82+
}
8383

84-
provenance, err := t.provenanceStore.GetProvenance(ctx, &tmpl, orgID)
85-
if err != nil {
86-
return definitions.NotificationTemplate{}, err
87-
}
88-
tmpl.Provenance = definitions.Provenance(provenance)
89-
return tmpl, nil
84+
tmpl := definitions.NotificationTemplate{
85+
UID: legacy_storage.NameToUid(existingName),
86+
Name: existingName,
87+
Template: existingContent,
88+
ResourceVersion: calculateTemplateFingerprint(existingContent),
9089
}
91-
return definitions.NotificationTemplate{}, ErrTemplateNotFound.Errorf("")
90+
91+
provenance, err := t.provenanceStore.GetProvenance(ctx, &tmpl, orgID)
92+
if err != nil {
93+
return definitions.NotificationTemplate{}, err
94+
}
95+
tmpl.Provenance = definitions.Provenance(provenance)
96+
return tmpl, nil
9297
}
9398

9499
func (t *TemplateService) UpsertTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) {
@@ -107,7 +112,11 @@ func (t *TemplateService) UpsertTemplate(ctx context.Context, orgID int64, tmpl
107112
if !errors.Is(err, ErrTemplateNotFound) {
108113
return d, err
109114
}
110-
if tmpl.ResourceVersion != "" { // if version is set then it's an update operation. Fail because resource does not exist anymore
115+
// If template was not found, this is assumed to be a create operation except for two cases:
116+
// - If a ResourceVersion is provided: we should assume that this was meant to be a conditional update operation.
117+
// - If UID is provided: custom UID for templates is not currently supported, so this was meant to be an update
118+
// operation without a ResourceVersion.
119+
if tmpl.ResourceVersion != "" || tmpl.UID != "" {
111120
return definitions.NotificationTemplate{}, ErrTemplateNotFound.Errorf("")
112121
}
113122
return t.createTemplate(ctx, revision, orgID, tmpl)
@@ -150,6 +159,7 @@ func (t *TemplateService) createTemplate(ctx context.Context, revision *legacy_s
150159
}
151160

152161
return definitions.NotificationTemplate{
162+
UID: legacy_storage.NameToUid(tmpl.Name),
153163
Name: tmpl.Name,
154164
Template: tmpl.Template,
155165
Provenance: tmpl.Provenance,
@@ -175,12 +185,28 @@ func (t *TemplateService) updateTemplate(ctx context.Context, revision *legacy_s
175185
revision.Config.TemplateFiles = map[string]string{}
176186
}
177187

178-
existingName := tmpl.Name
179-
exisitingContent, found := revision.Config.TemplateFiles[existingName]
188+
var found bool
189+
var existingName, existingContent string
190+
// if UID is specified, look by UID.
191+
if tmpl.UID != "" {
192+
existingName, existingContent, found = getTemplateByUid(revision.Config.TemplateFiles, tmpl.UID)
193+
// do not fall back to name because we address by UID, and resource can be deleted\renamed
194+
} else {
195+
existingName = tmpl.Name
196+
existingContent, found = revision.Config.TemplateFiles[existingName]
197+
}
180198
if !found {
181199
return definitions.NotificationTemplate{}, ErrTemplateNotFound.Errorf("")
182200
}
183201

202+
if existingName != tmpl.Name { // if template is renamed, check if this name is already taken
203+
_, ok := revision.Config.TemplateFiles[tmpl.Name]
204+
if ok {
205+
// return error if template is being renamed to one that already exists
206+
return definitions.NotificationTemplate{}, ErrTemplateExists.Errorf("")
207+
}
208+
}
209+
184210
// check that provenance is not changed in an invalid way
185211
storedProvenance, err := t.provenanceStore.GetProvenance(ctx, &tmpl, orgID)
186212
if err != nil {
@@ -190,14 +216,22 @@ func (t *TemplateService) updateTemplate(ctx context.Context, revision *legacy_s
190216
return definitions.NotificationTemplate{}, err
191217
}
192218

193-
err = t.checkOptimisticConcurrency(tmpl.Name, exisitingContent, models.Provenance(tmpl.Provenance), tmpl.ResourceVersion, "update")
219+
err = t.checkOptimisticConcurrency(tmpl.Name, existingContent, models.Provenance(tmpl.Provenance), tmpl.ResourceVersion, "update")
194220
if err != nil {
195221
return definitions.NotificationTemplate{}, err
196222
}
197223

198224
revision.Config.TemplateFiles[tmpl.Name] = tmpl.Template
199225

200226
err = t.xact.InTransaction(ctx, func(ctx context.Context) error {
227+
if existingName != tmpl.Name { // if template by was found by UID and it's name is different, then this is the rename operation. Delete old resources.
228+
delete(revision.Config.TemplateFiles, existingName)
229+
err := t.provenanceStore.DeleteProvenance(ctx, &definitions.NotificationTemplate{Name: existingName}, orgID)
230+
if err != nil {
231+
return err
232+
}
233+
}
234+
201235
if err := t.configStore.Save(ctx, revision, orgID); err != nil {
202236
return err
203237
}
@@ -208,14 +242,15 @@ func (t *TemplateService) updateTemplate(ctx context.Context, revision *legacy_s
208242
}
209243

210244
return definitions.NotificationTemplate{
245+
UID: legacy_storage.NameToUid(tmpl.Name), // if name was changed, this UID will not match the incoming one
211246
Name: tmpl.Name,
212247
Template: tmpl.Template,
213248
Provenance: tmpl.Provenance,
214249
ResourceVersion: calculateTemplateFingerprint(tmpl.Template),
215250
}, nil
216251
}
217252

218-
func (t *TemplateService) DeleteTemplate(ctx context.Context, orgID int64, name string, provenance definitions.Provenance, version string) error {
253+
func (t *TemplateService) DeleteTemplate(ctx context.Context, orgID int64, nameOrUid string, provenance definitions.Provenance, version string) error {
219254
revision, err := t.configStore.Get(ctx, orgID)
220255
if err != nil {
221256
return err
@@ -225,33 +260,37 @@ func (t *TemplateService) DeleteTemplate(ctx context.Context, orgID int64, name
225260
return nil
226261
}
227262

228-
existing, ok := revision.Config.TemplateFiles[name]
263+
existingName := nameOrUid
264+
existing, ok := revision.Config.TemplateFiles[nameOrUid]
265+
if !ok {
266+
existingName, existing, ok = getTemplateByUid(revision.Config.TemplateFiles, nameOrUid)
267+
}
229268
if !ok {
230269
return nil
231270
}
232271

233-
err = t.checkOptimisticConcurrency(name, existing, models.Provenance(provenance), version, "delete")
272+
err = t.checkOptimisticConcurrency(existingName, existing, models.Provenance(provenance), version, "delete")
234273
if err != nil {
235274
return err
236275
}
237276

238277
// check that provenance is not changed in an invalid way
239-
storedProvenance, err := t.provenanceStore.GetProvenance(ctx, &definitions.NotificationTemplate{Name: name}, orgID)
278+
storedProvenance, err := t.provenanceStore.GetProvenance(ctx, &definitions.NotificationTemplate{Name: existingName}, orgID)
240279
if err != nil {
241280
return err
242281
}
243282
if err = t.validator(storedProvenance, models.Provenance(provenance)); err != nil {
244283
return err
245284
}
246285

247-
delete(revision.Config.TemplateFiles, name)
286+
delete(revision.Config.TemplateFiles, existingName)
248287

249288
return t.xact.InTransaction(ctx, func(ctx context.Context) error {
250289
if err := t.configStore.Save(ctx, revision, orgID); err != nil {
251290
return err
252291
}
253292
tgt := definitions.NotificationTemplate{
254-
Name: name,
293+
Name: existingName,
255294
}
256295
return t.provenanceStore.DeleteProvenance(ctx, &tgt, orgID)
257296
})
@@ -277,3 +316,12 @@ func calculateTemplateFingerprint(t string) string {
277316
_, _ = sum.Write(unsafe.Slice(unsafe.StringData(t), len(t))) //nolint:gosec
278317
return fmt.Sprintf("%016x", sum.Sum64())
279318
}
319+
320+
func getTemplateByUid(templates map[string]string, uid string) (string, string, bool) {
321+
for n, tmpl := range templates {
322+
if legacy_storage.NameToUid(n) == uid {
323+
return n, tmpl, true
324+
}
325+
}
326+
return "", "", false
327+
}

0 commit comments

Comments
 (0)