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:
Ethan Knowlton 2023-10-08 14:46:51 -04:00
parent 5c5503085e
commit 967931fd25
11 changed files with 233 additions and 82 deletions

View File

@ -1,2 +1,3 @@
Dockerfile Dockerfile
Dockerfile.geoip /etc/systemd
Dockerfile.geoip

1
.gitignore vendored
View File

@ -4,3 +4,4 @@
.vscode/ .vscode/
.idea/ .idea/
/bin/ /bin/
.envrc

View File

@ -1,6 +1,6 @@
# Build # Build
FROM golang:1.15-buster AS build FROM golang:1.18-buster AS build
WORKDIR /go/src/github.com/mpolden/echoip WORKDIR /go/src/github.com/levelsoftware/echoip
COPY . . COPY . .
# Must build without cgo because libc is unavailable in runtime image # Must build without cgo because libc is unavailable in runtime image
@ -8,7 +8,7 @@ ENV GO111MODULE=on CGO_ENABLED=0
RUN make RUN make
# Run # Run
FROM scratch FROM alpine
EXPOSE 8080 EXPOSE 8080
COPY --from=build /go/bin/echoip /opt/echoip/ COPY --from=build /go/bin/echoip /opt/echoip/

View File

@ -1,5 +1,5 @@
DOCKER ?= docker DOCKER ?= docker
DOCKER_IMAGE ?= mpolden/echoip DOCKER_IMAGE ?= levelsoftware/echoip
OS := $(shell uname) OS := $(shell uname)
ifeq ($(OS),Linux) ifeq ($(OS),Linux)
TAR_OPTS := --wildcards TAR_OPTS := --wildcards
@ -22,11 +22,11 @@ check-fmt:
lint: check-fmt vet lint: check-fmt vet
install: install-config install:
go install ./... go install ./...
install-config: 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 databases := GeoLite2-City GeoLite2-Country GeoLite2-ASN
@ -51,7 +51,7 @@ docker-multiarch-builder:
$(DOCKER) run --rm --privileged multiarch/qemu-user-static --reset -p yes $(DOCKER) run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker-build: docker-build:
$(DOCKER) build -t $(DOCKER_IMAGE) . $(DOCKER) build -t $(DOCKER_IMAGE) .
docker-login: docker-login:
@echo "$(DOCKER_PASSWORD)" | $(DOCKER) login -u "$(DOCKER_USERNAME)" --password-stdin @echo "$(DOCKER_PASSWORD)" | $(DOCKER) login -u "$(DOCKER_USERNAME)" --password-stdin

View File

@ -132,6 +132,27 @@ CityFile = ""
AsnFile = "" 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 ### 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`. 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`.

View File

