├── .circleci └── config.yml ├── .gitignore ├── .whitesource ├── LICENSE ├── Makefile ├── README.md ├── api_endpoint_config.go ├── api_endpoint_config_test.go ├── aws_region.go ├── aws_region_test.go ├── dynamodb_client_config.go ├── dynamodb_client_config_test.go ├── dynamodb_table_config.go ├── dynamodb_table_config_test.go ├── go.mod ├── go.sum ├── remoteconfig.go ├── remoteconfig_test.go ├── s3_config.go ├── s3_config_test.go ├── s3_endpoint_expiry_config.go ├── s3_endpoint_expiry_config_test.go ├── sqs_client_config.go ├── sqs_client_config_test.go ├── sqs_queue_config.go ├── sqs_queue_config_test.go ├── storage_config.go ├── storage_config_test.go ├── storage_location.go └── storage_provider.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | jobs: 4 | build: 5 | docker: 6 | - image: circleci/golang 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | key: go-mod-{{ checksum "go.sum" }} 11 | - run: 12 | name: Download Go dependencies 13 | command: go mod download 14 | - save_cache: 15 | key: go-mod-{{ checksum "go.sum" }} 16 | paths: 17 | - /go/pkg/mod 18 | - run: 19 | name: Run unit tests 20 | command: | 21 | make 22 | experimental: 23 | notify: 24 | branches: 25 | only: 26 | - master 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | coverage/ 3 | vendor/ 4 | .idea 5 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "settingsInheritedFrom": "zencoder/whitesource-config@main" 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Brightcove, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Force-enable Go modules even if this project has been cloned within a user's GOPATH 2 | export GO111MODULE = on 3 | 4 | # Specify VERBOSE=1 to get verbose output from all executed commands 5 | ifdef VERBOSE 6 | V = -v 7 | X = -x 8 | else 9 | .SILENT: 10 | endif 11 | 12 | .PHONY: all 13 | all: build test 14 | 15 | .PHONY: clean 16 | clean: 17 | rm -rf bin/ coverage/ cucumber/logs/ 18 | go clean -i $(X) -cache -testcache 19 | 20 | .PHONY: build 21 | build: 22 | mkdir -p bin 23 | go build $(V) -o bin/go-remote-config 24 | 25 | .PHONY: fmt 26 | fmt: 27 | go fmt $(X) ./... 28 | 29 | .PHONY: test 30 | test: 31 | mkdir -p coverage 32 | go test $(V) -race -cover -coverprofile coverage/cover.profile ./... 33 | 34 | .PHONY: cover 35 | cover: 36 | go tool cover -html coverage/cover.profile 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-remote-config 2 | 3 | [![godoc](https://godoc.org/github.com/zencoder/go-remote-config?status.svg)](http://godoc.org/github.com/zencoder/go-remote-config) 4 | [![Circle CI](https://circleci.com/gh/zencoder/go-remote-config.svg?style=svg)](https://circleci.com/gh/zencoder/go-remote-config) 5 | 6 | A Go library for configuration management with JSON files in remote storage. 7 | 8 | ## Install 9 | 10 | go get github.com/zencoder/go-remote-config 11 | 12 | ## Supported Storage Providers 13 | 14 | * AWS S3 (Signed URLs) 15 | * HTTP/HTTPS 16 | 17 | ## Features 18 | 19 | * Reflection based config validation 20 | * Required fields 21 | * Optional fields 22 | * Custom Validate interface 23 | * Empty string checks 24 | * Struct & Slice, nested support 25 | * Built in config structs for services 26 | * AWS Regions 27 | * AWS DynamoDB (Client + Table) 28 | * AWS SQS (Client + Queue) 29 | * AWS S3 30 | * Generic HTTP Endpoints 31 | 32 | ## Future Features 33 | 34 | * More storage provider support 35 | * Google Cloud Storage 36 | * Rackspace CloudFiles 37 | * Default value support 38 | * Config download retry support 39 | * Live config reloading 40 | 41 | ## Example 42 | 43 | ```go 44 | type SampleConfig struct { 45 | SQSQueueOptional *SQSQueueConfig `json:"sqs_queue_optional,omitempty" remoteconfig:"optional"` 46 | SQSClientOptional *SQSClientConfig `json:"sqs_client_optional,omitempty" remoteconfig:"optional"` 47 | DynamoDBTableOptional *DynamoDBTableConfig `json:"dynamodb_table_optional,omitempty" remoteconfig:"optional"` 48 | DynamoDBClientOptional *DynamoDBClientConfig `json:"dynamodb_client_optional,omitempty" remoteconfig:"optional"` 49 | StrOptional *string `json:"str_optional,omitempty" remoteconfig:"optional"` 50 | StorageConfigOptional *StorageConfig `json:"storage_config_optional,omitempty" remoteconfig:"optional"` 51 | StorageConfigSliceOptional []*StorageConfig `json:"storage_config_slice_optional,omitempty" remoteconfig:"optional"` 52 | SQSQueue *SQSQueueConfig `json:"sqs_queue,omitempty"` 53 | SQSClient *SQSClientConfig `json:"sqs_client,omitempty"` 54 | DynamoDBTable *DynamoDBTableConfig `json:"dynamodb_table,omitempty"` 55 | DynamoDBClient *DynamoDBClientConfig `json:"dynamodb_client,omitempty"` 56 | Str *string `json:"str,omitempty"` 57 | StorageConfig *StorageConfig `json:"storage_config,omitempty"` 58 | StorageConfigSlice []*StorageConfig `json:"storage_config_slice,omitempty"` 59 | } 60 | 61 | var s SampleConfig 62 | LoadConfig(s) 63 | 64 | import ( 65 | "log" 66 | "os" 67 | 68 | "github.com/zencoder/go-remote-config" 69 | ) 70 | 71 | func LoadConfig(config interface{}) { 72 | // Load the config from S3 73 | configURL := os.Getenv("S3_CONFIG_URL") 74 | configRegion := remoteconfig.AWSRegion(os.Getenv("S3_CONFIG_REGION")) 75 | 76 | // Load an endpoint for S3 config (can be used to fake out S3 for testing) 77 | configEndpoint := os.Getenv("S3_CONFIG_ENDPOINT") 78 | 79 | // We should fail out if config environment variables are not set / valid 80 | if configURL == "" { 81 | log.Panic("S3 Configuration URL must be provided.") 82 | } 83 | 84 | if err := configRegion.Validate(); err != nil { 85 | log.Panic("Invalid Region for S3 Configuration") 86 | } 87 | 88 | log.Printf("Loading config file from S3. URL = %s, Region = %s", configURL, configRegion) 89 | 90 | if err := remoteconfig.LoadConfigFromS3(configURL, configRegion, configEndpoint, config); err != nil { 91 | log.Panicf("Failed to load config file, with error: %s", err.Error()) 92 | } 93 | 94 | log.Printf("Successfully loaded config file from S3. URL = %s, Region = %s", configURL, configRegion) 95 | log.Printf("%s", config) 96 | 97 | } 98 | ``` 99 | 100 | ## Development 101 | 102 | ### Build and run unit tests 103 | 104 | make test 105 | 106 | ### CI 107 | 108 | [This library builds on Circle CI, here.](https://circleci.com/gh/zencoder/go-remote-config/) 109 | 110 | ## License 111 | 112 | [Apache License Version 2.0](LICENSE) 113 | -------------------------------------------------------------------------------- /api_endpoint_config.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import "fmt" 4 | 5 | type APIEndpointConfig struct { 6 | BasePath *string `json:"base_path,omitempty"` 7 | SubPath *string `json:"sub_path,omitempty"` 8 | } 9 | 10 | func (c APIEndpointConfig) GetFullPath() string { 11 | return fmt.Sprintf("%s/%s", *c.BasePath, *c.SubPath) 12 | } 13 | 14 | func (c APIEndpointConfig) GetFullPathWithID(id string) string { 15 | return fmt.Sprintf("%s/%s/%s", *c.BasePath, *c.SubPath, id) 16 | } 17 | -------------------------------------------------------------------------------- /api_endpoint_config_test.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | ) 11 | 12 | const ( 13 | VALID_API_ENDPOINT_BASE_PATH string = "http://basepath" 14 | VALID_API_ENDPOINT_SUB_PATH string = "sub/path" 15 | VALID_API_ENDPOINT_ID string = "testid" 16 | ) 17 | 18 | var ( 19 | VALID_API_ENDPOINT_FULL_PATH string = fmt.Sprintf("%s/%s", VALID_API_ENDPOINT_BASE_PATH, VALID_API_ENDPOINT_SUB_PATH) 20 | VALID_API_ENDPOINT_FULL_PATH_WITH_ID string = fmt.Sprintf("%s/%s/%s", VALID_API_ENDPOINT_BASE_PATH, VALID_API_ENDPOINT_SUB_PATH, VALID_API_ENDPOINT_ID) 21 | ) 22 | 23 | type APIEndpointConfigSuite struct { 24 | suite.Suite 25 | } 26 | 27 | func TestAPIEndpointConfigSuite(t *testing.T) { 28 | suite.Run(t, new(APIEndpointConfigSuite)) 29 | } 30 | 31 | func (s *APIEndpointConfigSuite) SetupSuite() { 32 | } 33 | 34 | func (s *APIEndpointConfigSuite) SetupTest() { 35 | } 36 | 37 | func (s *APIEndpointConfigSuite) TestValidate() { 38 | basePath := VALID_API_ENDPOINT_BASE_PATH 39 | subPath := VALID_API_ENDPOINT_SUB_PATH 40 | 41 | a := &APIEndpointConfig{ 42 | BasePath: &basePath, 43 | SubPath: &subPath, 44 | } 45 | 46 | err := validateConfigWithReflection(a) 47 | assert.Nil(s.T(), err) 48 | } 49 | 50 | func (s *APIEndpointConfigSuite) TestValidateErrorBasePathNotSet() { 51 | a := &APIEndpointConfig{ 52 | BasePath: nil, 53 | } 54 | 55 | err := validateConfigWithReflection(a) 56 | assert.NotNil(s.T(), err) 57 | assert.Equal(s.T(), errors.New("Field: BasePath, not set"), err) 58 | } 59 | 60 | func (s *APIEndpointConfigSuite) TestValidateErrorSubPathNotSet() { 61 | basePath := VALID_API_ENDPOINT_BASE_PATH 62 | 63 | a := &APIEndpointConfig{ 64 | BasePath: &basePath, 65 | SubPath: nil, 66 | } 67 | 68 | err := validateConfigWithReflection(a) 69 | assert.NotNil(s.T(), err) 70 | assert.Equal(s.T(), errors.New("Field: SubPath, not set"), err) 71 | } 72 | 73 | func (s *APIEndpointConfigSuite) TestGetFullPath() { 74 | basePath := VALID_API_ENDPOINT_BASE_PATH 75 | subPath := VALID_API_ENDPOINT_SUB_PATH 76 | 77 | a := &APIEndpointConfig{ 78 | BasePath: &basePath, 79 | SubPath: &subPath, 80 | } 81 | 82 | fullPath := a.GetFullPath() 83 | assert.Equal(s.T(), VALID_API_ENDPOINT_FULL_PATH, fullPath) 84 | } 85 | 86 | func (s *APIEndpointConfigSuite) TestGetFullPathWithID() { 87 | basePath := VALID_API_ENDPOINT_BASE_PATH 88 | subPath := VALID_API_ENDPOINT_SUB_PATH 89 | 90 | a := &APIEndpointConfig{ 91 | BasePath: &basePath, 92 | SubPath: &subPath, 93 | } 94 | 95 | fullPathWithID := a.GetFullPathWithID(VALID_API_ENDPOINT_ID) 96 | assert.Equal(s.T(), VALID_API_ENDPOINT_FULL_PATH_WITH_ID, fullPathWithID) 97 | } 98 | -------------------------------------------------------------------------------- /aws_region.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import "errors" 4 | 5 | type AWSRegion string 6 | 7 | const ( 8 | AWS_REGION_US_EAST_1 AWSRegion = "us-east-1" 9 | AWS_REGION_US_WEST_1 AWSRegion = "us-west-1" 10 | AWS_REGION_US_WEST_2 AWSRegion = "us-west-2" 11 | AWS_REGION_US_GOV_WEST_1 AWSRegion = "us-gov-west-1" 12 | AWS_REGION_EU_WEST_1 AWSRegion = "eu-west-1" 13 | AWS_REGION_EU_CENTRAL_1 AWSRegion = "eu-central-1" 14 | AWS_REGION_AP_SOUTHEAST_1 AWSRegion = "ap-southeast-1" 15 | AWS_REGION_AP_SOUTHEAST_2 AWSRegion = "ap-southeast-2" 16 | AWS_REGION_AP_NORTHEAST_1 AWSRegion = "ap-northeast-1" 17 | AWS_REGION_SA_EAST_1 AWSRegion = "sa-east-1" 18 | ) 19 | 20 | var AWSRegions = []AWSRegion{ 21 | AWS_REGION_US_EAST_1, 22 | AWS_REGION_US_WEST_1, 23 | AWS_REGION_US_WEST_2, 24 | AWS_REGION_US_GOV_WEST_1, 25 | AWS_REGION_EU_WEST_1, 26 | AWS_REGION_EU_CENTRAL_1, 27 | AWS_REGION_AP_SOUTHEAST_1, 28 | AWS_REGION_AP_SOUTHEAST_2, 29 | AWS_REGION_AP_NORTHEAST_1, 30 | AWS_REGION_SA_EAST_1, 31 | } 32 | 33 | var ( 34 | ErrAWSRegionEmptyString = errors.New("Region cannot be empty") 35 | ErrAWSRegionInvalid = errors.New("Region is invalid") 36 | ) 37 | 38 | func (r *AWSRegion) UnmarshalText(data []byte) error { 39 | rString := string(data[:]) 40 | *r = (AWSRegion)(rString) 41 | return r.Validate() 42 | } 43 | 44 | func (r AWSRegion) Validate() error { 45 | if r == "" { 46 | return ErrAWSRegionEmptyString 47 | } 48 | 49 | if r != AWS_REGION_US_EAST_1 && r != AWS_REGION_US_WEST_1 && r != AWS_REGION_US_WEST_2 && r != AWS_REGION_US_GOV_WEST_1 && 50 | r != AWS_REGION_EU_WEST_1 && r != AWS_REGION_EU_CENTRAL_1 && 51 | r != AWS_REGION_AP_SOUTHEAST_1 && r != AWS_REGION_AP_SOUTHEAST_2 && r != AWS_REGION_AP_NORTHEAST_1 && 52 | r != AWS_REGION_SA_EAST_1 { 53 | return ErrAWSRegionInvalid 54 | } 55 | 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /aws_region_test.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/suite" 8 | ) 9 | 10 | type AWSRegionSuite struct { 11 | suite.Suite 12 | } 13 | 14 | func TestAWSRegionSuite(t *testing.T) { 15 | suite.Run(t, new(AWSRegionSuite)) 16 | } 17 | 18 | func (s *AWSRegionSuite) SetupSuite() { 19 | } 20 | 21 | func (s *AWSRegionSuite) SetupTest() { 22 | } 23 | 24 | func (s *AWSRegionSuite) TestValidateUSEast1() { 25 | r := AWS_REGION_US_EAST_1 26 | err := r.Validate() 27 | assert.Nil(s.T(), err) 28 | } 29 | 30 | func (s *AWSRegionSuite) TestValidateErrorNoRegion() { 31 | r := AWSRegion("") 32 | err := r.Validate() 33 | assert.NotNil(s.T(), err) 34 | assert.Equal(s.T(), ErrAWSRegionEmptyString, err) 35 | } 36 | 37 | func (s *AWSRegionSuite) TestValidateErrorInvalidRegion() { 38 | r := AWSRegion("invalidregion") 39 | err := r.Validate() 40 | assert.NotNil(s.T(), err) 41 | assert.Equal(s.T(), ErrAWSRegionInvalid, err) 42 | } 43 | 44 | func (s *AWSRegionSuite) TestUnmarshalText() { 45 | r := AWSRegion("") 46 | d := []byte(AWS_REGION_US_EAST_1) 47 | err := r.UnmarshalText(d) 48 | assert.Nil(s.T(), err) 49 | assert.Equal(s.T(), AWS_REGION_US_EAST_1, r) 50 | } 51 | 52 | func (s *AWSRegionSuite) TestUnmarshalErrorNoRegion() { 53 | r := AWSRegion("") 54 | d := []byte("") 55 | err := r.UnmarshalText(d) 56 | assert.NotNil(s.T(), err) 57 | assert.Equal(s.T(), ErrAWSRegionEmptyString, err) 58 | } 59 | 60 | func (s *AWSRegionSuite) TestUnmarshalErrorInvalidRegion() { 61 | r := AWSRegion("") 62 | d := []byte("invalidregion") 63 | err := r.UnmarshalText(d) 64 | assert.NotNil(s.T(), err) 65 | assert.Equal(s.T(), ErrAWSRegionInvalid, err) 66 | } 67 | -------------------------------------------------------------------------------- /dynamodb_client_config.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | type DynamoDBClientConfig struct { 4 | Region *AWSRegion `json:"region,omitempty"` 5 | Endpoint *string `json:"endpoint,omitempty" remoteconfig:"optional"` 6 | DisableSSL *bool `json:"disable_ssl,omit" remoteconfig:"optional"` 7 | } 8 | 9 | func (d DynamoDBClientConfig) GetRegion() AWSRegion { 10 | return *d.Region 11 | } 12 | 13 | func (d DynamoDBClientConfig) GetEndpoint() string { 14 | if d.Endpoint != nil { 15 | return *d.Endpoint 16 | } 17 | return "" 18 | } 19 | 20 | func (d DynamoDBClientConfig) GetDisableSSL() bool { 21 | if d.DisableSSL != nil { 22 | return *d.DisableSSL 23 | } 24 | return false 25 | } 26 | -------------------------------------------------------------------------------- /dynamodb_client_config_test.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/suite" 9 | ) 10 | 11 | const ( 12 | VALID_DYNAMODB_CLIENT_REGION AWSRegion = AWS_REGION_US_EAST_1 13 | VALID_DYNAMODB_CLIENT_ENDPOINT string = "http://localhost:8000/dynamodb" 14 | VALID_DYNAMODB_CLIENT_DISABLE_SSL bool = true 15 | ) 16 | 17 | type DynamoDBClientConfigSuite struct { 18 | suite.Suite 19 | } 20 | 21 | func TestDynamoDBClientConfigSuite(t *testing.T) { 22 | suite.Run(t, new(DynamoDBClientConfigSuite)) 23 | } 24 | 25 | func (s *DynamoDBClientConfigSuite) SetupSuite() { 26 | } 27 | 28 | func (s *DynamoDBClientConfigSuite) SetupTest() { 29 | } 30 | 31 | func (s *DynamoDBClientConfigSuite) TestValidate() { 32 | region := VALID_DYNAMODB_CLIENT_REGION 33 | endpoint := VALID_DYNAMODB_CLIENT_ENDPOINT 34 | 35 | d := &DynamoDBClientConfig{ 36 | Region: ®ion, 37 | Endpoint: &endpoint, 38 | } 39 | 40 | err := validateConfigWithReflection(d) 41 | assert.Nil(s.T(), err) 42 | } 43 | 44 | func (s *DynamoDBClientConfigSuite) TestValidateWithoutEndpoint() { 45 | region := VALID_DYNAMODB_CLIENT_REGION 46 | 47 | d := &DynamoDBClientConfig{ 48 | Region: ®ion, 49 | } 50 | 51 | err := validateConfigWithReflection(d) 52 | assert.Nil(s.T(), err) 53 | } 54 | 55 | func (s *DynamoDBClientConfigSuite) TestValidateErrorEmptyEndpoint() { 56 | region := VALID_DYNAMODB_CLIENT_REGION 57 | endpoint := "" 58 | 59 | d := &DynamoDBClientConfig{ 60 | Region: ®ion, 61 | Endpoint: &endpoint, 62 | } 63 | 64 | err := validateConfigWithReflection(d) 65 | assert.NotNil(s.T(), err) 66 | assert.Equal(s.T(), errors.New("String Field: Endpoint, contains an empty string"), err) 67 | } 68 | 69 | func (s *DynamoDBClientConfigSuite) TestValidateErrorRegion() { 70 | region := AWSRegion("") 71 | 72 | d := &DynamoDBClientConfig{ 73 | Region: ®ion, 74 | } 75 | 76 | err := validateConfigWithReflection(d) 77 | assert.NotNil(s.T(), err) 78 | assert.Equal(s.T(), errors.New("Validater Field: Region, failed to validate with error, Region cannot be empty"), err) 79 | } 80 | 81 | func (s *DynamoDBClientConfigSuite) TestGetRegion() { 82 | region := VALID_DYNAMODB_CLIENT_REGION 83 | 84 | d := &DynamoDBClientConfig{ 85 | Region: ®ion, 86 | } 87 | 88 | assert.Equal(s.T(), VALID_DYNAMODB_CLIENT_REGION, d.GetRegion()) 89 | } 90 | 91 | func (s *DynamoDBClientConfigSuite) TestGetEndpoint() { 92 | region := VALID_DYNAMODB_CLIENT_REGION 93 | endpoint := VALID_DYNAMODB_CLIENT_ENDPOINT 94 | 95 | d := &DynamoDBClientConfig{ 96 | Region: ®ion, 97 | Endpoint: &endpoint, 98 | } 99 | 100 | assert.Equal(s.T(), VALID_DYNAMODB_CLIENT_ENDPOINT, d.GetEndpoint()) 101 | } 102 | 103 | func (s *DynamoDBClientConfigSuite) TestGetEndpointNotSet() { 104 | region := VALID_DYNAMODB_CLIENT_REGION 105 | 106 | d := &DynamoDBClientConfig{ 107 | Region: ®ion, 108 | Endpoint: nil, 109 | } 110 | 111 | assert.Equal(s.T(), "", d.GetEndpoint()) 112 | } 113 | 114 | func (s *DynamoDBClientConfigSuite) TestGetDisableSSL() { 115 | region := VALID_DYNAMODB_CLIENT_REGION 116 | endpoint := VALID_DYNAMODB_CLIENT_ENDPOINT 117 | disableSSL := VALID_DYNAMODB_CLIENT_DISABLE_SSL 118 | 119 | d := &DynamoDBClientConfig{ 120 | Region: ®ion, 121 | Endpoint: &endpoint, 122 | DisableSSL: &disableSSL, 123 | } 124 | 125 | assert.Equal(s.T(), VALID_DYNAMODB_CLIENT_DISABLE_SSL, d.GetDisableSSL()) 126 | } 127 | 128 | func (s *DynamoDBClientConfigSuite) TestGetDisableSSLNotSet() { 129 | region := VALID_DYNAMODB_CLIENT_REGION 130 | endpoint := VALID_DYNAMODB_CLIENT_ENDPOINT 131 | 132 | d := &DynamoDBClientConfig{ 133 | Region: ®ion, 134 | Endpoint: &endpoint, 135 | DisableSSL: nil, 136 | } 137 | 138 | assert.Equal(s.T(), false, d.GetDisableSSL()) 139 | } 140 | -------------------------------------------------------------------------------- /dynamodb_table_config.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | type DynamoDBTableConfig struct { 4 | TableName *string `mapstructure:"table_name" json:"table_name,omitempty"` 5 | } 6 | 7 | func (d DynamoDBTableConfig) GetTableName() string { 8 | return *d.TableName 9 | } 10 | -------------------------------------------------------------------------------- /dynamodb_table_config_test.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/suite" 9 | ) 10 | 11 | const ( 12 | VALID_DYNAMODB_TABLE_TABLENAME string = "testTable" 13 | ) 14 | 15 | type DynamoDBTableConfigSuite struct { 16 | suite.Suite 17 | } 18 | 19 | func TestDynamoDBTableConfigSuite(t *testing.T) { 20 | suite.Run(t, new(DynamoDBTableConfigSuite)) 21 | } 22 | 23 | func (s *DynamoDBTableConfigSuite) SetupSuite() { 24 | } 25 | 26 | func (s *DynamoDBTableConfigSuite) SetupTest() { 27 | } 28 | 29 | func (s *DynamoDBTableConfigSuite) TestValidate() { 30 | tableName := VALID_DYNAMODB_TABLE_TABLENAME 31 | 32 | d := &DynamoDBTableConfig{ 33 | TableName: &tableName, 34 | } 35 | 36 | err := validateConfigWithReflection(d) 37 | assert.Nil(s.T(), err) 38 | } 39 | 40 | func (s *DynamoDBTableConfigSuite) TestValidateErrorTableName() { 41 | tableName := "" 42 | 43 | d := &DynamoDBTableConfig{ 44 | TableName: &tableName, 45 | } 46 | 47 | err := validateConfigWithReflection(d) 48 | assert.NotNil(s.T(), err) 49 | assert.Equal(s.T(), errors.New("String Field: TableName, contains an empty string"), err) 50 | } 51 | 52 | func (s *DynamoDBTableConfigSuite) TestGetTableName() { 53 | tableName := VALID_DYNAMODB_TABLE_TABLENAME 54 | 55 | d := &DynamoDBTableConfig{ 56 | TableName: &tableName, 57 | } 58 | 59 | assert.Equal(s.T(), VALID_DYNAMODB_TABLE_TABLENAME, d.GetTableName()) 60 | } 61 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zencoder/go-remote-config 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 7 | github.com/kr/text v0.2.0 // indirect 8 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 9 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 10 | github.com/stretchr/testify v1.8.4 11 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 5 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 7 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 8 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 9 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 10 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 11 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 12 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 14 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 16 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 17 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 18 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 19 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 20 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 21 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 22 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 23 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 24 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 25 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 26 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 27 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 28 | -------------------------------------------------------------------------------- /remoteconfig.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "reflect" 9 | "strings" 10 | ) 11 | 12 | const ( 13 | DEFAULT_S3_EXPIRY uint = 60 14 | DEFAULT_S3_ENDPOINT string = "" 15 | ) 16 | 17 | type Validater interface { 18 | Validate() error 19 | } 20 | 21 | // Downloads a configuration JSON file from S3. 22 | // Parses it to a particular struct type and runs a validation. 23 | // URL should be of the format s3://bucket/path/file.json 24 | func LoadConfigFromURL(configURL string, configStruct interface{}) error { 25 | resp, err := http.Get(configURL) 26 | if err != nil { 27 | return err 28 | } 29 | defer resp.Body.Close() 30 | 31 | if resp.StatusCode != http.StatusOK { 32 | return fmt.Errorf("Request to '%s' returned non-200 OK status '%d: %s'", configURL, resp.StatusCode, http.StatusText(resp.StatusCode)) 33 | } 34 | 35 | return ReadJSONValidate(resp.Body, configStruct) 36 | } 37 | 38 | // Downloads JSON from a URL, decodes it and then validates. 39 | func ReadJSONValidate(cfgReader io.Reader, configStruct interface{}) error { 40 | // Do a streaming JSON decode 41 | dec := json.NewDecoder(cfgReader) 42 | if err := dec.Decode(configStruct); err != nil { 43 | return fmt.Errorf("Failed to decode JSON, with error, %s", err.Error()) 44 | } 45 | 46 | // Run validation on the config 47 | if err := validateConfigWithReflection(configStruct); err != nil { 48 | return err 49 | } 50 | 51 | return nil 52 | } 53 | 54 | func isNilFixed(v reflect.Value) bool { 55 | switch v.Kind() { 56 | case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice, reflect.Func: 57 | // use of IsNil method 58 | return v.IsNil() 59 | } 60 | return false 61 | } 62 | 63 | // Validates a configuration struct. 64 | // Uses reflection to determine and call the correct Validation methods for each type. 65 | func validateConfigWithReflection(c interface{}) error { 66 | valueElem := reflect.ValueOf(c).Elem() 67 | typeElem := reflect.TypeOf(c).Elem() 68 | 69 | // Gets a refection Type value for the Validater interface 70 | validaterType := reflect.TypeOf((*Validater)(nil)).Elem() 71 | 72 | // If the Validater interface is implemented, call the Validate method 73 | if typeElem.Implements(validaterType) { 74 | if err := valueElem.Interface().(Validater).Validate(); err != nil { 75 | return fmt.Errorf("Validater Field: %s, failed to validate with error, %s", typeElem.Name(), err) 76 | } 77 | } 78 | 79 | for i := 0; i < valueElem.NumField(); i++ { 80 | valueField := valueElem.Field(i) 81 | typeField := typeElem.Field(i) 82 | 83 | tags := typeField.Tag.Get("remoteconfig") 84 | optional := strings.Contains(tags, "optional") 85 | 86 | if valueField.Kind() == reflect.Struct && typeField.Anonymous { 87 | continue 88 | } 89 | 90 | if isNilFixed(valueField) && !optional { 91 | return fmt.Errorf("Field: %s, not set", typeField.Name) 92 | } else if isNilFixed(valueField) && optional { 93 | continue 94 | } 95 | 96 | // Handle a slice type 97 | if valueField.Kind() == reflect.Slice { 98 | if valueField.Len() <= 0 { 99 | return fmt.Errorf("Slice Field: %s, is empty", typeField.Name) 100 | } 101 | for i := 0; i < valueField.Len(); i++ { 102 | sliceValue := valueField.Index(i) 103 | if sliceValue.Kind() != reflect.Ptr || sliceValue.IsNil() || sliceValue.Elem().Kind() != reflect.Struct { 104 | continue 105 | } 106 | if err := validateConfigWithReflection(sliceValue.Interface()); err != nil { 107 | return err 108 | } 109 | } 110 | continue 111 | } 112 | 113 | // Handle a map type 114 | if valueField.Kind() == reflect.Map { 115 | for _, key := range valueField.MapKeys() { 116 | mapValue := valueField.MapIndex(key) 117 | if mapValue.Kind() != reflect.Ptr || mapValue.IsNil() || mapValue.Elem().Kind() != reflect.Struct { 118 | continue 119 | } 120 | if err := validateConfigWithReflection(mapValue.Interface()); err != nil { 121 | return fmt.Errorf("Sub field of %s with key '%s' failed to validated with error, %s", typeField.Name, key, err) 122 | } 123 | } 124 | continue 125 | } 126 | 127 | // If this is a string pointer field, check that it isn't empty (unless optional) 128 | if s, ok := valueField.Interface().(*string); ok { 129 | if *s == "" { 130 | return fmt.Errorf("String Field: %s, contains an empty string", typeField.Name) 131 | } 132 | continue 133 | } 134 | 135 | // If this is a string field, check that it isn't empty (unless optional) 136 | if s, ok := valueField.Interface().(string); ok { 137 | if s == "" { 138 | return fmt.Errorf("String Field: %s, contains an empty string", typeField.Name) 139 | } 140 | continue 141 | } 142 | 143 | // If the Validater interface is implemented, call the Validate method 144 | if typeField.Type.Implements(validaterType) { 145 | if err := valueField.Interface().(Validater).Validate(); err != nil { 146 | return fmt.Errorf("Validater Field: %s, failed to validate with error, %s", typeField.Name, err) 147 | } 148 | continue 149 | } 150 | 151 | // If this field is a struct type, validate it with reflection 152 | // We can/should only check the sub-fields of a Struct 153 | if valueField.Elem().Kind() == reflect.Struct && valueField.Elem().NumField() > 0 { 154 | if err := validateConfigWithReflection(valueField.Interface()); err != nil { 155 | return fmt.Errorf("Sub Field of %s, failed to validate with error, %s", typeField.Name, err) 156 | } 157 | } 158 | } 159 | 160 | return nil 161 | } 162 | -------------------------------------------------------------------------------- /remoteconfig_test.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | "net/http/httptest" 9 | "regexp" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | "github.com/stretchr/testify/suite" 16 | ) 17 | 18 | const ( 19 | VALID_REMOTE_CONFIG_SQS_REGION AWSRegion = AWS_REGION_US_EAST_1 20 | VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID string = "345833302425" 21 | VALID_REMOTE_CONFIG_SQS_QUEUE_NAME string = "testQueue" 22 | VALID_REMOTE_CONFIG_DYNAMODB_CLIENT_REGION AWSRegion = AWS_REGION_US_EAST_1 23 | VALID_REMOTE_CONFIG_DYNAMODB_TABLE_NAME string = "testTable" 24 | VALID_REMOTE_CONFIG_STORAGE_CONFIG_PROVIDER StorageProvider = STORAGE_PROVIDER_AWS 25 | VALID_REMOTE_CONFIG_STORAGE_CONFIG_LOCATION StorageLocation = (StorageLocation)(AWS_REGION_US_WEST_2) 26 | ) 27 | 28 | type RemoteConfigSuite struct { 29 | suite.Suite 30 | } 31 | 32 | func TestRemoteConfigSuite(t *testing.T) { 33 | suite.Run(t, new(RemoteConfigSuite)) 34 | } 35 | 36 | type EmbeddedConfig struct { 37 | EmbeddedStr *string `json:"embedded_string,omitempty"` 38 | EmbeddedInt *int64 `json:"embedded_int,omitempty"` 39 | } 40 | 41 | type SampleConfig struct { 42 | EmbeddedConfig 43 | SQSQueueOptional *SQSQueueConfig `json:"sqs_queue_optional,omitempty" remoteconfig:"optional"` 44 | SQSClientOptional *SQSClientConfig `json:"sqs_client_optional,omitempty" remoteconfig:"optional"` 45 | DynamoDBTableOptional *DynamoDBTableConfig `json:"dynamodb_table_optional,omitempty" remoteconfig:"optional"` 46 | DynamoDBClientOptional *DynamoDBClientConfig `json:"dynamodb_client_optional,omitempty" remoteconfig:"optional"` 47 | StrOptional *string `json:"str_optional,omitempty" remoteconfig:"optional"` 48 | StorageConfigOptional *StorageConfig `json:"storage_config_optional,omitempty" remoteconfig:"optional"` 49 | StorageConfigSliceOptional []*StorageConfig `json:"storage_config_slice_optional,omitempty" remoteconfig:"optional"` 50 | SQSQueue *SQSQueueConfig `json:"sqs_queue,omitempty"` 51 | SQSClient *SQSClientConfig `json:"sqs_client,omitempty"` 52 | DynamoDBTable *DynamoDBTableConfig `json:"dynamodb_table,omitempty"` 53 | DynamoDBClient *DynamoDBClientConfig `json:"dynamodb_client,omitempty"` 54 | Str string `json:"str,omitempty"` 55 | StrPointer *string `json:"str_pointer,omitempty"` 56 | StorageConfig *StorageConfig `json:"storage_config,omitempty"` 57 | StorageConfigSlice []*StorageConfig `json:"storage_config_slice,omitempty"` 58 | StorageConfigMap map[string]*StorageConfig `json:"storage_config_map,omitempty"` 59 | StrSlice []*string `json:"str_slice,omitempty" remoteconfig:"optional"` 60 | MapStrStr map[string]*string `json:"map_str_str,omitempty"` 61 | } 62 | 63 | var validConfigJSON = ` 64 | { 65 | "embedded_string": "abc", 66 | "embedded_int": 123, 67 | "sqs_client" : { 68 | "region" : "us-east-1", 69 | "endpoint" : "http://localhost:3000/sqs" 70 | }, 71 | "sqs_queue" : { 72 | "region" : "us-east-1", 73 | "aws_account_id" : "345833302425", 74 | "queue_name" : "testQueue" 75 | }, 76 | "dynamodb_client" : { 77 | "region" : "us-east-1", 78 | "endpoint" : "http://localhost:8000/dynamodb" 79 | }, 80 | "dynamodb_table" : { 81 | "table_name" : "testTable" 82 | }, 83 | "str" : "testStr", 84 | "str_pointer" : "testStr", 85 | "storage_config" : { 86 | "provider" : "aws", 87 | "location" : "us-west-2" 88 | }, 89 | "storage_config_slice" : [{ 90 | "provider" : "aws", 91 | "location" : "us-west-2" 92 | }, 93 | { 94 | "provider" : "aws", 95 | "location" : "us-east-1" 96 | }], 97 | "storage_config_map": { 98 | "one": { 99 | "provider": "aws", 100 | "location": "us-west-2" 101 | } 102 | }, 103 | "str_slice": [ "hello" ], 104 | "map_str_str": { "key": "value" } 105 | }` 106 | 107 | func (s *RemoteConfigSuite) TestValidateConfigWithReflection() { 108 | c := s.buildValidSampleConfig() 109 | err := validateConfigWithReflection(c) 110 | s.Nil(err) 111 | } 112 | 113 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionWithOptional() { 114 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 115 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 116 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 117 | sqsQueue := &SQSQueueConfig{ 118 | Region: &sqsRegion, 119 | AWSAccountID: &sqsAWSAccountID, 120 | QueueName: &sqsQueueName, 121 | } 122 | sqsClient := &SQSClientConfig{ 123 | Region: &sqsRegion, 124 | } 125 | 126 | dynamodbTableName := VALID_REMOTE_CONFIG_DYNAMODB_TABLE_NAME 127 | dynamodbTable := &DynamoDBTableConfig{ 128 | TableName: &dynamodbTableName, 129 | } 130 | 131 | dynamodbClientRegion := VALID_REMOTE_CONFIG_DYNAMODB_CLIENT_REGION 132 | dynamodbClient := &DynamoDBClientConfig{ 133 | Region: &dynamodbClientRegion, 134 | } 135 | 136 | str := "testString" 137 | 138 | storageProvider := VALID_REMOTE_CONFIG_STORAGE_CONFIG_PROVIDER 139 | storageLocation := VALID_REMOTE_CONFIG_STORAGE_CONFIG_LOCATION 140 | storageConfig := &StorageConfig{ 141 | Provider: &storageProvider, 142 | Location: &storageLocation, 143 | } 144 | 145 | c := &SampleConfig{ 146 | SQSQueueOptional: sqsQueue, 147 | SQSClientOptional: sqsClient, 148 | DynamoDBTableOptional: dynamodbTable, 149 | DynamoDBClientOptional: dynamodbClient, 150 | StrOptional: &str, 151 | StorageConfigOptional: storageConfig, 152 | StorageConfigSliceOptional: []*StorageConfig{storageConfig}, 153 | SQSQueue: sqsQueue, 154 | SQSClient: sqsClient, 155 | DynamoDBTable: dynamodbTable, 156 | DynamoDBClient: dynamodbClient, 157 | Str: str, 158 | StrPointer: &str, 159 | StorageConfig: storageConfig, 160 | StorageConfigSlice: []*StorageConfig{storageConfig}, 161 | StorageConfigMap: map[string]*StorageConfig{"one": storageConfig}, 162 | MapStrStr: map[string]*string{"hello": &str}, 163 | } 164 | 165 | err := validateConfigWithReflection(c) 166 | assert.Nil(s.T(), err) 167 | } 168 | 169 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorSQSQueueConfigNotSet() { 170 | c := &SampleConfig{ 171 | SQSQueue: nil, 172 | } 173 | err := validateConfigWithReflection(c) 174 | assert.NotNil(s.T(), err) 175 | assert.Equal(s.T(), errors.New("Field: SQSQueue, not set"), err) 176 | } 177 | 178 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorSQSClientConfigNotSet() { 179 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 180 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 181 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 182 | sqsQueue := &SQSQueueConfig{ 183 | Region: &sqsRegion, 184 | AWSAccountID: &sqsAWSAccountID, 185 | QueueName: &sqsQueueName, 186 | } 187 | c := &SampleConfig{ 188 | SQSQueue: sqsQueue, 189 | SQSClient: nil, 190 | } 191 | err := validateConfigWithReflection(c) 192 | assert.NotNil(s.T(), err) 193 | assert.Equal(s.T(), errors.New("Field: SQSClient, not set"), err) 194 | } 195 | 196 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorSQSQueueConfigValidate() { 197 | sqsRegion := AWSRegion("invalidregion") 198 | sqsQueue := &SQSQueueConfig{ 199 | Region: &sqsRegion, 200 | } 201 | c := &SampleConfig{ 202 | SQSQueue: sqsQueue, 203 | } 204 | err := validateConfigWithReflection(c) 205 | assert.NotNil(s.T(), err) 206 | assert.Equal(s.T(), errors.New("Sub Field of SQSQueue, failed to validate with error, Validater Field: Region, failed to validate with error, Region is invalid"), err) 207 | } 208 | 209 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorDynamoDBTableConfigNotSet() { 210 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 211 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 212 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 213 | sqsQueue := &SQSQueueConfig{ 214 | Region: &sqsRegion, 215 | AWSAccountID: &sqsAWSAccountID, 216 | QueueName: &sqsQueueName, 217 | } 218 | sqsClient := &SQSClientConfig{ 219 | Region: &sqsRegion, 220 | } 221 | 222 | c := &SampleConfig{ 223 | SQSQueue: sqsQueue, 224 | SQSClient: sqsClient, 225 | DynamoDBTable: nil, 226 | } 227 | 228 | err := validateConfigWithReflection(c) 229 | assert.NotNil(s.T(), err) 230 | assert.Equal(s.T(), errors.New("Field: DynamoDBTable, not set"), err) 231 | } 232 | 233 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorDynamoDBClientConfigNotSet() { 234 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 235 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 236 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 237 | sqsQueue := &SQSQueueConfig{ 238 | Region: &sqsRegion, 239 | AWSAccountID: &sqsAWSAccountID, 240 | QueueName: &sqsQueueName, 241 | } 242 | sqsClient := &SQSClientConfig{ 243 | Region: &sqsRegion, 244 | } 245 | 246 | dynamodbTableName := VALID_REMOTE_CONFIG_DYNAMODB_TABLE_NAME 247 | dynamodbTable := &DynamoDBTableConfig{ 248 | TableName: &dynamodbTableName, 249 | } 250 | 251 | c := &SampleConfig{ 252 | SQSQueue: sqsQueue, 253 | SQSClient: sqsClient, 254 | DynamoDBTable: dynamodbTable, 255 | DynamoDBClient: nil, 256 | } 257 | 258 | err := validateConfigWithReflection(c) 259 | assert.NotNil(s.T(), err) 260 | assert.Equal(s.T(), errors.New("Field: DynamoDBClient, not set"), err) 261 | } 262 | 263 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorDynamoDBClientConfigValidate() { 264 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 265 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 266 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 267 | sqsQueue := &SQSQueueConfig{ 268 | Region: &sqsRegion, 269 | AWSAccountID: &sqsAWSAccountID, 270 | QueueName: &sqsQueueName, 271 | } 272 | sqsClient := &SQSClientConfig{ 273 | Region: &sqsRegion, 274 | } 275 | 276 | dynamodbTableName := VALID_REMOTE_CONFIG_DYNAMODB_TABLE_NAME 277 | dynamodbTable := &DynamoDBTableConfig{ 278 | TableName: &dynamodbTableName, 279 | } 280 | 281 | dynamodbRegion := AWSRegion("invalidregion") 282 | dynamodbClient := &DynamoDBClientConfig{ 283 | Region: &dynamodbRegion, 284 | } 285 | 286 | c := &SampleConfig{ 287 | SQSQueue: sqsQueue, 288 | SQSClient: sqsClient, 289 | DynamoDBTable: dynamodbTable, 290 | DynamoDBClient: dynamodbClient, 291 | } 292 | 293 | err := validateConfigWithReflection(c) 294 | assert.NotNil(s.T(), err) 295 | assert.Equal(s.T(), errors.New("Sub Field of DynamoDBClient, failed to validate with error, Validater Field: Region, failed to validate with error, Region is invalid"), err) 296 | } 297 | 298 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorDynamoDBTableConfigValidate() { 299 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 300 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 301 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 302 | sqsQueue := &SQSQueueConfig{ 303 | Region: &sqsRegion, 304 | AWSAccountID: &sqsAWSAccountID, 305 | QueueName: &sqsQueueName, 306 | } 307 | sqsClient := &SQSClientConfig{ 308 | Region: &sqsRegion, 309 | } 310 | 311 | dynamodbTableName := "" 312 | dynamodbTable := &DynamoDBTableConfig{ 313 | TableName: &dynamodbTableName, 314 | } 315 | 316 | c := &SampleConfig{ 317 | SQSQueue: sqsQueue, 318 | SQSClient: sqsClient, 319 | DynamoDBTable: dynamodbTable, 320 | } 321 | 322 | err := validateConfigWithReflection(c) 323 | assert.NotNil(s.T(), err) 324 | assert.Equal(s.T(), errors.New("Sub Field of DynamoDBTable, failed to validate with error, String Field: TableName, contains an empty string"), err) 325 | } 326 | 327 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorStrNotSet() { 328 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 329 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 330 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 331 | sqsQueue := &SQSQueueConfig{ 332 | Region: &sqsRegion, 333 | AWSAccountID: &sqsAWSAccountID, 334 | QueueName: &sqsQueueName, 335 | } 336 | sqsClient := &SQSClientConfig{ 337 | Region: &sqsRegion, 338 | } 339 | 340 | dynamodbTableName := VALID_REMOTE_CONFIG_DYNAMODB_TABLE_NAME 341 | dynamodbTable := &DynamoDBTableConfig{ 342 | TableName: &dynamodbTableName, 343 | } 344 | 345 | dynamodbClientRegion := VALID_REMOTE_CONFIG_DYNAMODB_CLIENT_REGION 346 | dynamodbClient := &DynamoDBClientConfig{ 347 | Region: &dynamodbClientRegion, 348 | } 349 | 350 | c := &SampleConfig{ 351 | SQSQueue: sqsQueue, 352 | SQSClient: sqsClient, 353 | DynamoDBTable: dynamodbTable, 354 | DynamoDBClient: dynamodbClient, 355 | Str: "testString", 356 | StrPointer: nil, 357 | } 358 | 359 | err := validateConfigWithReflection(c) 360 | assert.NotNil(s.T(), err) 361 | assert.Equal(s.T(), errors.New("Field: StrPointer, not set"), err) 362 | } 363 | 364 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorStrPointerEmpty() { 365 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 366 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 367 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 368 | sqsQueue := &SQSQueueConfig{ 369 | Region: &sqsRegion, 370 | AWSAccountID: &sqsAWSAccountID, 371 | QueueName: &sqsQueueName, 372 | } 373 | sqsClient := &SQSClientConfig{ 374 | Region: &sqsRegion, 375 | } 376 | 377 | dynamodbTableName := VALID_REMOTE_CONFIG_DYNAMODB_TABLE_NAME 378 | dynamodbTable := &DynamoDBTableConfig{ 379 | TableName: &dynamodbTableName, 380 | } 381 | 382 | dynamodbClientRegion := VALID_REMOTE_CONFIG_DYNAMODB_CLIENT_REGION 383 | dynamodbClient := &DynamoDBClientConfig{ 384 | Region: &dynamodbClientRegion, 385 | } 386 | 387 | str := "" 388 | 389 | c := &SampleConfig{ 390 | SQSQueue: sqsQueue, 391 | SQSClient: sqsClient, 392 | DynamoDBTable: dynamodbTable, 393 | DynamoDBClient: dynamodbClient, 394 | Str: "testString", 395 | StrPointer: &str, 396 | } 397 | 398 | err := validateConfigWithReflection(c) 399 | assert.NotNil(s.T(), err) 400 | assert.Equal(s.T(), errors.New("String Field: StrPointer, contains an empty string"), err) 401 | } 402 | 403 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorStrEmpty() { 404 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 405 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 406 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 407 | sqsQueue := &SQSQueueConfig{ 408 | Region: &sqsRegion, 409 | AWSAccountID: &sqsAWSAccountID, 410 | QueueName: &sqsQueueName, 411 | } 412 | sqsClient := &SQSClientConfig{ 413 | Region: &sqsRegion, 414 | } 415 | 416 | dynamodbTableName := VALID_REMOTE_CONFIG_DYNAMODB_TABLE_NAME 417 | dynamodbTable := &DynamoDBTableConfig{ 418 | TableName: &dynamodbTableName, 419 | } 420 | 421 | dynamodbClientRegion := VALID_REMOTE_CONFIG_DYNAMODB_CLIENT_REGION 422 | dynamodbClient := &DynamoDBClientConfig{ 423 | Region: &dynamodbClientRegion, 424 | } 425 | 426 | str := "" 427 | 428 | c := &SampleConfig{ 429 | SQSQueue: sqsQueue, 430 | SQSClient: sqsClient, 431 | DynamoDBTable: dynamodbTable, 432 | DynamoDBClient: dynamodbClient, 433 | Str: str, 434 | } 435 | 436 | err := validateConfigWithReflection(c) 437 | assert.NotNil(s.T(), err) 438 | assert.Equal(s.T(), errors.New("String Field: Str, contains an empty string"), err) 439 | } 440 | 441 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorStorageConfigNotSet() { 442 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 443 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 444 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 445 | sqsQueue := &SQSQueueConfig{ 446 | Region: &sqsRegion, 447 | AWSAccountID: &sqsAWSAccountID, 448 | QueueName: &sqsQueueName, 449 | } 450 | sqsClient := &SQSClientConfig{ 451 | Region: &sqsRegion, 452 | } 453 | 454 | dynamodbTableName := VALID_REMOTE_CONFIG_DYNAMODB_TABLE_NAME 455 | dynamodbTable := &DynamoDBTableConfig{ 456 | TableName: &dynamodbTableName, 457 | } 458 | 459 | dynamodbClientRegion := VALID_REMOTE_CONFIG_DYNAMODB_CLIENT_REGION 460 | dynamodbClient := &DynamoDBClientConfig{ 461 | Region: &dynamodbClientRegion, 462 | } 463 | 464 | str := "testString" 465 | 466 | c := &SampleConfig{ 467 | SQSQueue: sqsQueue, 468 | SQSClient: sqsClient, 469 | DynamoDBTable: dynamodbTable, 470 | DynamoDBClient: dynamodbClient, 471 | Str: str, 472 | StrPointer: &str, 473 | StorageConfig: nil, 474 | } 475 | 476 | err := validateConfigWithReflection(c) 477 | assert.NotNil(s.T(), err) 478 | assert.Equal(s.T(), errors.New("Field: StorageConfig, not set"), err) 479 | } 480 | 481 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorStorageConfigSliceNotSet() { 482 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 483 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 484 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 485 | sqsQueue := &SQSQueueConfig{ 486 | Region: &sqsRegion, 487 | AWSAccountID: &sqsAWSAccountID, 488 | QueueName: &sqsQueueName, 489 | } 490 | sqsClient := &SQSClientConfig{ 491 | Region: &sqsRegion, 492 | } 493 | 494 | dynamodbTableName := VALID_REMOTE_CONFIG_DYNAMODB_TABLE_NAME 495 | dynamodbTable := &DynamoDBTableConfig{ 496 | TableName: &dynamodbTableName, 497 | } 498 | 499 | dynamodbClientRegion := VALID_REMOTE_CONFIG_DYNAMODB_CLIENT_REGION 500 | dynamodbClient := &DynamoDBClientConfig{ 501 | Region: &dynamodbClientRegion, 502 | } 503 | 504 | str := "testString" 505 | 506 | storageProvider := VALID_REMOTE_CONFIG_STORAGE_CONFIG_PROVIDER 507 | storageLocation := VALID_REMOTE_CONFIG_STORAGE_CONFIG_LOCATION 508 | storageConfig := &StorageConfig{ 509 | Provider: &storageProvider, 510 | Location: &storageLocation, 511 | } 512 | 513 | c := &SampleConfig{ 514 | SQSQueue: sqsQueue, 515 | SQSClient: sqsClient, 516 | DynamoDBTable: dynamodbTable, 517 | DynamoDBClient: dynamodbClient, 518 | Str: str, 519 | StrPointer: &str, 520 | StorageConfig: storageConfig, 521 | StorageConfigSlice: nil, 522 | } 523 | 524 | err := validateConfigWithReflection(c) 525 | assert.NotNil(s.T(), err) 526 | assert.Equal(s.T(), errors.New("Field: StorageConfigSlice, not set"), err) 527 | } 528 | 529 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorStorageConfigSliceNested() { 530 | c := s.buildValidSampleConfig() 531 | storageProvider := VALID_REMOTE_CONFIG_STORAGE_CONFIG_PROVIDER 532 | invalidStorageLocation := StorageLocation("") 533 | c.StorageConfigSlice = []*StorageConfig{ 534 | &StorageConfig{ 535 | Provider: &storageProvider, 536 | Location: &invalidStorageLocation, 537 | }, 538 | } 539 | 540 | err := validateConfigWithReflection(c) 541 | s.NotNil(err) 542 | s.Equal(errors.New("Validater Field: StorageConfig, failed to validate with error, Region cannot be empty"), err) 543 | } 544 | 545 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorStorageConfigMapNested() { 546 | c := s.buildValidSampleConfig() 547 | storageProvider := VALID_REMOTE_CONFIG_STORAGE_CONFIG_PROVIDER 548 | invalidStorageLocation := StorageLocation("") 549 | c.StorageConfigMap = map[string]*StorageConfig{ 550 | "one": &StorageConfig{ 551 | Provider: &storageProvider, 552 | Location: &invalidStorageLocation, 553 | }, 554 | } 555 | 556 | err := validateConfigWithReflection(c) 557 | s.NotNil(err) 558 | s.Equal(errors.New("Sub field of StorageConfigMap with key 'one' failed to validated with error, Validater Field: StorageConfig, failed to validate with error, Region cannot be empty"), err) 559 | } 560 | 561 | func (s *RemoteConfigSuite) TestValidateConfigWithReflectionErrorStorageConfigSliceEmpty() { 562 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 563 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 564 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 565 | sqsQueue := &SQSQueueConfig{ 566 | Region: &sqsRegion, 567 | AWSAccountID: &sqsAWSAccountID, 568 | QueueName: &sqsQueueName, 569 | } 570 | sqsClient := &SQSClientConfig{ 571 | Region: &sqsRegion, 572 | } 573 | 574 | dynamodbTableName := VALID_REMOTE_CONFIG_DYNAMODB_TABLE_NAME 575 | dynamodbTable := &DynamoDBTableConfig{ 576 | TableName: &dynamodbTableName, 577 | } 578 | 579 | dynamodbClientRegion := VALID_REMOTE_CONFIG_DYNAMODB_CLIENT_REGION 580 | dynamodbClient := &DynamoDBClientConfig{ 581 | Region: &dynamodbClientRegion, 582 | } 583 | 584 | str := "testString" 585 | 586 | storageProvider := VALID_REMOTE_CONFIG_STORAGE_CONFIG_PROVIDER 587 | storageLocation := VALID_REMOTE_CONFIG_STORAGE_CONFIG_LOCATION 588 | storageConfig := &StorageConfig{ 589 | Provider: &storageProvider, 590 | Location: &storageLocation, 591 | } 592 | 593 | c := &SampleConfig{ 594 | SQSQueue: sqsQueue, 595 | SQSClient: sqsClient, 596 | DynamoDBTable: dynamodbTable, 597 | DynamoDBClient: dynamodbClient, 598 | Str: str, 599 | StrPointer: &str, 600 | StorageConfig: storageConfig, 601 | StorageConfigSlice: []*StorageConfig{}, 602 | } 603 | 604 | err := validateConfigWithReflection(c) 605 | assert.NotNil(s.T(), err) 606 | assert.Equal(s.T(), errors.New("Slice Field: StorageConfigSlice, is empty"), err) 607 | } 608 | 609 | func (s *RemoteConfigSuite) TestLoadConfigFromURL_Gold() { 610 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 611 | fmt.Fprintln(w, validConfigJSON) 612 | })) 613 | defer ts.Close() 614 | 615 | c := &SampleConfig{} 616 | err := LoadConfigFromURL(ts.URL, c) 617 | assert.Nil(s.T(), err) 618 | } 619 | 620 | func (s *RemoteConfigSuite) TestLoadConfigFromURL_NotOK() { 621 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 622 | w.WriteHeader(http.StatusNotFound) 623 | fmt.Fprintln(w, "Not Found") 624 | })) 625 | defer ts.Close() 626 | 627 | c := &SQSQueueConfig{} 628 | err := LoadConfigFromURL(ts.URL, c) 629 | assert.NotNil(s.T(), err) 630 | assert.Regexp(s.T(), regexp.MustCompile("Request to 'http://\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}:\\d{1,5}' returned non-200 OK status '404: Not Found'"), err.Error()) 631 | } 632 | 633 | func (s *RemoteConfigSuite) TestLoadConfigFromURLError() { 634 | c := &SQSQueueConfig{} 635 | err := LoadConfigFromURL("invalid", c) 636 | assert.NotNil(s.T(), err) 637 | assert.EqualError(s.T(), err, "Get \"invalid\": unsupported protocol scheme \"\"") 638 | } 639 | 640 | func (s *RemoteConfigSuite) TestReadJSONValidate() { 641 | cfgBuffer := bytes.NewBufferString(validConfigJSON) 642 | 643 | c := &SampleConfig{} 644 | err := ReadJSONValidate(cfgBuffer, c) 645 | assert.Nil(s.T(), err) 646 | } 647 | 648 | func (s *RemoteConfigSuite) TestReadJSONValidateWithFunctionType() { 649 | type ConfigWithFunc struct { 650 | MaxEntries *int `json:"max_entries" remoteconfig:"optional"` 651 | OnEvicted func(interface{}) `json:"_" remoteconfig:"optional"` 652 | } 653 | 654 | i := 5 655 | tests := []struct { 656 | name string 657 | in string 658 | out *ConfigWithFunc 659 | }{ 660 | {name: "no config", in: `{}`}, 661 | {name: "empty config", in: `{"cfg": {}}`, out: &ConfigWithFunc{}}, 662 | {name: "values in config", in: `{"cfg": {"max_entries": 5}}`, out: &ConfigWithFunc{MaxEntries: &i}}, 663 | } 664 | 665 | for _, test := range tests { 666 | s.T().Run(test.name, func(t *testing.T) { 667 | var found struct { 668 | Cfg *ConfigWithFunc `json:"cfg" remoteconfig:"optional"` 669 | } 670 | require.NoError(t, ReadJSONValidate(strings.NewReader(test.in), &found)) 671 | assert.Equal(t, test.out, found.Cfg) 672 | }) 673 | } 674 | } 675 | 676 | func (s *RemoteConfigSuite) TestReadJSONParseEmbeddedStruct() { 677 | cfgBuffer := bytes.NewBufferString(validConfigJSON) 678 | 679 | c := &SampleConfig{} 680 | err := ReadJSONValidate(cfgBuffer, c) 681 | assert.Nil(s.T(), err) 682 | assert.NotNil(s.T(), c.EmbeddedStr) 683 | assert.NotNil(s.T(), c.EmbeddedInt) 684 | assert.EqualValues(s.T(), "abc", *c.EmbeddedStr) 685 | assert.EqualValues(s.T(), 123, *c.EmbeddedInt) 686 | } 687 | 688 | func (s *RemoteConfigSuite) TestReadJSONValidateEmbeddedStruct() { 689 | invalidConfigJSON := strings.Replace(validConfigJSON, `"embedded_int": 123`, `"embedded_int": "123"`, 1) 690 | cfgBuffer := bytes.NewBufferString(invalidConfigJSON) 691 | 692 | c := &SampleConfig{} 693 | err := ReadJSONValidate(cfgBuffer, c) 694 | assert.Error(s.T(), err) 695 | assert.Contains(s.T(), err.Error(), "Failed to decode JSON") 696 | } 697 | 698 | func (s *RemoteConfigSuite) TestReadJSONValidateInvalidJSON() { 699 | cfgBuffer := bytes.NewBufferString("Not JSON") 700 | 701 | c := &SampleConfig{} 702 | err := ReadJSONValidate(cfgBuffer, c) 703 | assert.NotNil(s.T(), err) 704 | assert.Equal(s.T(), errors.New("Failed to decode JSON, with error, invalid character 'N' looking for beginning of value"), err) 705 | } 706 | 707 | func (s *RemoteConfigSuite) TestReadJSONValidateErrorValidation() { 708 | cfgBuffer := bytes.NewBufferString("{}") 709 | 710 | c := &SampleConfig{} 711 | err := ReadJSONValidate(cfgBuffer, c) 712 | assert.NotNil(s.T(), err) 713 | assert.Equal(s.T(), errors.New("Field: SQSQueue, not set"), err) 714 | } 715 | 716 | func (s *RemoteConfigSuite) TestReadJSONValidateErrorInvalidJSON() { 717 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 718 | fmt.Fprintln(w, "This is NOT JSON") 719 | })) 720 | defer ts.Close() 721 | 722 | resp, _ := http.Get(ts.URL) 723 | defer resp.Body.Close() 724 | 725 | c := &SampleConfig{} 726 | err := ReadJSONValidate(resp.Body, c) 727 | 728 | assert.NotNil(s.T(), err) 729 | assert.Equal(s.T(), errors.New("Failed to decode JSON, with error, invalid character 'T' looking for beginning of value"), err) 730 | } 731 | 732 | func (s *RemoteConfigSuite) buildValidSampleConfig() *SampleConfig { 733 | sqsRegion := VALID_REMOTE_CONFIG_SQS_REGION 734 | sqsAWSAccountID := VALID_REMOTE_CONFIG_SQS_AWS_ACCOUNT_ID 735 | sqsQueueName := VALID_REMOTE_CONFIG_SQS_QUEUE_NAME 736 | sqsQueue := &SQSQueueConfig{ 737 | Region: &sqsRegion, 738 | AWSAccountID: &sqsAWSAccountID, 739 | QueueName: &sqsQueueName, 740 | } 741 | sqsClient := &SQSClientConfig{ 742 | Region: &sqsRegion, 743 | } 744 | 745 | dynamodbTableName := VALID_REMOTE_CONFIG_DYNAMODB_TABLE_NAME 746 | dynamodbTable := &DynamoDBTableConfig{ 747 | TableName: &dynamodbTableName, 748 | } 749 | 750 | dynamodbClientRegion := VALID_REMOTE_CONFIG_DYNAMODB_CLIENT_REGION 751 | dynamodbClient := &DynamoDBClientConfig{ 752 | Region: &dynamodbClientRegion, 753 | } 754 | 755 | str := "testString" 756 | 757 | storageProvider := VALID_REMOTE_CONFIG_STORAGE_CONFIG_PROVIDER 758 | storageLocation := VALID_REMOTE_CONFIG_STORAGE_CONFIG_LOCATION 759 | storageConfig := &StorageConfig{ 760 | Provider: &storageProvider, 761 | Location: &storageLocation, 762 | } 763 | 764 | return &SampleConfig{ 765 | SQSQueue: sqsQueue, 766 | SQSClient: sqsClient, 767 | DynamoDBTable: dynamodbTable, 768 | DynamoDBClient: dynamodbClient, 769 | Str: str, 770 | StrPointer: &str, 771 | StorageConfig: storageConfig, 772 | StorageConfigSlice: []*StorageConfig{storageConfig}, 773 | StorageConfigMap: map[string]*StorageConfig{"one": storageConfig}, 774 | MapStrStr: map[string]*string{"hello": &str}, 775 | } 776 | } 777 | -------------------------------------------------------------------------------- /s3_config.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "errors" 5 | "net/url" 6 | ) 7 | 8 | const ( 9 | S3_CONFIG_DEFAULT_EXPIRY uint = 60 10 | ) 11 | 12 | type S3Config struct { 13 | Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" remoteconfig:"optional"` 14 | Bucket *string `json:"bucket,omitempty" yaml:"bucket,omitempty"` // i.e. bucket 15 | Region *AWSRegion `json:"region,omitempty" yaml:"region,omitempty"` // i.e. us-west-2 16 | Expiry *uint `json:"expiry,omitempty" yaml:"expiry,omitempty" remoteconfig:"optional"` // i.e. 60 17 | } 18 | 19 | func (c S3Config) GetEndpoint() string { 20 | if c.Endpoint != nil { 21 | return *c.Endpoint 22 | } 23 | return "" 24 | } 25 | 26 | func (c S3Config) GetExpiry() uint { 27 | if c.Expiry != nil { 28 | return *c.Expiry 29 | } 30 | return S3_CONFIG_DEFAULT_EXPIRY 31 | } 32 | 33 | func S3URLToConfig(s3URL string) (*S3Config, string, error) { 34 | // i.e. s3://bucket/test/path.json 35 | c := &S3Config{} 36 | 37 | pURL, err := url.Parse(s3URL) 38 | if err != nil { 39 | return nil, "", err 40 | } 41 | 42 | if pURL.Scheme != "s3" { 43 | return nil, "", errors.New("URL does not have the s3:// scheme") 44 | } 45 | 46 | c.Bucket = &pURL.Host 47 | 48 | return c, pURL.Path[1:], nil 49 | } 50 | -------------------------------------------------------------------------------- /s3_config_test.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | "github.com/stretchr/testify/suite" 11 | ) 12 | 13 | const ( 14 | VALID_S3_CONFIG_ENDPOINT string = "http://localhost:9500/s3" 15 | VALID_S3_CONFIG_BUCKET string = "bucket" 16 | VALID_S3_CONFIG_REGION AWSRegion = AWS_REGION_US_WEST_2 17 | VALID_S3_CONFIG_EXPIRY uint = 30 18 | VALID_S3_CONFIG_TEST_PATH string = "test/path.path" 19 | ) 20 | 21 | var ( 22 | VALID_S3_CONFIG_S3_SCHEME_URL string = fmt.Sprintf("s3://%s/%s", VALID_S3_CONFIG_BUCKET, VALID_S3_CONFIG_TEST_PATH) 23 | ) 24 | 25 | type S3ConfigSuite struct { 26 | suite.Suite 27 | assert *require.Assertions 28 | } 29 | 30 | func TestS3ConfigSuite(t *testing.T) { 31 | suite.Run(t, new(S3ConfigSuite)) 32 | } 33 | 34 | func (s *S3ConfigSuite) SetupSuite() { 35 | s.assert = require.New(s.T()) 36 | } 37 | 38 | func (s *S3ConfigSuite) TestValidate() { 39 | bucket := VALID_S3_CONFIG_BUCKET 40 | region := VALID_S3_CONFIG_REGION 41 | 42 | c := &S3Config{ 43 | Bucket: &bucket, 44 | Region: ®ion, 45 | } 46 | 47 | err := validateConfigWithReflection(c) 48 | s.Nil(err) 49 | } 50 | 51 | func (s *S3ConfigSuite) TestValidateWithOptional() { 52 | endpoint := VALID_S3_CONFIG_ENDPOINT 53 | bucket := VALID_S3_CONFIG_BUCKET 54 | region := VALID_S3_CONFIG_REGION 55 | expiry := VALID_S3_CONFIG_EXPIRY 56 | 57 | c := &S3Config{ 58 | Endpoint: &endpoint, 59 | Bucket: &bucket, 60 | Region: ®ion, 61 | Expiry: &expiry, 62 | } 63 | 64 | err := validateConfigWithReflection(c) 65 | s.Nil(err) 66 | } 67 | 68 | func (s *S3ConfigSuite) TestValidateErrorEndpoint() { 69 | endpoint := "" 70 | 71 | c := &S3Config{ 72 | Endpoint: &endpoint, 73 | } 74 | 75 | err := validateConfigWithReflection(c) 76 | s.NotNil(err) 77 | s.Equal(errors.New("String Field: Endpoint, contains an empty string"), err) 78 | } 79 | 80 | func (s *S3ConfigSuite) TestValidateErrorBucketNotSet() { 81 | c := &S3Config{ 82 | Bucket: nil, 83 | } 84 | 85 | err := validateConfigWithReflection(c) 86 | s.NotNil(err) 87 | s.Equal(errors.New("Field: Bucket, not set"), err) 88 | } 89 | 90 | func (s *S3ConfigSuite) TestValidateErrorBucketEmpty() { 91 | bucket := "" 92 | c := &S3Config{ 93 | Bucket: &bucket, 94 | } 95 | 96 | err := validateConfigWithReflection(c) 97 | s.NotNil(err) 98 | s.Equal(errors.New("String Field: Bucket, contains an empty string"), err) 99 | } 100 | 101 | func (s *S3ConfigSuite) TestValidateErrorRegionNotSet() { 102 | bucket := VALID_S3_CONFIG_BUCKET 103 | 104 | c := &S3Config{ 105 | Bucket: &bucket, 106 | Region: nil, 107 | } 108 | 109 | err := validateConfigWithReflection(c) 110 | s.NotNil(err) 111 | s.Equal(errors.New("Field: Region, not set"), err) 112 | } 113 | 114 | func (s *S3ConfigSuite) TestValidateErrorRegionInvalid() { 115 | bucket := VALID_S3_CONFIG_BUCKET 116 | region := AWSRegion("invalidregion") 117 | 118 | c := &S3Config{ 119 | Bucket: &bucket, 120 | Region: ®ion, 121 | } 122 | 123 | err := validateConfigWithReflection(c) 124 | s.NotNil(err) 125 | s.Equal(errors.New("Validater Field: Region, failed to validate with error, Region is invalid"), err) 126 | } 127 | 128 | func (s *S3ConfigSuite) TestGetEndpointNotSet() { 129 | bucket := VALID_S3_CONFIG_BUCKET 130 | region := VALID_S3_CONFIG_REGION 131 | 132 | c := &S3Config{ 133 | Endpoint: nil, 134 | Bucket: &bucket, 135 | Region: ®ion, 136 | } 137 | 138 | cEndpoint := c.GetEndpoint() 139 | s.Equal("", cEndpoint) 140 | } 141 | 142 | func (s *S3ConfigSuite) TestS3URLToConfig() { 143 | s3ConfigURL, path, err := S3URLToConfig(VALID_S3_CONFIG_S3_SCHEME_URL) 144 | s.Nil(err) 145 | s.NotNil(s3ConfigURL) 146 | s.NotEmpty(path) 147 | 148 | bucket := VALID_S3_CONFIG_BUCKET 149 | s3ConfigExpected := &S3Config{ 150 | Bucket: &bucket, 151 | } 152 | s.Equal(s3ConfigExpected, s3ConfigURL) 153 | s.Equal(VALID_S3_CONFIG_TEST_PATH, path) 154 | } 155 | 156 | func (s *S3ConfigSuite) TestS3URLToConfigErrorURLParse() { 157 | s3ConfigURL, path, err := S3URLToConfig("invalid%6") 158 | s.Nil(s3ConfigURL) 159 | s.Empty(path) 160 | s.NotNil(err) 161 | s.IsType(&url.Error{}, err) 162 | } 163 | 164 | func (s *S3ConfigSuite) TestS3URLToConfigErrorURLScheme() { 165 | s3ConfigURL, path, err := S3URLToConfig("s4://invalidscheme") 166 | s.Nil(s3ConfigURL) 167 | s.Empty(path) 168 | s.NotNil(err) 169 | s.Equal(errors.New("URL does not have the s3:// scheme"), err) 170 | } 171 | -------------------------------------------------------------------------------- /s3_endpoint_expiry_config.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | const ( 4 | S3_ENDPOINT_EXPIRY_CONFIG_DEFAULT_EXPIRY uint = 60 5 | ) 6 | 7 | type S3EndpointExpiryConfig struct { 8 | Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" remoteconfig:"optional"` 9 | Expiry *uint `json:"expiry,omitempty" yaml:"expiry,omitempty" remoteconfig:"optional"` 10 | } 11 | 12 | func (c S3EndpointExpiryConfig) GetEndpoint() string { 13 | if c.Endpoint != nil { 14 | return *c.Endpoint 15 | } 16 | return "" 17 | } 18 | 19 | func (c S3EndpointExpiryConfig) GetExpiry() uint { 20 | if c.Expiry != nil { 21 | return *c.Expiry 22 | } 23 | return S3_ENDPOINT_EXPIRY_CONFIG_DEFAULT_EXPIRY 24 | } 25 | -------------------------------------------------------------------------------- /s3_endpoint_expiry_config_test.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/suite" 9 | ) 10 | 11 | const ( 12 | VALID_S3_ENDPOINT_EXPIRY_CONFIG_ENDPOINT string = "http://localhost:9500/s3" 13 | VALID_S3_ENDPOINT_EXPIRY_CONFIG_EXPIRY uint = 30 14 | ) 15 | 16 | type S3EndpointExpiryConfigSuite struct { 17 | suite.Suite 18 | } 19 | 20 | func TestS3EndpointExpiryConfigSuite(t *testing.T) { 21 | suite.Run(t, new(S3EndpointExpiryConfigSuite)) 22 | } 23 | 24 | func (s *S3EndpointExpiryConfigSuite) SetupSuite() { 25 | } 26 | 27 | func (s *S3EndpointExpiryConfigSuite) SetupTest() { 28 | } 29 | 30 | func (s *S3EndpointExpiryConfigSuite) TestValidate() { 31 | endpoint := VALID_S3_CONFIG_ENDPOINT 32 | expiry := VALID_S3_CONFIG_EXPIRY 33 | 34 | c := &S3EndpointExpiryConfig{ 35 | Endpoint: &endpoint, 36 | Expiry: &expiry, 37 | } 38 | 39 | err := validateConfigWithReflection(c) 40 | assert.Nil(s.T(), err) 41 | } 42 | 43 | func (s *S3EndpointExpiryConfigSuite) TestValidateErrorEndpoint() { 44 | endpoint := "" 45 | 46 | c := &S3EndpointExpiryConfig{ 47 | Endpoint: &endpoint, 48 | } 49 | 50 | err := validateConfigWithReflection(c) 51 | assert.NotNil(s.T(), err) 52 | assert.Equal(s.T(), errors.New("String Field: Endpoint, contains an empty string"), err) 53 | } 54 | 55 | func (s *S3EndpointExpiryConfigSuite) TestGetExpirySet() { 56 | expiry := VALID_S3_CONFIG_EXPIRY 57 | 58 | c := &S3EndpointExpiryConfig{ 59 | Expiry: &expiry, 60 | } 61 | 62 | cExpiry := c.GetExpiry() 63 | assert.Equal(s.T(), VALID_S3_ENDPOINT_EXPIRY_CONFIG_EXPIRY, cExpiry) 64 | } 65 | 66 | func (s *S3EndpointExpiryConfigSuite) TestGetExpiryNotSet() { 67 | c := &S3EndpointExpiryConfig{ 68 | Expiry: nil, 69 | } 70 | 71 | cExpiry := c.GetExpiry() 72 | assert.Equal(s.T(), S3_ENDPOINT_EXPIRY_CONFIG_DEFAULT_EXPIRY, cExpiry) 73 | } 74 | 75 | func (s *S3EndpointExpiryConfigSuite) TestGetEndpointSet() { 76 | endpoint := VALID_S3_CONFIG_ENDPOINT 77 | 78 | c := &S3EndpointExpiryConfig{ 79 | Endpoint: &endpoint, 80 | } 81 | 82 | cEndpoint := c.GetEndpoint() 83 | assert.Equal(s.T(), VALID_S3_ENDPOINT_EXPIRY_CONFIG_ENDPOINT, cEndpoint) 84 | } 85 | 86 | func (s *S3EndpointExpiryConfigSuite) TestGetEndpointNotSet() { 87 | c := &S3EndpointExpiryConfig{ 88 | Endpoint: nil, 89 | } 90 | 91 | cEndpoint := c.GetEndpoint() 92 | assert.Equal(s.T(), "", cEndpoint) 93 | } 94 | -------------------------------------------------------------------------------- /sqs_client_config.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | type SQSClientConfig struct { 4 | Region *AWSRegion `json:"region,omitempty"` 5 | Endpoint *string `json:"endpoint,omitempty" remoteconfig:"optional"` 6 | } 7 | 8 | func (s SQSClientConfig) GetRegion() AWSRegion { 9 | return *s.Region 10 | } 11 | 12 | func (s SQSClientConfig) GetEndpoint() string { 13 | return *s.Endpoint 14 | } 15 | -------------------------------------------------------------------------------- /sqs_client_config_test.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/suite" 9 | ) 10 | 11 | const ( 12 | VALID_SQS_CLIENT_REGION AWSRegion = AWS_REGION_US_EAST_1 13 | VALID_SQS_CLIENT_ENDPOINT string = "http://localhost/testsqs" 14 | ) 15 | 16 | type SQSClientConfigSuite struct { 17 | suite.Suite 18 | } 19 | 20 | func TestSQSClientConfigSuite(t *testing.T) { 21 | suite.Run(t, new(SQSClientConfigSuite)) 22 | } 23 | 24 | func (s *SQSClientConfigSuite) SetupSuite() { 25 | } 26 | 27 | func (s *SQSClientConfigSuite) SetupTest() { 28 | } 29 | 30 | func (s *SQSClientConfigSuite) TestValidate() { 31 | region := VALID_SQS_CLIENT_REGION 32 | 33 | c := &SQSClientConfig{ 34 | Region: ®ion, 35 | } 36 | 37 | err := validateConfigWithReflection(c) 38 | assert.Nil(s.T(), err) 39 | } 40 | 41 | func (s *SQSClientConfigSuite) TestValidateWithEndpoint() { 42 | region := VALID_SQS_CLIENT_REGION 43 | endpoint := VALID_SQS_CLIENT_ENDPOINT 44 | 45 | c := &SQSClientConfig{ 46 | Region: ®ion, 47 | Endpoint: &endpoint, 48 | } 49 | 50 | err := validateConfigWithReflection(c) 51 | assert.Nil(s.T(), err) 52 | } 53 | 54 | func (s *SQSClientConfigSuite) TestValidateErrorRegion() { 55 | region := AWSRegion("invalidregion") 56 | 57 | c := &SQSClientConfig{ 58 | Region: ®ion, 59 | } 60 | 61 | err := validateConfigWithReflection(c) 62 | assert.NotNil(s.T(), err) 63 | assert.Equal(s.T(), errors.New("Validater Field: Region, failed to validate with error, Region is invalid"), err) 64 | } 65 | 66 | func (s *SQSClientConfigSuite) TestValidateErrorEndpoint() { 67 | region := VALID_SQS_CLIENT_REGION 68 | endpoint := "" 69 | 70 | c := &SQSClientConfig{ 71 | Region: ®ion, 72 | Endpoint: &endpoint, 73 | } 74 | 75 | err := validateConfigWithReflection(c) 76 | assert.NotNil(s.T(), err) 77 | assert.Equal(s.T(), errors.New("String Field: Endpoint, contains an empty string"), err) 78 | } 79 | 80 | func (s *SQSClientConfigSuite) TestGetRegion() { 81 | region := VALID_SQS_CLIENT_REGION 82 | 83 | c := &SQSClientConfig{ 84 | Region: ®ion, 85 | } 86 | 87 | sRegion := c.GetRegion() 88 | assert.Equal(s.T(), VALID_SQS_CLIENT_REGION, sRegion) 89 | } 90 | 91 | func (s *SQSClientConfigSuite) TestGetEndpoint() { 92 | region := VALID_SQS_CLIENT_REGION 93 | endpoint := VALID_SQS_CLIENT_ENDPOINT 94 | 95 | c := &SQSClientConfig{ 96 | Region: ®ion, 97 | Endpoint: &endpoint, 98 | } 99 | 100 | sEndpoint := c.GetEndpoint() 101 | assert.Equal(s.T(), VALID_SQS_CLIENT_ENDPOINT, sEndpoint) 102 | } 103 | -------------------------------------------------------------------------------- /sqs_queue_config.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import "fmt" 4 | 5 | type SQSQueueConfig struct { 6 | Region *AWSRegion `json:"region,omitempty"` 7 | AWSAccountID *string `json:"aws_account_id,omitempty"` 8 | QueueName *string `json:"queue_name,omitempty"` 9 | } 10 | 11 | // Returns a full SQS queue URL. 12 | func (s SQSQueueConfig) GetURL(endpoint string) string { 13 | if endpoint == "" { 14 | return fmt.Sprintf("https://sqs.%s.amazonaws.com/%s/%s", *s.Region, *s.AWSAccountID, *s.QueueName) 15 | } 16 | return fmt.Sprintf("%s/%s/%s", endpoint, *s.AWSAccountID, *s.QueueName) 17 | } 18 | -------------------------------------------------------------------------------- /sqs_queue_config_test.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/suite" 10 | ) 11 | 12 | const ( 13 | VALID_SQS_QUEUE_REGION AWSRegion = AWS_REGION_US_EAST_1 14 | VALID_SQS_QUEUE_AWS_ACCOUNT_ID string = "345833302425" 15 | VALID_SQS_QUEUE_QUEUE_NAME string = "testQueue" 16 | VALID_SQS_QUEUE_NO_ENDPOINT string = "" 17 | VALID_SQS_QUEUE_ENDPOINT string = "http://localhost:9500" 18 | ) 19 | 20 | var ( 21 | VALID_SQS_QUEUE_URL string = fmt.Sprintf("https://sqs.%s.amazonaws.com/%s/%s", VALID_SQS_QUEUE_REGION, VALID_SQS_QUEUE_AWS_ACCOUNT_ID, VALID_SQS_QUEUE_QUEUE_NAME) 22 | VALID_SQS_QUEUE_URL_ENDPOINT string = fmt.Sprintf("%s/%s/%s", VALID_SQS_QUEUE_ENDPOINT, VALID_SQS_QUEUE_AWS_ACCOUNT_ID, VALID_SQS_QUEUE_QUEUE_NAME) 23 | ) 24 | 25 | type SQSQueueConfigSuite struct { 26 | suite.Suite 27 | } 28 | 29 | func TestSQSQueueConfigSuite(t *testing.T) { 30 | suite.Run(t, new(SQSQueueConfigSuite)) 31 | } 32 | 33 | func (s *SQSQueueConfigSuite) SetupSuite() { 34 | } 35 | 36 | func (s *SQSQueueConfigSuite) SetupTest() { 37 | } 38 | 39 | func (s *SQSQueueConfigSuite) TestValidate() { 40 | region := VALID_SQS_QUEUE_REGION 41 | awsAccountID := VALID_SQS_QUEUE_AWS_ACCOUNT_ID 42 | queueName := VALID_SQS_QUEUE_QUEUE_NAME 43 | 44 | c := &SQSQueueConfig{ 45 | Region: ®ion, 46 | AWSAccountID: &awsAccountID, 47 | QueueName: &queueName, 48 | } 49 | 50 | err := validateConfigWithReflection(c) 51 | assert.Nil(s.T(), err) 52 | } 53 | 54 | func (s *SQSQueueConfigSuite) TestValidateErrorRegion() { 55 | region := AWSRegion("invalidregion") 56 | awsAccountID := VALID_SQS_QUEUE_AWS_ACCOUNT_ID 57 | queueName := VALID_SQS_QUEUE_QUEUE_NAME 58 | 59 | c := &SQSQueueConfig{ 60 | Region: ®ion, 61 | AWSAccountID: &awsAccountID, 62 | QueueName: &queueName, 63 | } 64 | 65 | err := validateConfigWithReflection(c) 66 | assert.NotNil(s.T(), err) 67 | assert.Equal(s.T(), errors.New("Validater Field: Region, failed to validate with error, Region is invalid"), err) 68 | } 69 | 70 | func (s *SQSQueueConfigSuite) TestValidateErrorAWSAccountID() { 71 | region := VALID_SQS_QUEUE_REGION 72 | awsAccountID := "" 73 | queueName := VALID_SQS_QUEUE_QUEUE_NAME 74 | 75 | c := &SQSQueueConfig{ 76 | Region: ®ion, 77 | AWSAccountID: &awsAccountID, 78 | QueueName: &queueName, 79 | } 80 | 81 | err := validateConfigWithReflection(c) 82 | assert.NotNil(s.T(), err) 83 | assert.Equal(s.T(), errors.New("String Field: AWSAccountID, contains an empty string"), err) 84 | } 85 | 86 | func (s *SQSQueueConfigSuite) TestValidateErrorQueueName() { 87 | region := VALID_SQS_QUEUE_REGION 88 | awsAccountID := VALID_SQS_QUEUE_AWS_ACCOUNT_ID 89 | queueName := "" 90 | 91 | c := &SQSQueueConfig{ 92 | Region: ®ion, 93 | AWSAccountID: &awsAccountID, 94 | QueueName: &queueName, 95 | } 96 | 97 | err := validateConfigWithReflection(c) 98 | assert.NotNil(s.T(), err) 99 | assert.Equal(s.T(), errors.New("String Field: QueueName, contains an empty string"), err) 100 | } 101 | 102 | func (s *SQSQueueConfigSuite) TestGetURLNoEndpoint() { 103 | region := VALID_SQS_QUEUE_REGION 104 | awsAccountID := VALID_SQS_QUEUE_AWS_ACCOUNT_ID 105 | queueName := VALID_SQS_QUEUE_QUEUE_NAME 106 | 107 | c := &SQSQueueConfig{ 108 | Region: ®ion, 109 | AWSAccountID: &awsAccountID, 110 | QueueName: &queueName, 111 | } 112 | 113 | url := c.GetURL(VALID_SQS_QUEUE_NO_ENDPOINT) 114 | assert.Equal(s.T(), VALID_SQS_QUEUE_URL, url) 115 | } 116 | 117 | func (s *SQSQueueConfigSuite) TestGetURLWithEndpoint() { 118 | region := VALID_SQS_QUEUE_REGION 119 | awsAccountID := VALID_SQS_QUEUE_AWS_ACCOUNT_ID 120 | queueName := VALID_SQS_QUEUE_QUEUE_NAME 121 | 122 | c := &SQSQueueConfig{ 123 | Region: ®ion, 124 | AWSAccountID: &awsAccountID, 125 | QueueName: &queueName, 126 | } 127 | 128 | url := c.GetURL(VALID_SQS_QUEUE_ENDPOINT) 129 | assert.Equal(s.T(), VALID_SQS_QUEUE_URL_ENDPOINT, url) 130 | } 131 | -------------------------------------------------------------------------------- /storage_config.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | type StorageConfig struct { 4 | Provider *StorageProvider `json:"provider,omitempty"` 5 | Location *StorageLocation `json:"location,omitempty"` 6 | } 7 | 8 | func (s StorageConfig) Validate() error { 9 | if err := s.Provider.Validate(); err != nil { 10 | return err 11 | } 12 | if *s.Provider == STORAGE_PROVIDER_AWS { 13 | if err := (*AWSRegion)(s.Location).Validate(); err != nil { 14 | return err 15 | } 16 | } 17 | 18 | return nil 19 | } 20 | 21 | func (s *StorageConfig) GetProvider() StorageProvider { 22 | return *s.Provider 23 | } 24 | 25 | func (s *StorageConfig) GetLocation() StorageLocation { 26 | return *s.Location 27 | } 28 | -------------------------------------------------------------------------------- /storage_config_test.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/suite" 9 | ) 10 | 11 | const ( 12 | VALID_STORAGE_CONFIG_PROVIDER StorageProvider = STORAGE_PROVIDER_AWS 13 | VALID_STORAGE_CONFIG_LOCATION StorageLocation = (StorageLocation)(AWS_REGION_US_WEST_2) 14 | ) 15 | 16 | type StorageConfigSuite struct { 17 | suite.Suite 18 | } 19 | 20 | func TestStorageConfigSuite(t *testing.T) { 21 | suite.Run(t, new(StorageConfigSuite)) 22 | } 23 | 24 | func (s *StorageConfigSuite) SetupSuite() { 25 | } 26 | 27 | func (s *StorageConfigSuite) SetupTest() { 28 | } 29 | 30 | func (s *StorageConfigSuite) TestValidateConfigWithReflection() { 31 | p := VALID_STORAGE_CONFIG_PROVIDER 32 | l := VALID_STORAGE_CONFIG_LOCATION 33 | c := &StorageConfig{ 34 | Provider: &p, 35 | Location: &l, 36 | } 37 | 38 | err := validateConfigWithReflection(c) 39 | assert.Nil(s.T(), err) 40 | } 41 | 42 | func (s *StorageConfigSuite) TestValidateConfigWithReflectionErrorProvider() { 43 | p := (StorageProvider)("invalid_provider") 44 | l := VALID_STORAGE_CONFIG_LOCATION 45 | c := &StorageConfig{ 46 | Provider: &p, 47 | Location: &l, 48 | } 49 | 50 | err := validateConfigWithReflection(c) 51 | assert.NotNil(s.T(), err) 52 | assert.Equal(s.T(), errors.New("Validater Field: StorageConfig, failed to validate with error, Invalid storage provider"), err) 53 | } 54 | 55 | func (s *StorageConfigSuite) TestValidateConfigWithReflectionErrorLocation() { 56 | p := VALID_STORAGE_CONFIG_PROVIDER 57 | l := (StorageLocation)("invalid_location") 58 | c := &StorageConfig{ 59 | Provider: &p, 60 | Location: &l, 61 | } 62 | 63 | err := validateConfigWithReflection(c) 64 | assert.NotNil(s.T(), err) 65 | assert.Equal(s.T(), errors.New("Validater Field: StorageConfig, failed to validate with error, Region is invalid"), err) 66 | } 67 | 68 | func (s *StorageConfigSuite) TestGetProvider() { 69 | p := VALID_STORAGE_CONFIG_PROVIDER 70 | c := &StorageConfig{ 71 | Provider: &p, 72 | } 73 | 74 | assert.Equal(s.T(), VALID_STORAGE_CONFIG_PROVIDER, c.GetProvider()) 75 | } 76 | 77 | func (s *StorageConfigSuite) TestGetLocation() { 78 | l := VALID_STORAGE_CONFIG_LOCATION 79 | c := &StorageConfig{ 80 | Location: &l, 81 | } 82 | 83 | assert.Equal(s.T(), VALID_STORAGE_CONFIG_LOCATION, c.GetLocation()) 84 | } 85 | -------------------------------------------------------------------------------- /storage_location.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | type StorageLocation string 4 | 5 | func (s *StorageLocation) UnmarshalText(data []byte) error { 6 | sString := string(data[:]) 7 | *s = (StorageLocation)(sString) 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /storage_provider.go: -------------------------------------------------------------------------------- 1 | package remoteconfig 2 | 3 | import "errors" 4 | 5 | type StorageProvider string 6 | 7 | const ( 8 | STORAGE_PROVIDER_AWS StorageProvider = "aws" 9 | ) 10 | 11 | func (s *StorageProvider) UnmarshalText(data []byte) error { 12 | sString := string(data[:]) 13 | *s = (StorageProvider)(sString) 14 | return s.Validate() 15 | } 16 | 17 | func (s StorageProvider) Validate() error { 18 | if s != STORAGE_PROVIDER_AWS { 19 | return errors.New("Invalid storage provider") 20 | } 21 | return nil 22 | } 23 | --------------------------------------------------------------------------------