Skip to content

Commit

Permalink
Merge pull request #4 from gopher-lib/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
mlukasik-dev authored Mar 10, 2021
2 parents 9725862 + ce58c6e commit b683821
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 67 deletions.
31 changes: 17 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
name: CI

on:
push:
branches: [ main, dev ]
pull_request:
branches: [ main, dev ]
on: ["push", "pull_request"]

jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, 'ci skip')"

steps:
- uses: actions/checkout@v2
- name: Cancel Previous Runs
uses: styfle/[email protected]
with:
access_token: ${{ github.token }}

- uses: actions/checkout@v2

- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16

- name: Build
run: go build -v ./...
- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
- name: Test
run: go test -v ./...
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2021 golang-config
Copyright (c) 2021 gopher-lib

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# [config](https://github.com/golang-config/config): Eloquent configuration for Golang apps.

## Features:

- Substitutes `$VARIABLE` and `${VARIABLE}` with variables found in a shell environment.
- By default loads `.env`, but with optional agruments to `config.LoadFile` can load other files.
47 changes: 23 additions & 24 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,54 @@ package config

import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/joho/godotenv"
"github.com/spf13/viper"
)

// Load loads configuration from provided file, interpolates
// environement variables and then unmarshals it into provided struct.
func Load(rawVal interface{}, filename string, envPath ...string) error {
func LoadFile(rawVal interface{}, filename string, envPath ...string) error {
if len(envPath) > 0 && envPath[0] != "" {
if err := godotenv.Load(envPath[0]); err != nil {
return fmt.Errorf("failed to load env. file: %v", err)
return fmt.Errorf("config: failed to load env. file: %v", err)
}
} else {
// Ignore error as we are loading default env. file.
_ = godotenv.Load(".env")
}
f, err := os.Open(filename)
if err != nil {
return fmt.Errorf("config: failed to open config file: %v", err)
}
return Load(f, rawVal, strings.TrimPrefix(filepath.Ext(filename), "."))
}

viper.SetConfigName(strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filepath.Base(filename))))
viper.SetConfigType("yaml")
viper.AddConfigPath(filepath.Dir(filename))
err := viper.ReadInConfig() // Find and read the config file
func Load(in io.Reader, rawVal interface{}, configType string) error {
viper.SetConfigType(configType)
err := viper.ReadConfig(in)
if err != nil {
return fmt.Errorf("failed to read in config: %w", err)
return fmt.Errorf("config: failed to read in config: %w", err)
}

replaceEnv(viper.AllKeys())
for _, key := range viper.AllKeys() {
newKey := os.Expand(viper.GetString(key), mapping)
viper.Set(key, newKey)
}

err = viper.Unmarshal(&rawVal)
if err != nil {
return fmt.Errorf("failed to unmarshal config: %w", err)
return fmt.Errorf("config: failed to unmarshal config: %w", err)
}
return nil
}

var validEnv = regexp.MustCompile(`^\$\{[a-zA-Z_]+[a-zA-Z0-9_]*\}$`)

func replaceEnv(keys []string) {
for _, key := range keys {
old := viper.GetString(key)
var new string
if validEnv.MatchString(old) {
new = os.Getenv(old[2 : len(old)-1])
} else {
new = old
}
viper.Set(key, new)
// mapping is second argument for os.Expand function.
func mapping(s string) string {
if strings.HasPrefix(s, "$") {
return s
}
return os.Getenv(s)
}
68 changes: 46 additions & 22 deletions config_test.go
Original file line number Diff line number Diff line change
@@ -1,43 +1,67 @@
package config

import (
"os"
"reflect"
"strings"
"testing"

"github.com/spf13/viper"
)

func TestLoadFile(t *testing.T) {
viper.Reset()

type db struct {
User string
Password string
}
type config struct {
Port int
AuthSecret string
Secret string
Dollar string
DB db
ConnectionString string
}
var conf config
err := LoadFile(&conf, "./testdata/config.testing.yaml", "./testdata/.env.testing")
if err != nil {
t.Fatal(err)
}
expected := config{8080, "secret", "", "$dollar", db{"root", "admin"}, "root:admin@tcp(localhost:3306)/core?parseTime=true"}
if !reflect.DeepEqual(conf, expected) {
t.Errorf("not equal: %v != %v", conf, expected)
}
}

func TestLoad(t *testing.T) {
t.Run("simple config with interpolation", func(t *testing.T) {
type Config struct {
Port int
Secret1 string
Secret2 string
}
var conf Config
err := Load(&conf, "testdata/config.testing.yaml", "testdata/.env.testing")
if err != nil {
t.Fatalf("error loading config: %v\n", err)
}
expected := Config{5432, "secret-value", ""}
if !reflect.DeepEqual(conf, expected) {
t.Errorf("not equal: %v != %v", conf, expected)
}
})
t.Run("variable substitution", func(t *testing.T) {
viper.Reset()

t.Run("more complex config with interpolation", func(t *testing.T) {
os.Setenv("DB_PASSWORD", "root")
const confStr = `
port: 1234
db:
user: postgres
password: ${DB_PASSWORD}
empty: emp${T}ty
`
type DB struct {
User string
Password string
}
type Config struct {
Port int
DB DB
Port int
DB DB
Empty string
}
var conf Config
err := Load(&conf, "testdata/config.testing.yaml", "testdata/.env.testing")
err := Load(strings.NewReader(confStr), &conf, "yaml")
if err != nil {
t.Fatalf("error loading config: %v\n", err)
t.Fatal(err)
}
expected := Config{5432, DB{"admin", "root"}}
expected := Config{1234, DB{"postgres", "root"}, "empty"}
if !reflect.DeepEqual(conf, expected) {
t.Errorf("not equal: %v != %v", conf, expected)
}
Expand Down
8 changes: 6 additions & 2 deletions testdata/.env.testing
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
SOME_SECRET=secret-value
DB_PASSWORD=root
AUTH_SECRET=secret
DB_USER=root
DB_PASSWORD=admin
DB_HOST=localhost
DB_PORT=3306
DB_NAME=core
10 changes: 6 additions & 4 deletions testdata/config.testing.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
port: 5432
secret1: ${SOME_SECRET}
secret2: ${SECRET}
port: 8080
authSecret: ${AUTH_SECRET}
secret: ${SECRET}
dollar: $$dollar
db:
user: admin
user: root
password: ${DB_PASSWORD}
connectionString: "${DB_USER}:${DB_PASSWORD}@tcp(${DB_HOST}:${DB_PORT})/${DB_NAME}?parseTime=true"

0 comments on commit b683821

Please sign in to comment.