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

[WIP] Add labels support to node_pools #205

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
125 changes: 125 additions & 0 deletions google/config_builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package google

import (
"bytes"
"fmt"
)

// ConfigBuilder is a helper class for generating Terraform config strings for use in tests.
type ConfigBuilder struct {
// ResourceName is the name of the resource (e.g. the 'foo' in 'resource "google_compute_instance" "foo" {').
ResourceName string
// ResourceType is the type of the resource (e.g. the 'google_compute_instance' in 'resource
// "google_compute_instance" "foo" {').
ResourceType string
// Attributes contains a mapping between all key/value pairs.
Attributes map[string]interface{}
}

// NewResourceConfigBuilder creates a ConfigBuilder for a resource with the provided type and name.
func NewResourceConfigBuilder(typ, name string) *ConfigBuilder {
return &ConfigBuilder{ResourceName: name, ResourceType: typ, Attributes: map[string]interface{}{}}
}

// NewNestedConfig is used for nesting maps (e.g. if you wanted to add a set of key/values for labels, you'd do
// something like:
//
// x := NewResourceConfigBuilder("google_container_cluster", "cluster-" + acctest.RandString(10)).
// WithAttribute("labels", NewNestedConfig().
// WithAttribute("my_label", "my_value"))
func NewNestedConfig() *ConfigBuilder {
return &ConfigBuilder{Attributes: map[string]interface{}{}}
}

// WithResourceName sets the Terraform resource name.
func (rb *ConfigBuilder) WithResourceName(name string) *ConfigBuilder {
rb.ResourceName = name
return rb
}

// WithResourceType sets the Terraform resource type.
func (rb *ConfigBuilder) WithResourceType(typ string) *ConfigBuilder {
rb.ResourceType = typ
return rb
}

// WithAttribute sets an attribute on the resource. Anything that implements the Stringer interface or is a primitive
// can be used here. See NewNestedConfig() as well for an example on how to embed an additional map structure.
func (rb *ConfigBuilder) WithAttribute(key string, obj interface{}) *ConfigBuilder {
rb.Attributes[key] = obj
return rb
}

// Name returns the "name" attribute (commonly used in GCP resources).
func (rb ConfigBuilder) Name() string {
return rb.Attributes["name"].(string)
}

// WithName sets the "name" attribute (commonly used in GCP resources).
func (rb *ConfigBuilder) WithName(name string) *ConfigBuilder {
rb.Attributes["name"] = name
return rb
}

// Zone returns the "zone" attribute (commonly used in GCP resources).
func (rb ConfigBuilder) Zone() string {
return rb.Attributes["zone"].(string)
}

// WithZone sets the "zone" attribute (commonly used in GCP resources).
func (rb *ConfigBuilder) WithZone(zone string) *ConfigBuilder {
rb.Attributes["zone"] = zone
return rb
}

// String returns a pretty-printed string of the config.
func (rb ConfigBuilder) String() string {
return rb.StringWithIndent(0, 4, false)
}

type StringWithIndenter interface {
// StringWithIndent is like String, but allows for control of multiline resources. 'indent' represents how much to
// indent every line. 'indentLen' controls how much indenting to add when adding additional indentation. 'embedded'
// represents whether or not the produced string is embedded in a larger structure, in which case the leading
// indentation on the first line is suppressed.
StringWithIndent(indent, indentLen int, embedded bool) string
}

func (rb ConfigBuilder) StringWithIndent(indent, indentLen int, embedded bool) string {
var buf bytes.Buffer

if !embedded {
buf.WriteString(spacesOfLength(indent))
}
if rb.ResourceName != "" && rb.ResourceType != "" {
buf.WriteString(fmt.Sprintf("resource \"%s\" \"%s\" ", rb.ResourceType, rb.ResourceName))
}
buf.WriteString("{\n")
for k, v := range rb.Attributes {
buf.WriteString(spacesOfLength(indent + indentLen))
buf.WriteString(fmt.Sprintf("%s ", k))
switch v.(type) {
case int:
buf.WriteString(fmt.Sprintf("= %d", v))
case string:
buf.WriteString(fmt.Sprintf("= \"%s\"", v))
case StringWithIndenter:
buf.WriteString("" + v.(StringWithIndenter).StringWithIndent(indent+indentLen, indentLen, true))
case fmt.Stringer:
buf.WriteString("= " + v.(fmt.Stringer).String())
}
buf.WriteString("\n")
}

buf.WriteString(spacesOfLength(indent) + "}\n")
return buf.String()
}

