Skip to content

Commit

Permalink
+ Etcd TLS option (#8)
Browse files Browse the repository at this point in the history
Co-authored-by: bogcon <bog.con.bc@gmail.com>
  • Loading branch information
actforgood and bogcon authored Oct 15, 2022
1 parent 1069c3c commit 1407470
Show file tree
Hide file tree
Showing 24 changed files with 459 additions and 119 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
timeout-minutes: 10
runs-on: ubuntu-latest

container: golang:1.19.1-bullseye
container: golang:1.19.2-bullseye

steps:
- name: Checkout code
Expand All @@ -70,8 +70,9 @@ jobs:
go mod download
make cover-integration
env:
CONSUL_HTTP_ADDR: integration-consul:8500
ETCD_ENDPOINTS: integration-etcd:2379
CONSUL_HTTP_ADDR: xconf-consul:8500
ETCD_ENDPOINTS: xconf-etcd:2379
ETCDS_ENDPOINTS: xconf-etcds:2389

- name: TearDown dockers
run: ./scripts/teardown_dockers.sh
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ bin/*

cover*.out
cover*.html

scripts/tls/certs
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
LINTER_VERSION=v1.49.0
LINTER_VERSION=v1.50.0
LINTER=./bin/golangci-lint
ifeq ($(OS),Windows_NT)
LINTER=./bin/golangci-lint.exe
Expand Down Expand Up @@ -33,6 +33,7 @@ test-integration: setup-test-integration-data ## Run integration tests (with rac

.PHONY: setup-test-integration-data
setup-test-integration-data: ## Run integration tests data setup scripts.
./scripts/setup_dockers_local.sh
./scripts/consul_data_provider.sh
./scripts/etcd_data_provider.sh

Expand Down
3 changes: 1 addition & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ func NewDefaultConfig(loader Loader, opts ...DefaultConfigOption) (*DefaultConfi
opt(config)
}

err := config.setConfigMap()
if err != nil {
if err := config.setConfigMap(); err != nil {
return nil, err
}

Expand Down
13 changes: 7 additions & 6 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,11 @@ func testDefaultConfigWithReloadErrorHandler(t *testing.T) {
expectedErr = errors.New("intentionally triggered Load error")
loader = xconf.LoaderFunc(func() (map[string]interface{}, error) {
atomic.AddUint32(&loaderCallsCnt, 1)
if atomic.LoadUint32(&loaderCallsCnt) == 1 {
return map[string]interface{}{"foo": "bar"}, nil
if atomic.LoadUint32(&loaderCallsCnt) == 2 {
return nil, expectedErr
}

return nil, expectedErr
return map[string]interface{}{"foo": "bar"}, nil
})
errHandlerCallsCnt uint32
errHandler = func(err error) {
Expand Down Expand Up @@ -1608,13 +1608,13 @@ func TestDefaultConfig_RegisterObserver(t *testing.T) {

// prepare second act
if err := os.Setenv("XCONF_TEST_DEFAULT_CONFIG_FOO_UPDATED", "foo got updated"); err != nil {
t.Skip("prerequisite failed", err)
t.Fatal("prerequisite failed:", err)
}
if err := os.Unsetenv("XCONF_TEST_DEFAULT_CONFIG_FOO_DELETED"); err != nil {
t.Skip("prerequisite failed", err)
t.Fatal("prerequisite failed:", err)
}
if err := os.Setenv("XCONF_TEST_DEFAULT_CONFIG_FOO_NEW", "foo to be added later"); err != nil {
t.Skip("prerequisite failed", err)
t.Fatal("prerequisite failed:", err)
}
time.Sleep(300 * time.Millisecond)

Expand Down Expand Up @@ -1766,6 +1766,7 @@ func benchmarkDefaultConfigGet(withReload, withDefValue bool) func(b *testing.B)
// The extra allocation between with/without default value comes from casting.
// TODO: maybe think at a solution not to have this allocation.
return func(b *testing.B) {
b.Helper()
var (
loader = xconf.PlainLoader(map[string]interface{}{
"foo": "bar",
Expand Down
12 changes: 6 additions & 6 deletions loader_consul_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,26 @@ import (
// Note: data from this test file can be generated with ./scripts/consul_data_provider.sh

func TestConsulLoader_withJSON_integration(t *testing.T) {
key := "json-key"
format := xconf.RemoteValueJSON
const key = "json-key"
const format = xconf.RemoteValueJSON

t.Run("single key", testConsulLoaderIntegration(format, key, false, ""))
t.Run("prefix key", testConsulLoaderIntegration(format, key, true, ""))
t.Run("datacenter query", testConsulLoaderIntegration(format, key, false, "dc1"))
}

func TestConsulLoader_withYAML_integration(t *testing.T) {
key := "yaml-key"
format := xconf.RemoteValueYAML
const key = "yaml-key"
const format = xconf.RemoteValueYAML

t.Run("single key", testConsulLoaderIntegration(format, key, false, ""))
t.Run("prefix key", testConsulLoaderIntegration(format, key, true, ""))
t.Run("datacenter query", testConsulLoaderIntegration(format, key, false, "dc1"))
}

func TestConsulLoader_withPlain_integration(t *testing.T) {
key := "plain-key"
format := xconf.RemoteValuePlain
const key = "plain-key"
const format = xconf.RemoteValuePlain

t.Run("single key", testConsulLoaderIntegration(format, key, false, ""))
t.Run("prefix key", testConsulLoaderIntegration(format, key, true, ""))
Expand Down
1 change: 1 addition & 0 deletions loader_consul_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ func getConsulExpectedConfigMapByFormatAndPrefix(format string, withPrefix bool)

func benchmarkConsulLoader(format string, withCache bool) func(b *testing.B) {
return func(b *testing.B) {
b.Helper()
content := consulResponseContent[format][true]
key := consulKeys[format]
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down
7 changes: 4 additions & 3 deletions loader_decorator_filecache_loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func testFileCacheLoaderSuccess(t *testing.T) {
// setup a file for which we will play with its modification time.
filePath, err := setUpTmpFile("xconf-filecacheloader-*.json", `{"foo":"bar"}`+"\n")
if err != nil {
t.Skip("prerequisite failed", err)
t.Fatal("prerequisite failed:", err)
}
defer tearDownTmpFile(filePath)

Expand Down Expand Up @@ -174,7 +174,7 @@ func TestFileCacheLoader_concurrency(t *testing.T) {
// setup a file for which we will play with its modification time.
filePath, err := setUpTmpFile("xconf-filecacheloader-concurrency-*.json", `{"foo":"bar"}`+"\n")
if err != nil {
t.Skip("prerequisite failed", err)
t.Fatal("prerequisite failed:", err)
}
defer tearDownTmpFile(filePath)

Expand Down Expand Up @@ -251,7 +251,7 @@ func tearDownTmpFile(filePath string) {

// writeToFile writes given content to the specified file.
func writeToFile(filePath, content string) error {
f, err := os.OpenFile(filePath, os.O_WRONLY, 0644)
f, err := os.OpenFile(filePath, os.O_WRONLY, 0o644)
if err != nil {
return err
}
Expand All @@ -264,6 +264,7 @@ func writeToFile(filePath, content string) error {

func benchmarkFileCacheLoader(loader xconf.Loader) func(b *testing.B) {
return func(b *testing.B) {
b.Helper()
b.ReportAllocs()
b.ResetTimer()

Expand Down
41 changes: 21 additions & 20 deletions loader_decorator_filter_kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,7 @@ func (filter FilterKVBlacklistFunc) Type() FilterType {
// if no filter denies it.
func FilterKVLoader(loader Loader, filters ...FilterKV) Loader {
// make 2 buckets of filters.
var (
blacklistFilters = make([]FilterKV, 0, len(filters))
whitelistFilters = make([]FilterKV, 0, len(filters))
)
for _, filter := range filters {
switch filter.Type() {
case FilterTypeWhitelist:
whitelistFilters = append(whitelistFilters, filter)
case FilterTypeBlacklist:
blacklistFilters = append(blacklistFilters, filter)
}
}
blacklistFilters, whitelistFilters := filterBuckets(filters...)

return LoaderFunc(func() (map[string]interface{}, error) {
configMap, err := loader.Load()
Expand All @@ -117,25 +106,37 @@ func FilterKVLoader(loader Loader, filters ...FilterKV) Loader {

// check if it is whitelisted
if len(whitelistFilters) > 0 {
isAllowed := false
for _, wlFilter := range whitelistFilters {
if wlFilter.IsAllowed(key, value) {
isAllowed = true

break
continue KvLoop
}
}

if !isAllowed {
delete(configMap, key)
}
delete(configMap, key)
}
}

return configMap, nil
})
}

// filterBuckets splits provided filters into 2 buckets (black, white).
func filterBuckets(filters ...FilterKV) ([]FilterKV, []FilterKV) {
var (
blacklistFilters = make([]FilterKV, 0, len(filters))
whitelistFilters = make([]FilterKV, 0, len(filters))
)
for _, filter := range filters {
switch filter.Type() {
case FilterTypeWhitelist:
whitelistFilters = append(whitelistFilters, filter)
case FilterTypeBlacklist:
blacklistFilters = append(blacklistFilters, filter)
}
}

return blacklistFilters, whitelistFilters
}

// FilterKeyWithPrefix returns true if a key has given prefix.
// It can be used as a FilterKV like:
//
Expand Down
9 changes: 9 additions & 0 deletions loader_etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package xconf

import (
"context"
"crypto/tls"
"io"
"os"
"strings"
Expand Down Expand Up @@ -131,6 +132,14 @@ func EtcdLoaderWithAuth(username, pwd string) EtcdLoaderOption {
}
}

// EtcdLoaderWithTLS sets the TLS configuration for secure
// communication between client and server.
func EtcdLoaderWithTLS(tlsCfg *tls.Config) EtcdLoaderOption {
return func(loader *EtcdLoader) {
loader.strategyInfo.clientCfg.TLS = tlsCfg.Clone()
}
}

// EtcdLoaderWithValueFormat sets the value format for a key.
//
// If is set to RemoteValueJSON, the key's value will be treated as JSON and configuration will be loaded from it.
Expand Down
Loading

0 comments on commit 1407470

Please sign in to comment.