Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AUT-10341 Add support for multiple dirs when reading config from fs #4

Merged
merged 5 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ on:
- master
- dev
pull_request:

permissions:
contents: read

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ cac --config examples/e2e/config.yaml pull --workspace cdr_australia-demo-c67evw

Merge configuration from a directory structure and push it into Cloudentity.

#### Push configuration from multiple directories

To push configration from multiple directories, either pass an array to the `storage.dir_path` or use `STORAGE_DIR_PATH` with multiple paths split by a comma.

Configurations are merged in the reverse order, so the first path has the highest priority, and will override everything else.

```bash
cac --config examples/e2e/config.yaml push --workspace cdr_australia-demo-c67evw7mj4
```
Expand Down
6 changes: 4 additions & 2 deletions internal/cac/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
type Application struct {
Config *config.Configuration
Client *client.Client
Storage *storage.Storage
Storage storage.Storage
}

func InitApp(configPath string) (app *Application, err error) {
Expand All @@ -31,7 +31,9 @@ func InitApp(configPath string) (app *Application, err error) {
return app, err
}

app.Storage = storage.InitStorage(app.Config.Storage)
if app.Storage, err = storage.InitMultiStorage(app.Config.Storage); err != nil {
return app, err
}

slog.With("app", app).Debug("Initiated application")

Expand Down
28 changes: 23 additions & 5 deletions internal/cac/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ var (
DefaultConfig = Configuration{
Client: client.DefaultConfig,
Logging: logging.DefaultLoggingConfig,
Storage: storage.DefaultConfig,
Storage: storage.DefaultMultiStorageConfig,
}
)

type Configuration struct {
Client client.Configuration `json:"client"`
Logging logging.Configuration `json:"logging"`
Storage storage.Configuration `json:"storage"`
Client client.Configuration `json:"client"`
Logging logging.Configuration `json:"logging"`
Storage storage.MultiStorageConfiguration `json:"storage"`
}

func InitConfig(path string) (_ *Configuration, err error) {
Expand Down Expand Up @@ -75,7 +75,7 @@ func InitConfig(path string) (_ *Configuration, err error) {
func configureDecoder(config *mapstructure.DecoderConfig) {
config.TagName = "json"
config.WeaklyTypedInput = true
config.DecodeHook = mapstructure.ComposeDecodeHookFunc(urlDecoder(), timeDecoder())
config.DecodeHook = mapstructure.ComposeDecodeHookFunc(urlDecoder(), timeDecoder(), stringToSlice())
}

func urlDecoder() mapstructure.DecodeHookFunc {
Expand Down Expand Up @@ -125,3 +125,21 @@ func timeDecoder() mapstructure.DecodeHookFunc {
}
}
}

func stringToSlice() mapstructure.DecodeHookFunc {
return func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
if f != reflect.String || t != reflect.Slice {
return data, nil
}

raw := data.(string)
if raw == "" {
return []string{}, nil
}

return strings.Split(raw, ","), nil
}
}
69 changes: 69 additions & 0 deletions internal/cac/storage/multi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package storage

import (
"github.com/cloudentity/acp-client-go/clients/hub/models"
"github.com/imdario/mergo"
"github.com/pkg/errors"
)

type MultiStorageConfiguration struct {
DirPath []string `json:"dir_path"`
}

var DefaultMultiStorageConfig = MultiStorageConfiguration{
DirPath: []string{"data"},
}

func InitMultiStorage(config MultiStorageConfiguration) (*MultiStorage, error) {
var storages []Storage

if len(config.DirPath) == 0 {
return nil, errors.New("at least one dir_path is required")
}

for _, config := range config.DirPath {
storages = append(storages, InitStorage(Configuration{
DirPath: config,
}))
}

return &MultiStorage{
Storages: storages,
Config: config,
}, nil
}

type MultiStorage struct {
Storages []Storage
Config MultiStorageConfiguration
}

var _ Storage = &MultiStorage{}

// Store for simplicity stores data in first storage only, it is responsibility of the user to move entities to other storages
func (m *MultiStorage) Store(workspace string, data *models.TreeServer) error {
return m.Storages[0].Store(workspace, data)
}

// Read data from all storages and merge them
func (m *MultiStorage) Read(workspace string) (models.TreeServer, error) {
var (
data models.TreeServer
err error
)

for i := len(m.Storages) - 1; i >= 0; i-- {
var data2 models.TreeServer

if data2, err = m.Storages[i].Read(workspace); err != nil {
return data, errors.Wrap(err, "failed to read data from storage")
}

if err = mergo.Merge(&data, data2, mergo.WithOverride); err != nil {
return data, errors.Wrap(err, "failed to merge data")
}

}

return data, nil
}
21 changes: 14 additions & 7 deletions internal/cac/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,24 @@ var DefaultConfig = Configuration{
DirPath: "data",
}

func InitStorage(config Configuration) *Storage {
return &Storage{
func InitStorage(config Configuration) *SingleStorage {
return &SingleStorage{
Config: config,
}
}

type Storage struct {
type Storage interface {
Store(workspace string, data *models.TreeServer) error
Read(workspace string) (models.TreeServer, error)
}

type SingleStorage struct {
Config Configuration
}

func (s *Storage) Store(workspace string, data *models.TreeServer) error {
var _ Storage = &SingleStorage{}

func (s *SingleStorage) Store(workspace string, data *models.TreeServer) error {
var (
workspacePath = s.workspacePath(workspace)
err error
Expand Down Expand Up @@ -126,7 +133,7 @@ func (s *Storage) Store(workspace string, data *models.TreeServer) error {
return nil
}

func (s *Storage) Read(workspace string) (models.TreeServer, error) {
func (s *SingleStorage) Read(workspace string) (models.TreeServer, error) {
var (
server = models.TreeServer{
Clients: models.TreeClients{},
Expand Down Expand Up @@ -235,11 +242,11 @@ func (s *Storage) Read(workspace string) (models.TreeServer, error) {
return server, nil
}

func (s *Storage) workspacePath(workspace string) string {
func (s *SingleStorage) workspacePath(workspace string) string {
return filepath.Join(s.Config.DirPath, "workspaces", workspace)
}

func (s *Storage) storeServer(workspace string, data *models.TreeServer) error {
func (s *SingleStorage) storeServer(workspace string, data *models.TreeServer) error {
var (
path = filepath.Join(s.workspacePath(workspace), "server")
server adminmodels.Server
Expand Down
34 changes: 20 additions & 14 deletions internal/cac/storage/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,34 +440,40 @@ module.exports = async function(context) {

for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
st := storage.InitStorage(storage.Configuration{
DirPath: t.TempDir(),
st, err := storage.InitMultiStorage(storage.MultiStorageConfiguration{
DirPath: []string{t.TempDir(), t.TempDir()},
})

err := st.Store("demo", tc.data)
require.NoError(t, err)

err = st.Store("demo", tc.data)
require.NoError(t, err)

var files []string
err = filepath.Walk(st.Config.DirPath, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}

if !info.IsDir() {
if path, err = filepath.Rel(st.Config.DirPath, path); err != nil {
for _, dir := range st.Config.DirPath {
err = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}

files = append(files, path)
}
return nil
})
if !info.IsDir() {
if path, err = filepath.Rel(dir, path); err != nil {
return err
}

files = append(files, path)
}
return nil
})
}

require.NoError(t, err)
require.ElementsMatch(t, slices.Compact(append(tc.files, "workspaces/demo/server.yaml")), files)

for _, f := range tc.files {
bts, err := os.ReadFile(filepath.Join(st.Config.DirPath, f))
// using first dirpath as multi storage stores everything there
bts, err := os.ReadFile(filepath.Join(st.Config.DirPath[0], f))
require.NoError(t, err)

if tc.assert != nil {
Expand Down