Compare commits
19 Commits
serjs-2022
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
c30bfd3d72 | ||
|
93554c03aa | ||
|
d0347549a4 | ||
|
246dd0bc11 | ||
|
902be14132 | ||
|
84b9c49bc8 | ||
|
4444e7821c | ||
|
d6a5236865 | ||
|
70d9b0bbce | ||
|
ef8b84fb61 | ||
|
2ada16b7eb | ||
|
2092ff3c7e | ||
|
cc194c57f7 | ||
|
3838358f76 | ||
|
bd58d89737 | ||
|
96594678f9 | ||
|
f931de4e46 | ||
|
4068548ec5 | ||
|
ca40be5600 |
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
61
.github/workflows/main.yml
vendored
Normal file
61
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
name: "Latest tag from master branch"
|
||||
env:
|
||||
DOCKERHUB_REPOSITORY: serjs/go-socks5-proxy
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: master
|
||||
push:
|
||||
branches: master
|
||||
release:
|
||||
types: [published, edited]
|
||||
|
||||
jobs:
|
||||
hadolint:
|
||||
name: hadolint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: hadolint
|
||||
uses: hadolint/hadolint-action@v1.5.0
|
||||
with:
|
||||
ignore: DL3018
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: crazy-max/ghaction-docker-buildx@v1.1.0
|
||||
-
|
||||
name: Print builder available platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||
-
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: serjs/go-socks5-proxy
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'master') }}
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{raw}}
|
||||
-
|
||||
name: Dockerhub login
|
||||
uses: docker/login-action@v2
|
||||
if: github.event_name != 'pull_request'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Run Buildx for amd64, armv7, arm64 architectures
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
44
.github/workflows/master.yml
vendored
44
.github/workflows/master.yml
vendored
@ -1,44 +0,0 @@
|
||||
name: "Latest tag from master branch"
|
||||
env:
|
||||
DOCKERHUB_REPOSITORY: serjs/go-socks5-proxy
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: master
|
||||
push:
|
||||
branches: master
|
||||
|
||||
jobs:
|
||||
hadolint:
|
||||
name: hadolint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: hadolint
|
||||
uses: hadolint/hadolint-action@v1.5.0
|
||||
with:
|
||||
ignore: DL3018
|
||||
buildx:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: crazy-max/ghaction-docker-buildx@v1.1.0
|
||||
-
|
||||
name: Print builder available platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||
- name: Dockerhub login
|
||||
run: docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
-
|
||||
name: Run Buildx
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 \
|
||||
--output "type=image,push=false" \
|
||||
--push \
|
||||
--tag "$DOCKERHUB_REPOSITORY:latest" \
|
||||
.
|
35
.github/workflows/release.yml
vendored
35
.github/workflows/release.yml
vendored
@ -1,35 +0,0 @@
|
||||
name: "Release tag"
|
||||
env:
|
||||
DOCKERHUB_REPOSITORY: serjs/go-socks5-proxy
|
||||
|
||||
on:
|
||||
release:
|
||||
# Only use the types keyword to narrow down the activity types that will trigger your workflow.
|
||||
types: [published, edited]
|
||||
|
||||
jobs:
|
||||
buildx:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: crazy-max/ghaction-docker-buildx@v1.1.0
|
||||
- name: Release version
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
|
||||
- name: Print release version
|
||||
run: echo ${{ env.RELEASE_VERSION }}
|
||||
- name: Dockerhub login
|
||||
run: docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
-
|
||||
name: Run Buildx
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 \
|
||||
--output "type=image,push=false" \
|
||||
--push \
|
||||
--tag "$DOCKERHUB_REPOSITORY:$RELEASE_VERSION" \
|
||||
.
|
@ -2,6 +2,13 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [Unreleased - available on :latest tag for docker image]
|
||||
### Changed
|
||||
- Migrate to distroless docker image from scratch
|
||||
-
|
||||
### Added
|
||||
- New ALLOWED_DEST_FQDN config env paramteter for filtering dest FQND based on regex patterns
|
||||
- New SetIPWhitelist config env paramteter for setting whitelist set of ip addresses which allowed to use proxy connection
|
||||
- Dependabot version updates automation
|
||||
|
||||
## [v0.0.3] - 2021-07-07
|
||||
### Added
|
||||
|
@ -1,4 +1,4 @@
|
||||
ARG GOLANG_VERSION="1.16.5"
|
||||
ARG GOLANG_VERSION="1.19.1"
|
||||
|
||||
FROM golang:$GOLANG_VERSION-alpine as builder
|
||||
RUN apk --no-cache add tzdata
|
||||
@ -6,8 +6,6 @@ WORKDIR /go/src/github.com/serjs/socks5
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-s' -o ./socks5
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
FROM gcr.io/distroless/static:nonroot
|
||||
COPY --from=builder /go/src/github.com/serjs/socks5/socks5 /
|
||||
ENTRYPOINT ["/socks5"]
|
||||
|
12
Dockerfile.armv6
Normal file
12
Dockerfile.armv6
Normal file
@ -0,0 +1,12 @@
|
||||
ARG GOLANG_VERSION="1.19.1"
|
||||
|
||||
FROM golang:$GOLANG_VERSION-alpine as builder
|
||||
RUN apk --no-cache add tzdata
|
||||
WORKDIR /go/src/github.com/serjs/socks5
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-s' -o ./socks5
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /go/src/github.com/serjs/socks5/socks5 /
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
ENTRYPOINT ["/socks5"]
|
36
README.md
36
README.md
@ -3,34 +3,48 @@
|
||||
![Latest tag from master branch](https://github.com/serjs/socks5-server/workflows/Latest%20tag%20from%20master%20branch/badge.svg)
|
||||
![Release tag](https://github.com/serjs/socks5-server/workflows/Release%20tag/badge.svg)
|
||||
|
||||
Simple socks5 server using go-socks5 with authentication options
|
||||
Simple socks5 server using go-socks5 with authentication, allowed ips list and destination FQDNs filtering
|
||||
|
||||
## Start container with proxy
|
||||
# Examples
|
||||
|
||||
- Run docker container using default container port 1080 and expose it to world using host port 1080, with auth creds
|
||||
|
||||
```docker run -d --name socks5 -p 1080:1080 -e PROXY_USER=<PROXY_USER> -e PROXY_PASSWORD=<PROXY_PASSWORD> serjs/go-socks5-proxy```
|
||||
|
||||
Leave `PROXY_USER` and `PROXY_PASSWORD` empty for skip authentication options while running socks5 server.
|
||||
- Leave `PROXY_USER` and `PROXY_PASSWORD` empty for skip authentication options while running socks5 server, see example below
|
||||
|
||||
## List of all supported config parameters
|
||||
- Run docker container using specifit container port and expose it to host port 1090, without auth creds
|
||||
|
||||
```docker run -d --name socks5 -p 1090:9090 -e PROXY_PORT=9090 serjs/go-socks5-proxy```
|
||||
|
||||
# List of supported config parameters
|
||||
|
||||
|ENV variable|Type|Default|Description|
|
||||
|------------|----|-------|-----------|
|
||||
|PROXY_USER|String|EMPTY|Set proxy user (also required existed PROXY_PASS)|
|
||||
|PROXY_PASSWORD|String|EMPTY|Set proxy password for auth, used with PROXY_USER|
|
||||
|PROXY_PORT|String|1080|Set listen port for application inside docker container|
|
||||
|TZ|String|UTC|Set Timezone like in many common Operation Systems|
|
||||
|ALLOWED_DEST_FQDN|String|EMPTY|Allowed destination address regular expression pattern. Default allows all.|
|
||||
|ALLOWED_IPS|String|Empty|Set allowed IP's that can connect to proxy, separator `,`|
|
||||
|
||||
## Test running service
|
||||
|
||||
Without authentication
|
||||
# Build your own image:
|
||||
`docker-compose -f docker-compose.build.yml up -d`\
|
||||
Just don't forget to set parameters in the `.env` file.
|
||||
|
||||
```curl --socks5 <docker host ip>:1080 http://ifcfg.co``` - result must show docker host ip (for bridged network)
|
||||
# Test running service
|
||||
|
||||
Assuming that you are using container on 1080 host docker port
|
||||
|
||||
## Without authentication
|
||||
|
||||
```curl --socks5 <docker host ip>:1080 https://ifcfg.co``` - result must show docker host ip (for bridged network)
|
||||
|
||||
or
|
||||
|
||||
```docker run --rm curlimages/curl:7.65.3 -s --socks5 <docker host ip>:1080 http://ifcfg.co```
|
||||
```docker run --rm curlimages/curl:7.65.3 -s --socks5 <docker host ip>:1080 https://ifcfg.co```
|
||||
|
||||
With authentication - result must show docker host ip (for bridged network)
|
||||
## With authentication
|
||||
|
||||
```curl --socks5 <docker host ip>:1080 -U <PROXY_USER>:<PROXY_PASSWORD> http://ifcfg.co```
|
||||
|
||||
@ -38,7 +52,7 @@ or
|
||||
|
||||
```docker run --rm curlimages/curl:7.65.3 -s --socks5 <PROXY_USER>:<PROXY_PASSWORD>@<docker host ip>:1080 http://ifcfg.co```
|
||||
|
||||
## Authors
|
||||
# Authors
|
||||
|
||||
* **Sergey Bogayrets**
|
||||
|
||||
|
11
docker-compose.build.yml
Normal file
11
docker-compose.build.yml
Normal file
@ -0,0 +1,11 @@
|
||||
version: '3'
|
||||
services:
|
||||
socks5-build:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: socks5-build
|
||||
env_file: .env
|
||||
ports:
|
||||
- "1080:1080"
|
||||
restart: unless-stopped
|
7
go.mod
7
go.mod
@ -1,9 +1,10 @@
|
||||
module github.com/serjs/socks5-server
|
||||
|
||||
go 1.16
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/caarlos0/env v3.3.1-0.20180521112546-3e0f30cbf50b+incompatible
|
||||
golang.org/x/net v0.0.0-20180406214816-61147c48b25b // indirect
|
||||
github.com/caarlos0/env/v6 v6.10.1
|
||||
)
|
||||
|
||||
require golang.org/x/net v0.17.0
|
||||
|
8
go.sum
8
go.sum
@ -1,6 +1,6 @@
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/caarlos0/env v3.3.1-0.20180521112546-3e0f30cbf50b+incompatible h1:FjzBjKb2W4ggJbyACel2jbTWSGqCpmu9YqXwcpUp+3M=
|
||||
github.com/caarlos0/env v3.3.1-0.20180521112546-3e0f30cbf50b+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
|
||||
golang.org/x/net v0.0.0-20180406214816-61147c48b25b h1:7rskAFQwNXGW6AD8E/6y0LDHW5mT9rsLD7ViLVFfh5w=
|
||||
golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
|
||||
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
|
24
ruleset.go
Normal file
24
ruleset.go
Normal file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/armon/go-socks5"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// PermitDestAddrPattern returns a RuleSet which selectively allows addresses
|
||||
func PermitDestAddrPattern(pattern string) socks5.RuleSet {
|
||||
return &PermitDestAddrPatternRuleSet{pattern}
|
||||
}
|
||||
|
||||
// PermitDestAddrPatternRuleSet is an implementation of the RuleSet which
|
||||
// enables filtering supported destination address
|
||||
type PermitDestAddrPatternRuleSet struct {
|
||||
AllowedFqdnPattern string
|
||||
}
|
||||
|
||||
func (p *PermitDestAddrPatternRuleSet) Allow(ctx context.Context, req *socks5.Request) (context.Context, bool) {
|
||||
match, _ := regexp.MatchString(p.AllowedFqdnPattern, req.DestAddr.FQDN)
|
||||
return ctx, match
|
||||
}
|
24
server.go
24
server.go
@ -2,16 +2,19 @@ package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/armon/go-socks5"
|
||||
"github.com/caarlos0/env"
|
||||
"github.com/caarlos0/env/v6"
|
||||
)
|
||||
|
||||
type params struct {
|
||||
User string `env:"PROXY_USER" envDefault:""`
|
||||
Password string `env:"PROXY_PASSWORD" envDefault:""`
|
||||
Port string `env:"PROXY_PORT" envDefault:"1080"`
|
||||
AllowedDestFqdn string `env:"ALLOWED_DEST_FQDN" envDefault:""`
|
||||
AllowedIPs []string `env:"ALLOWED_IPS" envSeparator:"," envDefault:""`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -23,7 +26,7 @@ func main() {
|
||||
}
|
||||
|
||||
//Initialize socks5 config
|
||||
socsk5conf := &socks5.Config{
|
||||
socks5conf := &socks5.Config{
|
||||
Logger: log.New(os.Stdout, "", log.LstdFlags),
|
||||
}
|
||||
|
||||
@ -32,14 +35,27 @@ func main() {
|
||||
os.Getenv("PROXY_USER"): os.Getenv("PROXY_PASSWORD"),
|
||||
}
|
||||
cator := socks5.UserPassAuthenticator{Credentials: creds}
|
||||
socsk5conf.AuthMethods = []socks5.Authenticator{cator}
|
||||
socks5conf.AuthMethods = []socks5.Authenticator{cator}
|
||||
}
|
||||
|
||||
server, err := socks5.New(socsk5conf)
|
||||
if cfg.AllowedDestFqdn != "" {
|
||||
socks5conf.Rules = PermitDestAddrPattern(cfg.AllowedDestFqdn)
|
||||
}
|
||||
|
||||
server, err := socks5.New(socks5conf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Set IP whitelist
|
||||
if len(cfg.AllowedIPs) > 0 {
|
||||
whitelist := make([]net.IP, len(cfg.AllowedIPs))
|
||||
for i, ip := range cfg.AllowedIPs {
|
||||
whitelist[i] = net.ParseIP(ip)
|
||||
}
|
||||
server.SetIPWhitelist(whitelist)
|
||||
}
|
||||
|
||||
log.Printf("Start listening proxy service on port %s\n", cfg.Port)
|
||||
if err := server.ListenAndServe("tcp", ":"+cfg.Port); err != nil {
|
||||
log.Fatal(err)
|
||||
|
1
vendor/github.com/caarlos0/env/.gitignore
generated
vendored
1
vendor/github.com/caarlos0/env/.gitignore
generated
vendored
@ -1 +0,0 @@
|
||||
coverage.out
|
2
vendor/github.com/caarlos0/env/.hound.yml
generated
vendored
2
vendor/github.com/caarlos0/env/.hound.yml
generated
vendored
@ -1,2 +0,0 @@
|
||||
go:
|
||||
enabled: true
|
16
vendor/github.com/caarlos0/env/.travis.yml
generated
vendored
16
vendor/github.com/caarlos0/env/.travis.yml
generated
vendored
@ -1,16 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- tip
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- go test -v -cover -race -coverprofile=coverage.out
|
||||
after_script:
|
||||
- go get github.com/mattn/goveralls
|
||||
- goveralls -coverprofile=coverage.out -service=travis-ci -repotoken='eCcizKmTdSaJCz8Ih33WDppdqb9kioYwi'
|
115
vendor/github.com/caarlos0/env/README.md
generated
vendored
115
vendor/github.com/caarlos0/env/README.md
generated
vendored
@ -1,115 +0,0 @@
|
||||
# env [![Build Status](https://travis-ci.org/caarlos0/env.svg?branch=master)](https://travis-ci.org/caarlos0/env) [![Coverage Status](https://coveralls.io/repos/caarlos0/env/badge.svg?branch=master&service=github)](https://coveralls.io/github/caarlos0/env?branch=master) [![](https://godoc.org/github.com/caarlos0/env?status.svg)](http://godoc.org/github.com/caarlos0/env) [![](http://goreportcard.com/badge/caarlos0/env)](http://goreportcard.com/report/caarlos0/env) [![SayThanks.io](https://img.shields.io/badge/SayThanks.io-%E2%98%BC-1EAEDB.svg?style=flat-square)](https://saythanks.io/to/caarlos0)
|
||||
|
||||
A KISS way to deal with environment variables in Go.
|
||||
|
||||
## Why
|
||||
|
||||
At first, it was boring for me to write down an entire function just to
|
||||
get some `var` from the environment and default to another in case it's missing.
|
||||
|
||||
For that manner, I wrote a `GetOr` function in the
|
||||
[go-idioms](https://github.com/caarlos0/go-idioms) project.
|
||||
|
||||
Then, I got pissed about writing `os.Getenv`, `os.Setenv`, `os.Unsetenv`...
|
||||
it kind of make more sense to me write it as `env.Get`, `env.Set`, `env.Unset`.
|
||||
So I did.
|
||||
|
||||
Then I got a better idea: to use `struct` tags to do all that work for me.
|
||||
|
||||
## Example
|
||||
|
||||
A very basic example (check the `examples` folder):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/caarlos0/env"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Home string `env:"HOME"`
|
||||
Port int `env:"PORT" envDefault:"3000"`
|
||||
IsProduction bool `env:"PRODUCTION"`
|
||||
Hosts []string `env:"HOSTS" envSeparator:":"`
|
||||
Duration time.Duration `env:"DURATION"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{}
|
||||
err := env.Parse(&cfg)
|
||||
if err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
}
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
You can run it like this:
|
||||
|
||||
```sh
|
||||
$ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s go run examples/first.go
|
||||
{Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s}
|
||||
```
|
||||
|
||||
## Supported types and defaults
|
||||
|
||||
The library has built-in support for the following types:
|
||||
|
||||
* `string`
|
||||
* `int`
|
||||
* `uint`
|
||||
* `int64`
|
||||
* `bool`
|
||||
* `float32`
|
||||
* `float64`
|
||||
* `time.Duration`
|
||||
* `[]string`
|
||||
* `[]int`
|
||||
* `[]bool`
|
||||
* `[]float32`
|
||||
* `[]float64`
|
||||
* `[]time.Duration`
|
||||
* .. or use/define a [custom parser func](#custom-parser-funcs) for any other type
|
||||
|
||||
If you set the `envDefault` tag for something, this value will be used in the
|
||||
case of absence of it in the environment. If you don't do that AND the
|
||||
environment variable is also not set, the zero-value
|
||||
of the type will be used: empty for `string`s, `false` for `bool`s
|
||||
and `0` for `int`s.
|
||||
|
||||
By default, slice types will split the environment value on `,`; you can change this behavior by setting the `envSeparator` tag.
|
||||
|
||||
## Custom Parser Funcs
|
||||
|
||||
If you have a type that is not supported out of the box by the lib, you are able
|
||||
to use (or define) and pass custom parsers (and their associated `reflect.Type`) to the
|
||||
`env.ParseWithFuncs()` function.
|
||||
|
||||
In addition to accepting a struct pointer (same as `Parse()`), this function also
|
||||
accepts a `env.CustomParsers` arg that under the covers is a `map[reflect.Type]env.ParserFunc`.
|
||||
|
||||
To see what this looks like in practice, take a look at the [commented block in the example](https://github.com/caarlos0/env/blob/master/examples/first.go#L35-L39).
|
||||
|
||||
`env` also ships with some pre-built custom parser funcs for common types. You
|
||||
can check them out [here](parsers/).
|
||||
|
||||
## Required fields
|
||||
|
||||
The `env` tag option `required` (e.g., `env:"tagKey,required"`) can be added
|
||||
to ensure that some environment variable is set. In the example above,
|
||||
an error is returned if the `config` struct is changed to:
|
||||
|
||||
|
||||
```go
|
||||
type config struct {
|
||||
Home string `env:"HOME"`
|
||||
Port int `env:"PORT" envDefault:"3000"`
|
||||
IsProduction bool `env:"PRODUCTION"`
|
||||
Hosts []string `env:"HOSTS" envSeparator:":"`
|
||||
SecretKey string `env:"SECRET_KEY,required"`
|
||||
}
|
||||
```
|
398
vendor/github.com/caarlos0/env/env.go
generated
vendored
398
vendor/github.com/caarlos0/env/env.go
generated
vendored
@ -1,398 +0,0 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotAStructPtr is returned if you pass something that is not a pointer to a
|
||||
// Struct to Parse
|
||||
ErrNotAStructPtr = errors.New("Expected a pointer to a Struct")
|
||||
// ErrUnsupportedType if the struct field type is not supported by env
|
||||
ErrUnsupportedType = errors.New("Type is not supported")
|
||||
// ErrUnsupportedSliceType if the slice element type is not supported by env
|
||||
ErrUnsupportedSliceType = errors.New("Unsupported slice type")
|
||||
// OnEnvVarSet is an optional convenience callback, such as for logging purposes.
|
||||
// If not nil, it's called after successfully setting the given field from the given value.
|
||||
OnEnvVarSet func(reflect.StructField, string)
|
||||
// Friendly names for reflect types
|
||||
sliceOfInts = reflect.TypeOf([]int(nil))
|
||||
sliceOfInt64s = reflect.TypeOf([]int64(nil))
|
||||
sliceOfUint64s = reflect.TypeOf([]uint64(nil))
|
||||
sliceOfStrings = reflect.TypeOf([]string(nil))
|
||||
sliceOfBools = reflect.TypeOf([]bool(nil))
|
||||
sliceOfFloat32s = reflect.TypeOf([]float32(nil))
|
||||
sliceOfFloat64s = reflect.TypeOf([]float64(nil))
|
||||
sliceOfDurations = reflect.TypeOf([]time.Duration(nil))
|
||||
)
|
||||
|
||||
// CustomParsers is a friendly name for the type that `ParseWithFuncs()` accepts
|
||||
type CustomParsers map[reflect.Type]ParserFunc
|
||||
|
||||
// ParserFunc defines the signature of a function that can be used within `CustomParsers`
|
||||
type ParserFunc func(v string) (interface{}, error)
|
||||
|
||||
// Parse parses a struct containing `env` tags and loads its values from
|
||||
// environment variables.
|
||||
func Parse(v interface{}) error {
|
||||
ptrRef := reflect.ValueOf(v)
|
||||
if ptrRef.Kind() != reflect.Ptr {
|
||||
return ErrNotAStructPtr
|
||||
}
|
||||
ref := ptrRef.Elem()
|
||||
if ref.Kind() != reflect.Struct {
|
||||
return ErrNotAStructPtr
|
||||
}
|
||||
return doParse(ref, make(map[reflect.Type]ParserFunc, 0))
|
||||
}
|
||||
|
||||
// ParseWithFuncs is the same as `Parse` except it also allows the user to pass
|
||||
// in custom parsers.
|
||||
func ParseWithFuncs(v interface{}, funcMap CustomParsers) error {
|
||||
ptrRef := reflect.ValueOf(v)
|
||||
if ptrRef.Kind() != reflect.Ptr {
|
||||
return ErrNotAStructPtr
|
||||
}
|
||||
ref := ptrRef.Elem()
|
||||
if ref.Kind() != reflect.Struct {
|
||||
return ErrNotAStructPtr
|
||||
}
|
||||
return doParse(ref, funcMap)
|
||||
}
|
||||
|
||||
func doParse(ref reflect.Value, funcMap CustomParsers) error {
|
||||
refType := ref.Type()
|
||||
var errorList []string
|
||||
|
||||
for i := 0; i < refType.NumField(); i++ {
|
||||
refField := ref.Field(i)
|
||||
if reflect.Ptr == refField.Kind() && !refField.IsNil() && refField.CanSet() {
|
||||
err := Parse(refField.Interface())
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
refTypeField := refType.Field(i)
|
||||
value, err := get(refTypeField)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
continue
|
||||
}
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if err := set(refField, refTypeField, value, funcMap); err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
continue
|
||||
}
|
||||
if OnEnvVarSet != nil {
|
||||
OnEnvVarSet(refTypeField, value)
|
||||
}
|
||||
}
|
||||
if len(errorList) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(strings.Join(errorList, ". "))
|
||||
}
|
||||
|
||||
func get(field reflect.StructField) (string, error) {
|
||||
var (
|
||||
val string
|
||||
err error
|
||||
)
|
||||
|
||||
key, opts := parseKeyForOption(field.Tag.Get("env"))
|
||||
|
||||
defaultValue := field.Tag.Get("envDefault")
|
||||
val = getOr(key, defaultValue)
|
||||
|
||||
if len(opts) > 0 {
|
||||
for _, opt := range opts {
|
||||
// The only option supported is "required".
|
||||
switch opt {
|
||||
case "":
|
||||
break
|
||||
case "required":
|
||||
val, err = getRequired(key)
|
||||
default:
|
||||
err = errors.New("Env tag option " + opt + " not supported.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return val, err
|
||||
}
|
||||
|
||||
// split the env tag's key into the expected key and desired option, if any.
|
||||
func parseKeyForOption(key string) (string, []string) {
|
||||
opts := strings.Split(key, ",")
|
||||
return opts[0], opts[1:]
|
||||
}
|
||||
|
||||
func getRequired(key string) (string, error) {
|
||||
if value, ok := os.LookupEnv(key); ok {
|
||||
return value, nil
|
||||
}
|
||||
// We do not use fmt.Errorf to avoid another import.
|
||||
return "", errors.New("Required environment variable " + key + " is not set")
|
||||
}
|
||||
|
||||
func getOr(key, defaultValue string) string {
|
||||
value, ok := os.LookupEnv(key)
|
||||
if ok {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func set(field reflect.Value, refType reflect.StructField, value string, funcMap CustomParsers) error {
|
||||
switch field.Kind() {
|
||||
case reflect.Slice:
|
||||
separator := refType.Tag.Get("envSeparator")
|
||||
return handleSlice(field, value, separator)
|
||||
case reflect.String:
|
||||
field.SetString(value)
|
||||
case reflect.Bool:
|
||||
bvalue, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetBool(bvalue)
|
||||
case reflect.Int:
|
||||
intValue, err := strconv.ParseInt(value, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetInt(intValue)
|
||||
case reflect.Uint:
|
||||
uintValue, err := strconv.ParseUint(value, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetUint(uintValue)
|
||||
case reflect.Float32:
|
||||
v, err := strconv.ParseFloat(value, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetFloat(v)
|
||||
case reflect.Float64:
|
||||
v, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Set(reflect.ValueOf(v))
|
||||
case reflect.Int64:
|
||||
if refType.Type.String() == "time.Duration" {
|
||||
dValue, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Set(reflect.ValueOf(dValue))
|
||||
} else {
|
||||
intValue, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetInt(intValue)
|
||||
}
|
||||
case reflect.Uint64:
|
||||
uintValue, err := strconv.ParseUint(value, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.SetUint(uintValue)
|
||||
case reflect.Struct:
|
||||
return handleStruct(field, refType, value, funcMap)
|
||||
default:
|
||||
parserFunc, ok := funcMap[refType.Type]
|
||||
if !ok {
|
||||
return ErrUnsupportedType
|
||||
}
|
||||
val, err := parserFunc(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Set(reflect.ValueOf(val))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleStruct(field reflect.Value, refType reflect.StructField, value string, funcMap CustomParsers) error {
|
||||
// Does the custom parser func map contain this type?
|
||||
parserFunc, ok := funcMap[field.Type()]
|
||||
if !ok {
|
||||
// Map does not contain a custom parser for this type
|
||||
return ErrUnsupportedType
|
||||
}
|
||||
|
||||
// Call on the custom parser func
|
||||
data, err := parserFunc(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Custom parser error: %v", err)
|
||||
}
|
||||
|
||||
// Set the field to the data returned by the customer parser func
|
||||
rv := reflect.ValueOf(data)
|
||||
field.Set(rv)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSlice(field reflect.Value, value, separator string) error {
|
||||
if separator == "" {
|
||||
separator = ","
|
||||
}
|
||||
|
||||
splitData := strings.Split(value, separator)
|
||||
|
||||
switch field.Type() {
|
||||
case sliceOfStrings:
|
||||
field.Set(reflect.ValueOf(splitData))
|
||||
case sliceOfInts:
|
||||
intData, err := parseInts(splitData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Set(reflect.ValueOf(intData))
|
||||
case sliceOfInt64s:
|
||||
int64Data, err := parseInt64s(splitData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Set(reflect.ValueOf(int64Data))
|
||||
case sliceOfUint64s:
|
||||
uint64Data, err := parseUint64s(splitData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Set(reflect.ValueOf(uint64Data))
|
||||
case sliceOfFloat32s:
|
||||
data, err := parseFloat32s(splitData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Set(reflect.ValueOf(data))
|
||||
case sliceOfFloat64s:
|
||||
data, err := parseFloat64s(splitData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Set(reflect.ValueOf(data))
|
||||
case sliceOfBools:
|
||||
boolData, err := parseBools(splitData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Set(reflect.ValueOf(boolData))
|
||||
case sliceOfDurations:
|
||||
durationData, err := parseDurations(splitData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Set(reflect.ValueOf(durationData))
|
||||
default:
|
||||
return ErrUnsupportedSliceType
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseInts(data []string) ([]int, error) {
|
||||
intSlice := make([]int, 0, len(data))
|
||||
|
||||
for _, v := range data {
|
||||
intValue, err := strconv.ParseInt(v, 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
intSlice = append(intSlice, int(intValue))
|
||||
}
|
||||
return intSlice, nil
|
||||
}
|
||||
|
||||
func parseInt64s(data []string) ([]int64, error) {
|
||||
intSlice := make([]int64, 0, len(data))
|
||||
|
||||
for _, v := range data {
|
||||
intValue, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
intSlice = append(intSlice, int64(intValue))
|
||||
}
|
||||
return intSlice, nil
|
||||
}
|
||||
|
||||
func parseUint64s(data []string) ([]uint64, error) {
|
||||
var uintSlice []uint64
|
||||
|
||||
for _, v := range data {
|
||||
uintValue, err := strconv.ParseUint(v, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uintSlice = append(uintSlice, uint64(uintValue))
|
||||
}
|
||||
return uintSlice, nil
|
||||
}
|
||||
|
||||
func parseFloat32s(data []string) ([]float32, error) {
|
||||
float32Slice := make([]float32, 0, len(data))
|
||||
|
||||
for _, v := range data {
|
||||
data, err := strconv.ParseFloat(v, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
float32Slice = append(float32Slice, float32(data))
|
||||
}
|
||||
return float32Slice, nil
|
||||
}
|
||||
|
||||
func parseFloat64s(data []string) ([]float64, error) {
|
||||
float64Slice := make([]float64, 0, len(data))
|
||||
|
||||
for _, v := range data {
|
||||
data, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
float64Slice = append(float64Slice, float64(data))
|
||||
}
|
||||
return float64Slice, nil
|
||||
}
|
||||
|
||||
func parseBools(data []string) ([]bool, error) {
|
||||
boolSlice := make([]bool, 0, len(data))
|
||||
|
||||
for _, v := range data {
|
||||
bvalue, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
boolSlice = append(boolSlice, bvalue)
|
||||
}
|
||||
return boolSlice, nil
|
||||
}
|
||||
|
||||
func parseDurations(data []string) ([]time.Duration, error) {
|
||||
durationSlice := make([]time.Duration, 0, len(data))
|
||||
|
||||
for _, v := range data {
|
||||
dvalue, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
durationSlice = append(durationSlice, dvalue)
|
||||
}
|
||||
return durationSlice, nil
|
||||
}
|
4
vendor/github.com/caarlos0/env/v6/.gitignore
generated
vendored
Normal file
4
vendor/github.com/caarlos0/env/v6/.gitignore
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
coverage.txt
|
||||
bin
|
||||
card.png
|
||||
dist
|
8
vendor/github.com/caarlos0/env/v6/.golangci.yml
generated
vendored
Normal file
8
vendor/github.com/caarlos0/env/v6/.golangci.yml
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
linters:
|
||||
enable:
|
||||
- thelper
|
||||
- gofumpt
|
||||
- tparallel
|
||||
- unconvert
|
||||
- unparam
|
||||
- wastedassign
|
3
vendor/github.com/caarlos0/env/v6/.goreleaser.yml
generated
vendored
Normal file
3
vendor/github.com/caarlos0/env/v6/.goreleaser.yml
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
includes:
|
||||
- from_url:
|
||||
url: https://raw.githubusercontent.com/caarlos0/.goreleaserfiles/main/lib.yml
|
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2016 Carlos Alexandro Becker
|
||||
Copyright (c) 2015-2022 Carlos Alexandro Becker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
37
vendor/github.com/caarlos0/env/v6/Makefile
generated
vendored
Normal file
37
vendor/github.com/caarlos0/env/v6/Makefile
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
SOURCE_FILES?=./...
|
||||
TEST_PATTERN?=.
|
||||
|
||||
export GO111MODULE := on
|
||||
|
||||
setup:
|
||||
go mod tidy
|
||||
.PHONY: setup
|
||||
|
||||
build:
|
||||
go build
|
||||
.PHONY: build
|
||||
|
||||
test:
|
||||
go test -v -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m
|
||||
.PHONY: test
|
||||
|
||||
cover: test
|
||||
go tool cover -html=coverage.txt
|
||||
.PHONY: cover
|
||||
|
||||
fmt:
|
||||
gofumpt -w -l .
|
||||
.PHONY: fmt
|
||||
|
||||
lint:
|
||||
golangci-lint run ./...
|
||||
.PHONY: lint
|
||||
|
||||
ci: build test
|
||||
.PHONY: ci
|
||||
|
||||
card:
|
||||
wget -O card.png -c "https://og.caarlos0.dev/**env**: parse envs to structs.png?theme=light&md=1&fontSize=100px&images=https://github.com/caarlos0.png"
|
||||
.PHONY: card
|
||||
|
||||
.DEFAULT_GOAL := ci
|
452
vendor/github.com/caarlos0/env/v6/README.md
generated
vendored
Normal file
452
vendor/github.com/caarlos0/env/v6/README.md
generated
vendored
Normal file
@ -0,0 +1,452 @@
|
||||
# env
|
||||
|
||||
[![Build Status](https://img.shields.io/github/workflow/status/caarlos0/env/build?style=for-the-badge)](https://github.com/caarlos0/env/actions?workflow=build)
|
||||
[![Coverage Status](https://img.shields.io/codecov/c/gh/caarlos0/env.svg?logo=codecov&style=for-the-badge)](https://codecov.io/gh/caarlos0/env)
|
||||
[![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v6)
|
||||
|
||||
A simple and zero-dependencies library to parse environment variables into structs.
|
||||
|
||||
## Example
|
||||
|
||||
Get the module with:
|
||||
|
||||
```sh
|
||||
go get github.com/caarlos0/env/v6
|
||||
```
|
||||
|
||||
The usage looks like this:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Home string `env:"HOME"`
|
||||
Port int `env:"PORT" envDefault:"3000"`
|
||||
Password string `env:"PASSWORD,unset"`
|
||||
IsProduction bool `env:"PRODUCTION"`
|
||||
Hosts []string `env:"HOSTS" envSeparator:":"`
|
||||
Duration time.Duration `env:"DURATION"`
|
||||
TempFolder string `env:"TEMP_FOLDER" envDefault:"${HOME}/tmp" envExpand:"true"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
You can run it like this:
|
||||
|
||||
```sh
|
||||
$ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s go run main.go
|
||||
{Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s}
|
||||
```
|
||||
|
||||
⚠️⚠️⚠️ **Attention:** _unexported fields_ will be **ignored**.
|
||||
|
||||
## Supported types and defaults
|
||||
|
||||
Out of the box all built-in types are supported, plus a few others that
|
||||
are commonly used.
|
||||
|
||||
Complete list:
|
||||
|
||||
- `string`
|
||||
- `bool`
|
||||
- `int`
|
||||
- `int8`
|
||||
- `int16`
|
||||
- `int32`
|
||||
- `int64`
|
||||
- `uint`
|
||||
- `uint8`
|
||||
- `uint16`
|
||||
- `uint32`
|
||||
- `uint64`
|
||||
- `float32`
|
||||
- `float64`
|
||||
- `time.Duration`
|
||||
- `encoding.TextUnmarshaler`
|
||||
- `url.URL`
|
||||
|
||||
Pointers, slices and slices of pointers of those types are also supported.
|
||||
|
||||
You can also use/define a [custom parser func](#custom-parser-funcs) for any
|
||||
other type you want.
|
||||
|
||||
If you set the `envDefault` tag for something, this value will be used in the
|
||||
case of absence of it in the environment.
|
||||
|
||||
By default, slice types will split the environment value on `,`; you can change
|
||||
this behavior by setting the `envSeparator` tag.
|
||||
|
||||
If you set the `envExpand` tag, environment variables (either in `${var}` or
|
||||
`$var` format) in the string will be replaced according with the actual value
|
||||
of the variable.
|
||||
|
||||
## Custom Parser Funcs
|
||||
|
||||
If you have a type that is not supported out of the box by the lib, you are able
|
||||
to use (or define) and pass custom parsers (and their associated `reflect.Type`)
|
||||
to the `env.ParseWithFuncs()` function.
|
||||
|
||||
In addition to accepting a struct pointer (same as `Parse()`), this function
|
||||
also accepts a `map[reflect.Type]env.ParserFunc`.
|
||||
|
||||
If you add a custom parser for, say `Foo`, it will also be used to parse
|
||||
`*Foo` and `[]Foo` types.
|
||||
|
||||
Check the examples in the [go doc](http://pkg.go.dev/github.com/caarlos0/env/v6)
|
||||
for more info.
|
||||
|
||||
### A note about `TextUnmarshaler` and `time.Time`
|
||||
|
||||
Env supports by default anything that implements the `TextUnmarshaler` interface.
|
||||
That includes things like `time.Time` for example.
|
||||
The upside is that depending on the format you need, you don't need to change anything.
|
||||
The downside is that if you do need time in another format, you'll need to create your own type.
|
||||
|
||||
Its fairly straightforward:
|
||||
|
||||
```go
|
||||
type MyTime time.Time
|
||||
|
||||
func (t *MyTime) UnmarshalText(text []byte) error {
|
||||
tt, err := time.Parse("2006-01-02", string(text))
|
||||
*t = MyTime(tt)
|
||||
return err
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
SomeTime MyTime `env:"SOME_TIME"`
|
||||
}
|
||||
```
|
||||
|
||||
And then you can parse `Config` with `env.Parse`.
|
||||
|
||||
## Required fields
|
||||
|
||||
The `env` tag option `required` (e.g., `env:"tagKey,required"`) can be added to ensure that some environment variable is set.
|
||||
In the example above, an error is returned if the `config` struct is changed to:
|
||||
|
||||
```go
|
||||
type config struct {
|
||||
SecretKey string `env:"SECRET_KEY,required"`
|
||||
}
|
||||
```
|
||||
|
||||
## Not Empty fields
|
||||
|
||||
While `required` demands the environment variable to be check, it doesn't check its value.
|
||||
If you want to make sure the environment is set and not empty, you need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`).
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
type config struct {
|
||||
SecretKey string `env:"SECRET_KEY,notEmpty"`
|
||||
}
|
||||
```
|
||||
|
||||
## Unset environment variable after reading it
|
||||
|
||||
The `env` tag option `unset` (e.g., `env:"tagKey,unset"`) can be added
|
||||
to ensure that some environment variable is unset after reading it.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
type config struct {
|
||||
SecretKey string `env:"SECRET_KEY,unset"`
|
||||
}
|
||||
```
|
||||
|
||||
## From file
|
||||
|
||||
The `env` tag option `file` (e.g., `env:"tagKey,file"`) can be added
|
||||
to in order to indicate that the value of the variable shall be loaded from a file. The path of that file is given
|
||||
by the environment variable associated with it
|
||||
Example below
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/caarlos0/env/v6"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Secret string `env:"SECRET,file"`
|
||||
Password string `env:"PASSWORD,file" envDefault:"/tmp/password"`
|
||||
Certificate string `env:"CERTIFICATE,file" envDefault:"${CERTIFICATE_FILE}" envExpand:"true"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
$ echo qwerty > /tmp/secret
|
||||
$ echo dvorak > /tmp/password
|
||||
$ echo coleman > /tmp/certificate
|
||||
|
||||
$ SECRET=/tmp/secret \
|
||||
CERTIFICATE_FILE=/tmp/certificate \
|
||||
go run main.go
|
||||
{Secret:qwerty Password:dvorak Certificate:coleman}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### Environment
|
||||
|
||||
By setting the `Options.Environment` map you can tell `Parse` to add those `keys` and `values`
|
||||
as env vars before parsing is done. These envs are stored in the map and never actually set by `os.Setenv`.
|
||||
This option effectively makes `env` ignore the OS environment variables: only the ones provided in the option are used.
|
||||
|
||||
This can make your testing scenarios a bit more clean and easy to handle.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Password string `env:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &Config{}
|
||||
opts := &env.Options{Environment: map[string]string{
|
||||
"PASSWORD": "MY_PASSWORD",
|
||||
}}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.Parse(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg.envData)
|
||||
}
|
||||
```
|
||||
|
||||
### Changing default tag name
|
||||
|
||||
You can change what tag name to use for setting the env vars by setting the `Options.TagName`
|
||||
variable.
|
||||
|
||||
For example
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Password string `json:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &Config{}
|
||||
opts := &env.Options{TagName: "json"}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.Parse(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg.envData)
|
||||
}
|
||||
```
|
||||
|
||||
### Prefixes
|
||||
|
||||
You can prefix sub-structs env tags, as well as a whole `env.Parse` call.
|
||||
|
||||
Here's an example flexing it a bit:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Home string `env:"HOME"`
|
||||
}
|
||||
|
||||
type ComplexConfig struct {
|
||||
Foo Config `envPrefix:"FOO_"`
|
||||
Clean Config
|
||||
Bar Config `envPrefix:"BAR_"`
|
||||
Blah string `env:"BLAH"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := ComplexConfig{}
|
||||
if err := Parse(&cfg, Options{
|
||||
Prefix: "T_",
|
||||
Environment: map[string]string{
|
||||
"T_FOO_HOME": "/foo",
|
||||
"T_BAR_HOME": "/bar",
|
||||
"T_BLAH": "blahhh",
|
||||
"T_HOME": "/clean",
|
||||
},
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.Parse(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg.envData)
|
||||
}
|
||||
```
|
||||
|
||||
### On set hooks
|
||||
|
||||
You might want to listen to value sets and, for example, log something or do some other kind of logic.
|
||||
You can do this by passing a `OnSet` option:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Username string `env:"USERNAME" envDefault:"admin"`
|
||||
Password string `env:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &Config{}
|
||||
opts := &env.Options{
|
||||
OnSet: func(tag string, value interface{}, isDefault bool) {
|
||||
fmt.Printf("Set %s to %v (default? %v)\n", tag, value, isDefault)
|
||||
},
|
||||
}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.Parse(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg.envData)
|
||||
}
|
||||
```
|
||||
|
||||
## Making all fields to required
|
||||
|
||||
You can make all fields that don't have a default value be required by setting the `RequiredIfNoDef: true` in the `Options`.
|
||||
|
||||
For example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Username string `env:"USERNAME" envDefault:"admin"`
|
||||
Password string `env:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &Config{}
|
||||
opts := &env.Options{RequiredIfNoDef: true}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.Parse(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg.envData)
|
||||
}
|
||||
```
|
||||
|
||||
## Defaults from code
|
||||
|
||||
You may define default value also in code, by initialising the config data before it's filled by `env.Parse`.
|
||||
Default values defined as struct tags will overwrite existing values during Parse.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Username string `env:"USERNAME" envDefault:"admin"`
|
||||
Password string `env:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
var cfg = Config{
|
||||
Username: "test",
|
||||
Password: "123456",
|
||||
}
|
||||
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
fmt.Println("failed:", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v", cfg) // {Username:admin Password:123456}
|
||||
}
|
||||
```
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[![Stargazers over time](https://starchart.cc/caarlos0/env.svg)](https://starchart.cc/caarlos0/env)
|
504
vendor/github.com/caarlos0/env/v6/env.go
generated
vendored
Normal file
504
vendor/github.com/caarlos0/env/v6/env.go
generated
vendored
Normal file
@ -0,0 +1,504 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// nolint: gochecknoglobals
|
||||
var (
|
||||
// ErrNotAStructPtr is returned if you pass something that is not a pointer to a
|
||||
// Struct to Parse.
|
||||
ErrNotAStructPtr = errors.New("env: expected a pointer to a Struct")
|
||||
|
||||
defaultBuiltInParsers = map[reflect.Kind]ParserFunc{
|
||||
reflect.Bool: func(v string) (interface{}, error) {
|
||||
return strconv.ParseBool(v)
|
||||
},
|
||||
reflect.String: func(v string) (interface{}, error) {
|
||||
return v, nil
|
||||
},
|
||||
reflect.Int: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseInt(v, 10, 32)
|
||||
return int(i), err
|
||||
},
|
||||
reflect.Int16: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseInt(v, 10, 16)
|
||||
return int16(i), err
|
||||
},
|
||||
reflect.Int32: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseInt(v, 10, 32)
|
||||
return int32(i), err
|
||||
},
|
||||
reflect.Int64: func(v string) (interface{}, error) {
|
||||
return strconv.ParseInt(v, 10, 64)
|
||||
},
|
||||
reflect.Int8: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseInt(v, 10, 8)
|
||||
return int8(i), err
|
||||
},
|
||||
reflect.Uint: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseUint(v, 10, 32)
|
||||
return uint(i), err
|
||||
},
|
||||
reflect.Uint16: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseUint(v, 10, 16)
|
||||
return uint16(i), err
|
||||
},
|
||||
reflect.Uint32: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseUint(v, 10, 32)
|
||||
return uint32(i), err
|
||||
},
|
||||
reflect.Uint64: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseUint(v, 10, 64)
|
||||
return i, err
|
||||
},
|
||||
reflect.Uint8: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseUint(v, 10, 8)
|
||||
return uint8(i), err
|
||||
},
|
||||
reflect.Float64: func(v string) (interface{}, error) {
|
||||
return strconv.ParseFloat(v, 64)
|
||||
},
|
||||
reflect.Float32: func(v string) (interface{}, error) {
|
||||
f, err := strconv.ParseFloat(v, 32)
|
||||
return float32(f), err
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func defaultTypeParsers() map[reflect.Type]ParserFunc {
|
||||
return map[reflect.Type]ParserFunc{
|
||||
reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) {
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse URL: %v", err)
|
||||
}
|
||||
return *u, nil
|
||||
},
|
||||
reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) {
|
||||
s, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse duration: %v", err)
|
||||
}
|
||||
return s, err
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ParserFunc defines the signature of a function that can be used within `CustomParsers`.
|
||||
type ParserFunc func(v string) (interface{}, error)
|
||||
|
||||
// OnSetFn is a hook that can be run when a value is set.
|
||||
type OnSetFn func(tag string, value interface{}, isDefault bool)
|
||||
|
||||
// Options for the parser.
|
||||
type Options struct {
|
||||
// Environment keys and values that will be accessible for the service.
|
||||
Environment map[string]string
|
||||
|
||||
// TagName specifies another tagname to use rather than the default env.
|
||||
TagName string
|
||||
|
||||
// RequiredIfNoDef automatically sets all env as required if they do not declare 'envDefault'
|
||||
RequiredIfNoDef bool
|
||||
|
||||
// OnSet allows to run a function when a value is set
|
||||
OnSet OnSetFn
|
||||
|
||||
// Prefix define a prefix for each key
|
||||
Prefix string
|
||||
|
||||
// Sets to true if we have already configured once.
|
||||
configured bool
|
||||
}
|
||||
|
||||
// configure will do the basic configurations and defaults.
|
||||
func configure(opts []Options) []Options {
|
||||
// If we have already configured the first item
|
||||
// of options will have been configured set to true.
|
||||
if len(opts) > 0 && opts[0].configured {
|
||||
return opts
|
||||
}
|
||||
|
||||
// Created options with defaults.
|
||||
opt := Options{
|
||||
TagName: "env",
|
||||
Environment: toMap(os.Environ()),
|
||||
configured: true,
|
||||
}
|
||||
|
||||
// Loop over all opts structs and set
|
||||
// to opt if value is not default/empty.
|
||||
for _, item := range opts {
|
||||
if item.Environment != nil {
|
||||
opt.Environment = item.Environment
|
||||
}
|
||||
if item.TagName != "" {
|
||||
opt.TagName = item.TagName
|
||||
}
|
||||
if item.OnSet != nil {
|
||||
opt.OnSet = item.OnSet
|
||||
}
|
||||
if item.Prefix != "" {
|
||||
opt.Prefix = item.Prefix
|
||||
}
|
||||
opt.RequiredIfNoDef = item.RequiredIfNoDef
|
||||
}
|
||||
|
||||
return []Options{opt}
|
||||
}
|
||||
|
||||
func getOnSetFn(opts []Options) OnSetFn {
|
||||
return opts[0].OnSet
|
||||
}
|
||||
|
||||
// getTagName returns the tag name.
|
||||
func getTagName(opts []Options) string {
|
||||
return opts[0].TagName
|
||||
}
|
||||
|
||||
// getEnvironment returns the environment map.
|
||||
func getEnvironment(opts []Options) map[string]string {
|
||||
return opts[0].Environment
|
||||
}
|
||||
|
||||
// Parse parses a struct containing `env` tags and loads its values from
|
||||
// environment variables.
|
||||
func Parse(v interface{}, opts ...Options) error {
|
||||
return ParseWithFuncs(v, map[reflect.Type]ParserFunc{}, opts...)
|
||||
}
|
||||
|
||||
// ParseWithFuncs is the same as `Parse` except it also allows the user to pass
|
||||
// in custom parsers.
|
||||
func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc, opts ...Options) error {
|
||||
opts = configure(opts)
|
||||
|
||||
ptrRef := reflect.ValueOf(v)
|
||||
if ptrRef.Kind() != reflect.Ptr {
|
||||
return ErrNotAStructPtr
|
||||
}
|
||||
ref := ptrRef.Elem()
|
||||
if ref.Kind() != reflect.Struct {
|
||||
return ErrNotAStructPtr
|
||||
}
|
||||
parsers := defaultTypeParsers()
|
||||
for k, v := range funcMap {
|
||||
parsers[k] = v
|
||||
}
|
||||
|
||||
return doParse(ref, parsers, opts)
|
||||
}
|
||||
|
||||
func doParse(ref reflect.Value, funcMap map[reflect.Type]ParserFunc, opts []Options) error {
|
||||
refType := ref.Type()
|
||||
|
||||
var agrErr aggregateError
|
||||
|
||||
for i := 0; i < refType.NumField(); i++ {
|
||||
refField := ref.Field(i)
|
||||
refTypeField := refType.Field(i)
|
||||
|
||||
if err := doParseField(refField, refTypeField, funcMap, opts); err != nil {
|
||||
if val, ok := err.(aggregateError); ok {
|
||||
agrErr.errors = append(agrErr.errors, val.errors...)
|
||||
} else {
|
||||
agrErr.errors = append(agrErr.errors, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(agrErr.errors) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return agrErr
|
||||
}
|
||||
|
||||
func doParseField(refField reflect.Value, refTypeField reflect.StructField, funcMap map[reflect.Type]ParserFunc, opts []Options) error {
|
||||
if !refField.CanSet() {
|
||||
return nil
|
||||
}
|
||||
if reflect.Ptr == refField.Kind() && refField.Elem().Kind() == reflect.Struct {
|
||||
return ParseWithFuncs(refField.Interface(), funcMap, optsWithPrefix(refTypeField, opts)...)
|
||||
}
|
||||
if reflect.Struct == refField.Kind() && refField.CanAddr() && refField.Type().Name() == "" {
|
||||
return ParseWithFuncs(refField.Addr().Interface(), funcMap, optsWithPrefix(refTypeField, opts)...)
|
||||
}
|
||||
value, err := get(refTypeField, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if value != "" {
|
||||
return set(refField, refTypeField, value, funcMap)
|
||||
}
|
||||
|
||||
if reflect.Struct == refField.Kind() {
|
||||
return doParse(refField, funcMap, optsWithPrefix(refTypeField, opts))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func get(field reflect.StructField, opts []Options) (val string, err error) {
|
||||
var exists bool
|
||||
var isDefault bool
|
||||
var loadFile bool
|
||||
var unset bool
|
||||
var notEmpty bool
|
||||
|
||||
required := opts[0].RequiredIfNoDef
|
||||
prefix := opts[0].Prefix
|
||||
ownKey, tags := parseKeyForOption(field.Tag.Get(getTagName(opts)))
|
||||
key := prefix + ownKey
|
||||
for _, tag := range tags {
|
||||
switch tag {
|
||||
case "":
|
||||
continue
|
||||
case "file":
|
||||
loadFile = true
|
||||
case "required":
|
||||
required = true
|
||||
case "unset":
|
||||
unset = true
|
||||
case "notEmpty":
|
||||
notEmpty = true
|
||||
default:
|
||||
return "", fmt.Errorf("tag option %q not supported", tag)
|
||||
}
|
||||
}
|
||||
expand := strings.EqualFold(field.Tag.Get("envExpand"), "true")
|
||||
defaultValue, defExists := field.Tag.Lookup("envDefault")
|
||||
val, exists, isDefault = getOr(key, defaultValue, defExists, getEnvironment(opts))
|
||||
|
||||
if expand {
|
||||
val = os.ExpandEnv(val)
|
||||
}
|
||||
|
||||
if unset {
|
||||
defer os.Unsetenv(key)
|
||||
}
|
||||
|
||||
if required && !exists && len(ownKey) > 0 {
|
||||
return "", fmt.Errorf(`required environment variable %q is not set`, key)
|
||||
}
|
||||
|
||||
if notEmpty && val == "" {
|
||||
return "", fmt.Errorf("environment variable %q should not be empty", key)
|
||||
}
|
||||
|
||||
if loadFile && val != "" {
|
||||
filename := val
|
||||
val, err = getFromFile(filename)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(`could not load content of file "%s" from variable %s: %v`, filename, key, err)
|
||||
}
|
||||
}
|
||||
|
||||
if onSetFn := getOnSetFn(opts); onSetFn != nil {
|
||||
onSetFn(key, val, isDefault)
|
||||
}
|
||||
return val, err
|
||||
}
|
||||
|
||||
// split the env tag's key into the expected key and desired option, if any.
|
||||
func parseKeyForOption(key string) (string, []string) {
|
||||
opts := strings.Split(key, ",")
|
||||
return opts[0], opts[1:]
|
||||
}
|
||||
|
||||
func getFromFile(filename string) (value string, err error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
func getOr(key, defaultValue string, defExists bool, envs map[string]string) (string, bool, bool) {
|
||||
value, exists := envs[key]
|
||||
switch {
|
||||
case (!exists || key == "") && defExists:
|
||||
return defaultValue, true, true
|
||||
case !exists:
|
||||
return "", false, false
|
||||
}
|
||||
|
||||
return value, true, false
|
||||
}
|
||||
|
||||
func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[reflect.Type]ParserFunc) error {
|
||||
if tm := asTextUnmarshaler(field); tm != nil {
|
||||
if err := tm.UnmarshalText([]byte(value)); err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
typee := sf.Type
|
||||
fieldee := field
|
||||
if typee.Kind() == reflect.Ptr {
|
||||
typee = typee.Elem()
|
||||
fieldee = field.Elem()
|
||||
}
|
||||
|
||||
parserFunc, ok := funcMap[typee]
|
||||
if ok {
|
||||
val, err := parserFunc(value)
|
||||
if err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
|
||||
fieldee.Set(reflect.ValueOf(val))
|
||||
return nil
|
||||
}
|
||||
|
||||
parserFunc, ok = defaultBuiltInParsers[typee.Kind()]
|
||||
if ok {
|
||||
val, err := parserFunc(value)
|
||||
if err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
|
||||
fieldee.Set(reflect.ValueOf(val).Convert(typee))
|
||||
return nil
|
||||
}
|
||||
|
||||
if field.Kind() == reflect.Slice {
|
||||
return handleSlice(field, value, sf, funcMap)
|
||||
}
|
||||
|
||||
return newNoParserError(sf)
|
||||
}
|
||||
|
||||
func handleSlice(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error {
|
||||
separator := sf.Tag.Get("envSeparator")
|
||||
if separator == "" {
|
||||
separator = ","
|
||||
}
|
||||
parts := strings.Split(value, separator)
|
||||
|
||||
typee := sf.Type.Elem()
|
||||
if typee.Kind() == reflect.Ptr {
|
||||
typee = typee.Elem()
|
||||
}
|
||||
|
||||
if _, ok := reflect.New(typee).Interface().(encoding.TextUnmarshaler); ok {
|
||||
return parseTextUnmarshalers(field, parts, sf)
|
||||
}
|
||||
|
||||
parserFunc, ok := funcMap[typee]
|
||||
if !ok {
|
||||
parserFunc, ok = defaultBuiltInParsers[typee.Kind()]
|
||||
if !ok {
|
||||
return newNoParserError(sf)
|
||||
}
|
||||
}
|
||||
|
||||
result := reflect.MakeSlice(sf.Type, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
r, err := parserFunc(part)
|
||||
if err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
v := reflect.ValueOf(r).Convert(typee)
|
||||
if sf.Type.Elem().Kind() == reflect.Ptr {
|
||||
v = reflect.New(typee)
|
||||
v.Elem().Set(reflect.ValueOf(r).Convert(typee))
|
||||
}
|
||||
result = reflect.Append(result, v)
|
||||
}
|
||||
field.Set(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler {
|
||||
if reflect.Ptr == field.Kind() {
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.New(field.Type().Elem()))
|
||||
}
|
||||
} else if field.CanAddr() {
|
||||
field = field.Addr()
|
||||
}
|
||||
|
||||
tm, ok := field.Interface().(encoding.TextUnmarshaler)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return tm
|
||||
}
|
||||
|
||||
func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.StructField) error {
|
||||
s := len(data)
|
||||
elemType := field.Type().Elem()
|
||||
slice := reflect.MakeSlice(reflect.SliceOf(elemType), s, s)
|
||||
for i, v := range data {
|
||||
sv := slice.Index(i)
|
||||
kind := sv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
sv = reflect.New(elemType.Elem())
|
||||
} else {
|
||||
sv = sv.Addr()
|
||||
}
|
||||
tm := sv.Interface().(encoding.TextUnmarshaler)
|
||||
if err := tm.UnmarshalText([]byte(v)); err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
if kind == reflect.Ptr {
|
||||
slice.Index(i).Set(sv)
|
||||
}
|
||||
}
|
||||
|
||||
field.Set(slice)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newParseError(sf reflect.StructField, err error) error {
|
||||
return parseError{
|
||||
sf: sf,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
type parseError struct {
|
||||
sf reflect.StructField
|
||||
err error
|
||||
}
|
||||
|
||||
func (e parseError) Error() string {
|
||||
return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.sf.Name, e.sf.Type, e.err)
|
||||
}
|
||||
|
||||
func newNoParserError(sf reflect.StructField) error {
|
||||
return fmt.Errorf(`no parser found for field "%s" of type "%s"`, sf.Name, sf.Type)
|
||||
}
|
||||
|
||||
func optsWithPrefix(field reflect.StructField, opts []Options) []Options {
|
||||
subOpts := make([]Options, len(opts))
|
||||
copy(subOpts, opts)
|
||||
if prefix := field.Tag.Get("envPrefix"); prefix != "" {
|
||||
subOpts[0].Prefix += prefix
|
||||
}
|
||||
return subOpts
|
||||
}
|
||||
|
||||
type aggregateError struct {
|
||||
errors []error
|
||||
}
|
||||
|
||||
func (e aggregateError) Error() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("env:")
|
||||
|
||||
for _, err := range e.errors {
|
||||
sb.WriteString(fmt.Sprintf(" %v;", err.Error()))
|
||||
}
|
||||
|
||||
return strings.TrimRight(sb.String(), ";")
|
||||
}
|
15
vendor/github.com/caarlos0/env/v6/env_unix.go
generated
vendored
Normal file
15
vendor/github.com/caarlos0/env/v6/env_unix.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package env
|
||||
|
||||
import "strings"
|
||||
|
||||
func toMap(env []string) map[string]string {
|
||||
r := map[string]string{}
|
||||
for _, e := range env {
|
||||
p := strings.SplitN(e, "=", 2)
|
||||
r[p[0]] = p[1]
|
||||
}
|
||||
return r
|
||||
}
|
25
vendor/github.com/caarlos0/env/v6/env_windows.go
generated
vendored
Normal file
25
vendor/github.com/caarlos0/env/v6/env_windows.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
package env
|
||||
|
||||
import "strings"
|
||||
|
||||
func toMap(env []string) map[string]string {
|
||||
r := map[string]string{}
|
||||
for _, e := range env {
|
||||
p := strings.SplitN(e, "=", 2)
|
||||
|
||||
// On Windows, environment variables can start with '='. If so, Split at next character.
|
||||
// See env_windows.go in the Go source: https://github.com/golang/go/blob/master/src/syscall/env_windows.go#L58
|
||||
prefixEqualSign := false
|
||||
if len(e) > 0 && e[0] == '=' {
|
||||
e = e[1:]
|
||||
prefixEqualSign = true
|
||||
}
|
||||
p = strings.SplitN(e, "=", 2)
|
||||
if prefixEqualSign {
|
||||
p[0] = "=" + p[0]
|
||||
}
|
||||
|
||||
r[p[0]] = p[1]
|
||||
}
|
||||
return r
|
||||
}
|
3
vendor/golang.org/x/net/AUTHORS
generated
vendored
3
vendor/golang.org/x/net/AUTHORS
generated
vendored
@ -1,3 +0,0 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
3
vendor/golang.org/x/net/CONTRIBUTORS
generated
vendored
3
vendor/golang.org/x/net/CONTRIBUTORS
generated
vendored
@ -1,3 +0,0 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
5
vendor/golang.org/x/net/context/go17.go
generated
vendored
5
vendor/golang.org/x/net/context/go17.go
generated
vendored
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.7
|
||||
// +build go1.7
|
||||
|
||||
package context
|
||||
@ -31,7 +32,7 @@ var DeadlineExceeded = context.DeadlineExceeded
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||
ctx, f := context.WithCancel(parent)
|
||||
return ctx, CancelFunc(f)
|
||||
return ctx, f
|
||||
}
|
||||
|
||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||
@ -45,7 +46,7 @@ func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||
ctx, f := context.WithDeadline(parent, deadline)
|
||||
return ctx, CancelFunc(f)
|
||||
return ctx, f
|
||||
}
|
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
|
1
vendor/golang.org/x/net/context/go19.go
generated
vendored
1
vendor/golang.org/x/net/context/go19.go
generated
vendored
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.9
|
||||
// +build go1.9
|
||||
|
||||
package context
|
||||
|
1
vendor/golang.org/x/net/context/pre_go17.go
generated
vendored
1
vendor/golang.org/x/net/context/pre_go17.go
generated
vendored
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.7
|
||||
// +build !go1.7
|
||||
|
||||
package context
|
||||
|
1
vendor/golang.org/x/net/context/pre_go19.go
generated
vendored
1
vendor/golang.org/x/net/context/pre_go19.go
generated
vendored
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.9
|
||||
// +build !go1.9
|
||||
|
||||
package context
|
||||
|
10
vendor/modules.txt
vendored
10
vendor/modules.txt
vendored
@ -1,9 +1,9 @@
|
||||
# github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
## explicit
|
||||
github.com/armon/go-socks5
|
||||
# github.com/caarlos0/env v3.3.1-0.20180521112546-3e0f30cbf50b+incompatible
|
||||
## explicit
|
||||
github.com/caarlos0/env
|
||||
# golang.org/x/net v0.0.0-20180406214816-61147c48b25b
|
||||
## explicit
|
||||
# github.com/caarlos0/env/v6 v6.10.1
|
||||
## explicit; go 1.17
|
||||
github.com/caarlos0/env/v6
|
||||
# golang.org/x/net v0.17.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/net/context
|
||||
|
Loading…
Reference in New Issue
Block a user