Skip to content

Commit

Permalink
Add support for Google RuntimeConfig (hashicorp#315)
Browse files Browse the repository at this point in the history
* Vendor runtimeconfig

* Add support for RuntimeConfig config and variable resources

This allows users to create/manage Google RuntimeConfig resources and
variables. More information here:
https://cloud.google.com/deployment-manager/runtime-configurator/

Closes hashicorp#236

* Remove typo

* Use top-level declaration rather than init()

* Cleanup testing-related code by using ConflictsWith

Also adds better comments around how update works
  • Loading branch information
selmanj authored Aug 14, 2017
1 parent d27cb0e commit 152dadf
Show file tree
Hide file tree
Showing 12 changed files with 6,555 additions and 0 deletions.
9 changes: 9 additions & 0 deletions google/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"google.golang.org/api/dns/v1"
"google.golang.org/api/iam/v1"
"google.golang.org/api/pubsub/v1"
"google.golang.org/api/runtimeconfig/v1beta1"
"google.golang.org/api/servicemanagement/v1"
"google.golang.org/api/sourcerepo/v1"
"google.golang.org/api/spanner/v1"
Expand All @@ -46,6 +47,7 @@ type Config struct {
clientDns *dns.Service
clientPubsub *pubsub.Service
clientResourceManager *cloudresourcemanager.Service
clientRuntimeconfig *runtimeconfig.Service
clientSpanner *spanner.Service
clientSourceRepo *sourcerepo.Service
clientStorage *storage.Service
Expand Down Expand Up @@ -177,6 +179,13 @@ func (c *Config) loadAndValidate() error {
}
c.clientResourceManager.UserAgent = userAgent

log.Printf("[INFO] Instantiating Google Cloud Runtimeconfig Client...")
c.clientRuntimeconfig, err = runtimeconfig.New(client)
if err != nil {
return err
}
c.clientRuntimeconfig.UserAgent = userAgent

log.Printf("[INFO] Instantiating Google Cloud IAM Client...")
c.clientIAM, err = iam.New(client)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ func Provider() terraform.ResourceProvider {
"google_project_services": resourceGoogleProjectServices(),
"google_pubsub_topic": resourcePubsubTopic(),
"google_pubsub_subscription": resourcePubsubSubscription(),
"google_runtimeconfig_config": resourceRuntimeconfigConfig(),
"google_runtimeconfig_variable": resourceRuntimeconfigVariable(),
"google_service_account": resourceGoogleServiceAccount(),
"google_storage_bucket": resourceStorageBucket(),
"google_storage_bucket_acl": resourceStorageBucketAcl(),
Expand Down
146 changes: 146 additions & 0 deletions google/resource_runtimeconfig_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package google

import (
"fmt"
"regexp"

"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/runtimeconfig/v1beta1"
)

var runtimeConfigFullName *regexp.Regexp = regexp.MustCompile("^projects/([^/]+)/configs/(.+)$")

func resourceRuntimeconfigConfig() *schema.Resource {
return &schema.Resource{
Create: resourceRuntimeconfigConfigCreate,
Read: resourceRuntimeconfigConfigRead,
Update: resourceRuntimeconfigConfigUpdate,
Delete: resourceRuntimeconfigConfigDelete,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateGCPName,
},

"description": {
Type: schema.TypeString,
Optional: true,
},

"project": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
}
}

func resourceRuntimeconfigConfigCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

project, err := getProject(d, config)
if err != nil {
return err
}

name := d.Get("name").(string)
fullName := resourceRuntimeconfigFullName(project, name)
runtimeConfig := runtimeconfig.RuntimeConfig{
Name: fullName,
}

if val, ok := d.GetOk("description"); ok {
runtimeConfig.Description = val.(string)
}

_, err = config.clientRuntimeconfig.Projects.Configs.Create("projects/"+project, &runtimeConfig).Do()

if err != nil {
return err
}
d.SetId(fullName)

return nil
}

func resourceRuntimeconfigConfigRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

fullName := d.Id()
runConfig, err := config.clientRuntimeconfig.Projects.Configs.Get(fullName).Do()
if err != nil {
return err
}

project, name, err := resourceRuntimeconfigParseFullName(runConfig.Name)
if err != nil {
return err
}
// Check to see if project matches our current defined value - if it doesn't, we'll explicitly set it
curProject, err := getProject(d, config)
if err != nil {
return err
}
if project != curProject {
d.Set("project", project)
}

d.Set("name", name)
d.Set("description", runConfig.Description)

return nil
}

func resourceRuntimeconfigConfigUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

// Update works more like an 'overwrite' method - we build a new runtimeconfig.RuntimeConfig struct and it becomes
// the new config. This means our Update logic looks an awful lot like Create (and hence, doesn't use
// schema.ResourceData.hasChange()).
fullName := d.Id()
runtimeConfig := runtimeconfig.RuntimeConfig{
Name: fullName,
}
if v, ok := d.GetOk("description"); ok {
runtimeConfig.Description = v.(string)
}

_, err := config.clientRuntimeconfig.Projects.Configs.Update(fullName, &runtimeConfig).Do()
if err != nil {
return err
}
return nil
}

func resourceRuntimeconfigConfigDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

fullName := d.Id()

_, err := config.clientRuntimeconfig.Projects.Configs.Delete(fullName).Do()
if err != nil {
return err
}
d.SetId("")
return nil
}

