Skip to content

Commit 69589e5

Browse files
authored
Merge pull request #2147 from terraform-providers/paddy_new_appengine
Add google_app_engine_application resource.
2 parents 2156539 + 8f6d256 commit 69589e5

6 files changed

+452
-11
lines changed

google/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ func Provider() terraform.ResourceProvider {
107107
GeneratedRedisResourcesMap,
108108
GeneratedResourceManagerResourcesMap,
109109
map[string]*schema.Resource{
110+
"google_app_engine_application": resourceAppEngineApplication(),
110111
"google_bigquery_dataset": resourceBigQueryDataset(),
111112
"google_bigquery_table": resourceBigQueryTable(),
112113
"google_bigtable_instance": resourceBigtableInstance(),
+285
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package google
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/hashicorp/terraform/helper/customdiff"
8+
"github.com/hashicorp/terraform/helper/schema"
9+
"github.com/hashicorp/terraform/helper/validation"
10+
appengine "google.golang.org/api/appengine/v1"
11+
)
12+
13+
func resourceAppEngineApplication() *schema.Resource {
14+
return &schema.Resource{
15+
Create: resourceAppEngineApplicationCreate,
16+
Read: resourceAppEngineApplicationRead,
17+
Update: resourceAppEngineApplicationUpdate,
18+
Delete: resourceAppEngineApplicationDelete,
19+
20+
Importer: &schema.ResourceImporter{
21+
State: schema.ImportStatePassthrough,
22+
},
23+
24+
CustomizeDiff: customdiff.All(
25+
appEngineApplicationLocationIDCustomizeDiff,
26+
),
27+
28+
Schema: map[string]*schema.Schema{
29+
"project": &schema.Schema{
30+
Type: schema.TypeString,
31+
Optional: true,
32+
Computed: true,
33+
ForceNew: true,
34+
ValidateFunc: validateProjectID(),
35+
},
36+
"auth_domain": &schema.Schema{
37+
Type: schema.TypeString,
38+
Optional: true,
39+
Computed: true,
40+
},
41+
"location_id": &schema.Schema{
42+
Type: schema.TypeString,
43+
Required: true,
44+
ValidateFunc: validation.StringInSlice([]string{
45+
"northamerica-northeast1",
46+
"us-central",
47+
"us-west2",
48+
"us-east1",
49+
"us-east4",
50+
"southamerica-east1",
51+
"europe-west",
52+
"europe-west2",
53+
"europe-west3",
54+
"asia-northeast1",
55+
"asia-south1",
56+
"australia-southeast1",
57+
}, false),
58+
},
59+
"serving_status": &schema.Schema{
60+
Type: schema.TypeString,
61+
Optional: true,
62+
ValidateFunc: validation.StringInSlice([]string{
63+
"UNSPECIFIED",
64+
"SERVING",
65+
"USER_DISABLED",
66+
"SYSTEM_DISABLED",
67+
}, false),
68+
Computed: true,
69+
},
70+
"feature_settings": &schema.Schema{
71+
Type: schema.TypeList,
72+
Optional: true,
73+
Computed: true,
74+
MaxItems: 1,
75+
Elem: appEngineApplicationFeatureSettingsResource(),
76+
},
77+
"name": &schema.Schema{
78+
Type: schema.TypeString,
79+
Computed: true,
80+
},
81+
"url_dispatch_rule": &schema.Schema{
82+
Type: schema.TypeList,
83+
Computed: true,
84+
Elem: appEngineApplicationURLDispatchRuleResource(),
85+
},
86+
"code_bucket": &schema.Schema{
87+
Type: schema.TypeString,
88+
Computed: true,
89+
},
90+
"default_hostname": &schema.Schema{
91+
Type: schema.TypeString,
92+
Computed: true,
93+
},
94+
"default_bucket": &schema.Schema{
95+
Type: schema.TypeString,
96+
Computed: true,
97+
},
98+
"gcr_domain": &schema.Schema{
99+
Type: schema.TypeString,
100+
Computed: true,
101+
},
102+
},
103+
}
104+
}
105+
106+
func appEngineApplicationURLDispatchRuleResource() *schema.Resource {
107+
return &schema.Resource{
108+
Schema: map[string]*schema.Schema{
109+
"domain": &schema.Schema{
110+
Type: schema.TypeString,
111+
Computed: true,
112+
},
113+
"path": &schema.Schema{
114+
Type: schema.TypeString,
115+
Computed: true,
116+
},
117+
"service": &schema.Schema{
118+
Type: schema.TypeString,
119+
Computed: true,
120+
},
121+
},
122+
}
123+
}
124+
125+
func appEngineApplicationFeatureSettingsResource() *schema.Resource {
126+
return &schema.Resource{
127+
Schema: map[string]*schema.Schema{
128+
"split_health_checks": &schema.Schema{
129+
Type: schema.TypeBool,
130+
Optional: true,
131+
},
132+
},
133+
}
134+
}
135+
136+
func appEngineApplicationLocationIDCustomizeDiff(d *schema.ResourceDiff, meta interface{}) error {
137+
old, new := d.GetChange("location_id")
138+
if old != "" && old != new {
139+
return fmt.Errorf("Cannot change location_id once the resource is created.")
140+
}
141+
return nil
142+
}
143+
144+
func resourceAppEngineApplicationCreate(d *schema.ResourceData, meta interface{}) error {
145+
config := meta.(*Config)
146+
147+
project, err := getProject(d, config)
148+
if err != nil {
149+
return err
150+
}
151+
app, err := expandAppEngineApplication(d, project)
152+
if err != nil {
153+
return err
154+
}
155+
log.Printf("[DEBUG] Creating App Engine App")
156+
op, err := config.clientAppEngine.Apps.Create(app).Do()
157+
if err != nil {
158+
return fmt.Errorf("Error creating App Engine application: %s", err.Error())
159+
}
160+
161+
d.SetId(project)
162+
163+
// Wait for the operation to complete
164+
waitErr := appEngineOperationWait(config.clientAppEngine, op, project, "App Engine app to create")
165+
if waitErr != nil {
166+
d.SetId("")
167+
return waitErr
168+
}
169+
log.Printf("[DEBUG] Created App Engine App")
170+
171+
return resourceAppEngineApplicationRead(d, meta)
172+
}
173+
174+
func resourceAppEngineApplicationRead(d *schema.ResourceData, meta interface{}) error {
175+
config := meta.(*Config)
176+
pid := d.Id()
177+
178+
app, err := config.clientAppEngine.Apps.Get(pid).Do()
179+
if err != nil {
180+
return handleNotFoundError(err, d, fmt.Sprintf("App Engine Application %q", pid))
181+
}
182+
d.Set("auth_domain", app.AuthDomain)
183+
d.Set("code_bucket", app.CodeBucket)
184+
d.Set("default_bucket", app.DefaultBucket)
185+
d.Set("default_hostname", app.DefaultHostname)
186+
d.Set("location_id", app.LocationId)
187+
d.Set("name", app.Name)
188+
d.Set("serving_status", app.ServingStatus)
189+
d.Set("project", pid)
190+
dispatchRules, err := flattenAppEngineApplicationDispatchRules(app.DispatchRules)
191+
if err != nil {
192+
return err
193+
}
194+
err = d.Set("url_dispatch_rule", dispatchRules)
195+
if err != nil {
196+
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())
197+
}
198+
featureSettings, err := flattenAppEngineApplicationFeatureSettings(app.FeatureSettings)
199+
if err != nil {
200+
return err
201+
}
202+
err = d.Set("feature_settings", featureSettings)
203+
if err != nil {
204+
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())
205+
}
206+
return nil
207+
}
208+
209+
func resourceAppEngineApplicationUpdate(d *schema.ResourceData, meta interface{}) error {
210+
config := meta.(*Config)
211+
pid := d.Id()
212+
app, err := expandAppEngineApplication(d, pid)
213+
if err != nil {
214+
return err
215+
}
216+
log.Printf("[DEBUG] Updating App Engine App")
217+
op, err := config.clientAppEngine.Apps.Patch(pid, app).UpdateMask("authDomain,servingStatus,featureSettings.splitHealthChecks").Do()
218+
if err != nil {
219+
return fmt.Errorf("Error updating App Engine application: %s", err.Error())
220+
}
221+
222+
// Wait for the operation to complete
223+
waitErr := appEngineOperationWait(config.clientAppEngine, op, pid, "App Engine app to update")
224+
if waitErr != nil {
225+
return waitErr
226+
}
227+
log.Printf("[DEBUG] Updated App Engine App")
228+
229+
return resourceAppEngineApplicationRead(d, meta)
230+
}
231+
232+
func resourceAppEngineApplicationDelete(d *schema.ResourceData, meta interface{}) error {
233+
log.Println("[WARN] App Engine applications cannot be destroyed once created. The project must be deleted to delete the application.")
234+
return nil
235+
}
236+
237+
func expandAppEngineApplication(d *schema.ResourceData, project string) (*appengine.Application, error) {
238+
result := &appengine.Application{
239+
AuthDomain: d.Get("auth_domain").(string),
240+
LocationId: d.Get("location_id").(string),
241+
Id: project,
242+
GcrDomain: d.Get("gcr_domain").(string),
243+
ServingStatus: d.Get("serving_status").(string),
244+
}
245+
featureSettings, err := expandAppEngineApplicationFeatureSettings(d)
246+
if err != nil {
247+
return nil, err
248+
}
249+
result.FeatureSettings = featureSettings
250+
return result, nil
251+
}
252+
253+
func expandAppEngineApplicationFeatureSettings(d *schema.ResourceData) (*appengine.FeatureSettings, error) {
254+
blocks := d.Get("feature_settings").([]interface{})
255+
if len(blocks) < 1 {
256+
return nil, nil
257+
}
258+
return &appengine.FeatureSettings{
259+
SplitHealthChecks: d.Get("feature_settings.0.split_health_checks").(bool),
260+
// force send SplitHealthChecks, so if it's set to false it still gets disabled
261+
ForceSendFields: []string{"SplitHealthChecks"},
262+
}, nil
263+
}
264+
265+
func flattenAppEngineApplicationFeatureSettings(settings *appengine.FeatureSettings) ([]map[string]interface{}, error) {
266+
if settings == nil {
267+
return []map[string]interface{}{}, nil
268+
}
269+
result := map[string]interface{}{
270+
"split_health_checks": settings.SplitHealthChecks,
271+
}
272+
return []map[string]interface{}{result}, nil
273+
}
274+
275+
func flattenAppEngineApplicationDispatchRules(rules []*appengine.UrlDispatchRule) ([]map[string]interface{}, error) {
276+
results := make([]map[string]interface{}, 0, len(rules))
277+
for _, rule := range rules {
278+
results = append(results, map[string]interface{}{
279+
"domain": rule.Domain,
280+
"path": rule.Path,
281+
"service": rule.Service,
282+
})
283+
}
284+
return results, nil
285+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package google
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform/helper/acctest"
8+
"github.com/hashicorp/terraform/helper/resource"
9+
)
10+
11+
func TestAccAppEngineApplication_basic(t *testing.T) {
12+
t.Parallel()
13+
14+
org := getTestOrgFromEnv(t)
15+
pid := acctest.RandomWithPrefix("tf-test")
16+
resource.Test(t, resource.TestCase{
17+
PreCheck: func() { testAccPreCheck(t) },
18+
Providers: testAccProviders,
19+
Steps: []resource.TestStep{
20+
{
21+
Config: testAccAppEngineApplication_basic(pid, org),
22+
Check: resource.ComposeTestCheckFunc(
23+
resource.TestCheckResourceAttrSet("google_app_engine_application.acceptance", "url_dispatch_rule.#"),
24+
resource.TestCheckResourceAttrSet("google_app_engine_application.acceptance", "name"),
25+
resource.TestCheckResourceAttrSet("google_app_engine_application.acceptance", "code_bucket"),
26+
resource.TestCheckResourceAttrSet("google_app_engine_application.acceptance", "default_hostname"),
27+
resource.TestCheckResourceAttrSet("google_app_engine_application.acceptance", "default_bucket"),
28+
),
29+
},
30+
{
31+
ResourceName: "google_app_engine_application.acceptance",
32+
ImportState: true,
33+
ImportStateVerify: true,
34+
},
35+
{
36+
Config: testAccAppEngineApplication_update(pid, org),
37+
},
38+
{
39+
ResourceName: "google_app_engine_application.acceptance",
40+
ImportState: true,
41+
ImportStateVerify: true,
42+
},
43+
},
44+
})
45+
}
46+
47+
func testAccAppEngineApplication_basic(pid, org string) string {
48+
return fmt.Sprintf(`
49+
resource "google_project" "acceptance" {
50+
project_id = "%s"
51+
name = "%s"
52+
org_id = "%s"
53+
}
54+
55+
resource "google_app_engine_application" "acceptance" {
56+
project = "${google_project.acceptance.project_id}"
57+
auth_domain = "hashicorptest.com"
58+
location_id = "us-central"
59+
serving_status = "SERVING"
60+
}`, pid, pid, org)
61+
}
62+
63+
func testAccAppEngineApplication_update(pid, org string) string {
64+
return fmt.Sprintf(`
65+
resource "google_project" "acceptance" {
66+
project_id = "%s"
67+
name = "%s"
68+
org_id = "%s"
69+
}
70+
71+
resource "google_app_engine_application" "acceptance" {
72+
project = "${google_project.acceptance.project_id}"
73+
auth_domain = "tf-test.club"
74+
location_id = "us-central"
75+
serving_status = "USER_DISABLED"
76+
}`, pid, pid, org)
77+
}

0 commit comments

Comments
 (0)