@ -9,6 +9,7 @@ import (
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/levelsoftware/echoip/cache" "github.com/levelsoftware/echoip/cache"
"github.com/levelsoftware/echoip/config"
"github.com/levelsoftware/echoip/http" "github.com/levelsoftware/echoip/http"
"github.com/levelsoftware/echoip/iputil" "github.com/levelsoftware/echoip/iputil"
"github.com/levelsoftware/echoip/iputil/geo" "github.com/levelsoftware/echoip/iputil/geo"
@ -33,61 +34,40 @@ func init() {
log.SetFlags(log.Lshortfile) 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() { func main() {
file, err := os.Open("/etc/echoip/config.toml") runConfig, err := config.GetConfig()
if err != nil { if err != nil {
panic(err) log.Fatalf("Error building configuration: %s", err)
} }
file, err := os.Open("/etc/echoip/config.toml")
defer file.Close() defer file.Close()
var config Config
b, err := io.ReadAll(file)
if err != nil { 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 {
if err != nil { var b []byte
panic(err) b, err = io.ReadAll(file)
if err != nil {
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 var parser parser.Parser
if config.Database == "geoip" { if runConfig.Database == "geoip" {
log.Print("Using GeoIP for IP database") log.Print("Using GeoIP for IP database")
geo, err := geo.Open( geo, err := geo.Open(
config.GeoIP.CountryFile, runConfig.GeoIP.CountryFile,
config.GeoIP.CityFile, runConfig.GeoIP.CityFile,
config.GeoIP.AsnFile, runConfig.GeoIP.AsnFile,
) )
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -95,27 +75,27 @@ func main() {
parser = &geo parser = &geo
} }
if config.Database == "ipstack" { if runConfig.Database == "ipstack" {
log.Print("Using GeoIP for IP database") log.Print("Using IP Stack for IP database")
if config.IPStack.EnableSecurity { if runConfig.IPStack.EnableSecurity {
log.Print("Enable Security Module ( Requires Professional Plus account )") log.Print("Enable IP Stack Security Module ( Requires Professional Plus account )")
} }
enableSecurity := ipstackApi.ParamEnableSecurity(config.IPStack.EnableSecurity) enableSecurity := ipstackApi.ParamEnableSecurity(runConfig.IPStack.EnableSecurity)
apiKey := ipstackApi.ParamToken(config.IPStack.ApiKey) apiKey := ipstackApi.ParamToken(runConfig.IPStack.ApiKey)
useHttps := ipstackApi.ParamUseHTTPS(config.IPStack.UseHttps) useHttps := ipstackApi.ParamUseHTTPS(runConfig.IPStack.UseHttps)
if config.IPStack.UseHttps { if runConfig.IPStack.UseHttps {
log.Print("Use IP Stack HTTPS API ( Requires non-free account )") log.Print("Use IP Stack HTTPS API ( Requires non-free account )")
} }
if err := ipstackApi.Init(apiKey, enableSecurity, useHttps); err != nil { if err := ipstackApi.Init(apiKey, enableSecurity, useHttps); err != nil {
log.Fatal(err) log.Fatalf("Error initializing IP Stack client: %s", err)
} }
ips := ipstack.IPStack{} ips := ipstack.IPStack{}
parser = &ips parser = &ips
} }
var serverCache cache.Cache var serverCache cache.Cache
if len(config.RedisUrl) > 0 { if len(runConfig.RedisUrl) > 0 {
redisCache, err := cache.RedisCache(config.RedisUrl) redisCache, err := cache.RedisCache(runConfig.RedisUrl)
serverCache = &redisCache serverCache = &redisCache
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -124,34 +104,35 @@ func main() {
serverCache = &cache.Null{} serverCache = &cache.Null{}
} }
server := http.New(parser, serverCache, config.CacheTtl, config.Profile) server := http.New(parser, serverCache, runConfig.CacheTtl, runConfig.Profile)
server.IPHeaders = config.TrustedHeaders server.IPHeaders = runConfig.TrustedHeaders
if _, err := os.Stat(config.TemplateDir); err == nil { if _, err := os.Stat(runConfig.TemplateDir); err == nil {
server.Template = config.TemplateDir server.Template = runConfig.TemplateDir
} else { } 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") log.Println("Enabling reverse lookup")
server.LookupAddr = iputil.LookupAddr server.LookupAddr = iputil.LookupAddr
} }
if config.PortLookup { if runConfig.PortLookup {
log.Println("Enabling port lookup") log.Println("Enabling port lookup")
server.LookupPort = iputil.LookupPort server.LookupPort = iputil.LookupPort
} }
if config.ShowSponsor { if runConfig.ShowSponsor {
log.Println("Enabling sponsor logo") log.Println("Enabling sponsor logo")
server.Sponsor = config.ShowSponsor server.Sponsor = runConfig.ShowSponsor
} }
if len(config.TrustedHeaders) > 0 { if len(runConfig.TrustedHeaders) > 0 {
log.Printf("Trusting remote IP from header(s): %s", config.TrustedHeaders) log.Printf("Trusting remote IP from header(s): %s", runConfig.TrustedHeaders)
} }
if config.Profile { if runConfig.Profile {
log.Printf("Enabling profiling handlers") 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) log.Fatal(err)
} }
} }

135
config/config.go Normal file
View File

@ -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
}

View File

@ -1,6 +1,14 @@
version: '3.8' version: '3.8'
services: services:
echoip:
image: hub.01a.in/echoip:latest
environment:
- ECHOIP_DATABASE="ipstack"
- ECHOIP_IPSTACK_API_KEY="ipstack"
ports:
- '8080:8080'
cache: cache:
image: redis:6.2-alpine image: redis:6.2-alpine
restart: always restart: always

View File

@ -1,5 +1,5 @@
Listen = ":8080" Listen = ":8080"
TemplateDir = "html/index.html" TemplateDir = "html"
RedisUrl = "redis://localhost:6379" RedisUrl = "redis://localhost:6379"
CacheTtl = 3600 # in seconds CacheTtl = 3600 # in seconds
ReverseLookup = true ReverseLookup = true

5
go.mod
View File

@ -18,6 +18,7 @@ require (
github.com/vmihailenco/go-tinylfu v0.2.2 // indirect github.com/vmihailenco/go-tinylfu v0.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/sync v0.1.0 // indirect golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.4.0 // indirect golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.11.0 // indirect
) )

11
go.sum
View File

@ -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.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 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.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.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-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-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-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-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.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.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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.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.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.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/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.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.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.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.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-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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=