diff --git a/README.adoc b/README.adoc index 6095a17..9212a0e 100644 --- a/README.adoc +++ b/README.adoc @@ -21,7 +21,36 @@ The request query parameter `d` can be used to request GitHub like patterns by s === Configuration -Configuration is available by using environment variables. +Configuration is available by using a config file or by using environment variables. + +==== Config file + +If config file `/etc/identicon/config.toml` exists, its configuration will be used on application start. + +Use application argument `-c` to use another file. +.... +$ identicon -c ./config.toml +.... + +In addition to configuration for default values, the configuration file can also be used to create specific configs for users. + +.... +[defaults] +color-scheme = "v2" # Default color scheme + +[[users]] +id = "me@example.com" # The users ID in plain text +alias = "42" # The alias to be used, e.g. for mapping users to other IDs +color-scheme = "gh" # The color scheme to be used for this user +pattern = "github" # The pattern to be used for this user + +[[users]] +... +.... + +If config file is not present, the application will ignore it. Using environment variables will override default settings. + +==== Environment Variables You can use `COLORSCHEME` to define the default color scheme to be used. Fallback value will be `v2`. diff --git a/go.mod b/go.mod index cce1834..e023916 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,8 @@ module idicon -go 1.16 +go 1.17 -require github.com/gorilla/mux v1.8.0 +require ( + github.com/BurntSushi/toml v1.0.0 + github.com/gorilla/mux v1.8.0 +) diff --git a/go.sum b/go.sum index 5350288..58ed9e4 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= +github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/idicon.go b/idicon.go index c183464..560a5eb 100644 --- a/idicon.go +++ b/idicon.go @@ -3,6 +3,7 @@ package main import ( "crypto/md5" "encoding/hex" + "flag" "image" "image/color" "image/draw" @@ -14,6 +15,7 @@ import ( "strconv" "strings" + "github.com/BurntSushi/toml" "github.com/gorilla/mux" ) @@ -133,12 +135,24 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) { colorScheme := r.URL.Query().Get("c") if colorScheme == "" { - colorScheme = os.Getenv("COLORSCHEME") + colorScheme = config.Defaults.ColorScheme } pattern := r.URL.Query().Get("d") if pattern == "" { - pattern = os.Getenv("PATTERN") + pattern = config.Defaults.Pattern + } + + for _, userConfig := range config.Users { + if hashBytes(id) == hashBytes(userConfig.Id) { + id = userConfig.Alias + if len(userConfig.ColorScheme) > 0 { + colorScheme = userConfig.ColorScheme + } + if len(userConfig.Pattern) > 0 { + pattern = userConfig.Pattern + } + } } w.Header().Add("Content-Type", "image/png") @@ -157,7 +171,54 @@ func RequestHandler(w http.ResponseWriter, r *http.Request) { } +type Config struct { + Defaults Defaults `toml:"defaults"` + Users []UserConfig `toml:"users"` +} + +type Defaults struct { + ColorScheme string `toml:"color-scheme"` + Pattern string `toml:"pattern"` +} + +type UserConfig struct { + Id string `toml:"id"` + Alias string `toml:"alias"` + ColorScheme string `toml:"color-scheme"` + Pattern string `toml:"pattern"` +} + +var ( + config Config +) + +func configure(configFile string) { + if file, err := os.OpenFile(configFile, os.O_RDONLY, 0); err == nil { + c := &Config{} + _, err := toml.NewDecoder(file).Decode(c) + if err != nil { + log.Printf("Invalid config file '%s' - ignore it.\n", configFile) + } + + defer file.Close() + config = *c + } + + if os.Getenv("COLORSCHEME") != "" { + config.Defaults.ColorScheme = os.Getenv("COLORSCHEME") + } + + if os.Getenv("PATTERN") != "" { + config.Defaults.Pattern = os.Getenv("PATTERN") + } +} + func main() { + configFile := flag.String("c", "/etc/idicon/config.toml", "-c ") + flag.Parse() + + configure(*configFile) + router := mux.NewRouter() router.HandleFunc("/avatar/{id}", RequestHandler) log.Println("Starting ...") diff --git a/idicon_test.go b/idicon_test.go index 055be17..c025fa5 100644 --- a/idicon_test.go +++ b/idicon_test.go @@ -6,6 +6,7 @@ import ( "image/png" "net/http" "net/http/httptest" + "os" "reflect" "testing" @@ -19,7 +20,10 @@ var v1 []byte var v2 []byte //go:embed testdata/1a79a4d60de6718e8e5b326e338ae533_gh.png -var gh []byte +var gh1 []byte + +//go:embed testdata/a1d0c6e83f027327d8461063f4ac58a6_gh.png +var gh2 []byte func testRouter() *mux.Router { router := mux.NewRouter() @@ -78,7 +82,7 @@ func TestCorrectResponseForGHColorSchemeAndPattern(t *testing.T) { rr := httptest.NewRecorder() testRouter().ServeHTTP(rr, req) - if !reflect.DeepEqual(rr.Body.Bytes(), gh) { + if !reflect.DeepEqual(rr.Body.Bytes(), gh1) { t.Errorf("returned image does not match expected image") } } @@ -108,6 +112,49 @@ func TestStringMatchesHash(t *testing.T) { } } +func TestUsesConfig(t *testing.T) { + configure("./testdata/testconfig.toml") + + if config.Defaults.ColorScheme != "gh" || + config.Users[0].Id != "example" || + config.Users[0].Alias != "42" || + config.Users[0].ColorScheme != "gh" || + config.Users[0].Pattern != "github" { + t.Errorf("Config not applied as expected") + } +} + +func TestUsesConfigWithEnvVar(t *testing.T) { + os.Setenv("COLORSCHEME", "v1") + os.Setenv("PATTERN", "default") + + configure("./testdata/testconfig.toml") + + if config.Defaults.ColorScheme != "v1" || + config.Users[0].Id != "example" || + config.Users[0].Alias != "42" || + config.Users[0].ColorScheme != "gh" || + config.Users[0].Pattern != "github" { + t.Errorf("Config not applied as expected") + } +} + +func TestCorrectResponseForUserConfig(t *testing.T) { + configure("./testdata/testconfig.toml") + + req, err := http.NewRequest("GET", "/avatar/example", nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + testRouter().ServeHTTP(rr, req) + + if !reflect.DeepEqual(rr.Body.Bytes(), gh2) { + t.Errorf("returned image does not match expected image for mapped alias '42'") + } +} + func TestHSLtoRGB(t *testing.T) { red := hslToRgba(0, 100, 50) if red.R != 255 || red.G != 0 || red.B != 0 { diff --git a/testdata/a1d0c6e83f027327d8461063f4ac58a6_gh.png b/testdata/a1d0c6e83f027327d8461063f4ac58a6_gh.png new file mode 100644 index 0000000..beec58e Binary files /dev/null and b/testdata/a1d0c6e83f027327d8461063f4ac58a6_gh.png differ diff --git a/testdata/testconfig.toml b/testdata/testconfig.toml new file mode 100644 index 0000000..452de3a --- /dev/null +++ b/testdata/testconfig.toml @@ -0,0 +1,8 @@ +[defaults] +color-scheme = "gh" + +[[users]] +id = "example" +alias = "42" +color-scheme = "gh" +pattern = "github"