Skip to content

Commit

Permalink
Merge pull request #2147 from terraform-providers/paddy_new_appengine
Browse files Browse the repository at this point in the history
Add google_app_engine_application resource.
  • Loading branch information
paddycarver authored Oct 3, 2018
2 parents 2156539 + 8f6d256 commit 69589e5
Show file tree
Hide file tree
Showing 6 changed files with 452 additions and 11 deletions.
1 change: 1 addition & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func Provider() terraform.ResourceProvider {
GeneratedRedisResourcesMap,
GeneratedResourceManagerResourcesMap,
map[string]*schema.Resource{
"google_app_engine_application": resourceAppEngineApplication(),
"google_bigquery_dataset": resourceBigQueryDataset(),
"google_bigquery_table": resourceBigQueryTable(),
"google_bigtable_instance": resourceBigtableInstance(),
Expand Down
285 changes: 285 additions & 0 deletions google/resource_app_engine_application.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
package google

import (
"fmt"
"log"

"github.com/hashicorp/terraform/helper/customdiff"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
appengine "google.golang.org/api/appengine/v1"
)

func resourceAppEngineApplication() *schema.Resource {
return &schema.Resource{
Create: resourceAppEngineApplicationCreate,
Read: resourceAppEngineApplicationRead,
Update: resourceAppEngineApplicationUpdate,
Delete: resourceAppEngineApplicationDelete,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

CustomizeDiff: customdiff.All(
appEngineApplicationLocationIDCustomizeDiff,
),

Schema: map[string]*schema.Schema{
"project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validateProjectID(),
},
"auth_domain": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"location_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
"northamerica-northeast1",
"us-central",
"us-west2",
"us-east1",
"us-east4",
"southamerica-east1",
"europe-west",
"europe-west2",
"europe-west3",
"asia-northeast1",
"asia-south1",
"australia-southeast1",
}, false),
},
"serving_status": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"UNSPECIFIED",
"SERVING",
"USER_DISABLED",
"SYSTEM_DISABLED",
}, false),
Computed: true,
},
"feature_settings": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: appEngineApplicationFeatureSettingsResource(),
},
"name": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"url_dispatch_rule": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: appEngineApplicationURLDispatchRuleResource(),
},
"code_bucket": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"default_hostname": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"default_bucket": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"gcr_domain": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}

func appEngineApplicationURLDispatchRuleResource() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"domain": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"path": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"service": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}

func appEngineApplicationFeatureSettingsResource() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"split_health_checks": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
},
}
}

func appEngineApplicationLocationIDCustomizeDiff(d *schema.ResourceDiff, meta interface{}) error {
old, new := d.GetChange("location_id")
if old != "" && old != new {
return fmt.Errorf("Cannot change location_id once the resource is created.")
}
return nil
}

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

project, err := getProject(d, config)
if err != nil {
return err
}
app, err := expandAppEngineApplication(d, project)
if err != nil {
return err
}
log.Printf("[DEBUG] Creating App Engine App")
op, err := config.clientAppEngine.Apps.Create(app).Do()
if err != nil {
return fmt.Errorf("Error creating App Engine application: %s", err.Error())
}

d.SetId(project)

// Wait for the operation to complete
waitErr := appEngineOperationWait(config.clientAppEngine, op, project, "App Engine app to create")
if waitErr != nil {
d.SetId("")
return waitErr
}
log.Printf("[DEBUG] Created App Engine App")

return resourceAppEngineApplicationRead(d, meta)
}

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

app, err := config.clientAppEngine.Apps.Get(pid).Do()
if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("App Engine Application %q", pid))
}
d.Set("auth_domain", app.AuthDomain)
d.Set("code_bucket", app.CodeBucket)
d.Set("default_bucket", app.DefaultBucket)
d.Set("default_hostname", app.DefaultHostname)
d.Set("location_id", app.LocationId)
d.Set("name", app.Name)
d.Set("serving_status", app.ServingStatus)
d.Set("project", pid)
dispatchRules, err := flattenAppEngineApplicationDispatchRules(app.DispatchRules)
if err != nil {
return err
}
err = d.Set("url_dispatch_rule", dispatchRules)
if err != nil {
return fmt.Errorf("Error setting dispatch rules in state. This is a bug, please report it at https://github.com/terraform-providers/terraform-provider-google/issues. Error is:\n%s", err.Error())
}
featureSettings, err := flattenAppEngineApplicationFeatureSettings(app.FeatureSettings)
if err != nil {
return err
}
err = d.Set("feature_settings", featureSettings)
if err != nil {
return fmt.Errorf("Error setting feature settings in state. This is a bug, please report it at https://github.com/terraform-providers/terraform-provider-google/issues. Error is:\n%s", err.Error())
}
return nil
}

