Allow inserting env variables anywhere in the config

This commit is contained in:
Svilen Markov 2024-12-16 23:59:25 +00:00
parent 8d2639b349
commit dbcc13a5cf
13 changed files with 90 additions and 101 deletions

View File

@ -2,7 +2,6 @@ package glance
import ( import (
"fmt" "fmt"
"os"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -112,61 +111,6 @@ func (d *durationField) UnmarshalYAML(node *yaml.Node) error {
return nil return nil
} }
var optionalEnvFieldPattern = regexp.MustCompile(`(^|.)\$\{([A-Z0-9_]+)\}`)
type optionalEnvField string
func (f *optionalEnvField) UnmarshalYAML(node *yaml.Node) error {
var value string
err := node.Decode(&value)
if err != nil {
return err
}
replaced := optionalEnvFieldPattern.ReplaceAllStringFunc(value, func(match string) string {
if err != nil {
return ""
}
groups := optionalEnvFieldPattern.FindStringSubmatch(match)
if len(groups) != 3 {
return match
}
prefix, key := groups[1], groups[2]
if prefix == `\` {
if len(match) >= 2 {
return match[1:]
} else {
return ""
}
}
value, found := os.LookupEnv(key)
if !found {
err = fmt.Errorf("environment variable %s not found", key)
return ""
}
return prefix + value
})
if err != nil {
return err
}
*f = optionalEnvField(replaced)
return nil
}
func (f *optionalEnvField) String() string {
return string(*f)
}
type customIconField struct { type customIconField struct {
URL string URL string
IsFlatIcon bool IsFlatIcon bool

View File

@ -69,10 +69,15 @@ type page struct {
} }
func newConfigFromYAML(contents []byte) (*config, error) { func newConfigFromYAML(contents []byte) (*config, error) {
contents, err := parseConfigEnvVariables(contents)
if err != nil {
return nil, err
}
config := &config{} config := &config{}
config.Server.Port = 8080 config.Server.Port = 8080
err := yaml.Unmarshal(contents, config) err = yaml.Unmarshal(contents, config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -94,6 +99,46 @@ func newConfigFromYAML(contents []byte) (*config, error) {
return config, nil return config, nil
} }
var configEnvVariablePattern = regexp.MustCompile(`(^|.)\$\{([A-Z0-9_]+)\}`)
func parseConfigEnvVariables(contents []byte) ([]byte, error) {
var err error
replaced := configEnvVariablePattern.ReplaceAllFunc(contents, func(match []byte) []byte {
if err != nil {
return nil
}
groups := configEnvVariablePattern.FindSubmatch(match)
if len(groups) != 3 {
return match
}
prefix, key := string(groups[1]), string(groups[2])
if prefix == `\` {
if len(match) >= 2 {
return match[1:]
} else {
return nil
}
}
value, found := os.LookupEnv(key)
if !found {
err = fmt.Errorf("environment variable %s not found", key)
return nil
}
return []byte(prefix + value)
})
if err != nil {
return nil, err
}
return replaced, nil
}
func formatWidgetInitError(err error, w widget) error { func formatWidgetInitError(err error, w widget) error {
return fmt.Errorf("%s widget: %v", w.GetType(), err) return fmt.Errorf("%s widget: %v", w.GetType(), err)
} }

View File

@ -13,7 +13,7 @@
<img class="bookmarks-icon{{ if .Icon.IsFlatIcon }} flat-icon{{ end }}" src="{{ .Icon.URL }}" alt="" loading="lazy"> <img class="bookmarks-icon{{ if .Icon.IsFlatIcon }} flat-icon{{ end }}" src="{{ .Icon.URL }}" alt="" loading="lazy">
</div> </div>
{{ end }} {{ end }}
<a href="{{ .URL.String | safeURL }}" class="bookmarks-link {{ if .HideArrow }}bookmarks-link-no-arrow {{ end }}color-highlight size-h4" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a> <a href="{{ .URL | safeURL }}" class="bookmarks-link {{ if .HideArrow }}bookmarks-link-no-arrow {{ end }}color-highlight size-h4" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a>
</li> </li>
{{ end }} {{ end }}
</ul> </ul>

View File

@ -21,7 +21,7 @@
{{ end }} {{ end }}
{{ define "site" }} {{ define "site" }}
<a class="size-title-dynamic color-highlight text-truncate block grow" href="{{ .URL.String | safeURL }}" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a> <a class="size-title-dynamic color-highlight text-truncate block grow" href="{{ .URL | safeURL }}" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a>
{{ if not .Status.TimedOut }}<div>{{ .Status.ResponseTime.Milliseconds | formatNumber }}ms</div>{{ end }} {{ if not .Status.TimedOut }}<div>{{ .Status.ResponseTime.Milliseconds | formatNumber }}ms</div>{{ end }}
{{ if eq .StatusStyle "ok" }} {{ if eq .StatusStyle "ok" }}
<div class="monitor-site-status-icon-compact" title="{{ .Status.Code }}"> <div class="monitor-site-status-icon-compact" title="{{ .Status.Code }}">

View File

@ -25,7 +25,7 @@
<img class="monitor-site-icon{{ if .Icon.IsFlatIcon }} flat-icon{{ end }}" src="{{ .Icon.URL }}" alt="" loading="lazy"> <img class="monitor-site-icon{{ if .Icon.IsFlatIcon }} flat-icon{{ end }}" src="{{ .Icon.URL }}" alt="" loading="lazy">
{{ end }} {{ end }}
<div class="min-width-0"> <div class="min-width-0">
<a class="size-h3 color-highlight text-truncate block" href="{{ .URL.String | safeURL }}" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a> <a class="size-h3 color-highlight text-truncate block" href="{{ .URL | safeURL }}" {{ if not .SameTab }}target="_blank"{{ end }} rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text"> <ul class="list-horizontal-text">
{{ if not .Status.Error }} {{ if not .Status.Error }}
<li title="{{ .Status.Code }}">{{ .StatusText }}</li> <li title="{{ .Status.Code }}">{{ .StatusText }}</li>

View File

@ -13,11 +13,11 @@ type bookmarksWidget struct {
Title string `yaml:"title"` Title string `yaml:"title"`
Color *hslColorField `yaml:"color"` Color *hslColorField `yaml:"color"`
Links []struct { Links []struct {
Title string `yaml:"title"` Title string `yaml:"title"`
URL optionalEnvField `yaml:"url"` URL string `yaml:"url"`
Icon customIconField `yaml:"icon"` Icon customIconField `yaml:"icon"`
SameTab bool `yaml:"same-tab"` SameTab bool `yaml:"same-tab"`
HideArrow bool `yaml:"hide-arrow"` HideArrow bool `yaml:"hide-arrow"`
} `yaml:"links"` } `yaml:"links"`
} `yaml:"groups"` } `yaml:"groups"`
} }

View File

@ -18,7 +18,7 @@ type changeDetectionWidget struct {
ChangeDetections changeDetectionWatchList `yaml:"-"` ChangeDetections changeDetectionWatchList `yaml:"-"`
WatchUUIDs []string `yaml:"watches"` WatchUUIDs []string `yaml:"watches"`
InstanceURL string `yaml:"instance-url"` InstanceURL string `yaml:"instance-url"`
Token optionalEnvField `yaml:"token"` Token string `yaml:"token"`
Limit int `yaml:"limit"` Limit int `yaml:"limit"`
CollapseAfter int `yaml:"collapse-after"` CollapseAfter int `yaml:"collapse-after"`
} }

View File

@ -18,13 +18,13 @@ var customAPIWidgetTemplate = mustParseTemplate("custom-api.html", "widget-base.
type customAPIWidget struct { type customAPIWidget struct {
widgetBase `yaml:",inline"` widgetBase `yaml:",inline"`
URL optionalEnvField `yaml:"url"` URL string `yaml:"url"`
Template string `yaml:"template"` Template string `yaml:"template"`
Frameless bool `yaml:"frameless"` Frameless bool `yaml:"frameless"`
Headers map[string]optionalEnvField `yaml:"headers"` Headers map[string]string `yaml:"headers"`
APIRequest *http.Request `yaml:"-"` APIRequest *http.Request `yaml:"-"`
compiledTemplate *template.Template `yaml:"-"` compiledTemplate *template.Template `yaml:"-"`
CompiledHTML template.HTML `yaml:"-"` CompiledHTML template.HTML `yaml:"-"`
} }
func (widget *customAPIWidget) initialize() error { func (widget *customAPIWidget) initialize() error {
@ -45,13 +45,13 @@ func (widget *customAPIWidget) initialize() error {
widget.compiledTemplate = compiledTemplate widget.compiledTemplate = compiledTemplate
req, err := http.NewRequest(http.MethodGet, widget.URL.String(), nil) req, err := http.NewRequest(http.MethodGet, widget.URL, nil)
if err != nil { if err != nil {
return err return err
} }
for key, value := range widget.Headers { for key, value := range widget.Headers {
req.Header.Add(key, value.String()) req.Header.Add(key, value)
} }
widget.APIRequest = req widget.APIRequest = req

View File

@ -20,13 +20,13 @@ type dnsStatsWidget struct {
TimeLabels [8]string `yaml:"-"` TimeLabels [8]string `yaml:"-"`
Stats *dnsStats `yaml:"-"` Stats *dnsStats `yaml:"-"`
HourFormat string `yaml:"hour-format"` HourFormat string `yaml:"hour-format"`
Service string `yaml:"service"` Service string `yaml:"service"`
AllowInsecure bool `yaml:"allow-insecure"` AllowInsecure bool `yaml:"allow-insecure"`
URL optionalEnvField `yaml:"url"` URL string `yaml:"url"`
Token optionalEnvField `yaml:"token"` Token string `yaml:"token"`
Username optionalEnvField `yaml:"username"` Username string `yaml:"username"`
Password optionalEnvField `yaml:"password"` Password string `yaml:"password"`
} }
func makeDNSWidgetTimeLabels(format string) [8]string { func makeDNSWidgetTimeLabels(format string) [8]string {

View File

@ -109,9 +109,9 @@ func statusCodeToStyle(status int, altStatusCodes []int) string {
} }
type SiteStatusRequest struct { type SiteStatusRequest struct {
URL optionalEnvField `yaml:"url"` URL string `yaml:"url"`
CheckURL optionalEnvField `yaml:"check-url"` CheckURL string `yaml:"check-url"`
AllowInsecure bool `yaml:"allow-insecure"` AllowInsecure bool `yaml:"allow-insecure"`
} }
type siteStatus struct { type siteStatus struct {
@ -123,10 +123,10 @@ type siteStatus struct {
func fetchSiteStatusTask(statusRequest *SiteStatusRequest) (siteStatus, error) { func fetchSiteStatusTask(statusRequest *SiteStatusRequest) (siteStatus, error) {
var url string var url string
if statusRequest.CheckURL.String() != "" { if statusRequest.CheckURL != "" {
url = statusRequest.CheckURL.String() url = statusRequest.CheckURL
} else { } else {
url = statusRequest.URL.String() url = statusRequest.URL
} }
request, err := http.NewRequest(http.MethodGet, url, nil) request, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil { if err != nil {

View File

@ -20,8 +20,8 @@ type releasesWidget struct {
Releases appReleaseList `yaml:"-"` Releases appReleaseList `yaml:"-"`
releaseRequests []*releaseRequest `yaml:"-"` releaseRequests []*releaseRequest `yaml:"-"`
Repositories []string `yaml:"repositories"` Repositories []string `yaml:"repositories"`
Token optionalEnvField `yaml:"token"` Token string `yaml:"token"`
GitLabToken optionalEnvField `yaml:"gitlab-token"` GitLabToken string `yaml:"gitlab-token"`
Limit int `yaml:"limit"` Limit int `yaml:"limit"`
CollapseAfter int `yaml:"collapse-after"` CollapseAfter int `yaml:"collapse-after"`
ShowSourceIcon bool `yaml:"show-source-icon"` ShowSourceIcon bool `yaml:"show-source-icon"`
@ -38,8 +38,8 @@ func (widget *releasesWidget) initialize() error {
widget.CollapseAfter = 5 widget.CollapseAfter = 5
} }
var tokenAsString = widget.Token.String() var tokenAsString = widget.Token
var gitLabTokenAsString = widget.GitLabToken.String() var gitLabTokenAsString = widget.GitLabToken
for _, repository := range widget.Repositories { for _, repository := range widget.Repositories {
parts := strings.SplitN(repository, ":", 2) parts := strings.SplitN(repository, ":", 2)

View File

@ -14,12 +14,12 @@ var repositoryWidgetTemplate = mustParseTemplate("repository.html", "widget-base
type repositoryWidget struct { type repositoryWidget struct {
widgetBase `yaml:",inline"` widgetBase `yaml:",inline"`
RequestedRepository string `yaml:"repository"` RequestedRepository string `yaml:"repository"`
Token optionalEnvField `yaml:"token"` Token string `yaml:"token"`
PullRequestsLimit int `yaml:"pull-requests-limit"` PullRequestsLimit int `yaml:"pull-requests-limit"`
IssuesLimit int `yaml:"issues-limit"` IssuesLimit int `yaml:"issues-limit"`
CommitsLimit int `yaml:"commits-limit"` CommitsLimit int `yaml:"commits-limit"`
Repository repository `yaml:"-"` Repository repository `yaml:"-"`
} }
func (widget *repositoryWidget) initialize() error { func (widget *repositoryWidget) initialize() error {

View File

@ -139,7 +139,7 @@ func shortenFeedDescriptionLen(description string, maxLen int) string {
} }
type rssFeedRequest struct { type rssFeedRequest struct {
URL optionalEnvField `yaml:"url"` URL string `yaml:"url"`
Title string `yaml:"title"` Title string `yaml:"title"`
HideCategories bool `yaml:"hide-categories"` HideCategories bool `yaml:"hide-categories"`
HideDescription bool `yaml:"hide-description"` HideDescription bool `yaml:"hide-description"`
@ -161,7 +161,7 @@ func (f rssFeedItemList) sortByNewest() rssFeedItemList {
var feedParser = gofeed.NewParser() var feedParser = gofeed.NewParser()
func fetchItemsFromRSSFeedTask(request rssFeedRequest) ([]rssFeedItem, error) { func fetchItemsFromRSSFeedTask(request rssFeedRequest) ([]rssFeedItem, error) {
req, err := http.NewRequest("GET", request.URL.String(), nil) req, err := http.NewRequest("GET", request.URL, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -206,7 +206,7 @@ func fetchItemsFromRSSFeedTask(request rssFeedRequest) ([]rssFeedItem, error) {
} else { } else {
parsedUrl, err := url.Parse(feed.Link) parsedUrl, err := url.Parse(feed.Link)
if err != nil { if err != nil {
parsedUrl, err = url.Parse(request.URL.String()) parsedUrl, err = url.Parse(request.URL)
} }
if err == nil { if err == nil {