Skip to content

Commit

Permalink
Merge pull request #46 from rodcloutier/value_files_support
Browse files Browse the repository at this point in the history
[Feature] Added multiple values files support for release
  • Loading branch information
mcuadros authored Feb 12, 2018
2 parents 4265847 + 904c23a commit f7d11f7
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 16 deletions.
7 changes: 5 additions & 2 deletions docs/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ A Chart is a Helm package. It contains all of the resource definitions necessary
resource "helm_release" "example" {
name = "my_redis"
chart = "redis"
values = [
"${file("values.yaml")}"
]
}
```

Expand All @@ -23,7 +26,7 @@ The following arguments are supported:
* `chart` - (Required) Chart name to be installed.
* `devel` - (Optional) Use chart development versions, too. Equivalent to version '>0.0.0-0'. If version is set, this is ignored.
* `version` - (Optional) Specify the exact chart version to install. If this is not specified, the latest version is installed.
* `values` - (Optional) Values in raw yaml file to pass to helm.
* `values` - (Optional) List of values in raw yaml to pass to helm. Values will be merged, in order, as Helm does with multiple `-f` options.
* `set` - (Optional) Value block with custom values to be merge with the values.yaml.
* `namespace` - (Optional) Namespace to install the release into.
* `verify` - (Optional) Verify the package before installing it.
Expand Down Expand Up @@ -55,7 +58,7 @@ The `metadata` block supports:
* `revision` - Version is an int32 which represents the version of the release.
* `status` - Status of the release.
* `version` - A SemVer 2 conformant version string of the chart.

* `values` - The compounded values from `values` and `set`

## Import

Expand Down
3 changes: 3 additions & 0 deletions helm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
return NewMeta(d)
}

// Meta is the meta information structure for the provider
type Meta struct {
Settings *helm_env.EnvSettings
TLSConfig *tls.Config
Expand All @@ -201,6 +202,7 @@ type Meta struct {
sync.Mutex
}

// NewMeta will construct a new Meta from the provided ResourceData
func NewMeta(d *schema.ResourceData) (*Meta, error) {
m := &Meta{data: d}
m.buildSettings(m.data)
Expand Down Expand Up @@ -328,6 +330,7 @@ func getK8sConfig(d *schema.ResourceData) (clientcmd.ClientConfig, error) {
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides), nil
}

// GetHelmClient will return a new Helm client
func (m *Meta) GetHelmClient() (helm.Interface, error) {
if err := m.initialize(); err != nil {
return nil, err
Expand Down
69 changes: 58 additions & 11 deletions helm/resource_release.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"k8s.io/helm/pkg/strvals"
)

// ErrReleaseNotFound is the error when a Helm release is not found
var ErrReleaseNotFound = errors.New("release not found")

func resourceRelease() *schema.Resource {
Expand Down Expand Up @@ -63,9 +64,10 @@ func resourceRelease() *schema.Resource {
Description: "Use chart development versions, too. Equivalent to version '>0.0.0-0'. If version is set, this is ignored",
},
"values": {
Type: schema.TypeString,
Type: schema.TypeList,
Optional: true,
Description: "Values in raw yaml file to pass to helm.",
Description: "List of values in raw yaml file to pass to helm.",
Elem: &schema.Schema{Type: schema.TypeString},
},
"set": {
Type: schema.TypeSet,
Expand Down Expand Up @@ -168,6 +170,11 @@ func resourceRelease() *schema.Resource {
Computed: true,
Description: "A SemVer 2 conformant version string of the chart.",
},
"values": {
Type: schema.TypeString,
Computed: true,
Description: "The raw yaml values used for the chart.",
},
},
},
},
Expand Down Expand Up @@ -195,7 +202,7 @@ func prepareTillerForNewRelease(d *schema.ResourceData, c helm.Interface, name s

switch r.Info.Status.GetCode() {
case release.Status_DEPLOYED:
return setIdAndMetadataFromRelease(d, r)
return setIDAndMetadataFromRelease(d, r)
case release.Status_FAILED:
// delete and recreate it
debug("release %s status is FAILED deleting it", name)
Expand Down Expand Up @@ -266,7 +273,7 @@ func resourceReleaseCreate(d *schema.ResourceData, meta interface{}) error {
return err
}

return setIdAndMetadataFromRelease(d, res.Release)
return setIDAndMetadataFromRelease(d, res.Release)
}

func resourceReleaseRead(d *schema.ResourceData, meta interface{}) error {
Expand All @@ -283,10 +290,12 @@ func resourceReleaseRead(d *schema.ResourceData, meta interface{}) error {
return err
}

return setIdAndMetadataFromRelease(d, r)
// d.Set("values_source_detected_md5", d.Get("values_sources_md5"))

return setIDAndMetadataFromRelease(d, r)
}

func setIdAndMetadataFromRelease(d *schema.ResourceData, r *release.Release) error {
func setIDAndMetadataFromRelease(d *schema.ResourceData, r *release.Release) error {
d.SetId(r.Name)

return d.Set("metadata", []map[string]interface{}{{
Expand All @@ -296,6 +305,7 @@ func setIdAndMetadataFromRelease(d *schema.ResourceData, r *release.Release) err
"status": r.Info.Status.Code.String(),
"chart": r.Chart.Metadata.Name,
"version": r.Chart.Metadata.Version,
"values": r.Config.Raw,
}})
}

Expand Down Expand Up @@ -332,7 +342,7 @@ func resourceReleaseUpdate(d *schema.ResourceData, meta interface{}) error {
return err
}

return setIdAndMetadataFromRelease(d, res.Release)
return setIDAndMetadataFromRelease(d, res.Release)
}

func resourceReleaseDelete(d *schema.ResourceData, meta interface{}) error {
Expand Down Expand Up @@ -427,13 +437,50 @@ func getChart(d *schema.ResourceData, m *Meta) (c *chart.Chart, path string, err
return
}

// Merges source and destination map, preferring values from the source map
// Taken from github.com/helm/cmd/install.go
func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
for k, v := range src {
// If the key doesn't exist already, then just set the key to that value
if _, exists := dest[k]; !exists {
dest[k] = v
continue
}
nextMap, ok := v.(map[string]interface{})
// If it isn't another map, overwrite the value
if !ok {
dest[k] = v
continue
}
// If the key doesn't exist already, then just set the key to that value
if _, exists := dest[k]; !exists {
dest[k] = nextMap
continue
}
// Edge case: If the key exists in the destination, but isn't a map
destMap, isMap := dest[k].(map[string]interface{})
// If the source map has a map for this key, prefer it
if !isMap {
dest[k] = v
continue
}
// If we got to this point, it is a map in both, so merge them
dest[k] = mergeValues(destMap, nextMap)
}
return dest
}

func getValues(d *schema.ResourceData) ([]byte, error) {
base := map[string]interface{}{}

values := d.Get("values").(string)
if values != "" {
if err := yaml.Unmarshal([]byte(values), &base); err != nil {
return nil, err
for _, raw := range d.Get("values").([]interface{}) {
values := raw.(string)
if values != "" {
currentMap := map[string]interface{}{}
if err := yaml.Unmarshal([]byte(values), &currentMap); err != nil {
return nil, err
}
base = mergeValues(base, currentMap)
}
}

Expand Down
81 changes: 80 additions & 1 deletion helm/resource_release_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package helm
import (
"fmt"
"regexp"
"strconv"
"strings"
"sync"
"testing"

Expand Down Expand Up @@ -84,6 +86,54 @@ func TestAccResourceRelease_update(t *testing.T) {
})
}

func TestAccResourceRelease_updateValues(t *testing.T) {
resource.Test(t, resource.TestCase{
Providers: testAccProviders,
CheckDestroy: testAccCheckHelmReleaseDestroy,
Steps: []resource.TestStep{{
Config: testAccHelmReleaseConfigValues(testReleaseName, testNamespace, testReleaseName, "0.6.2", []string{"foo: bar"}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.revision", "1"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.version", "0.6.2"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.status", "DEPLOYED"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.values", "foo: bar\n"),
),
}, {
Config: testAccHelmReleaseConfigValues(testReleaseName, testNamespace, testReleaseName, "0.6.2", []string{"foo: baz"}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.revision", "2"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.version", "0.6.2"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.status", "DEPLOYED"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.values", "foo: baz\n"),
),
}},
})
}

func TestAccResourceRelease_updateMultipleValues(t *testing.T) {
resource.Test(t, resource.TestCase{
Providers: testAccProviders,
CheckDestroy: testAccCheckHelmReleaseDestroy,
Steps: []resource.TestStep{{
Config: testAccHelmReleaseConfigValues(testReleaseName, testNamespace, testReleaseName, "0.6.2", []string{"foo: bar"}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.revision", "1"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.version", "0.6.2"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.status", "DEPLOYED"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.values", "foo: bar\n"),
),
}, {
Config: testAccHelmReleaseConfigValues(testReleaseName, testNamespace, testReleaseName, "0.6.2", []string{"foo: bar", "foo:baz"}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.revision", "2"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.version", "0.6.2"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.status", "DEPLOYED"),
resource.TestCheckResourceAttr("helm_release.test", "metadata.0.values", "foo: baz\n"),
),
}},
})
}

func TestAccResourceRelease_repository(t *testing.T) {
resource.Test(t, resource.TestCase{
Providers: testAccProviders,
Expand Down Expand Up @@ -177,9 +227,29 @@ func testAccHelmReleaseConfigBasic(resource, ns, name, version string) string {
`, resource, name, ns, version)
}

