Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/go_modules/utils/github.com/spf…
Browse files Browse the repository at this point in the history
…13/afero-1.9.5
  • Loading branch information
acabarbaye committed Apr 11, 2023
2 parents 78614da + 0bbcec4 commit d331433
Show file tree
Hide file tree
Showing 20 changed files with 336 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/scorecards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
persist-credentials: false

- name: "Run analysis"
uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af # v2.1.3
with:
results_file: results.sarif
results_format: sarif
Expand Down
26 changes: 21 additions & 5 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -100,34 +100,50 @@
}
],
"results": {
"utils/config/fixtures/config-test.json": [
{
"type": "Secret Keyword",
"filename": "utils/config/fixtures/config-test.json",
"hashed_secret": "e38ad214943daad1d64c102faec29de4afe9da3d",
"is_verified": false,
"line_number": 10
},
{
"type": "Secret Keyword",
"filename": "utils/config/fixtures/config-test.json",
"hashed_secret": "2aa60a8ff7fcd473d321e0146afd9e26df395147",
"is_verified": false,
"line_number": 19
}
],
"utils/config/service_configuration_test.go": [
{
"type": "Secret Keyword",
"filename": "utils/config/service_configuration_test.go",
"hashed_secret": "ddcec2f503a5d58f432a0beee3fb9544fa581f54",
"is_verified": false,
"line_number": 29
"line_number": 31
},
{
"type": "Secret Keyword",
"filename": "utils/config/service_configuration_test.go",
"hashed_secret": "7ca1cc114e7e5f955880bb96a5bf391b4dc20ab6",
"is_verified": false,
"line_number": 366
"line_number": 368
},
{
"type": "Secret Keyword",
"filename": "utils/config/service_configuration_test.go",
"hashed_secret": "11519c144be4850d95b34220a40030cbd5a36b57",
"is_verified": false,
"line_number": 461
"line_number": 463
},
{
"type": "Secret Keyword",
"filename": "utils/config/service_configuration_test.go",
"hashed_secret": "15fae91d8fa7f2c531c1cf3ddc745e1f4473c02d",
"is_verified": false,
"line_number": 468
"line_number": 470
}
],
"utils/filesystem/filehash_test.go": [
Expand Down Expand Up @@ -232,5 +248,5 @@
}
]
},
"generated_at": "2023-02-16T13:57:06Z"
"generated_at": "2023-04-04T13:50:30Z"
}
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,40 @@ beta releases are not included in this history.

