go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/luci_notify/config/emailtemplate.go (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package config 16 17 import ( 18 "context" 19 "fmt" 20 "regexp" 21 22 "go.chromium.org/luci/common/errors" 23 "go.chromium.org/luci/common/logging" 24 configInterface "go.chromium.org/luci/config" 25 "go.chromium.org/luci/gae/service/datastore" 26 27 "go.chromium.org/luci/luci_notify/common" 28 "go.chromium.org/luci/luci_notify/mailtmpl" 29 ) 30 31 // emailTemplateFilenameRegexp returns a regular expression for email template 32 // file names. 33 func emailTemplateFilenameRegexp(c context.Context) (*regexp.Regexp, error) { 34 appID, err := common.GetAppID(c) 35 if err != nil { 36 return nil, errors.Annotate(err, "failed to get app ID").Err() 37 } 38 39 return regexp.MustCompile(fmt.Sprintf( 40 `^%s+/email-templates/([a-z][a-z0-9_]*)%s$`, 41 regexp.QuoteMeta(appID), 42 regexp.QuoteMeta(mailtmpl.FileExt), 43 )), nil 44 } 45 46 // EmailTemplate is a Datastore entity directly under Project entity that 47 // represents an email template. 48 // It is managed by the cron job that ingests configs. 49 type EmailTemplate struct { 50 // ProjectKey is a datastore key of the LUCI project containing this email 51 // template. 52 ProjectKey *datastore.Key `gae:"$parent"` 53 54 // Name identifies the email template. It is unique within the project. 55 Name string `gae:"$id"` 56 57 // SubjectTextTemplate is a text.Template of the email subject. 58 SubjectTextTemplate string `gae:",noindex"` 59 60 // BodyHTMLTemplate is a html.Template of the email body. 61 BodyHTMLTemplate string `gae:",noindex"` 62 63 // DefinitionURL is a URL to human-viewable page that contains the definition 64 // of this email template. 65 DefinitionURL string `gae:",noindex"` 66 } 67 68 // Template converts t to *mailtmpl.Template. 69 func (t *EmailTemplate) Template() *mailtmpl.Template { 70 return &mailtmpl.Template{ 71 Name: t.Name, 72 SubjectTextTemplate: t.SubjectTextTemplate, 73 BodyHTMLTemplate: t.BodyHTMLTemplate, 74 DefinitionURL: t.DefinitionURL, 75 } 76 } 77 78 // fetchAllEmailTemplates fetches all valid email templates of the project from 79 // a config service. Returned EmailTemplate entities do not have ProjectKey set. 80 func fetchAllEmailTemplates(c context.Context, configService configInterface.Interface, projectID string) (map[string]*EmailTemplate, error) { 81 configSet, err := configInterface.ProjectSet(projectID) 82 if err != nil { 83 return nil, err 84 } 85 files, err := configService.ListFiles(c, configSet) 86 if err != nil { 87 return nil, err 88 } 89 90 ret := map[string]*EmailTemplate{} 91 92 // This runs in a cron job. It is not performance critical, so we don't have 93 // to fetch files concurrently. 94 filenameRegexp, err := emailTemplateFilenameRegexp(c) 95 if err != nil { 96 return nil, err 97 } 98 for _, f := range files { 99 m := filenameRegexp.FindStringSubmatch(f) 100 if m == nil { 101 // Not a template file or a template of another instance of luci-notify. 102 continue 103 } 104 templateName := m[1] 105 106 logging.Infof(c, "fetching email template from %s:%s", configSet, f) 107 config, err := configService.GetConfig(c, configSet, f, false) 108 if err != nil { 109 return nil, errors.Annotate(err, "failed to fetch %q", f).Err() 110 } 111 112 subject, body, err := mailtmpl.SplitTemplateFile(config.Content) 113 if err != nil { 114 // Should not happen. luci-config should not have passed this commit in 115 // because luci-notify exposes its validation code to luci-conifg. 116 logging.Warningf(c, "invalid email template content in %q: %s", f, err) 117 continue 118 } 119 120 ret[templateName] = &EmailTemplate{ 121 Name: templateName, 122 SubjectTextTemplate: subject, 123 BodyHTMLTemplate: body, 124 DefinitionURL: config.ViewURL, 125 } 126 } 127 return ret, nil 128 }