Skip to content

Commit

Permalink
Merge pull request #20 from 0verbyte/feature/18-env-var-tagging
Browse files Browse the repository at this point in the history
Feature/18 env var tagging
  • Loading branch information
0verbyte authored Sep 12, 2023
2 parents ef7d008 + 326dcb3 commit d02e702
Show file tree
Hide file tree
Showing 7 changed files with 251 additions and 19 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@ Note: mold expects an array of environment variables with the following structur
type: string
# whether the variable is required or not. If required is true, then mold will ask to fill the value.
required: false
# tags are used to filter environment variables. An OR expression will be applied to these tags.
# List syntax can also be used to define tags.
tags: []
```
The following command line options are available for mold.
```
% ./bin/mold -h
Usage of mold (v0.1.0):
Usage of mold (dev-unknown-unknown):
-debug
Enables debug logging
-output string
Where environment variables will be written. File path or stdout (default "stdout")
-tags string
Filter environment variables matching tags
-template string
Path to the mold environment template file (default "mold.yaml")
```
Expand Down
53 changes: 42 additions & 11 deletions internal/mold/mold.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,39 @@ type MoldTemplateVariable struct {
Value interface{} `yaml:"value"`
Type string `yaml:"type"`
Required bool `yaml:"required"`
Tags []string `yaml:"tags"`
}

func (m *MoldTemplateVariable) String() string {
return fmt.Sprintf("%s = %v (type=%s, required=%t)", m.Name, m.Value, m.Type, m.Required)
}

func (m *MoldTemplateVariable) AllTags() []string {
return m.Tags
}

func (m *MoldTemplateVariable) HasTag(tag string) bool {
for _, v := range m.Tags {
if v == tag {
return true
}
}
return false
}

func (m *MoldTemplateVariable) HasTags() bool {
return len(m.Tags) > 0
}

// MoldTemplate data representation for the MoldTemplate.
type MoldTemplate struct {
variables map[string]MoldTemplateVariable
variables []MoldTemplateVariable

promptReader *bufio.Reader
}

// New creates a new MoldTemplate from an io.Reader. Use the helper functions to read from the respective input.
func New(r io.Reader) (*MoldTemplate, error) {
func New(r io.Reader, tags *[]string) (*MoldTemplate, error) {
b, err := io.ReadAll(r)
if err != nil {
return nil, err
Expand All @@ -56,12 +74,27 @@ func New(r io.Reader) (*MoldTemplate, error) {
return nil, err
}

moldTemplateVariables := make(map[string]MoldTemplateVariable)
moldTemplateVariables := []MoldTemplateVariable{}
for _, moldTemplateVariable := range moldTemplate {
if moldTemplateVariable.Name == "" {
return nil, ErrMissingVariableName
}

var skipEnvVar bool
if tags != nil && moldTemplateVariable.HasTags() {
skipEnvVar = true
for _, tag := range *tags {
if moldTemplateVariable.HasTag(tag) {
skipEnvVar = false
break
}
}
}

if skipEnvVar {
continue
}

// Check for type constraint on the value field
switch moldTemplateVariable.Value.(type) {
case int, float32, float64:
Expand All @@ -78,7 +111,7 @@ func New(r io.Reader) (*MoldTemplate, error) {
}
}

moldTemplateVariables[moldTemplateVariable.Name] = moldTemplateVariable
moldTemplateVariables = append(moldTemplateVariables, moldTemplateVariable)
}

return &MoldTemplate{
Expand Down Expand Up @@ -152,19 +185,17 @@ func (m *MoldTemplate) Generate() error {

// GetVariable gets a MoldTemplateVariable by key. If the key does not exist an error will be returned.
func (m *MoldTemplate) GetVariable(key string) (*MoldTemplateVariable, error) {
if v, ok := m.variables[key]; ok {
return &v, nil
for _, v := range m.variables {
if v.Name == key {
return &v, nil
}
}
return nil, ErrEnvironmentVariableDoesNotExist
}

// GetAllVariables returns all the variables in the mold.
func (m *MoldTemplate) GetAllVariables() []MoldTemplateVariable {
variables := []MoldTemplateVariable{}
for _, v := range m.variables {
variables = append(variables, v)
}
return variables
return m.variables
}

func (m *MoldTemplate) WriteEnvironment(w Writer) error {
Expand Down
188 changes: 184 additions & 4 deletions internal/mold/mold_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestMoldNew(t *testing.T) {
required: false
`