// spacesOfLength is a helper function for generating a string consisting of just spaces.
func spacesOfLength(len int) string {
sp := make([]byte, len)
for idx := range sp {
sp[idx] = byte(' ')
}
return string(sp)
}
87 changes: 44 additions & 43 deletions google/resource_container_node_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,35 @@ package google

import (
"fmt"
"strconv"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"google.golang.org/api/container/v1"
)

func TestAccContainerNodePool_basic(t *testing.T) {
name := "tf-nodepool-test-" + acctest.RandString(10)
zone := "us-central1-a"
clusterConfig := SomeGoogleContainerCluster()
nodepoolConfig := SomeGoogleContainerNodePool(clusterConfig).
WithAttribute("name", name).
WithAttribute("zone", zone).
WithAttribute("initial_node_count", 2)

var nodePool container.NodePool

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckContainerNodePoolDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccContainerNodePool_basic,
{
Config: clusterConfig.String() + nodepoolConfig.String(),
Check: resource.ComposeTestCheckFunc(
testAccCheckContainerNodePoolMatches("google_container_node_pool.np"),
testAccCheckContainerNodePoolExists(zone, clusterConfig.Name(), name, &nodePool),
testAccCheckContainerNodePoolHasInitialNodeCount(&nodePool, 2),
),
},
},
Expand All @@ -45,57 +56,47 @@ func testAccCheckContainerNodePoolDestroy(s *terraform.State) error {
return nil
}

func testAccCheckContainerNodePoolMatches(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
func SomeGoogleContainerCluster() *ConfigBuilder {
return NewResourceConfigBuilder("google_container_cluster", "cluster-"+acctest.RandString(10)).
WithAttribute("name", "tf-cluster-nodepool-test-"+acctest.RandString(10)).
WithAttribute("zone", "us-central1-a").
WithAttribute("initial_node_count", 3).
WithAttribute("master_auth", NewNestedConfig().
WithAttribute("username", "mr.yoda").
WithAttribute("password", "adoy.rm"))
}

rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
func SomeGoogleContainerNodePool(cluster *ConfigBuilder) *ConfigBuilder {
return NewResourceConfigBuilder("google_container_node_pool", "nodepool-"+acctest.RandString(10)).
WithAttribute("name", "tf-nodepool-test-"+acctest.RandString(10)).
WithAttribute("zone", "us-central1-a").
WithAttribute("cluster", fmt.Sprintf("${google_container_cluster.%s.name}", cluster.ResourceName)).
WithAttribute("initial_node_count", 2)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
func testAccCheckContainerNodePoolExists(zone, clusterName, nodePoolName string, nodePool *container.NodePool) resource.TestCheckFunc {
return func(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)

attributes := rs.Primary.Attributes
found, err := config.clientContainer.Projects.Zones.Clusters.NodePools.Get(
config.Project, attributes["zone"], attributes["cluster"], attributes["name"]).Do()
found, err := config.clientContainer.Projects.Zones.Clusters.NodePools.Get(config.Project, zone, clusterName, nodePoolName).Do()
if err != nil {
return err
}

if found.Name != attributes["name"] {
return fmt.Errorf("NodePool not found")
if found == nil {
return fmt.Errorf("Unable to find resource")
}

inc, err := strconv.Atoi(attributes["initial_node_count"])
if err != nil {
return err
}
if found.InitialNodeCount != int64(inc) {
return fmt.Errorf("Mismatched initialNodeCount. TF State: %s. GCP State: %d",
attributes["initial_node_count"], found.InitialNodeCount)
}
*nodePool = *found
return nil
}
}

var testAccContainerNodePool_basic = fmt.Sprintf(`
resource "google_container_cluster" "cluster" {
name = "tf-cluster-nodepool-test-%s"
zone = "us-central1-a"
initial_node_count = 3

master_auth {
username = "mr.yoda"
password = "adoy.rm"
func testAccCheckContainerNodePoolHasInitialNodeCount(nodePool *container.NodePool, count int64) resource.TestCheckFunc {
return func(s *terraform.State) error {
if nodePool.InitialNodeCount != count {
return fmt.Errorf("Expected initial_node_count %d but found %d", count, nodePool.InitialNodeCount)
}
return nil
}
}

resource "google_container_node_pool" "np" {
name = "tf-nodepool-test-%s"
zone = "us-central1-a"
cluster = "${google_container_cluster.cluster.name}"
initial_node_count = 2
}`, acctest.RandString(10), acctest.RandString(10))