mirror of https://github.com/mpolden/echoip
Enable use of Environment Variables
I didn't want to kep the API Key in a file because it could be exposed so I thought a environment variable would be good. Might of got carried away and didn't need to put all config options in environment variables. commit ba35fa73512a5d0d868c9e9147f55fe50db0ec99 Author: Ethan Knowlton <ethan@dubtown.dev> Date: Sat Oct 7 22:35:31 2023 -0400 removed debug statement commit 3808cc26dfac70e5c205df807b26855bf4562196 Author: Ethan Knowlton <ethan@dubtown.dev> Date: Sat Oct 7 22:32:27 2023 -0400 Could Cause Problems Shouldn't need to run make as super user, but usually you would run `make install`. I could add this to the install step and remove the install from all step but it could mess up publishing to ip.level.io. Just going to remove it for now. commit aa0cb39b08e71e1ef897f2d3f3a4fc7e5aff4c03 Author: Ethan Knowlton <ethan@dubtown.dev> Date: Sat Oct 7 20:05:14 2023 -0400 adding env variables to readme
This commit is contained in:
parent
5c5503085e
commit
967931fd25
|
@ -1,2 +1,3 @@
|
|||
Dockerfile
|
||||
/etc/systemd
|
||||
Dockerfile.geoip
|
|
@ -4,3 +4,4 @@
|
|||
.vscode/
|
||||
.idea/
|
||||
/bin/
|
||||
.envrc
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Build
|
||||
FROM golang:1.15-buster AS build
|
||||
WORKDIR /go/src/github.com/mpolden/echoip
|
||||
FROM golang:1.18-buster AS build
|
||||
WORKDIR /go/src/github.com/levelsoftware/echoip
|
||||
COPY . .
|
||||
|
||||
# Must build without cgo because libc is unavailable in runtime image
|
||||
|
@ -8,7 +8,7 @@ ENV GO111MODULE=on CGO_ENABLED=0
|
|||
RUN make
|
||||
|
||||
# Run
|
||||
FROM scratch
|
||||
FROM alpine
|
||||
EXPOSE 8080
|
||||
|
||||
COPY --from=build /go/bin/echoip /opt/echoip/
|
||||
|
|
6
Makefile
6
Makefile
|
@ -1,5 +1,5 @@
|
|||
DOCKER ?= docker
|
||||
DOCKER_IMAGE ?= mpolden/echoip
|
||||
DOCKER_IMAGE ?= levelsoftware/echoip
|
||||
OS := $(shell uname)
|
||||
ifeq ($(OS),Linux)
|
||||
TAR_OPTS := --wildcards
|
||||
|
@ -22,11 +22,11 @@ check-fmt:
|
|||
|
||||
lint: check-fmt vet
|
||||
|
||||
install: install-config
|
||||
install:
|
||||
go install ./...
|
||||
|
||||
install-config:
|
||||
sudo install -D etc/echoip/config.toml /etc/echoip/config.toml
|
||||
install -D etc/echoip/config.toml /etc/echoip/config.toml
|
||||
|
||||
databases := GeoLite2-City GeoLite2-Country GeoLite2-ASN
|
||||
|
||||
|
|
21
README.md
21
README.md
|
@ -132,6 +132,27 @@ CityFile = ""
|
|||
AsnFile = ""
|
||||
```
|
||||
|
||||
### Environment Variables for Configuration
|
||||
You can also use environment variables for configuration, most likely used for Docker.
|
||||
|
||||
```
|
||||
ECHOIP_LISTEN=":8080"
|
||||
ECHOIP_TEMPLATE_DIR="html/"
|
||||
ECHOIP_REDIS_URL="redis://localhost:6379"
|
||||
ECHOIP_DATABASE="ipstack"
|
||||
ECHOIP_IPSTACK_API_KEY="askdfj39sjdkf29dsjfk39sdfkj3"
|
||||
ECHOIP_GEOIP_COUNTRY_FILE="/full/path/to/file.db"
|
||||
ECHOIP_GEOIP_CITY_FILE="/full/path/to/file.db"
|
||||
ECHOIP_GEOIP_ASN_FILE="/full/path/to/file.db"
|
||||
ECHOIP_CACHE_TTL=3600
|
||||
ECHOIP_REVERSE_LOOKUP=true
|
||||
ECHOIP_PORT_LOOKUP=true
|
||||
ECHOIP_SHOW_SPONSOR=true
|
||||
ECHOIP_PROFILE=false
|
||||
ECHOIP_IPSTACK_USE_HTTPS=true
|
||||
ECHOIP_IPSTACK_ENABLE_SECURITY=true
|
||||
```
|
||||
|
||||
### Caching with Redis
|
||||
|
||||
You can connect EchoIP to a Redis client to cache each request per IP. You can configure the life of the key in `config.CacheTtl`.
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/levelsoftware/echoip/cache"
|
||||
"github.com/levelsoftware/echoip/config"
|
||||
"github.com/levelsoftware/echoip/http"
|
||||
"github.com/levelsoftware/echoip/iputil"
|
||||
"github.com/levelsoftware/echoip/iputil/geo"
|
||||
|
@ -33,61 +34,40 @@ func init() {
|
|||
log.SetFlags(log.Lshortfile)
|
||||
}
|
||||
|
||||
type IPStack struct {
|
||||
ApiKey string
|
||||
UseHttps bool
|
||||
EnableSecurity bool
|
||||
}
|
||||
|
||||
type GeoIP struct {
|
||||
CountryFile string
|
||||
CityFile string
|
||||
AsnFile string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Listen string
|
||||
TemplateDir string
|
||||
RedisUrl string
|
||||
CacheTtl int
|
||||
ReverseLookup bool
|
||||
PortLookup bool
|
||||
ShowSponsor bool
|
||||
TrustedHeaders []string
|
||||
|
||||
Database string
|
||||
Profile bool
|
||||
|
||||
IPStack IPStack
|
||||
GeoIP GeoIP
|
||||
}
|
||||
|
||||
func main() {
|
||||
file, err := os.Open("/etc/echoip/config.toml")
|
||||
runConfig, err := config.GetConfig()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatalf("Error building configuration: %s", err)
|
||||
}
|
||||
|
||||
file, err := os.Open("/etc/echoip/config.toml")
|
||||
defer file.Close()
|
||||
|
||||
var config Config
|
||||
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Printf("Error opening config file (/etc/echoip/config.toml): %s", err)
|
||||
}
|
||||
|
||||
err = toml.Unmarshal(b, &config)
|
||||
if err == nil {
|
||||
var b []byte
|
||||
b, err = io.ReadAll(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Printf("Error reading config file (/etc/echoip/config.toml): %s", err)
|
||||
}
|
||||
|
||||
err = toml.Unmarshal(b, &runConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("Error parsing config file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
var parser parser.Parser
|
||||
if config.Database == "geoip" {
|
||||
if runConfig.Database == "geoip" {
|
||||
log.Print("Using GeoIP for IP database")
|
||||
geo, err := geo.Open(
|
||||
config.GeoIP.CountryFile,
|
||||
config.GeoIP.CityFile,
|
||||
config.GeoIP.AsnFile,
|
||||
runConfig.GeoIP.CountryFile,
|
||||
runConfig.GeoIP.CityFile,
|
||||
runConfig.GeoIP.AsnFile,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -95,27 +75,27 @@ func main() {
|
|||
parser = &geo
|
||||
}
|
||||
|
||||
if config.Database == "ipstack" {
|
||||
log.Print("Using GeoIP for IP database")
|
||||
if config.IPStack.EnableSecurity {
|
||||
log.Print("Enable Security Module ( Requires Professional Plus account )")
|
||||
if runConfig.Database == "ipstack" {
|
||||
log.Print("Using IP Stack for IP database")
|
||||
if runConfig.IPStack.EnableSecurity {
|
||||
log.Print("Enable IP Stack Security Module ( Requires Professional Plus account )")
|
||||
}
|
||||
enableSecurity := ipstackApi.ParamEnableSecurity(config.IPStack.EnableSecurity)
|
||||
apiKey := ipstackApi.ParamToken(config.IPStack.ApiKey)
|
||||
useHttps := ipstackApi.ParamUseHTTPS(config.IPStack.UseHttps)
|
||||
if config.IPStack.UseHttps {
|
||||
enableSecurity := ipstackApi.ParamEnableSecurity(runConfig.IPStack.EnableSecurity)
|
||||
apiKey := ipstackApi.ParamToken(runConfig.IPStack.ApiKey)
|
||||
useHttps := ipstackApi.ParamUseHTTPS(runConfig.IPStack.UseHttps)
|
||||
if runConfig.IPStack.UseHttps {
|
||||
log.Print("Use IP Stack HTTPS API ( Requires non-free account )")
|
||||
}
|
||||
if err := ipstackApi.Init(apiKey, enableSecurity, useHttps); err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatalf("Error initializing IP Stack client: %s", err)
|
||||
}
|
||||
ips := ipstack.IPStack{}
|
||||
parser = &ips
|
||||
}
|
||||
|
||||
var serverCache cache.Cache
|
||||
if len(config.RedisUrl) > 0 {
|
||||
redisCache, err := cache.RedisCache(config.RedisUrl)
|
||||
if len(runConfig.RedisUrl) > 0 {
|
||||
redisCache, err := cache.RedisCache(runConfig.RedisUrl)
|
||||
serverCache = &redisCache
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -124,34 +104,35 @@ func main() {
|
|||
serverCache = &cache.Null{}
|
||||
}
|
||||
|
||||
server := http.New(parser, serverCache, config.CacheTtl, config.Profile)
|
||||
server.IPHeaders = config.TrustedHeaders
|
||||
server := http.New(parser, serverCache, runConfig.CacheTtl, runConfig.Profile)
|
||||
server.IPHeaders = runConfig.TrustedHeaders
|
||||
|
||||
if _, err := os.Stat(config.TemplateDir); err == nil {
|
||||
server.Template = config.TemplateDir
|
||||
if _, err := os.Stat(runConfig.TemplateDir); err == nil {
|
||||
server.Template = runConfig.TemplateDir
|
||||
} else {
|
||||
log.Printf("Not configuring default handler: Template not found: %s", config.TemplateDir)
|
||||
log.Printf("Not configuring default handler: Template not found: %s", runConfig.TemplateDir)
|
||||
}
|
||||
if config.ReverseLookup {
|
||||
if runConfig.ReverseLookup {
|
||||
log.Println("Enabling reverse lookup")
|
||||
server.LookupAddr = iputil.LookupAddr
|
||||
}
|
||||
if config.PortLookup {
|
||||
if runConfig.PortLookup {
|
||||
log.Println("Enabling port lookup")
|
||||
server.LookupPort = iputil.LookupPort
|
||||
}
|
||||
if config.ShowSponsor {
|
||||
if runConfig.ShowSponsor {
|
||||
log.Println("Enabling sponsor logo")
|
||||
server.Sponsor = config.ShowSponsor
|
||||
server.Sponsor = runConfig.ShowSponsor
|
||||
}
|
||||
if len(config.TrustedHeaders) > 0 {
|
||||
log.Printf("Trusting remote IP from header(s): %s", config.TrustedHeaders)
|
||||
if len(runConfig.TrustedHeaders) > 0 {
|
||||
log.Printf("Trusting remote IP from header(s): %s", runConfig.TrustedHeaders)
|
||||
}
|
||||
if config.Profile {
|
||||
if runConfig.Profile {
|
||||
log.Printf("Enabling profiling handlers")
|
||||
}
|
||||
log.Printf("Listening on http://%s", config.Listen)
|
||||
if err := server.ListenAndServe(config.Listen); err != nil {
|
||||
|
||||
log.Printf("Listening on http://%s", runConfig.Listen)
|
||||
if err := server.ListenAndServe(runConfig.Listen); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type IPStack struct {
|
||||
ApiKey string
|
||||
UseHttps bool
|
||||
EnableSecurity bool
|
||||
}
|
||||
|
||||
type GeoIP struct {
|
||||
CountryFile string
|
||||
CityFile string
|
||||
AsnFile string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Listen string
|
||||
TemplateDir string
|
||||
RedisUrl string
|
||||
CacheTtl int
|
||||
ReverseLookup bool
|
||||
PortLookup bool
|
||||
ShowSponsor bool
|
||||
TrustedHeaders []string
|
||||
|
||||
Database string
|
||||
Profile bool
|
||||
|
||||
IPStack IPStack
|
||||
GeoIP GeoIP
|
||||
}
|
||||
|
||||
func GetConfig() (Config, error) {
|
||||
defaultConfig := Config{
|
||||
Listen: getenv_string("ECHOIP_LISTEN", ":8080"),
|
||||
TemplateDir: getenv_string("ECHOIP_TEMPLATE_DIR", "html/"),
|
||||
RedisUrl: getenv_string("ECHOIP_REDIS_URL", ""),
|
||||
TrustedHeaders: nil,
|
||||
Database: getenv_string("ECHOIP_DATABASE", "geoip"),
|
||||
IPStack: IPStack{
|
||||
ApiKey: getenv_string("ECHOIP_IPSTACK_API_KEY", ""),
|
||||
},
|
||||
GeoIP: GeoIP{
|
||||
CountryFile: getenv_string("ECHOIP_GEOIP_COUNTRY_FILE", ""),
|
||||
CityFile: getenv_string("ECHOIP_GEOIP_CITY_FILE", ""),
|
||||
AsnFile: getenv_string("ECHOIP_GEOIP_ASN_FILE", ""),
|
||||
},
|
||||
}
|
||||
|
||||
cacheTtl, err := getenv_int("ECHOIP_CACHE_TTL", 3600)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
defaultConfig.CacheTtl = cacheTtl
|
||||
|
||||
reverseLookup, err := getenv_bool("ECHOIP_REVERSE_LOOKUP", false)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
defaultConfig.ReverseLookup = reverseLookup
|
||||
|
||||
portLookup, err := getenv_bool("ECHOIP_PORT_LOOKUP", false)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
defaultConfig.PortLookup = portLookup
|
||||
|
||||
showSponsor, err := getenv_bool("ECHOIP_SHOW_SPONSOR", false)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
defaultConfig.ShowSponsor = showSponsor
|
||||
|
||||
profile, err := getenv_bool("ECHOIP_PROFILE", false)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
defaultConfig.Profile = profile
|
||||
|
||||
ipStackUseHttps, err := getenv_bool("ECHOIP_IPSTACK_USE_HTTPS", false)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
defaultConfig.IPStack.UseHttps = ipStackUseHttps
|
||||
|
||||
ipStackEnableSecurity, err := getenv_bool("ECHOIP_IPSTACK_ENABLE_SECURITY", false)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
defaultConfig.IPStack.EnableSecurity = ipStackEnableSecurity
|
||||
|
||||
return defaultConfig, nil
|
||||
}
|
||||
|
||||
func getenv_int(key string, fallback int) (int, error) {
|
||||
value := os.Getenv(key)
|
||||
|
||||
if len(value) > 0 {
|
||||
intValue, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return intValue, nil
|
||||
}
|
||||
|
||||
return fallback, nil
|
||||
}
|
||||
|
||||
func getenv_string(key string, fallback string) string {
|
||||
value := os.Getenv(key)
|
||||
if len(value) == 0 {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func getenv_bool(key string, fallback bool) (bool, error) {
|
||||
value := os.Getenv(key)
|
||||
|
||||
if len(value) > 0 {
|
||||
boolValue, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return boolValue, nil
|
||||
}
|
||||
|
||||
return fallback, nil
|
||||
}
|
|
@ -1,6 +1,14 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
echoip:
|
||||
image: hub.01a.in/echoip:latest
|
||||
environment:
|
||||
- ECHOIP_DATABASE="ipstack"
|
||||
- ECHOIP_IPSTACK_API_KEY="ipstack"
|
||||
ports:
|
||||
- '8080:8080'
|
||||
|
||||
cache:
|
||||
image: redis:6.2-alpine
|
||||
restart: always
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
Listen = ":8080"
|
||||
TemplateDir = "html/index.html"
|
||||
TemplateDir = "html"
|
||||
RedisUrl = "redis://localhost:6379"
|
||||
CacheTtl = 3600 # in seconds
|
||||
ReverseLookup = true
|
||||
|
|
5
go.mod
5
go.mod
|
@ -18,6 +18,7 @@ require (
|
|||
github.com/vmihailenco/go-tinylfu v0.2.2 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
)
|
||||
|
|
11
go.sum
11
go.sum
|
@ -128,15 +128,17 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -160,8 +162,9 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -174,8 +177,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
|
|
Loading…
Reference in New Issue