mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-09-25 11:13:51 +02:00
Env var parsing
This commit is contained in:
80
env/env.go
vendored
Normal file
80
env/env.go
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ParseFunc is a function type for parsing environment variable values
|
||||
type ParseFunc[T any] func(string) (T, error)
|
||||
|
||||
// ValidateFunc is a function type for validating parsed environment variable values
|
||||
type ValidateFunc[T any] func(T) bool
|
||||
|
||||
// generic number (add more types if needed)
|
||||
type number = interface {
|
||||
~int | ~int64 | ~float32 | ~float64
|
||||
}
|
||||
|
||||
// EnvVar represents an environment variable with its metadata
|
||||
type EnvVar[T any] struct {
|
||||
name string // ENV variable name
|
||||
description string // Description for help message
|
||||
formatDesc string // Description of the format
|
||||
def T // Default value (if any)
|
||||
parse ParseFunc[T] // Function to parse the value from string
|
||||
validate []ValidateFunc[T] // Optional validation function(s)
|
||||
}
|
||||
|
||||
// Define creates a new EnvVar with the given parameters
|
||||
func Define[T any](name, description, formatDesc string, parse ParseFunc[T], def T, val ...ValidateFunc[T]) EnvVar[T] {
|
||||
return EnvVar[T]{
|
||||
name: name,
|
||||
description: description,
|
||||
parse: parse,
|
||||
formatDesc: formatDesc,
|
||||
def: def,
|
||||
validate: val,
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves the value of the environment variable, parses it,
|
||||
// validates it, and sets it to the provided pointer.
|
||||
func (e EnvVar[T]) Get(value *T) (err error) {
|
||||
val, exists := os.LookupEnv(e.name)
|
||||
|
||||
// First, let's fill with a value (either default or parsed)
|
||||
if !exists {
|
||||
*value = e.def
|
||||
return nil
|
||||
} else {
|
||||
*value, err = e.parse(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing %s (required: %v): %v", e.name, e.formatDesc, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Then, validate the value
|
||||
for _, validate := range e.validate {
|
||||
if !validate(*value) {
|
||||
return fmt.Errorf("invalid value for %s (required: %v): %v", e.name, e.formatDesc, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Default returns the default value of the environment variable
|
||||
func (e EnvVar[T]) Default() T {
|
||||
return e.def
|
||||
}
|
||||
|
||||
// NotEmpty is a validation function that checks if a string is not empty
|
||||
func NotEmpty(v string) bool {
|
||||
return v != ""
|
||||
}
|
||||
|
||||
// Positive is a validation function that checks if a number is greater than zero
|
||||
func Positive[T number](v T) bool {
|
||||
return v > 0
|
||||
}
|
60
env/parsers.go
vendored
Normal file
60
env/parsers.go
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// String parses an environment variable as a string
|
||||
func String(value string) (string, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Int parses an environment variable as an integer
|
||||
func Int(value string) (int, error) {
|
||||
return strconv.Atoi(value)
|
||||
}
|
||||
|
||||
// Duration parses an environment variable as a time.Duration in seconds
|
||||
func DurationSec(value string) (time.Duration, error) {
|
||||
sec, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return time.Duration(sec) * time.Second, nil
|
||||
}
|
||||
|
||||
// func Float(i *float64, name string) {
|
||||
// if env, err := strconv.ParseFloat(os.Getenv(name), 64); err == nil {
|
||||
// *i = env
|
||||
// }
|
||||
// }
|
||||
|
||||
// func MegaInt(f *int, name string) {
|
||||
// if env, err := strconv.ParseFloat(os.Getenv(name), 64); err == nil {
|
||||
// *f = int(env * 1000000)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func String(s *string, name string) {
|
||||
// if env := os.Getenv(name); len(env) > 0 {
|
||||
// *s = env
|
||||
// }
|
||||
// }
|
||||
|
||||
// func StringSliceSep(s *[]string, name, sep string) {
|
||||
// if env := os.Getenv(name); len(env) > 0 {
|
||||
// parts := strings.Split(env, sep)
|
||||
|
||||
// for i, p := range parts {
|
||||
// parts[i] = strings.TrimSpace(p)
|
||||
// }
|
||||
|
||||
// *s = parts
|
||||
|
||||
// return
|
||||
// }
|
||||
|
||||
// *s = []string{}
|
||||
// }
|
@@ -2,14 +2,27 @@ package processing
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/config"
|
||||
"github.com/imgproxy/imgproxy/v3/ensure"
|
||||
"github.com/imgproxy/imgproxy/v3/env"
|
||||
"github.com/imgproxy/imgproxy/v3/imagetype"
|
||||
"github.com/imgproxy/imgproxy/v3/vips"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
preferredFormats = env.Define(
|
||||
"IMGPROXY_PREFERRED_FORMATS",
|
||||
"Preferred image formats, comma-separated",
|
||||
"jpeg, png, webp, tiff, avif, heic, gif, jp2", // what else?
|
||||
parsePreferredFormats,
|
||||
[]imagetype.Type{imagetype.JPEG, imagetype.PNG, imagetype.GIF},
|
||||
)
|
||||
)
|
||||
|
||||
// Config holds pipeline-related configuration.
|
||||
type Config struct {
|
||||
PreferredFormats []imagetype.Type
|
||||
@@ -22,11 +35,7 @@ type Config struct {
|
||||
func NewDefaultConfig() Config {
|
||||
return Config{
|
||||
WatermarkOpacity: 1,
|
||||
PreferredFormats: []imagetype.Type{
|
||||
imagetype.JPEG,
|
||||
imagetype.PNG,
|
||||
imagetype.GIF,
|
||||
},
|
||||
PreferredFormats: preferredFormats.Default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +46,10 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
|
||||
c.WatermarkOpacity = config.WatermarkOpacity
|
||||
c.DisableShrinkOnLoad = config.DisableShrinkOnLoad
|
||||
c.UseLinearColorspace = config.UseLinearColorspace
|
||||
c.PreferredFormats = config.PreferredFormats
|
||||
|
||||
if err := preferredFormats.Get(&c.PreferredFormats); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
@@ -68,3 +80,23 @@ func (c *Config) Validate() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parsePreferredFormats(s string) ([]imagetype.Type, error) {
|
||||
parts := strings.Split(s, ",")
|
||||
it := make([]imagetype.Type, 0, len(parts))
|
||||
|
||||
for _, p := range parts {
|
||||
part := strings.TrimSpace(p)
|
||||
|
||||
// For every part passed through the environment variable,
|
||||
// check if it matches any of the image types defined in
|
||||
// the imagetype package or return error.
|
||||
t, ok := imagetype.GetTypeByName(part)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown image format: %s", part)
|
||||
}
|
||||
it = append(it, t)
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
@@ -8,12 +8,49 @@ import (
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/config"
|
||||
"github.com/imgproxy/imgproxy/v3/ensure"
|
||||
"github.com/imgproxy/imgproxy/v3/env"
|
||||
"github.com/imgproxy/imgproxy/v3/server/responsewriter"
|
||||
)
|
||||
|
||||
var (
|
||||
bind = env.Define(
|
||||
"IMGPROXY_BIND",
|
||||
"Address and port to bind to",
|
||||
"host:port, not empty",
|
||||
env.String,
|
||||
":8080",
|
||||
env.NotEmpty,
|
||||
)
|
||||
|
||||
network = env.Define(
|
||||
"IMGPROXY_NETWORK",
|
||||
"Network type",
|
||||
"tcp/unix/udp",
|
||||
env.String,
|
||||
"tcp",
|
||||
)
|
||||
|
||||
maxClients = env.Define(
|
||||
"IMGPROXY_MAX_CLIENTS",
|
||||
"Maximum number of concurrent clients",
|
||||
"number > 0",
|
||||
env.Int,
|
||||
2048,
|
||||
env.Positive,
|
||||
)
|
||||
|
||||
readRequestTimeout = env.Define(
|
||||
"IMGPROXY_READ_REQUEST_TIMEOUT",
|
||||
"Timeout for reading requests",
|
||||
"seconds > 0",
|
||||
env.DurationSec,
|
||||
time.Second*10,
|
||||
env.Positive,
|
||||
)
|
||||
)
|
||||
|
||||
// Config represents HTTP server config
|
||||
type Config struct {
|
||||
Listen string // Address to listen on
|
||||
Network string // Network type (tcp, unix)
|
||||
Bind string // Bind address
|
||||
PathPrefix string // Path prefix for the server
|
||||
@@ -37,10 +74,10 @@ type Config struct {
|
||||
// NewDefaultConfig returns default config values
|
||||
func NewDefaultConfig() Config {
|
||||
return Config{
|
||||
Network: "tcp",
|
||||
Bind: ":8080",
|
||||
Network: network.Default(),
|
||||
Bind: bind.Default(),
|
||||
PathPrefix: "",
|
||||
MaxClients: 2048,
|
||||
MaxClients: maxClients.Default(),
|
||||
ReadRequestTimeout: 10 * time.Second,
|
||||
KeepAliveTimeout: 10 * time.Second,
|
||||
GracefulTimeout: 20 * time.Second,
|
||||
@@ -60,11 +97,22 @@ func NewDefaultConfig() Config {
|
||||
func LoadConfigFromEnv(c *Config) (*Config, error) {
|
||||
c = ensure.Ensure(c, NewDefaultConfig)
|
||||
|
||||
c.Network = config.Network
|
||||
c.Bind = config.Bind
|
||||
err := errors.Join(
|
||||
network.Get(&c.Network),
|
||||
bind.Get(&c.Bind),
|
||||
maxClients.Get(&c.MaxClients),
|
||||
readRequestTimeout.Get(&c.ReadRequestTimeout),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// c.Network = config.Network
|
||||
// c.Bind = config.Bind
|
||||
c.PathPrefix = config.PathPrefix
|
||||
c.MaxClients = config.MaxClients
|
||||
c.ReadRequestTimeout = time.Duration(config.ReadRequestTimeout) * time.Second
|
||||
// c.MaxClients = config.MaxClients
|
||||
// c.ReadRequestTimeout = time.Duration(config.ReadRequestTimeout) * time.Second
|
||||
c.KeepAliveTimeout = time.Duration(config.KeepAliveTimeout) * time.Second
|
||||
c.GracefulTimeout = time.Duration(config.GracefulStopTimeout) * time.Second
|
||||
c.CORSAllowOrigin = config.AllowOrigin
|
||||
@@ -88,9 +136,9 @@ func (c *Config) Validate() error {
|
||||
return errors.New("bind address is not defined")
|
||||
}
|
||||
|
||||
if c.MaxClients < 0 {
|
||||
return fmt.Errorf("max clients number should be greater than or equal 0, now - %d", c.MaxClients)
|
||||
}
|
||||
// if c.MaxClients < 0 {
|
||||
// return fmt.Errorf("max clients number should be greater than or equal 0, now - %d", c.MaxClients)
|
||||
// }
|
||||
|
||||
if c.ReadRequestTimeout <= 0 {
|
||||
return fmt.Errorf("read request timeout should be greater than 0, now - %d", c.ReadRequestTimeout)
|
||||
|
Reference in New Issue
Block a user