mirror of
https://github.com/glanceapp/glance.git
synced 2025-06-21 18:31:24 +02:00
Allow inserting env variables anywhere in the config
This commit is contained in:
parent
8d2639b349
commit
dbcc13a5cf
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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 }}">
|
||||||
|
@ -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>
|
||||||
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user