func testAccHelmReleaseConfigValues(resource, ns, name, version string, values []string) string {
vals := make([]string, len(values))
for i, v := range values {
vals[i] = strconv.Quote(v)
}
return fmt.Sprintf(`
resource "helm_release" "%s" {
name = %q
namespace = %q
chart = "stable/mariadb"
version = %q
values = [ %q ]
}
`, resource, name, ns, version, strings.Join(vals, ","))
}

func TestGetValues(t *testing.T) {
d := resourceRelease().Data(nil)
d.Set("values", `foo: bar`)
d.Set("values", []string{
"foo: bar\nbaz: corge",
"first: present\nbaz: grault",
"second: present\nbaz: uier",
})
d.Set("set", []interface{}{
map[string]interface{}{"name": "foo", "value": "qux"},
})
Expand All @@ -200,6 +270,15 @@ func TestGetValues(t *testing.T) {
if base["foo"] != "qux" {
t.Fatalf("error merging values, expected %q, got %q", "qux", base["foo"])
}
if base["first"] != "present" {
t.Fatalf("error merging values from file, expected value file %q not read", "testdata/get_values_first.yaml")
}
if base["second"] != "present" {
t.Fatalf("error merging values from file, expected value file %q not read", "testdata/get_values_second.yaml")
}
if base["baz"] != "uier" {
t.Fatalf("error merging values from file, expected %q, got %q", "uier", base["baz"])
}
}

func testAccHelmReleaseConfigRepository(ns, name string) string {
Expand Down
5 changes: 3 additions & 2 deletions helm/resource_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"k8s.io/helm/pkg/repo"
)

// ErrRepositoryNotFound is the error when a Helm repository is not found
var ErrRepositoryNotFound = errors.New("repository not found")

func resourceRepository() *schema.Resource {
Expand Down Expand Up @@ -102,7 +103,7 @@ func resourceRepositoryRead(d *schema.ResourceData, meta interface{}) error {
return err
}

return setIdAndMetadataFromRepository(d, r)
return setIDAndMetadataFromRepository(d, r)
}

func resourceRepositoryDelete(d *schema.ResourceData, meta interface{}) error {
Expand All @@ -118,7 +119,7 @@ func resourceRepositoryDelete(d *schema.ResourceData, meta interface{}) error {
return nil
}

func setIdAndMetadataFromRepository(d *schema.ResourceData, r *repo.Entry) error {
func setIDAndMetadataFromRepository(d *schema.ResourceData, r *repo.Entry) error {
d.SetId(r.Name)
return d.Set("metadata", []map[string]interface{}{{
"name": r.Name,
Expand Down

0 comments on commit f7d11f7

Please sign in to comment.