[//]: # (begin_release_notes)

"1.31.0" (2023-04-04)
=====================

Features
--------

- :sparkles: `[config]` Add a way via `LoadFromEnvironment` to read configuration from a file as long as the format is supported by [Viper](https://github.com/spf13/viper#what-is-viper) (#20230404144534)


Bugfixes
--------

- Dependency upgrade: scorecard-action-2.1.3 (#20230330110538, #20230330110540, #20230330110620)
- Dependency upgrade: v3-3.23.3 (#20230403110100, #20230403110109, #20230403110141, #20230403110148, #20230403110226)


"1.30.0" (2023-03-17)
=====================

Features
--------

- `[field]` Extend utilities to set or retrieve `time.Time` fields (#20230317105143)


Bugfixes
--------

- Dependency upgrade: v5-5.6.0 (#20230301151839)
- Dependency upgrade: zap-1.24.0 (#20230316161820, #20230316161822)
- Dependency upgrade: v5-5.6.1 (#20230316165500, #20230316165503, #20230316165619, #20230316165625, #20230316165719, #20230316165806, #20230316165905, #20230316165944, #20230316170056, #20230316170118, #20230316170232, #20230316170303, #20230316170423, #20230316170442, #20230316170545, #20230316170614, #20230316170740, #20230316170800, #20230316170857, #20230316170917, #20230316171033, #20230316171040, #20230316171226, #20230316171243, #20230316171337, #20230316171401, #20230316171508, #20230316171530, #20230316171650, #20230316171712, #20230316171805, #20230316171819, #20230316172018, #20230316172035, #20230316172139, #20230316172208, #20230316172305, #20230316172346, #20230316172458, #20230316172510, #20230316172609, #20230316172642, #20230316172801, #20230316172815, #20230316172938, #20230316172958, #20230316173110, #20230316173140, #20230316173249, #20230316173313, #20230316173434, #20230316173506, #20230316173621, #20230316173656, #20230316173808, #20230316173832, #20230316173954, #20230316174030, #20230316174128, #20230316174209, #20230316174310, #20230316174350, #20230316174431, #20230316174530, #20230316174638, #20230316174721, #20230316174742, #20230316174810, #20230316174915, #20230316175009, #20230316175048, #20230316175118, #20230316175159, #20230316175259, #20230316175342, #20230316175441, #20230316175511, #20230316175604, #20230316175623, #20230316175735, #20230316175804, #20230316175934, #20230316175956, #20230316180047, #20230316180115, #20230316180316, #20230316180327, #20230316180421, #20230316180450, #20230316180614, #20230316180624, #20230316180728, #20230316180747, #20230316180849, #20230316180935, #20230316181031, #20230316181057, #20230316181217, #20230316181230, #20230316181327, #20230316181356, #20230316181513, #20230316181523, #20230316181623, #20230316181700, #20230316181802, #20230316181828, #20230316181926, #20230316181944, #20230316182112, #20230316182121, #20230316182257, #20230316182313, #20230316182421, #20230316182450, #20230316182537, #20230316182603, #20230316182716, #20230316182729, #20230316182848, #20230316182906, #20230316183025, #20230316183037, #20230316183150, #20230316183221, #20230316183319, #20230316183349, #20230316183452, #20230316183514, #20230316183618, #20230316183700, #20230316183748, #20230316183808, #20230316183920, #20230316183945, #20230316184103, #20230316184123, #20230316184236, #20230316184243, #20230316184415, #20230316184430, #20230316184616, #20230316184631, #20230316184817, #20230316184828, #20230316185002, #20230316185015, #20230316185119, #20230316185200, #20230316185330, #20230316185407, #20230316185456, #20230316185539, #20230316185654, #20230316185734, #20230316185838, #20230316185853, #20230316190049, #20230316190107, #20230316190225, #20230316190309, #20230316190432, #20230316190456, #20230316190624, #20230316190654, #20230316190817, #20230316190844, #20230316190951, #20230316191023, #20230316191045, #20230316191106, #20230316191128, #20230316191145, #20230316191235, #20230316191258, #20230316191322, #20230316191426, #20230316191449, #20230316191526, #20230316191533, #20230316191606, #20230316191645, #20230316191710, #20230316191752, #20230316191833, #20230316191907, #20230316191915, #20230316191946, #20230316192006, #20230316192048, #20230316192107, #20230316192148, #20230316192206, #20230316192254, #20230316192315, #20230316192344, #20230316192409, #20230316192429, #20230316192451, #20230316192516, #20230316192622, #20230316192631, #20230316192700, #20230316192740, #20230316192754, #20230316192837, #20230316192847, #20230316192919, #20230316193011)
- `[commonerrors]` make `commonerrors.CorrespondTo` case-insensitive (#20230317104522)


"1.29.0" (2023-03-16)
=====================

Expand Down
1 change: 0 additions & 1 deletion changes/20230301151839.bugfix

This file was deleted.

1 change: 0 additions & 1 deletion changes/20230316161820.bugfix

This file was deleted.

1 change: 0 additions & 1 deletion changes/20230316161822.bugfix

This file was deleted.

1 change: 1 addition & 0 deletions changes/20230406105920.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dependency upgrade: text-0.9.0
7 changes: 4 additions & 3 deletions utils/commonerrors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func None(target error, err ...error) bool {
}

// CorrespondTo determines whether a `target` error corresponds to a specific error described by `description`
// It will check whether the error contains the string in its description.
// It will check whether the error contains the string in its description. It is not case-sensitive.
// ```code
//
// CorrespondTo(errors.New("feature a is not supported", "not supported") = True
Expand All @@ -75,9 +75,10 @@ func CorrespondTo(target error, description ...string) bool {
if target == nil {
return false
}
desc := target.Error()
desc := strings.ToLower(target.Error())
for i := range description {
if strings.Contains(desc, description[i]) {
d := strings.ToLower(description[i])
if desc == d || strings.Contains(desc, d) {
return true
}
}
Expand Down
4 changes: 4 additions & 0 deletions utils/commonerrors/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package commonerrors
import (
"context"
"fmt"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -39,9 +40,12 @@ func TestNone(t *testing.T) {
}

func TestCorrespondTo(t *testing.T) {
assert.False(t, CorrespondTo(nil))
assert.False(t, CorrespondTo(nil, faker.Sentence()))
assert.False(t, CorrespondTo(ErrNotImplemented, ErrInvalid.Error(), ErrUnknown.Error()))
assert.True(t, CorrespondTo(ErrNotImplemented, ErrInvalid.Error(), ErrNotImplemented.Error()))
assert.True(t, CorrespondTo(fmt.Errorf("%v %w", faker.Sentence(), ErrUndefined), ErrUndefined.Error()))
assert.True(t, CorrespondTo(fmt.Errorf("%v %v", faker.Sentence(), strings.ToUpper(ErrUndefined.Error())), strings.ToLower(ErrUndefined.Error())))
}

func TestContextErrorConversion(t *testing.T) {
Expand Down
23 changes: 23 additions & 0 deletions utils/config/fixtures/config-test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"dummy_string": "test string",
"dummy_int": 1,
"dummy_time": "54s",
"dummyconfig": {
"dummy_host": "host1",
"port": 20,
"db": "db1",
"user": "user1",
"password": "password1",
"flag": true,
"healthcheck_period": "1s"
},
"dummy_config": {
"dummy_host": "host2",
"port": 304,
"db": "db2",
"user": "user2",
"password": "password2",
"flag": false,
"healthcheck_period": "24m"
}
}
11 changes: 9 additions & 2 deletions utils/config/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@
* Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

// Package config provides utilities to load configuration from an environment and perform validation at load time.
package config

//go:generate mockgen -destination=../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/$GOPACKAGE IServiceConfiguration
//go:generate mockgen -destination=../mocks/mock_$GOPACKAGE.go -package=mocks github.com/ARM-software/golang-utils/utils/$GOPACKAGE IServiceConfiguration,Validator

// IServiceConfiguration defines a typical service configuration.
type IServiceConfiguration interface {
// Validate validates configuration entries.
Validator
}

// Validator defines an object which can perform some validation on itself.
type Validator interface {
Validate() error
}
53 changes: 51 additions & 2 deletions utils/config/service_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package config

import (
Expand Down Expand Up @@ -38,11 +39,24 @@ func Load(envVarPrefix string, configurationToSet IServiceConfiguration, default
// 1) values set using explicit calls to `Set`
// 2) flags
// 3) environment (variables or `.env`)
// 4) key/value store
// 5) default values (set via flag default values, or calls to `SetDefault` or via `defaultConfiguration` argument provided)
// Nonetheless, when it comes to default values. It differs slightly from Viper as default values from the default Configuration (i.e. `defaultConfiguration` argument provided) will take precedence over defaults set via `SetDefault` or flags unless they are considered empty values according to `reflection.IsEmpty`.
func LoadFromViper(viperSession *viper.Viper, envVarPrefix string, configurationToSet IServiceConfiguration, defaultConfiguration IServiceConfiguration) error {
return LoadFromEnvironment(viperSession, envVarPrefix, configurationToSet, defaultConfiguration, "")
}

// LoadFromEnvironment is the same as `LoadFromViper` but also gives the ability to load the configuration from a configuration file as long as the format is supported by [Viper](https://github.com/spf13/viper#what-is-viper)
// Important note:
// Viper's precedence order is maintained:
// 1) values set using explicit calls to `Set`
// 2) flags
// 3) environment (variables or `.env`)
// 4) configuration file
// 5) key/value store
// 6) default values (set via flag default values, or calls to `SetDefault` or via `defaultConfiguration` argument provided)
// Nonetheless, when it comes to default values. It differs slightly from Viper as default values from the default Configuration (i.e. `defaultConfiguration` argument provided) will take precedence over defaults set via `SetDefault` or flags unless they are considered empty values according to `reflection.IsEmpty`.
func LoadFromViper(viperSession *viper.Viper, envVarPrefix string, configurationToSet IServiceConfiguration, defaultConfiguration IServiceConfiguration) (err error) {
func LoadFromEnvironment(viperSession *viper.Viper, envVarPrefix string, configurationToSet IServiceConfiguration, defaultConfiguration IServiceConfiguration, configFile string) (err error) {
// Load Defaults
var defaults map[string]interface{}
err = mapstructure.Decode(defaultConfiguration, &defaults)
Expand All @@ -62,17 +76,52 @@ func LoadFromViper(viperSession *viper.Viper, envVarPrefix string, configuration

linkFlagKeysToStructureKeys(viperSession, envVarPrefix)

if configFile != "" {
err = LoadFromConfigurationFile(viperSession, configFile)
if err != nil {
return
}
}

// Merge together all the sources and unmarshal into struct
err = viperSession.Unmarshal(configurationToSet)
if err != nil {
err = fmt.Errorf("unable to decode config into struct, %w", err)
err = fmt.Errorf("%w: unable to fill configuration structure from the configuration session: %v", commonerrors.ErrMarshalling, err.Error())
return
}
// Run validation
err = configurationToSet.Validate()
return
}

// LoadFromConfigurationFile loads the configuration from the environment.
// If the format is not supported, an error is raised and the same happens if the file cannot be found.
// Supported formats are the same as what [viper](https://github.com/spf13/viper#what-is-viper) supports
func LoadFromConfigurationFile(viperSession *viper.Viper, configFile string) (err error) {
if configFile == "" {
err = fmt.Errorf("%w: missing configuration file", commonerrors.ErrUndefined)
return
}
viperSession.SetConfigFile(configFile)
err = convertViperError(viperSession.ReadInConfig())
return
}

func convertViperError(vErr error) (err error) {
switch {
case vErr == nil:
case commonerrors.CorrespondTo(vErr, "unsupported"):
err = fmt.Errorf("%w: %v", commonerrors.ErrUnsupported, vErr.Error())
case commonerrors.CorrespondTo(vErr, "not found"):
err = fmt.Errorf("%w: %v", commonerrors.ErrNotFound, vErr.Error())
case commonerrors.CorrespondTo(vErr, "parsing", "marshaling", "decoding"):
err = fmt.Errorf("%w: %v", commonerrors.ErrMarshalling, vErr.Error())
default:
err = fmt.Errorf("%w: %v", commonerrors.ErrUnexpected, vErr.Error())
}
return
}

// BindFlagToEnv binds pflags to environment variable.
// Envvar is the environment variable string with or without the prefix envVarPrefix
func BindFlagToEnv(viperSession *viper.Viper, envVarPrefix string, envVar string, flag *pflag.Flag) (err error) {
Expand Down
89 changes: 89 additions & 0 deletions utils/config/service_configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
package config

import (
"errors"
"fmt"
"math/rand"
"os"
"path/filepath"
"testing"
"time"

Expand Down Expand Up @@ -495,3 +497,90 @@ func TestGenerateEnvFile_Empty(t *testing.T) {
_, err := DetermineConfigurationEnvironmentVariables(prefix, struct{ IServiceConfiguration }{})
require.ErrorIs(t, err, commonerrors.ErrUndefined)
}

func Test_convertViperError(t *testing.T) {
tests := []struct {
viperErr error
expectedError error
}{
{
viperErr: nil,
expectedError: nil,
},
{
viperErr: viper.ConfigFileNotFoundError{},
expectedError: commonerrors.ErrNotFound,
},
// Note: the following errors were considered but could not be created outside the viper module (non exposed fields)
// {
// viperErr: viper.ConfigParseError{},
// expectedError: commonerrors.ErrMarshalling,
// },
// {
// viperErr: viper.ConfigMarshalError{},
// expectedError: commonerrors.ErrMarshalling,
// },
{
viperErr: viper.UnsupportedConfigError(faker.Sentence()),
expectedError: commonerrors.ErrUnsupported,
},
{
viperErr: viper.UnsupportedRemoteProviderError(faker.Sentence()),
expectedError: commonerrors.ErrUnsupported,
},
{
viperErr: viper.ConfigFileAlreadyExistsError(faker.Sentence()),
expectedError: commonerrors.ErrUnexpected,
},
{
viperErr: viper.RemoteConfigError(faker.Sentence()),
expectedError: commonerrors.ErrUnexpected,
},
{
viperErr: errors.New(faker.Name()),
expectedError: commonerrors.ErrUnexpected,
},
}
for i := range tests {
t.Run(fmt.Sprintf("%v", i), func(t *testing.T) {
test := tests[i]
require.True(t, commonerrors.Any(convertViperError(test.viperErr), test.expectedError))
})
}
}

func TestServiceConfigurationLoadFromFile(t *testing.T) {
os.Clearenv()
session := viper.New()
err := LoadFromConfigurationFile(session, "")
assert.Error(t, err)
err = LoadFromConfigurationFile(session, fmt.Sprintf("doesnotexist-%v.test", faker.DomainName()))
assert.Error(t, err)
err = LoadFromConfigurationFile(session, filepath.Join(".", "fixtures", "config-test.json"))
assert.NoError(t, err)
value := session.Get("dummy_string")
assert.NotEmpty(t, value)
assert.Equal(t, "test string", value)
}

func TestServiceConfigurationLoadFromEnvironment(t *testing.T) {
os.Clearenv()
session := viper.New()
configTest := &ConfigurationTest{}
defaults := DefaultConfiguration()
err := LoadFromEnvironment(session, "test", configTest, defaults, filepath.Join(".", "fixtures", "config-test.json"))
require.NoError(t, err)
require.NoError(t, configTest.Validate())
assert.Equal(t, "test string", configTest.TestString)
assert.Equal(t, 1, configTest.TestInt)
assert.Equal(t, 54*time.Second, configTest.TestTime)
assert.Equal(t, 20, configTest.TestConfig.Port)
assert.Equal(t, "host1", configTest.TestConfig.Host)
assert.Equal(t, "password2", configTest.TestConfig2.Password)
assert.Equal(t, "db2", configTest.TestConfig2.DB)
assert.NotEqual(t, expectedHost, configTest.TestConfig2.Host)
assert.NotEqual(t, expectedPassword, configTest.TestConfig.Password)
assert.NotEqual(t, expectedDB, configTest.TestConfig.DB)
assert.True(t, configTest.TestConfig.Flag)
assert.False(t, configTest.TestConfig2.Flag)
}
Loading

0 comments on commit d331433

Please sign in to comment.