mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-24 16:41:35 -04:00
feat: drop github.com/a8m/envsubst in favor of gookit/config (#9028)
This commit is contained in:
@@ -3,7 +3,8 @@ directives:
|
||||
- '''self'''
|
||||
connect-src:
|
||||
- '''self'''
|
||||
- 'https://${KEYCLOAK_DOMAIN:-keycloak.owncloud.test}/'
|
||||
# In contrary to bash and docker the default is given after the | character
|
||||
- 'https://${KEYCLOAK_DOMAIN|keycloak.owncloud.test}/'
|
||||
default-src:
|
||||
- '''none'''
|
||||
font-src:
|
||||
|
||||
@@ -12,14 +12,16 @@ directives:
|
||||
frame-src:
|
||||
- '''self'''
|
||||
- 'https://embed.diagrams.net/'
|
||||
- 'https://${ONLYOFFICE_DOMAIN:-onlyoffice.owncloud.test}/'
|
||||
- 'https://${COLLABORA_DOMAIN:-collabora.owncloud.test}/'
|
||||
# In contrary to bash and docker the default is given after the | character
|
||||
- 'https://${ONLYOFFICE_DOMAIN|onlyoffice.owncloud.test}/'
|
||||
- 'https://${COLLABORA_DOMAIN|collabora.owncloud.test}/'
|
||||
img-src:
|
||||
- '''self'''
|
||||
- 'data:'
|
||||
- 'blob:'
|
||||
- 'https://${ONLYOFFICE_DOMAIN:-onlyoffice.owncloud.test}/'
|
||||
- 'https://${COLLABORA_DOMAIN:-collabora.owncloud.test}/'
|
||||
# In contrary to bash and docker the default is given after the | character
|
||||
- 'https://${ONLYOFFICE_DOMAIN|onlyoffice.owncloud.test}/'
|
||||
- 'https://${COLLABORA_DOMAIN|collabora.owncloud.test}/'
|
||||
manifest-src:
|
||||
- '''self'''
|
||||
media-src:
|
||||
|
||||
@@ -44,7 +44,7 @@ Let's explore with examples this approach.
|
||||
- docker images: `/etc/ocis/`
|
||||
- binary releases: `$HOME/.ocis/config/`
|
||||
|
||||
followed by the `<extension name>.yaml`, eg `proxy.yaml` for the extension configuration. You also can put an `ocis.yaml` config file to the expected loading location to use a single config file.
|
||||
followed by the `<extension name>.yaml`, e.g. `proxy.yaml` for the extension configuration. You also can put an `ocis.yaml` config file to the expected loading location to use a single config file.
|
||||
|
||||
You can set another directory as config path in the environment variable `OCIS_CONFIG_DIR`. It will then pick the same file names, but from the folder you configured.
|
||||
|
||||
@@ -99,6 +99,21 @@ log:
|
||||
level: info
|
||||
```
|
||||
|
||||
#### Substitute ENV variables in configuration files
|
||||
|
||||
Environment variables can be used in the configurations files and will be replaced by oCIS when loading these.
|
||||
Default values can be specified after a `|` character - see below.
|
||||
|
||||
```yaml
|
||||
proxy:
|
||||
http:
|
||||
addr: ${PROXY_HTTP_ADDR|localhost:4321}
|
||||
log:
|
||||
pretty: true
|
||||
color: true
|
||||
level: info
|
||||
```
|
||||
|
||||
### Workflows
|
||||
|
||||
Since one can run an extension using the runtime (supervised) or not (unsupervised), we ensure correct behavior in both modes, expecting the same outputs.
|
||||
|
||||
1
go.mod
1
go.mod
@@ -9,7 +9,6 @@ require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/MicahParks/keyfunc v1.9.0
|
||||
github.com/Nerzal/gocloak/v13 v13.9.0
|
||||
github.com/a8m/envsubst v1.4.2
|
||||
github.com/bbalet/stopwords v1.0.0
|
||||
github.com/beevik/etree v1.3.0
|
||||
github.com/blevesearch/bleve/v2 v2.4.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -826,8 +826,6 @@ github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVO
|
||||
github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
|
||||
github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
|
||||
github.com/aduffeck/gowebdav v0.0.0-20231215102054-212d4a4374f6 h1:ws0yvsikTQdmheKINP16tBzAHdttrHwbz/q3Fgl9X1Y=
|
||||
github.com/aduffeck/gowebdav v0.0.0-20231215102054-212d4a4374f6/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/a8m/envsubst"
|
||||
gofig "github.com/gookit/config/v2"
|
||||
"github.com/gookit/config/v2/yaml"
|
||||
"github.com/owncloud/ocis/v2/services/proxy/pkg/config"
|
||||
"github.com/unrolled/secure"
|
||||
"github.com/unrolled/secure/cspbuilder"
|
||||
"gopkg.in/yaml.v2"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
@@ -16,15 +16,23 @@ func LoadCSPConfig(proxyCfg *config.Config) (*config.CSP, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// replace env vars ..
|
||||
yamlContent, err = envsubst.Bytes(yamlContent)
|
||||
return loadCSPConfig(yamlContent)
|
||||
}
|
||||
|
||||
// LoadCSPConfig loads CSP header configuration from a yaml file.
|
||||
func loadCSPConfig(yamlContent []byte) (*config.CSP, error) {
|
||||
// substitute env vars and load to struct
|
||||
gofig.WithOptions(gofig.ParseEnv)
|
||||
gofig.AddDriver(yaml.Driver)
|
||||
|
||||
err := gofig.LoadSources("yaml", yamlContent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// read yaml
|
||||
cspConfig := config.CSP{}
|
||||
err = yaml.Unmarshal(yamlContent, &cspConfig)
|
||||
err = gofig.BindStruct("", &cspConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
27
services/proxy/pkg/middleware/security_test.go
Normal file
27
services/proxy/pkg/middleware/security_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"gotest.tools/v3/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadCSPConfig(t *testing.T) {
|
||||
// setup test env
|
||||
yaml := `
|
||||
directives:
|
||||
frame-src:
|
||||
- '''self'''
|
||||
- 'https://embed.diagrams.net/'
|
||||
- 'https://${ONLYOFFICE_DOMAIN|onlyoffice.owncloud.test}/'
|
||||
- 'https://${COLLABORA_DOMAIN|collabora.owncloud.test}/'
|
||||
`
|
||||
|
||||
config, err := loadCSPConfig([]byte(yaml))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, config.Directives["frame-src"][0], "'self'")
|
||||
assert.Equal(t, config.Directives["frame-src"][1], "https://embed.diagrams.net/")
|
||||
assert.Equal(t, config.Directives["frame-src"][2], "https://onlyoffice.owncloud.test/")
|
||||
assert.Equal(t, config.Directives["frame-src"][3], "https://collabora.owncloud.test/")
|
||||
}
|
||||
7
vendor/github.com/a8m/envsubst/.travis.yml
generated
vendored
7
vendor/github.com/a8m/envsubst/.travis.yml
generated
vendored
@@ -1,7 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.13
|
||||
- 1.14
|
||||
- tip
|
||||
scripts:
|
||||
- go test
|
||||
21
vendor/github.com/a8m/envsubst/LICENSE
generated
vendored
21
vendor/github.com/a8m/envsubst/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 envsubst contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
107
vendor/github.com/a8m/envsubst/README.md
generated
vendored
107
vendor/github.com/a8m/envsubst/README.md
generated
vendored
@@ -1,107 +0,0 @@
|
||||
# envsubst
|
||||
[![GoDoc][godoc-img]][godoc-url]
|
||||
[![License][license-image]][license-url]
|
||||
[![Build status][travis-image]][travis-url]
|
||||
[![Github All Releases][releases-image]][releases]
|
||||
|
||||
> Environment variables substitution for Go. see docs [below](#docs)
|
||||
|
||||
#### Installation:
|
||||
|
||||
##### From binaries
|
||||
Latest stable `envsubst` [prebuilt binaries for 64-bit Linux, or Mac OS X][releases] are available via Github releases.
|
||||
|
||||
###### Linux and MacOS
|
||||
```console
|
||||
curl -L https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst-`uname -s`-`uname -m` -o envsubst
|
||||
chmod +x envsubst
|
||||
sudo mv envsubst /usr/local/bin
|
||||
```
|
||||
|
||||
###### Windows
|
||||
Download the latest prebuilt binary from [releases page][releases], or if you have curl installed:
|
||||
```console
|
||||
curl -L https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst.exe
|
||||
```
|
||||
|
||||
##### With go
|
||||
You can install via `go get` (provided you have installed go):
|
||||
```console
|
||||
go get github.com/a8m/envsubst/cmd/envsubst
|
||||
```
|
||||
|
||||
|
||||
#### Using via cli
|
||||
```sh
|
||||
envsubst < input.tmpl > output.text
|
||||
echo 'welcome $HOME ${USER:=a8m}' | envsubst
|
||||
envsubst -help
|
||||
```
|
||||
|
||||
#### Imposing restrictions
|
||||
There are three command line flags with which you can cause the substitution to stop with an error code, should the restriction associated with the flag not be met. This can be handy if you want to avoid creating e.g. configuration files with unset or empty parameters.
|
||||
Setting a `-fail-fast` flag in conjunction with either no-unset or no-empty or both will result in a faster feedback loop, this can be especially useful when running through a large file or byte array input, otherwise a list of errors is returned.
|
||||
|
||||
The flags and their restrictions are:
|
||||
|
||||
|__Option__ | __Meaning__ | __Type__ | __Default__ |
|
||||
| ------------| -------------- | ------------ | ------------ |
|
||||
|`-i` | input file | ```string | stdin``` | `stdin`
|
||||
|`-o` | output file | ```string | stdout``` | `stdout`
|
||||
|`-no-digit` | do not replace variables starting with a digit, e.g. $1 and ${1} | `flag` | `false`
|
||||
|`-no-unset` | fail if a variable is not set | `flag` | `false`
|
||||
|`-no-empty` | fail if a variable is set but empty | `flag` | `false`
|
||||
|`-fail-fast` | fails at first occurence of an error, if `-no-empty` or `-no-unset` flags were **not** specified this is ignored | `flag` | `false`
|
||||
|
||||
These flags can be combined to form tighter restrictions.
|
||||
|
||||
#### Using `envsubst` programmatically ?
|
||||
You can take a look on [`_example/main`](https://github.com/a8m/envsubst/blob/master/_example/main.go) or see the example below.
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/a8m/envsubst"
|
||||
)
|
||||
|
||||
func main() {
|
||||
input := "welcom $HOME"
|
||||
str, err := envsubst.String(input)
|
||||
// ...
|
||||
buf, err := envsubst.Bytes([]byte(input))
|
||||
// ...
|
||||
buf, err := envsubst.ReadFile("filename")
|
||||
}
|
||||
```
|
||||
### Docs
|
||||
> api docs here: [![GoDoc][godoc-img]][godoc-url]
|
||||
|
||||
|__Expression__ | __Meaning__ |
|
||||
| ----------------- | -------------- |
|
||||
|`${var}` | Value of var (same as `$var`)
|
||||
|`${var-$DEFAULT}` | If var not set, evaluate expression as $DEFAULT
|
||||
|`${var:-$DEFAULT}` | If var not set or is empty, evaluate expression as $DEFAULT
|
||||
|`${var=$DEFAULT}` | If var not set, evaluate expression as $DEFAULT
|
||||
|`${var:=$DEFAULT}` | If var not set or is empty, evaluate expression as $DEFAULT
|
||||
|`${var+$OTHER}` | If var set, evaluate expression as $OTHER, otherwise as empty string
|
||||
|`${var:+$OTHER}` | If var set, evaluate expression as $OTHER, otherwise as empty string
|
||||
|`$$var` | Escape expressions. Result will be `$var`.
|
||||
|
||||
<sub>Most of the rows in this table were taken from [here](http://www.tldp.org/LDP/abs/html/refcards.html#AEN22728)</sub>
|
||||
|
||||
### See also
|
||||
|
||||
* `os.ExpandEnv(s string) string` - only supports `$var` and `${var}` notations
|
||||
|
||||
#### License
|
||||
MIT
|
||||
|
||||
[releases]: https://github.com/a8m/envsubst/releases
|
||||
[releases-image]: https://img.shields.io/github/downloads/a8m/envsubst/total.svg?style=for-the-badge
|
||||
[godoc-url]: https://godoc.org/github.com/a8m/envsubst
|
||||
[godoc-img]: https://img.shields.io/badge/godoc-reference-blue.svg?style=for-the-badge
|
||||
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge
|
||||
[license-url]: LICENSE
|
||||
[travis-image]: https://img.shields.io/travis/a8m/envsubst.svg?style=for-the-badge
|
||||
[travis-url]: https://travis-ci.org/a8m/envsubst
|
||||
74
vendor/github.com/a8m/envsubst/envsubst.go
generated
vendored
74
vendor/github.com/a8m/envsubst/envsubst.go
generated
vendored
@@ -1,74 +0,0 @@
|
||||
package envsubst
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/a8m/envsubst/parse"
|
||||
)
|
||||
|
||||
// String returns the parsed template string after processing it.
|
||||
// If the parser encounters invalid input, it returns an error describing the failure.
|
||||
func String(s string) (string, error) {
|
||||
return StringRestricted(s, false, false)
|
||||
}
|
||||
|
||||
// StringRestricted returns the parsed template string after processing it.
|
||||
// If the parser encounters invalid input, or a restriction is violated, it returns
|
||||
// an error describing the failure.
|
||||
// Errors on first failure or returns a collection of failures if failOnFirst is false
|
||||
func StringRestricted(s string, noUnset, noEmpty bool) (string, error) {
|
||||
return StringRestrictedNoDigit(s, noUnset, noEmpty , false)
|
||||
}
|
||||
|
||||
// Like StringRestricted but additionally allows to ignore env variables which start with a digit.
|
||||
func StringRestrictedNoDigit(s string, noUnset, noEmpty bool, noDigit bool) (string, error) {
|
||||
return parse.New("string", os.Environ(),
|
||||
&parse.Restrictions{noUnset, noEmpty, noDigit}).Parse(s)
|
||||
}
|
||||
|
||||
// Bytes returns the bytes represented by the parsed template after processing it.
|
||||
// If the parser encounters invalid input, it returns an error describing the failure.
|
||||
func Bytes(b []byte) ([]byte, error) {
|
||||
return BytesRestricted(b, false, false)
|
||||
}
|
||||
|
||||
// BytesRestricted returns the bytes represented by the parsed template after processing it.
|
||||
// If the parser encounters invalid input, or a restriction is violated, it returns
|
||||
// an error describing the failure.
|
||||
func BytesRestricted(b []byte, noUnset, noEmpty bool) ([]byte, error) {
|
||||
return BytesRestrictedNoDigit(b, noUnset, noEmpty, false)
|
||||
}
|
||||
|
||||
// Like BytesRestricted but additionally allows to ignore env variables which start with a digit.
|
||||
func BytesRestrictedNoDigit(b []byte, noUnset, noEmpty bool, noDigit bool) ([]byte, error) {
|
||||
s, err := parse.New("bytes", os.Environ(),
|
||||
&parse.Restrictions{noUnset, noEmpty, noDigit}).Parse(string(b))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(s), nil
|
||||
}
|
||||
|
||||
// ReadFile call io.ReadFile with the given file name.
|
||||
// If the call to io.ReadFile failed it returns the error; otherwise it will
|
||||
// call envsubst.Bytes with the returned content.
|
||||
func ReadFile(filename string) ([]byte, error) {
|
||||
return ReadFileRestricted(filename, false, false)
|
||||
}
|
||||
|
||||
// ReadFileRestricted calls io.ReadFile with the given file name.
|
||||
// If the call to io.ReadFile failed it returns the error; otherwise it will
|
||||
// call envsubst.Bytes with the returned content.
|
||||
func ReadFileRestricted(filename string, noUnset, noEmpty bool) ([]byte, error) {
|
||||
return ReadFileRestrictedNoDigit(filename, noUnset, noEmpty, false)
|
||||
}
|
||||
|
||||
// Like ReadFileRestricted but additionally allows to ignore env variables which start with a digit.
|
||||
func ReadFileRestrictedNoDigit(filename string, noUnset, noEmpty bool, noDigit bool) ([]byte, error) {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return BytesRestrictedNoDigit(b, noUnset, noEmpty, noDigit)
|
||||
}
|
||||
27
vendor/github.com/a8m/envsubst/parse/env.go
generated
vendored
27
vendor/github.com/a8m/envsubst/parse/env.go
generated
vendored
@@ -1,27 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Env []string
|
||||
|
||||
func (e Env) Get(name string) string {
|
||||
v, _ := e.Lookup(name)
|
||||
return v
|
||||
}
|
||||
|
||||
func (e Env) Has(name string) bool {
|
||||
_, ok := e.Lookup(name)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (e Env) Lookup(name string) (string, bool) {
|
||||
prefix := name + "="
|
||||
for _, pair := range e {
|
||||
if strings.HasPrefix(pair, prefix) {
|
||||
return pair[len(prefix):], true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
269
vendor/github.com/a8m/envsubst/parse/lex.go
generated
vendored
269
vendor/github.com/a8m/envsubst/parse/lex.go
generated
vendored
@@ -1,269 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// itemType identifies the type of lex items.
|
||||
type itemType int
|
||||
|
||||
// Pos represents a byte position in the original input text from which
|
||||
// this template was parsed.
|
||||
type Pos int
|
||||
|
||||
// item represents a token or text string returned from the scanner.
|
||||
type item struct {
|
||||
typ itemType // The type of this item.
|
||||
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||
val string // The value of this item.
|
||||
}
|
||||
|
||||
func (i item) String() string {
|
||||
typ := "OP"
|
||||
if t, ok := tokens[i.typ]; ok {
|
||||
typ = t
|
||||
}
|
||||
return fmt.Sprintf("%s: %.40q", typ, i.val)
|
||||
}
|
||||
|
||||
const (
|
||||
eof = -1
|
||||
itemError itemType = iota // error occurred; value is text of error
|
||||
itemEOF
|
||||
itemText // plain text
|
||||
itemPlus // plus('+')
|
||||
itemDash // dash('-')
|
||||
itemEquals // equals
|
||||
itemColonEquals // colon-equals (':=')
|
||||
itemColonDash // colon-dash(':-')
|
||||
itemColonPlus // colon-plus(':+')
|
||||
itemVariable // variable starting with '$', such as '$hello' or '$1'
|
||||
itemLeftDelim // left action delimiter '${'
|
||||
itemRightDelim // right action delimiter '}'
|
||||
)
|
||||
|
||||
var tokens = map[itemType]string{
|
||||
itemEOF: "EOF",
|
||||
itemError: "ERROR",
|
||||
itemText: "TEXT",
|
||||
itemVariable: "VAR",
|
||||
itemLeftDelim: "START EXP",
|
||||
itemRightDelim: "END EXP",
|
||||
}
|
||||
|
||||
// stateFn represents the state of the lexer as a function that returns the next state.
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
// lexer holds the state of the scanner
|
||||
type lexer struct {
|
||||
input string // the string being lexed
|
||||
state stateFn // the next lexing function to enter
|
||||
pos Pos // current position in the input
|
||||
start Pos // start position of this item
|
||||
width Pos // width of last rune read from input
|
||||
lastPos Pos // position of most recent item returned by nextItem
|
||||
items chan item // channel of lexed items
|
||||
subsDepth int // depth of substitution
|
||||
noDigit bool // if the lexer skips variables that start with a digit
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (l *lexer) next() rune {
|
||||
if int(l.pos) >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = Pos(w)
|
||||
l.pos += l.width
|
||||
return r
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
// emit passes an item back to the client.
|
||||
func (l *lexer) emit(t itemType) {
|
||||
l.items <- item{t, l.start, l.input[l.start:l.pos]}
|
||||
l.lastPos = l.start
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (l *lexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// errorf returns an error token and terminates the scan by passing
|
||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextItem returns the next item from the input.
|
||||
// Called by the parser, not in the lexing goroutine.
|
||||
func (l *lexer) nextItem() item {
|
||||
item := <-l.items
|
||||
return item
|
||||
}
|
||||
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(input string, noDigit bool) *lexer {
|
||||
l := &lexer{
|
||||
input: input,
|
||||
items: make(chan item),
|
||||
noDigit: noDigit,
|
||||
}
|
||||
go l.run()
|
||||
return l
|
||||
}
|
||||
|
||||
// run runs the state machine for the lexer.
|
||||
func (l *lexer) run() {
|
||||
for l.state = lexText; l.state != nil; {
|
||||
l.state = l.state(l)
|
||||
}
|
||||
close(l.items)
|
||||
}
|
||||
|
||||
// lexText scans until encountering with "$" or an opening action delimiter, "${".
|
||||
func lexText(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch r := l.next(); r {
|
||||
case '$':
|
||||
l.pos--
|
||||
// emit the text we've found until here, if any.
|
||||
if l.pos > l.start {
|
||||
l.emit(itemText)
|
||||
}
|
||||
l.pos++
|
||||
switch r := l.peek(); {
|
||||
case l.noDigit && unicode.IsDigit(r):
|
||||
// ignore variable starting with digit like $1.
|
||||
l.next()
|
||||
l.emit(itemText)
|
||||
case r == '$':
|
||||
// ignore the previous '$'.
|
||||
l.ignore()
|
||||
l.next()
|
||||
l.emit(itemText)
|
||||
case r == '{':
|
||||
l.next()
|
||||
r2 := l.peek()
|
||||
if l.noDigit && unicode.IsDigit(r2) {
|
||||
// ignore variable starting with digit like ${1}.
|
||||
l.next()
|
||||
l.emit(itemText)
|
||||
break
|
||||
}
|
||||
l.subsDepth++
|
||||
l.emit(itemLeftDelim)
|
||||
return lexSubstitutionOperator
|
||||
case isAlphaNumeric(r):
|
||||
return lexVariable
|
||||
}
|
||||
case eof:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
// Correctly reached EOF.
|
||||
if l.pos > l.start {
|
||||
l.emit(itemText)
|
||||
}
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
// lexVariable scans a Variable: $Alphanumeric.
|
||||
// The $ has been scanned.
|
||||
func lexVariable(l *lexer) stateFn {
|
||||
var r rune
|
||||
for {
|
||||
r = l.next()
|
||||
if !isAlphaNumeric(r) {
|
||||
l.backup()
|
||||
break
|
||||
}
|
||||
}
|
||||
if v := l.input[l.start:l.pos]; v == "_" || v == "$_" {
|
||||
return lexText
|
||||
}
|
||||
l.emit(itemVariable)
|
||||
if l.subsDepth > 0 {
|
||||
return lexSubstitutionOperator
|
||||
}
|
||||
return lexText
|
||||
}
|
||||
|
||||
// lexSubstitutionOperator scans a starting substitution operator (if any) and continues with lexSubstitution
|
||||
func lexSubstitutionOperator(l *lexer) stateFn {
|
||||
switch r := l.next(); {
|
||||
case r == '}':
|
||||
l.subsDepth--
|
||||
l.emit(itemRightDelim)
|
||||
return lexText
|
||||
case r == eof || isEndOfLine(r):
|
||||
return l.errorf("closing brace expected")
|
||||
case isAlphaNumeric(r) && strings.HasPrefix(l.input[l.lastPos:], "${"):
|
||||
return lexVariable
|
||||
case r == '+':
|
||||
l.emit(itemPlus)
|
||||
case r == '-':
|
||||
l.emit(itemDash)
|
||||
case r == '=':
|
||||
l.emit(itemEquals)
|
||||
case r == ':':
|
||||
switch l.next() {
|
||||
case '-':
|
||||
l.emit(itemColonDash)
|
||||
case '=':
|
||||
l.emit(itemColonEquals)
|
||||
case '+':
|
||||
l.emit(itemColonPlus)
|
||||
}
|
||||
}
|
||||
return lexSubstitution
|
||||
}
|
||||
|
||||
// lexSubstitution scans the elements inside substitution delimiters.
|
||||
func lexSubstitution(l *lexer) stateFn {
|
||||
switch r := l.next(); {
|
||||
case r == '}':
|
||||
l.subsDepth--
|
||||
l.emit(itemRightDelim)
|
||||
return lexText
|
||||
case r == eof || isEndOfLine(r):
|
||||
return l.errorf("closing brace expected")
|
||||
case isAlphaNumeric(r) && strings.HasPrefix(l.input[l.lastPos:], "${"):
|
||||
fallthrough
|
||||
case r == '$':
|
||||
return lexVariable
|
||||
default:
|
||||
l.emit(itemText)
|
||||
}
|
||||
return lexSubstitution
|
||||
}
|
||||
|
||||
// isEndOfLine reports whether r is an end-of-line character.
|
||||
func isEndOfLine(r rune) bool {
|
||||
return r == '\r' || r == '\n'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
}
|
||||
107
vendor/github.com/a8m/envsubst/parse/node.go
generated
vendored
107
vendor/github.com/a8m/envsubst/parse/node.go
generated
vendored
@@ -1,107 +0,0 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Node interface {
|
||||
Type() NodeType
|
||||
String() (string, error)
|
||||
}
|
||||
|
||||
// NodeType identifies the type of a node.
|
||||
type NodeType int
|
||||
|
||||
// Type returns itself and provides an easy default implementation
|
||||
// for embedding in a Node. Embedded in all non-trivial Nodes.
|
||||
func (t NodeType) Type() NodeType {
|
||||
return t
|
||||
}
|
||||
|
||||
const (
|
||||
NodeText NodeType = iota
|
||||
NodeSubstitution
|
||||
NodeVariable
|
||||
)
|
||||
|
||||
type TextNode struct {
|
||||
NodeType
|
||||
Text string
|
||||
}
|
||||
|
||||
func NewText(text string) *TextNode {
|
||||
return &TextNode{NodeText, text}
|
||||
}
|
||||
|
||||
func (t *TextNode) String() (string, error) {
|
||||
return t.Text, nil
|
||||
}
|
||||
|
||||
type VariableNode struct {
|
||||
NodeType
|
||||
Ident string
|
||||
Env Env
|
||||
Restrict *Restrictions
|
||||
}
|
||||
|
||||
func NewVariable(ident string, env Env, restrict *Restrictions) *VariableNode {
|
||||
return &VariableNode{NodeVariable, ident, env, restrict}
|
||||
}
|
||||
|
||||
func (t *VariableNode) String() (string, error) {
|
||||
if err := t.validateNoUnset(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
value := t.Env.Get(t.Ident)
|
||||
if err := t.validateNoEmpty(value); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (t *VariableNode) isSet() bool {
|
||||
return t.Env.Has(t.Ident)
|
||||
}
|
||||
|
||||
func (t *VariableNode) validateNoUnset() error {
|
||||
if t.Restrict.NoUnset && !t.isSet() {
|
||||
return fmt.Errorf("variable ${%s} not set", t.Ident)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *VariableNode) validateNoEmpty(value string) error {
|
||||
if t.Restrict.NoEmpty && value == "" && t.isSet() {
|
||||
return fmt.Errorf("variable ${%s} set but empty", t.Ident)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SubstitutionNode struct {
|
||||
NodeType
|
||||
ExpType itemType
|
||||
Variable *VariableNode
|
||||
Default Node // Default could be variable or text
|
||||
}
|
||||
|
||||
func (t *SubstitutionNode) String() (string, error) {
|
||||
if t.ExpType >= itemPlus && t.Default != nil {
|
||||
switch t.ExpType {
|
||||
case itemColonDash, itemColonEquals:
|
||||
if s, _ := t.Variable.String(); s != "" {
|
||||
return s, nil
|
||||
}
|
||||
return t.Default.String()
|
||||
case itemPlus, itemColonPlus:
|
||||
if t.Variable.isSet() {
|
||||
return t.Default.String()
|
||||
}
|
||||
return "", nil
|
||||
default:
|
||||
if !t.Variable.isSet() {
|
||||
return t.Default.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
return t.Variable.String()
|
||||
}
|
||||
189
vendor/github.com/a8m/envsubst/parse/parse.go
generated
vendored
189
vendor/github.com/a8m/envsubst/parse/parse.go
generated
vendored
@@ -1,189 +0,0 @@
|
||||
// Most of the code in this package taken from golang/text/template/parse
|
||||
package parse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A mode value is a set of flags (or 0). They control parser behavior.
|
||||
type Mode int
|
||||
|
||||
// Mode for parser behaviour
|
||||
const (
|
||||
Quick Mode = iota // stop parsing after first error encoutered and return
|
||||
AllErrors // report all errors
|
||||
)
|
||||
|
||||
// The restrictions option controls the parsring restriction.
|
||||
type Restrictions struct {
|
||||
NoUnset bool
|
||||
NoEmpty bool
|
||||
NoDigit bool
|
||||
}
|
||||
|
||||
// Restrictions specifier
|
||||
var (
|
||||
Relaxed = &Restrictions{false, false, false}
|
||||
NoEmpty = &Restrictions{false, true, false}
|
||||
NoUnset = &Restrictions{true, false, false}
|
||||
Strict = &Restrictions{true, true, false}
|
||||
)
|
||||
|
||||
// Parser type initializer
|
||||
type Parser struct {
|
||||
Name string // name of the processing template
|
||||
Env Env
|
||||
Restrict *Restrictions
|
||||
Mode Mode
|
||||
// parsing state;
|
||||
lex *lexer
|
||||
token [3]item // three-token lookahead
|
||||
peekCount int
|
||||
nodes []Node
|
||||
}
|
||||
|
||||
// New allocates a new Parser with the given name.
|
||||
func New(name string, env []string, r *Restrictions) *Parser {
|
||||
return &Parser{
|
||||
Name: name,
|
||||
Env: Env(env),
|
||||
Restrict: r,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parses the given string.
|
||||
func (p *Parser) Parse(text string) (string, error) {
|
||||
p.lex = lex(text, p.Restrict.NoDigit)
|
||||
// Build internal array of all unset or empty vars here
|
||||
var errs []error
|
||||
// clean parse state
|
||||
p.nodes = make([]Node, 0)
|
||||
p.peekCount = 0
|
||||
if err := p.parse(); err != nil {
|
||||
switch p.Mode {
|
||||
case Quick:
|
||||
return "", err
|
||||
case AllErrors:
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
var out string
|
||||
for _, node := range p.nodes {
|
||||
s, err := node.String()
|
||||
if err != nil {
|
||||
switch p.Mode {
|
||||
case Quick:
|
||||
return "", err
|
||||
case AllErrors:
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
out += s
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
var b strings.Builder
|
||||
for i, err := range errs {
|
||||
if i > 0 {
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
b.WriteString(err.Error())
|
||||
}
|
||||
return "", errors.New(b.String())
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// parse is the top-level parser for the template.
|
||||
// It runs to EOF and return an error if something isn't right.
|
||||
func (p *Parser) parse() error {
|
||||
Loop:
|
||||
for {
|
||||
switch t := p.next(); t.typ {
|
||||
case itemEOF:
|
||||
break Loop
|
||||
case itemError:
|
||||
return p.errorf(t.val)
|
||||
case itemVariable:
|
||||
varNode := NewVariable(strings.TrimPrefix(t.val, "$"), p.Env, p.Restrict)
|
||||
p.nodes = append(p.nodes, varNode)
|
||||
case itemLeftDelim:
|
||||
if p.peek().typ == itemVariable {
|
||||
n, err := p.action()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.nodes = append(p.nodes, n)
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
textNode := NewText(t.val)
|
||||
p.nodes = append(p.nodes, textNode)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse substitution. first item is a variable.
|
||||
func (p *Parser) action() (Node, error) {
|
||||
var expType itemType
|
||||
var defaultNode Node
|
||||
varNode := NewVariable(p.next().val, p.Env, p.Restrict)
|
||||
Loop:
|
||||
for {
|
||||
switch t := p.next(); t.typ {
|
||||
case itemRightDelim:
|
||||
break Loop
|
||||
case itemError:
|
||||
return nil, p.errorf(t.val)
|
||||
case itemVariable:
|
||||
defaultNode = NewVariable(strings.TrimPrefix(t.val, "$"), p.Env, p.Restrict)
|
||||
case itemText:
|
||||
n := NewText(t.val)
|
||||
Text:
|
||||
for {
|
||||
switch p.peek().typ {
|
||||
case itemRightDelim, itemError, itemEOF:
|
||||
break Text
|
||||
default:
|
||||
// patch to accept all kind of chars
|
||||
n.Text += p.next().val
|
||||
}
|
||||
}
|
||||
defaultNode = n
|
||||
default:
|
||||
expType = t.typ
|
||||
}
|
||||
}
|
||||
return &SubstitutionNode{NodeSubstitution, expType, varNode, defaultNode}, nil
|
||||
}
|
||||
|
||||
func (p *Parser) errorf(s string) error {
|
||||
return errors.New(s)
|
||||
}
|
||||
|
||||
// next returns the next token.
|
||||
func (p *Parser) next() item {
|
||||
if p.peekCount > 0 {
|
||||
p.peekCount--
|
||||
} else {
|
||||
p.token[0] = p.lex.nextItem()
|
||||
}
|
||||
return p.token[p.peekCount]
|
||||
}
|
||||
|
||||
// backup backs the input stream up one token.
|
||||
func (p *Parser) backup() {
|
||||
p.peekCount++
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next token.
|
||||
func (p *Parser) peek() item {
|
||||
if p.peekCount > 0 {
|
||||
return p.token[p.peekCount-1]
|
||||
}
|
||||
p.peekCount = 1
|
||||
p.token[0] = p.lex.nextItem()
|
||||
return p.token[0]
|
||||
}
|
||||
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
@@ -69,10 +69,6 @@ github.com/ProtonMail/go-crypto/openpgp/s2k
|
||||
github.com/RoaringBitmap/roaring
|
||||
github.com/RoaringBitmap/roaring/internal
|
||||
github.com/RoaringBitmap/roaring/roaring64
|
||||
# github.com/a8m/envsubst v1.4.2
|
||||
## explicit; go 1.17
|
||||
github.com/a8m/envsubst
|
||||
github.com/a8m/envsubst/parse
|
||||
# github.com/agnivade/levenshtein v1.1.1
|
||||
## explicit; go 1.13
|
||||
github.com/agnivade/levenshtein
|
||||
|
||||
Reference in New Issue
Block a user