Skip to content

Commit

Permalink
Add base64 encoding support to file provider (#167)
Browse files Browse the repository at this point in the history
* Add base64 encoding support to file provider

Signed-off-by: Stepan Kokhanovskiy <[email protected]>

* Fix git fatal error in forks

Signed-off-by: Stepan Kokhanovskiy <[email protected]>

* Add tests for file provider

Signed-off-by: Stepan Kokhanovskiy <[email protected]>

---------

Signed-off-by: Stepan Kokhanovskiy <[email protected]>
Co-authored-by: Stepan Kokhanovskiy <[email protected]>
  • Loading branch information
skokhanovskiy and Stepan Kokhanovskiy authored Sep 21, 2023
1 parent b51a963 commit 601350d
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Version := $(shell git describe --tags --dirty)
Version := $(shell git describe --tags --dirty --always)
GitCommit := $(shell git rev-parse HEAD)
LDFLAGS := "-X main.version=$(Version) -X main.commit=$(GitCommit)"

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ Examples:
- `ref+file://foo/bar` loads the file at `foo/bar`
- `ref+file:///home/foo/bar` loads the file at `/home/foo/bar`
- `ref+file://foo/bar?encode=base64` loads the file at `foo/bar` and encodes its content to a base64 string
- `ref+file://some.yaml#/foo/bar` loads the YAML file at `some.yaml` and reads the value for the path `$.foo.bar`.
Let's say `some.yaml` contains `{"foo":{"bar":"BAR"}}`, `key1: ref+file://some.yaml#/foo/bar` results in `key1: BAR`.
Expand Down
28 changes: 25 additions & 3 deletions pkg/providers/file/file.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package file

import (
"encoding/base64"
"fmt"
"os"
"strings"

Expand All @@ -10,25 +12,45 @@ import (
)

type provider struct {
Encode string
fileReader func(string) ([]byte, error)
}

func New(cfg api.StaticConfig) *provider {
p := &provider{}
p.fileReader = readFile
p.Encode = cfg.String("encode")
if p.Encode == "" {
p.Encode = "raw"
}
return p
}

func readFile(name string) ([]byte, error) {
return os.ReadFile(name)
}

func (p *provider) GetString(key string) (string, error) {
res := ""
key = strings.TrimSuffix(key, "/")
bs, err := os.ReadFile(key)
bs, err := p.fileReader(key)
if err != nil {
return "", err
}
return string(bs), nil
switch p.Encode {
case "raw":
res = string(bs)
case "base64":
res = base64.StdEncoding.EncodeToString(bs)
default:
return "", fmt.Errorf("Unsupported encode parameter: '%s'.", p.Encode)
}
return res, nil
}

func (p *provider) GetStringMap(key string) (map[string]interface{}, error) {
key = strings.TrimSuffix(key, "/")
bs, err := os.ReadFile(key)
bs, err := p.fileReader(key)
if err != nil {
return nil, err
}
Expand Down
208 changes: 208 additions & 0 deletions pkg/providers/file/file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package file

import (
"encoding/base64"
"errors"
"fmt"
"testing"

"github.com/helmfile/vals/pkg/config"
)

const textFileContent = "file content"

const yamlFileContent = `
---
foo:
bar: baz
`

// Mock implementation of os.ReadFile for testing
func mockReadFile(name string) ([]byte, error) {
switch name {
case "path/to/file.txt":
return []byte(textFileContent), nil
case "path/to/empty_file.txt":
return []byte{}, nil
case "path/to/file.yaml":
return []byte(yamlFileContent), nil
case "path/to/error_file.txt":
return nil, errors.New("error reading file")
default:
return nil, errors.New("file not found")
}
}

func Test_provider_GetString(t *testing.T) {
type params struct {
Encode string
}
type args struct {
key string
}
tests := []struct {
name string
params params
args args
want string
wantErr bool
}{
{
name: "Encode parameter is empty",
params: params{
Encode: "",
},
args: args{
key: "path/to/file.txt",
},
want: textFileContent,
wantErr: false,
},
{
name: "Encode parameter is 'raw'",
params: params{
Encode: "raw",
},
args: args{
key: "path/to/file.txt",
},
want: textFileContent,
wantErr: false,
},
{
name: "Encode parameter is 'base64'",
params: params{
Encode: "base64",
},
args: args{
key: "path/to/file.txt",
},
want: base64.StdEncoding.EncodeToString([]byte(textFileContent)),
wantErr: false,
},
{
name: "File is empty",
params: params{
Encode: "raw",
},
args: args{
key: "path/to/empty_file.txt",
},
want: "",
wantErr: false,
},
{
name: "Error reading file",
params: params{
Encode: "raw",
},
args: args{
key: "path/to/error_file.txt",
},
want: "",
wantErr: true,
},
{
name: "File not found",
params: params{
Encode: "raw",
},
args: args{
key: "path/to/nonexistent_file.txt",
},
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create provider with mock
conf := map[string]interface{}{}
conf["encode"] = tt.params.Encode
p := New(config.MapConfig{M: conf})
p.fileReader = mockReadFile

got, err := p.GetString(tt.args.key)
if (err != nil) != tt.wantErr {
t.Errorf("provider.GetString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("provider.GetString() = %v, want %v", got, tt.want)
}
})
}
}

func Test_provider_GetStringMap(t *testing.T) {
type args struct {
key string
}
tests := []struct {
name string
args args
want map[string]interface{}
wantErr bool
}{
{
name: "Unmarshal valid yaml file",
args: args{
key: "path/to/file.yaml",
},
want: map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
},
},
wantErr: false,
},
{
name: "Unmarshal invalid yaml file",
args: args{
key: "path/to/file.txt",
},
want: nil,
wantErr: true,
},
{
name: "File is empty",
args: args{
key: "path/to/empty_file.txt",
},
want: map[string]interface{}{},
wantErr: false,
},
{
name: "Error reading file",
args: args{
key: "path/to/error_file.txt",
},
want: nil,
wantErr: true,
},
{
name: "File not found",
args: args{
key: "path/to/nonexistent_file.txt",
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
conf := map[string]interface{}{}
p := New(config.MapConfig{M: conf})
p.fileReader = mockReadFile

got, err := p.GetStringMap(tt.args.key)
if (err != nil) != tt.wantErr {
t.Errorf("provider.GetStringMap() error = %v, wantErr %v", err, tt.wantErr)
return
}
if fmt.Sprint(got) != fmt.Sprint(tt.want) {
t.Errorf("provider.GetStringMap() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 601350d

Please sign in to comment.