if _, err := New(strings.NewReader(moldTemplate)); err != nil {
if _, err := New(strings.NewReader(moldTemplate), nil); err != nil {
t.Errorf("Failed to create new mold: %v", err)
return
}
Expand All @@ -37,7 +37,7 @@ func TestCheckKeysInMold(t *testing.T) {
required: false
`

mold, err := New(strings.NewReader(moldTemplate))
mold, err := New(strings.NewReader(moldTemplate), nil)
if err != nil {
t.Errorf("Failed to create new mold: %v", err)
return
Expand Down Expand Up @@ -75,7 +75,7 @@ func TestVerifyTypeConstraint(t *testing.T) {
required: false
`

if _, err := New(strings.NewReader(moldTemplate)); err != ErrInvalidDataType {
if _, err := New(strings.NewReader(moldTemplate), nil); err != ErrInvalidDataType {
t.Errorf("Failed to create new mold: %v", err)
return
}
Expand All @@ -94,7 +94,7 @@ func TestMoldPromptReader(t *testing.T) {
required: false
`

m, err := New(strings.NewReader(moldTemplate))
m, err := New(strings.NewReader(moldTemplate), nil)
if err != nil {
t.Errorf("Failed to create new mold: %v", err)
return
Expand Down Expand Up @@ -130,3 +130,183 @@ data`
}
}
}

func TestHasTag(t *testing.T) {
var moldTemplate = `
- name: foo
value: "bar"
type: string
required: true
tags: ["local", "dev"]
- name: debug
value: true
type: boolean
required: false
`

m, err := New(strings.NewReader(moldTemplate), nil)
if err != nil {
t.Errorf("Failed to create new mold: %v", err)
return
}

specs := []struct {
envVar string
input string
output bool
}{
{envVar: "foo", input: "local", output: true},
{envVar: "foo", input: "production", output: false},
}

for _, spec := range specs {
envVar, err := m.GetVariable(spec.envVar)
if err != nil {
t.Errorf("Got error: %v", err)
return
}
got := envVar.HasTag(spec.input)
if got != spec.output {
t.Errorf("Failed tag lookup for %s. Expected %t, got %t", spec.input, spec.output, got)
}
}
}

func TestTagsAll(t *testing.T) {
var moldTemplate = `
- name: foo
value: "bar"
type: string
required: true
tags: ["local", "dev"]
- name: debug
value: true
type: boolean
required: false
`

m, err := New(strings.NewReader(moldTemplate), nil)
if err != nil {
t.Errorf("Failed to create new mold: %v", err)
return
}

specs := []struct {
envVar string
output []string
}{
{envVar: "foo", output: []string{"local", "dev"}},
{envVar: "debug", output: []string{}},
}

contains := func(items []string, key string) bool {
for _, item := range items {
if key == item {
return true
}
}
return false
}

for _, spec := range specs {
envVar, err := m.GetVariable(spec.envVar)
if err != nil {
t.Errorf("Got error: %v", err)
return
}

gotTags := envVar.AllTags()
for _, expected := range spec.output {
if !contains(gotTags, expected) {
t.Errorf("Expected %s in tags, got tags %s", expected, strings.Join(gotTags, ", "))
}
}
}
}

func TestTagFiltering(t *testing.T) {
var moldTemplate = `
- name: foo
value: "bar"
type: string
required: true
tags: ["test"]
- name: debug
value: true
type: boolean
required: false
tags:
- debug
- local
`

m, err := New(strings.NewReader(moldTemplate), &[]string{"test"})
if err != nil {
t.Errorf("Failed to create new mold: %v", err)
return
}

specs := []struct {
envVar string
err error
}{
{envVar: "foo", err: nil},
{envVar: "debug", err: ErrEnvironmentVariableDoesNotExist},
}

for _, spec := range specs {
if _, err := m.GetVariable(spec.envVar); err != spec.err {
t.Errorf("Expected %+v, got %+v", spec.err, err)
}
}
}

func TestMultiTagFiltering(t *testing.T) {
var moldTemplate = `
- name: foo
value: "bar"
type: string
required: true
tags: ["test"]
- name: debug
value: true
type: boolean
required: false
tags:
- debug
- local
- name: log_type
value: stdout
type: string
required: false
tags:
- staging
- local
`

m, err := New(strings.NewReader(moldTemplate), &[]string{"test", "debug"})
if err != nil {
t.Errorf("Failed to create new mold: %v", err)
return
}

specs := []struct {
envVar string
err error
}{
{envVar: "foo", err: nil},
{envVar: "debug", err: nil},
{envVar: "log_type", err: ErrEnvironmentVariableDoesNotExist},
}

for _, spec := range specs {
if _, err := m.GetVariable(spec.envVar); err != spec.err {
t.Errorf("Expected %+v, got %+v", spec.err, err)
}
}
}
2 changes: 1 addition & 1 deletion internal/mold/std_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const (

type StdoutWriter struct{}

func (w *StdoutWriter) Write(envVars map[string]MoldTemplateVariable) error {
func (w *StdoutWriter) Write(envVars []MoldTemplateVariable) error {
lines := []string{}
for _, v := range envVars {
valueFmt := "%s"
Expand Down
2 changes: 1 addition & 1 deletion internal/mold/writer.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package mold

type Writer interface {
Write(map[string]MoldTemplateVariable) error
Write([]MoldTemplateVariable) error
}
Loading

0 comments on commit d02e702

Please sign in to comment.