func resourceAppEngineApplicationUpdate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
pid := d.Id()
app, err := expandAppEngineApplication(d, pid)
if err != nil {
return err
}
log.Printf("[DEBUG] Updating App Engine App")
op, err := config.clientAppEngine.Apps.Patch(pid, app).UpdateMask("authDomain,servingStatus,featureSettings.splitHealthChecks").Do()
if err != nil {
return fmt.Errorf("Error updating App Engine application: %s", err.Error())
}

// Wait for the operation to complete
waitErr := appEngineOperationWait(config.clientAppEngine, op, pid, "App Engine app to update")
if waitErr != nil {
return waitErr
}
log.Printf("[DEBUG] Updated App Engine App")

return resourceAppEngineApplicationRead(d, meta)
}

func resourceAppEngineApplicationDelete(d *schema.ResourceData, meta interface{}) error {
log.Println("[WARN] App Engine applications cannot be destroyed once created. The project must be deleted to delete the application.")
return nil
}

func expandAppEngineApplication(d *schema.ResourceData, project string) (*appengine.Application, error) {
result := &appengine.Application{
AuthDomain: d.Get("auth_domain").(string),
LocationId: d.Get("location_id").(string),
Id: project,
GcrDomain: d.Get("gcr_domain").(string),
ServingStatus: d.Get("serving_status").(string),
}
featureSettings, err := expandAppEngineApplicationFeatureSettings(d)
if err != nil {
return nil, err
}
result.FeatureSettings = featureSettings
return result, nil
}

func expandAppEngineApplicationFeatureSettings(d *schema.ResourceData) (*appengine.FeatureSettings, error) {
blocks := d.Get("feature_settings").([]interface{})
if len(blocks) < 1 {
return nil, nil
}
return &appengine.FeatureSettings{
SplitHealthChecks: d.Get("feature_settings.0.split_health_checks").(bool),
// force send SplitHealthChecks, so if it's set to false it still gets disabled
ForceSendFields: []string{"SplitHealthChecks"},
}, nil
}

func flattenAppEngineApplicationFeatureSettings(settings *appengine.FeatureSettings) ([]map[string]interface{}, error) {
if settings == nil {
return []map[string]interface{}{}, nil
}
result := map[string]interface{}{
"split_health_checks": settings.SplitHealthChecks,
}
return []map[string]interface{}{result}, nil
}

func flattenAppEngineApplicationDispatchRules(rules []*appengine.UrlDispatchRule) ([]map[string]interface{}, error) {
results := make([]map[string]interface{}, 0, len(rules))
for _, rule := range rules {
results = append(results, map[string]interface{}{
"domain": rule.Domain,
"path": rule.Path,
"service": rule.Service,
})
}
return results, nil
}
77 changes: 77 additions & 0 deletions google/resource_app_engine_application_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package google

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)

func TestAccAppEngineApplication_basic(t *testing.T) {
t.Parallel()

org := getTestOrgFromEnv(t)
pid := acctest.RandomWithPrefix("tf-test")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccAppEngineApplication_basic(pid, org),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("google_app_engine_application.acceptance", "url_dispatch_rule.#"),
resource.TestCheckResourceAttrSet("google_app_engine_application.acceptance", "name"),
resource.TestCheckResourceAttrSet("google_app_engine_application.acceptance", "code_bucket"),
resource.TestCheckResourceAttrSet("google_app_engine_application.acceptance", "default_hostname"),
resource.TestCheckResourceAttrSet("google_app_engine_application.acceptance", "default_bucket"),
),
},
{
ResourceName: "google_app_engine_application.acceptance",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccAppEngineApplication_update(pid, org),
},
{
ResourceName: "google_app_engine_application.acceptance",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccAppEngineApplication_basic(pid, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
}
resource "google_app_engine_application" "acceptance" {
project = "${google_project.acceptance.project_id}"
auth_domain = "hashicorptest.com"
location_id = "us-central"
serving_status = "SERVING"
}`, pid, pid, org)
}

func testAccAppEngineApplication_update(pid, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
}
resource "google_app_engine_application" "acceptance" {
project = "${google_project.acceptance.project_id}"
auth_domain = "tf-test.club"
location_id = "us-central"
serving_status = "USER_DISABLED"
}`, pid, pid, org)
}
Loading

0 comments on commit 69589e5

Please sign in to comment.