Remove google_project.app_engine (#2386)

<!-- This change is generated by MagicModules. -->
/cc @rileykarson
This commit is contained in:
The Magician 2018-11-02 09:47:16 -07:00 committed by Nathan McKinley
parent 1b1538a6a4
commit ed18fb9ea0
3 changed files with 23 additions and 463 deletions

View File

@ -9,8 +9,6 @@ import (
"time" "time"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
appengine "google.golang.org/api/appengine/v1"
"google.golang.org/api/cloudbilling/v1" "google.golang.org/api/cloudbilling/v1"
"google.golang.org/api/cloudresourcemanager/v1" "google.golang.org/api/cloudresourcemanager/v1"
"google.golang.org/api/googleapi" "google.golang.org/api/googleapi"
@ -30,8 +28,7 @@ func resourceGoogleProject() *schema.Resource {
Importer: &schema.ResourceImporter{ Importer: &schema.ResourceImporter{
State: resourceProjectImportState, State: resourceProjectImportState,
}, },
MigrateState: resourceGoogleProjectMigrateState, MigrateState: resourceGoogleProjectMigrateState,
CustomizeDiff: resourceGoogleProjectCustomizeDiff,
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"project_id": &schema.Schema{ "project_id": &schema.Schema{
@ -92,12 +89,10 @@ func resourceGoogleProject() *schema.Resource {
Set: schema.HashString, Set: schema.HashString,
}, },
"app_engine": &schema.Schema{ "app_engine": &schema.Schema{
Type: schema.TypeList, Type: schema.TypeList,
Optional: true, Elem: appEngineResource(),
Computed: true, Computed: true,
Elem: appEngineResource(), Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
MaxItems: 1,
Deprecated: "Use the google_app_engine_application resource instead.",
}, },
}, },
} }
@ -110,68 +105,57 @@ func appEngineResource() *schema.Resource {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true, Computed: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
}, },
"location_id": &schema.Schema{ "location_id": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true, Computed: true,
ValidateFunc: validation.StringInSlice([]string{ Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
"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{ "serving_status": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"UNSPECIFIED",
"SERVING",
"USER_DISABLED",
"SYSTEM_DISABLED",
}, false),
Computed: true, Computed: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
}, },
"feature_settings": &schema.Schema{ "feature_settings": &schema.Schema{
Type: schema.TypeList, Type: schema.TypeList,
Optional: true, Optional: true,
Computed: true, Computed: true,
MaxItems: 1, Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
Elem: appEngineFeatureSettingsResource(), Elem: appEngineFeatureSettingsResource(),
}, },
"name": &schema.Schema{ "name": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
}, },
"url_dispatch_rule": &schema.Schema{ "url_dispatch_rule": &schema.Schema{
Type: schema.TypeList, Type: schema.TypeList,
Computed: true, Computed: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
Elem: appEngineURLDispatchRuleResource(), Elem: appEngineURLDispatchRuleResource(),
}, },
"code_bucket": &schema.Schema{ "code_bucket": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
}, },
"default_hostname": &schema.Schema{ "default_hostname": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
}, },
"default_bucket": &schema.Schema{ "default_bucket": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
}, },
"gcr_domain": &schema.Schema{ "gcr_domain": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
}, },
}, },
} }
@ -183,14 +167,17 @@ func appEngineURLDispatchRuleResource() *schema.Resource {
"domain": &schema.Schema{ "domain": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
}, },
"path": &schema.Schema{ "path": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
}, },
"service": &schema.Schema{ "service": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
}, },
}, },
} }
@ -202,18 +189,12 @@ func appEngineFeatureSettingsResource() *schema.Resource {
"split_health_checks": &schema.Schema{ "split_health_checks": &schema.Schema{
Type: schema.TypeBool, Type: schema.TypeBool,
Optional: true, Optional: true,
Removed: "This field has been removed. Use the google_app_engine_application resource instead.",
}, },
}, },
} }
} }
func resourceGoogleProjectCustomizeDiff(diff *schema.ResourceDiff, meta interface{}) error {
if old, _ := diff.GetChange("app_engine.0.location_id"); diff.HasChange("app_engine.0.location_id") && old != nil && old.(string) != "" {
return fmt.Errorf("Cannot change app_engine.0.location_id once the app is created.")
}
return nil
}
func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error { func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config) config := meta.(*Config)
@ -261,24 +242,6 @@ func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error
} }
} }
// set up App Engine, too
app, err := expandAppEngineApp(d)
if err != nil {
return err
}
if app != nil {
log.Printf("[DEBUG] Enabling App Engine")
// enable the app engine APIs so we can create stuff
if err = enableService("appengine.googleapis.com", project.ProjectId, config); err != nil {
return fmt.Errorf("Error enabling the App Engine Admin API required to configure App Engine applications: %s", err)
}
log.Printf("[DEBUG] Enabled App Engine")
err = createAppEngineApp(config, pid, app)
if err != nil {
return err
}
}
err = resourceGoogleProjectRead(d, meta) err = resourceGoogleProjectRead(d, meta)
if err != nil { if err != nil {
return err return err
@ -301,23 +264,6 @@ func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error
return nil return nil
} }
func createAppEngineApp(config *Config, pid string, app *appengine.Application) error {
app.Id = pid
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())
}
// Wait for the operation to complete
waitErr := appEngineOperationWait(config.clientAppEngine, op, pid, "App Engine app to create")
if waitErr != nil {
return waitErr
}
log.Printf("[DEBUG] Created App Engine App")
return nil
}
func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error { func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config) config := meta.(*Config)
pid := d.Id() pid := d.Id()
@ -340,6 +286,10 @@ func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error {
d.Set("name", p.Name) d.Set("name", p.Name)
d.Set("labels", p.Labels) d.Set("labels", p.Labels)
// We get app_engine.#: "" => "<computed>" without this set
// Remove when app_engine field is removed from schema completely
d.Set("app_engine", nil)
if p.Parent != nil { if p.Parent != nil {
switch p.Parent.Type { switch p.Parent.Type {
case "organization": case "organization":
@ -371,29 +321,6 @@ func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error {
d.Set("billing_account", _ba) d.Set("billing_account", _ba)
} }
// read the App Engine app, if one exists
// we don't have the config available for import, so we can't rely on
// that to read it. And honestly, we want to know if an App exists that
// shouldn't. So this tries to read it, sets it to empty if none exists,
// or sets it in state if one does exist.
app, err := config.clientAppEngine.Apps.Get(pid).Do()
if err != nil && !isGoogleApiErrorWithCode(err, 404) && !isApiNotEnabledError(err) {
return fmt.Errorf("Error retrieving App Engine application %q: %s", pid, err.Error())
} else if isGoogleApiErrorWithCode(err, 404) {
d.Set("app_engine", []map[string]interface{}{})
} else if isApiNotEnabledError(err) {
log.Printf("[WARN] App Engine Admin API not enabled, please enable it to read App Engine info about project %q: %s", pid, err.Error())
d.Set("app_engine", []map[string]interface{}{})
} else {
appBlocks, err := flattenAppEngineApp(app)
if err != nil {
return fmt.Errorf("Error serializing App Engine app: %s", err.Error())
}
err = d.Set("app_engine", appBlocks)
if err != nil {
return fmt.Errorf("Error setting App Engine application 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 return nil
} }
@ -498,39 +425,7 @@ func resourceGoogleProjectUpdate(d *schema.ResourceData, meta interface{}) error
d.SetPartial("labels") d.SetPartial("labels")
} }
// App Engine App has changed
if ok := d.HasChange("app_engine"); ok {
app, err := expandAppEngineApp(d)
if err != nil {
return err
}
// ignore if app is now not set; that should force new resource using customizediff
if app != nil {
if old, new := d.GetChange("app_engine.#"); (old == nil || old.(int) < 1) && new != nil && new.(int) > 0 {
err = createAppEngineApp(config, pid, app)
if err != nil {
return err
}
} else {
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 creating 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")
}
d.SetPartial("app_engine")
}
}
d.Partial(false) d.Partial(false)
return resourceGoogleProjectRead(d, meta) return resourceGoogleProjectRead(d, meta)
} }
@ -617,86 +512,3 @@ func updateProjectBillingAccount(d *schema.ResourceData, config *Config) error {
return fmt.Errorf("Timed out waiting for billing account to return correct value. Waiting for %s, got %s.", return fmt.Errorf("Timed out waiting for billing account to return correct value. Waiting for %s, got %s.",
name, strings.TrimPrefix(ba.BillingAccountName, "billingAccounts/")) name, strings.TrimPrefix(ba.BillingAccountName, "billingAccounts/"))
} }
func expandAppEngineApp(d *schema.ResourceData) (*appengine.Application, error) {
blocks := d.Get("app_engine").([]interface{})
if len(blocks) < 1 {
return nil, nil
}
if len(blocks) > 1 {
return nil, fmt.Errorf("only one app_engine block may be defined per project")
}
result := &appengine.Application{
AuthDomain: d.Get("app_engine.0.auth_domain").(string),
LocationId: d.Get("app_engine.0.location_id").(string),
Id: d.Get("project_id").(string),
GcrDomain: d.Get("app_engine.0.gcr_domain").(string),
ServingStatus: d.Get("app_engine.0.serving_status").(string),
}
featureSettings, err := expandAppEngineFeatureSettings(d, "app_engine.0.")
if err != nil {
return nil, err
}
result.FeatureSettings = featureSettings
return result, nil
}
func flattenAppEngineApp(app *appengine.Application) ([]map[string]interface{}, error) {
result := map[string]interface{}{
"auth_domain": app.AuthDomain,
"code_bucket": app.CodeBucket,
"default_bucket": app.DefaultBucket,
"default_hostname": app.DefaultHostname,
"location_id": app.LocationId,
"name": app.Name,
"serving_status": app.ServingStatus,
}
dispatchRules, err := flattenAppEngineDispatchRules(app.DispatchRules)
if err != nil {
return nil, err
}
result["url_dispatch_rule"] = dispatchRules
featureSettings, err := flattenAppEngineFeatureSettings(app.FeatureSettings)
if err != nil {
return nil, err
}
result["feature_settings"] = featureSettings
return []map[string]interface{}{result}, nil
}
func expandAppEngineFeatureSettings(d *schema.ResourceData, prefix string) (*appengine.FeatureSettings, error) {
blocks := d.Get(prefix + "feature_settings").([]interface{})
if len(blocks) < 1 {
return nil, nil
}
if len(blocks) > 1 {
return nil, fmt.Errorf("only one feature_settings block may be defined per app")
}
return &appengine.FeatureSettings{
SplitHealthChecks: d.Get(prefix + "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 flattenAppEngineFeatureSettings(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 flattenAppEngineDispatchRules(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
}

View File

@ -187,134 +187,6 @@ func TestAccProject_parentFolder(t *testing.T) {
}) })
} }
func TestAccProject_appEngineBasic(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: testAccProject_appEngineBasic(pid, org),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.name"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.url_dispatch_rule.#"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.code_bucket"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_hostname"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_bucket"),
),
},
resource.TestStep{
ResourceName: "google_project.acceptance",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccProject_appEngineBasicWithBilling(t *testing.T) {
t.Parallel()
org := getTestOrgFromEnv(t)
pid := acctest.RandomWithPrefix("tf-test")
billingId := getTestBillingAccountFromEnv(t)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccProject_appEngineBasicWithBilling(pid, org, billingId),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.name"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.url_dispatch_rule.#"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.code_bucket"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_hostname"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_bucket"),
),
},
resource.TestStep{
ResourceName: "google_project.acceptance",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccProject_appEngineUpdate(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: testAccProject_appEngineNoApp(pid, org),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectExists("google_project.acceptance", pid),
),
},
{
Config: testAccProject_appEngineBasic(pid, org),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.name"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.url_dispatch_rule.#"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.code_bucket"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_hostname"),
resource.TestCheckResourceAttrSet("google_project.acceptance", "app_engine.0.default_bucket"),
),
},
resource.TestStep{
ResourceName: "google_project.acceptance",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccProject_appEngineUpdate(pid, org),
},
resource.TestStep{
ResourceName: "google_project.acceptance",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccProject_appEngineFeatureSettings(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: testAccProject_appEngineFeatureSettings(pid, org),
},
resource.TestStep{
ResourceName: "google_project.acceptance",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccProject_appEngineFeatureSettingsUpdate(pid, org),
},
resource.TestStep{
ResourceName: "google_project.acceptance",
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func testAccCheckGoogleProjectExists(r, pid string) resource.TestCheckFunc { func testAccCheckGoogleProjectExists(r, pid string) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[r] rs, ok := s.RootModule().Resources[r]
@ -495,96 +367,6 @@ resource "google_folder" "folder1" {
`, pid, projectName, folderName, org) `, pid, projectName, folderName, org)
} }
func testAccProject_appEngineNoApp(pid, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
}`, pid, pid, org)
}
func testAccProject_appEngineBasic(pid, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
app_engine {
auth_domain = "hashicorptest.com"
location_id = "us-central"
serving_status = "SERVING"
}
}`, pid, pid, org)
}
func testAccProject_appEngineBasicWithBilling(pid, org, billing string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
billing_account = "%s"
app_engine {
auth_domain = "hashicorptest.com"
location_id = "us-central"
serving_status = "SERVING"
}
}`, pid, pid, org, billing)
}
func testAccProject_appEngineUpdate(pid, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
app_engine {
auth_domain = "tf-test.club"
location_id = "us-central"
serving_status = "USER_DISABLED"
}
}`, pid, pid, org)
}
func testAccProject_appEngineFeatureSettings(pid, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
app_engine {
location_id = "us-central"
feature_settings {
"split_health_checks" = true
}
}
}`, pid, pid, org)
}
func testAccProject_appEngineFeatureSettingsUpdate(pid, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
app_engine {
location_id = "us-central"
feature_settings {
"split_health_checks" = false
}
}
}`, pid, pid, org)
}
func skipIfEnvNotSet(t *testing.T, envs ...string) { func skipIfEnvNotSet(t *testing.T, envs ...string) {
for _, k := range envs { for _, k := range envs {
if os.Getenv(k) == "" { if os.Getenv(k) == "" {

View File

@ -61,20 +61,6 @@ resource "google_folder" "department1" {
} }
``` ```
To create a project with an App Engine app attached
```hcl
resource "google_project" "my-app-engine-app" {
name = "App Engine Project"
project_id = "app-engine-project"
org_id = "1234567"
app_engine {
location_id = "us-central"
}
}
```
## Argument Reference ## Argument Reference
The following arguments are supported: The following arguments are supported:
@ -113,19 +99,6 @@ The following arguments are supported:
name to match the GCP Console UI. Setting this field to false will enable the Compute Engine name to match the GCP Console UI. Setting this field to false will enable the Compute Engine
API which is required to delete the network. API which is required to delete the network.
* `app_engine` - (Optional) A block of configuration to enable an App Engine app. Setting this
field will enabled the App Engine Admin API, which is required to manage the app.
The `app_engine` block has the following configuration options:
* `location_id` - (Required) The [location](https://cloud.google.com/appengine/docs/locations)
to serve the app from.
* `auth_domain` - (Optional) The domain to authenticate users with when using App Engine's User API.
* `serving_status` - (Optional) The serving status of the app. Note that this can't be updated at the moment.
* `feature_settings` - (Optional) A block of optional settings to configure specific App Engine features:
* `split_health_checks` - (Optional) Set to false to use the legacy health check instead of the readiness
and liveness checks.
## Attributes Reference ## Attributes Reference
In addition to the arguments listed above, the following computed attributes are In addition to the arguments listed above, the following computed attributes are
@ -133,13 +106,6 @@ exported:
* `number` - The numeric identifier of the project. * `number` - The numeric identifier of the project.
* `app_engine.0.name` - Unique name of the app, usually `apps/{PROJECT_ID}`
* `app_engine.0.url_dispatch_rule` - A list of dispatch rule blocks. Each block has a `domain`, `path`, and `service` field.
* `app_engine.0.code_bucket` - The GCS bucket code is being stored in for this app.
* `app_engine.0.default_hostname` - The default hostname for this app.
* `app_engine.0.default_bucket` - The GCS bucket content is being stored in for this app.
* `app_engine.0.gcr_domain` - The GCR domain used for storing managed Docker images for this app.
## Import ## Import
Projects can be imported using the `project_id`, e.g. Projects can be imported using the `project_id`, e.g.