// resourceRuntimeconfigFullName turns a given project and a 'short name' for a runtime config into a full name
// (e.g. projects/my-project/configs/my-config).
func resourceRuntimeconfigFullName(project, name string) string {
return fmt.Sprintf("projects/%s/configs/%s", project, name)
}

// resourceRuntimeconfigParseFullName parses a full name (e.g. projects/my-project/configs/my-config) by parsing out the
// project and the short name. Returns "", "", nil upon error.
func resourceRuntimeconfigParseFullName(fullName string) (project, name string, err error) {
matches := runtimeConfigFullName.FindStringSubmatch(fullName)
if matches == nil {
return "", "", fmt.Errorf("Given full name doesn't match expected regexp; fullname = '%s'", fullName)
}
return matches[1], matches[2], nil
}
159 changes: 159 additions & 0 deletions google/resource_runtimeconfig_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package google

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"google.golang.org/api/runtimeconfig/v1beta1"
)

func TestAccRuntimeconfigConfig_basic(t *testing.T) {
var runtimeConfig runtimeconfig.RuntimeConfig
configName := fmt.Sprintf("runtimeconfig-test-%s", acctest.RandString(10))
description := "my test description"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRuntimeconfigConfigDestroy,
Steps: []resource.TestStep{
{
Config: testAccRuntimeconfigConfig_basicDescription(configName, description),
Check: resource.ComposeTestCheckFunc(
testAccCheckRuntimeConfigExists(
"google_runtimeconfig_config.foobar", &runtimeConfig),
testAccCheckRuntimeConfigDescription(&runtimeConfig, description),
),
},
},
})
}

func TestAccRuntimeconfig_update(t *testing.T) {
var runtimeConfig runtimeconfig.RuntimeConfig
configName := fmt.Sprintf("runtimeconfig-test-%s", acctest.RandString(10))
firstDescription := "my test description"
secondDescription := "my updated test description"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRuntimeconfigConfigDestroy,
Steps: []resource.TestStep{
{
Config: testAccRuntimeconfigConfig_basicDescription(configName, firstDescription),
Check: resource.ComposeTestCheckFunc(
testAccCheckRuntimeConfigExists(
"google_runtimeconfig_config.foobar", &runtimeConfig),
testAccCheckRuntimeConfigDescription(&runtimeConfig, firstDescription),
),
}, {
Config: testAccRuntimeconfigConfig_basicDescription(configName, secondDescription),
Check: resource.ComposeTestCheckFunc(
testAccCheckRuntimeConfigExists(
"google_runtimeconfig_config.foobar", &runtimeConfig),
testAccCheckRuntimeConfigDescription(&runtimeConfig, secondDescription),
),
},
},
})
}

func TestAccRuntimeconfig_updateEmptyDescription(t *testing.T) {
var runtimeConfig runtimeconfig.RuntimeConfig
configName := fmt.Sprintf("runtimeconfig-test-%s", acctest.RandString(10))
description := "my test description"

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckRuntimeconfigConfigDestroy,
Steps: []resource.TestStep{
{
Config: testAccRuntimeconfigConfig_basicDescription(configName, description),
Check: resource.ComposeTestCheckFunc(
testAccCheckRuntimeConfigExists(
"google_runtimeconfig_config.foobar", &runtimeConfig),
testAccCheckRuntimeConfigDescription(&runtimeConfig, description),
),
}, {
Config: testAccRuntimeconfigConfig_emptyDescription(configName),
Check: resource.ComposeTestCheckFunc(
testAccCheckRuntimeConfigExists(
"google_runtimeconfig_config.foobar", &runtimeConfig),
testAccCheckRuntimeConfigDescription(&runtimeConfig, ""),
),
},
},
})
}

func testAccCheckRuntimeConfigDescription(runtimeConfig *runtimeconfig.RuntimeConfig, description string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if runtimeConfig.Description != description {
return fmt.Errorf("On runtime config '%s', expected description '%s', but found '%s'",
runtimeConfig.Name, description, runtimeConfig.Description)
}
return nil
}
}

func testAccCheckRuntimeConfigExists(resourceName string, runtimeConfig *runtimeconfig.RuntimeConfig) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("Not found: %s", resourceName)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}

config := testAccProvider.Meta().(*Config)

found, err := config.clientRuntimeconfig.Projects.Configs.Get(rs.Primary.ID).Do()
if err != nil {
return err
}

*runtimeConfig = *found

return nil
}
}

func testAccCheckRuntimeconfigConfigDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)

for _, rs := range s.RootModule().Resources {
if rs.Type != "google_runtimeconfig_config" {
continue
}

_, err := config.clientRuntimeconfig.Projects.Configs.Get(rs.Primary.ID).Do()

if err == nil {
return fmt.Errorf("Runtimeconfig still exists")
}
}

return nil
}

func testAccRuntimeconfigConfig_basicDescription(name, description string) string {
return fmt.Sprintf(`
resource "google_runtimeconfig_config" "foobar" {
name = "%s"
description = "%s"
}`, name, description)
}

func testAccRuntimeconfigConfig_emptyDescription(name string) string {
return fmt.Sprintf(`
resource "google_runtimeconfig_config" "foobar" {
name = "%s"
}`, name)
}
Loading

0 comments on commit 152dadf

Please sign in to comment.