Skip to content

Commit

Permalink
Add tests for embedded structs
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholasjackson committed Feb 28, 2024
1 parent ebe8eb6 commit 849dbcf
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 0 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,43 @@ in the normal go way.
fmt.Println("loc other 2", conf.OtherDBConnections[1].Location)
```

### Defining shared fields for resources
It is common that you might have two resources that are similar but have some
differences. For example, you might have two `database`, `postgres` and `mysql`
that share some common fields like `location` and `port` but have some differences
that are specific to the implementation.

To enable code reuse you can define a `shared` struct that contains the common fields
and then embed this struct into the `postgres` and `mysql` structs.

To enable this you define a common type that embed the `ResourceBase` type and
then you can embed this type into the `postgres` and `mysql` types.
Note: you must use the `hcl:",remain"` tag to ensure that the fields from the shared
type.

```go
type DB struct {
// For a resource to be parsed by HCLConfig it needs to embed the ResourceInfo type and
// add the methods from the `Resource` interface
types.ResourceBase `hcl:",remain"`

Location string `hcl:"location,optional"`
Port int `hcl:"port,optional"`
}

type PostgreSQL struct {
DB `hcl:",remain"`

MaxLocks int `hcl:"max_locks"`
}

type MySQL struct {
DB `hcl:",remain"`

CacheSize int `hcl:"cache_size"`
}
```

## Variables

Variables allow dynamic values to be set in your configuration, they are defined
Expand Down
74 changes: 74 additions & 0 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/hashicorp/hcl/v2"
"github.com/jumppad-labs/hclconfig/errors"
"github.com/jumppad-labs/hclconfig/test_fixtures/embedded"
"github.com/jumppad-labs/hclconfig/test_fixtures/structs"
"github.com/jumppad-labs/hclconfig/types"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -1008,3 +1009,76 @@ func TestParseFileReturnsConfigErrorWhenInvalidFileFails(t *testing.T) {
ce := err.(*errors.ConfigError)
require.Len(t, ce.Errors, 1)
}

func TestParseDoesNotOverwiteWithMeta(t *testing.T) {
f, pathErr := filepath.Abs("./test_fixtures/embedded/config.hcl")
if pathErr != nil {
t.Fatal(pathErr)
}

p := NewParser(nil)
p.RegisterType(embedded.TypeContainer, &embedded.Container{})
p.RegisterType(embedded.TypeSidecar, &embedded.Sidecar{})

c, err := p.ParseFile(f)
require.NoError(t, err)

r1, err := c.FindResource("resource.container.mine")
require.NoError(t, err)

// test that when the meta is set it does not overwrite any
// existing fields when they have the same name
cont := r1.(*embedded.Container)
require.Equal(t, "resource.container.mine", cont.Meta.ID)
require.Equal(t, "mycontainer", cont.ID)
}

func TestParseHandlesCommonTypes(t *testing.T) {
f, pathErr := filepath.Abs("./test_fixtures/embedded/config.hcl")
if pathErr != nil {
t.Fatal(pathErr)
}

p := NewParser(nil)
p.RegisterType(embedded.TypeContainer, &embedded.Container{})
p.RegisterType(embedded.TypeSidecar, &embedded.Sidecar{})

c, err := p.ParseFile(f)
require.NoError(t, err)

r1, err := c.FindResource("resource.container.mine")
require.NoError(t, err)

cont := r1.(*embedded.Container)

// test embedded properties
require.Equal(t, "mine", cont.Meta.Name)
require.Equal(t, "mycontainer", cont.ID)
require.Contains(t, cont.Entrypoint, "echo")
require.Contains(t, cont.Command, "hello")
require.Equal(t, "value", cont.Env["NAME"])
require.Contains(t, cont.DNS, "container-dns")
require.True(t, cont.Privileged)
require.Equal(t, 5, cont.MaxRestartCount)

// test specific properties
require.Equal(t, "mycontainer", cont.ContainerID)

r2, err := c.FindResource("resource.sidecar.mine")
require.NoError(t, err)

side := r2.(*embedded.Sidecar)

// test embedded properties
require.Equal(t, "mine", side.Meta.Name)
require.Equal(t, "mycontainer", side.ID)
require.Contains(t, side.Entrypoint, "echo")
require.Contains(t, side.Command, "hello")
require.Equal(t, "value", side.Env["NAME"])
require.Contains(t, side.DNS, "container-dns")
require.False(t, side.Privileged)
require.Equal(t, 3, side.MaxRestartCount)

// test specific properties
require.Equal(t, "mysidecar", side.SidecarID)
}
31 changes: 31 additions & 0 deletions test_fixtures/embedded/config.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
resource "container" "mine" {
// common properties
id = "mycontainer"
entrypoint = ["echo"]
command = ["hello", "world"]
env = {
NAME = "value"
}
dns = ["container-dns"]
privileged = true
max_restart_count = 5

// specific container properties
container_id = "mycontainer"
}

resource "sidecar" "mine" {
// common properties
id = resource.container.mine.id
entrypoint = ["echo"]
command = ["hello", "world"]
env = {
NAME = "value"
}
dns = ["container-dns"]
privileged = false
max_restart_count = 3

// specific sidecar properties
sidecar_id = "mysidecar"
}
44 changes: 44 additions & 0 deletions test_fixtures/embedded/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package embedded

import "github.com/jumppad-labs/hclconfig/types"

type ContainerBase struct {
// embedded type holding name, etc
types.ResourceBase `hcl:",remain"`

ID string `hcl:"id,optional" json:"id,omitempty"`
Entrypoint []string `hcl:"entrypoint,optional" json:"entrypoint,omitempty"` // entrypoint to use when starting the container
Command []string `hcl:"command,optional" json:"command,omitempty"` // command to use when starting the container
Env map[string]string `hcl:"env,optional" json:"env,omitempty"` // environment variables to set when starting the container
DNS []string `hcl:"dns,optional" json:"dns,omitempty"` // Add custom DNS servers to the container
Volumes []Volume `hcl:"volume,block" json:"volumes,omitempty"` // volumes to attach to the container

Privileged bool `hcl:"privileged,optional" json:"privileged,omitempty"` // run the container in privileged mode?

MaxRestartCount int `hcl:"max_restart_count,optional" json:"max_restart_count,omitempty" mapstructure:"max_restart_count"`
}

type Volume struct {
Source string `hcl:"source" json:"source"` // source path on the local machine for the volume
Destination string `hcl:"destination" json:"destination"` // path to mount the volume inside the container
Type string `hcl:"type,optional" json:"type,omitempty"` // type of the volume to mount [bind, volume, tmpfs]
ReadOnly bool `hcl:"read_only,optional" json:"read_only,omitempty" mapstructure:"read_only"` // specify that the volume is mounted read only
BindPropagation string `hcl:"bind_propagation,optional" json:"bind_propagation,omitempty" mapstructure:"bind_propagation"` // propagation mode for bind mounts [shared, private, slave, rslave, rprivate]
BindPropagationNonRecursive bool `hcl:"bind_propagation_non_recursive,optional" json:"bind_propagation_non_recursive,omitempty" mapstructure:"bind_propagation_non_recursive"` // recursive bind mount, default true
}

const TypeContainer = "container"

type Container struct {
ContainerBase `hcl:",remain"`

ContainerID string `hcl:"container_id,optional" json:"container_id,omitempty"`
}

const TypeSidecar = "sidecar"

type Sidecar struct {
ContainerBase `hcl:",remain"`

SidecarID string `hcl:"sidecar_id,optional" json:"sidecar_id,omitempty"`
}

0 comments on commit 849dbcf

Please sign in to comment.