2016-08-10 04:44:53 +00:00
package google
import (
"fmt"
"log"
"net/http"
"strconv"
2017-02-20 17:32:24 +00:00
"strings"
2018-04-16 21:25:01 +00:00
"time"
2016-08-10 04:44:53 +00:00
"github.com/hashicorp/terraform/helper/schema"
2018-05-10 22:01:22 +00:00
"github.com/hashicorp/terraform/helper/validation"
appengine "google.golang.org/api/appengine/v1"
2017-02-20 17:32:24 +00:00
"google.golang.org/api/cloudbilling/v1"
2016-08-10 04:44:53 +00:00
"google.golang.org/api/cloudresourcemanager/v1"
"google.golang.org/api/googleapi"
)
2016-08-21 07:25:00 +00:00
// resourceGoogleProject returns a *schema.Resource that allows a customer
2016-11-23 06:55:40 +00:00
// to declare a Google Cloud Project resource.
2016-08-10 04:44:53 +00:00
func resourceGoogleProject ( ) * schema . Resource {
return & schema . Resource {
2016-11-23 06:55:40 +00:00
SchemaVersion : 1 ,
2016-08-10 04:44:53 +00:00
Create : resourceGoogleProjectCreate ,
Read : resourceGoogleProjectRead ,
Update : resourceGoogleProjectUpdate ,
Delete : resourceGoogleProjectDelete ,
2016-11-23 06:55:40 +00:00
2016-12-01 18:38:27 +00:00
Importer : & schema . ResourceImporter {
2018-04-10 22:23:04 +00:00
State : resourceProjectImportState ,
2016-12-01 18:38:27 +00:00
} ,
2018-05-10 22:01:22 +00:00
MigrateState : resourceGoogleProjectMigrateState ,
CustomizeDiff : resourceGoogleProjectCustomizeDiff ,
2016-08-10 04:44:53 +00:00
Schema : map [ string ] * schema . Schema {
2016-11-23 06:55:40 +00:00
"project_id" : & schema . Schema {
2018-05-22 19:45:28 +00:00
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
ValidateFunc : validateProjectID ( ) ,
2016-08-10 04:44:53 +00:00
} ,
2016-11-23 06:55:40 +00:00
"skip_delete" : & schema . Schema {
Type : schema . TypeBool ,
2016-08-10 04:44:53 +00:00
Optional : true ,
2016-11-23 06:55:40 +00:00
Computed : true ,
2016-08-10 04:44:53 +00:00
} ,
2018-04-10 22:23:04 +00:00
"auto_create_network" : & schema . Schema {
Type : schema . TypeBool ,
Optional : true ,
Default : true ,
} ,
2016-08-10 04:44:53 +00:00
"name" : & schema . Schema {
2018-05-22 19:45:28 +00:00
Type : schema . TypeString ,
Required : true ,
ValidateFunc : validateProjectName ( ) ,
2016-08-10 04:44:53 +00:00
} ,
2016-11-23 06:55:40 +00:00
"org_id" : & schema . Schema {
2018-05-03 16:53:22 +00:00
Type : schema . TypeString ,
Optional : true ,
Computed : true ,
2017-09-22 18:03:08 +00:00
} ,
"folder_id" : & schema . Schema {
2018-05-03 16:53:22 +00:00
Type : schema . TypeString ,
Optional : true ,
Computed : true ,
StateFunc : parseFolderId ,
2016-11-23 06:55:40 +00:00
} ,
"policy_data" : & schema . Schema {
2017-03-13 23:19:53 +00:00
Type : schema . TypeString ,
Optional : true ,
Computed : true ,
Removed : "Use the 'google_project_iam_policy' resource to define policies for a Google Project" ,
2016-11-23 06:55:40 +00:00
} ,
"policy_etag" : & schema . Schema {
2017-03-13 23:19:53 +00:00
Type : schema . TypeString ,
Computed : true ,
Removed : "Use the the 'google_project_iam_policy' resource to define policies for a Google Project" ,
2016-11-23 06:55:40 +00:00
} ,
2016-08-10 04:44:53 +00:00
"number" : & schema . Schema {
Type : schema . TypeString ,
Computed : true ,
} ,
2017-02-20 17:32:24 +00:00
"billing_account" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
} ,
2017-09-14 17:39:21 +00:00
"labels" : & schema . Schema {
Type : schema . TypeMap ,
Optional : true ,
Elem : & schema . Schema { Type : schema . TypeString } ,
Set : schema . HashString ,
} ,
2018-05-10 22:01:22 +00:00
"app_engine" : & schema . Schema {
Type : schema . TypeList ,
Optional : true ,
Elem : appEngineResource ( ) ,
MaxItems : 1 ,
} ,
} ,
}
}
func appEngineResource ( ) * schema . Resource {
return & schema . Resource {
Schema : map [ string ] * schema . Schema {
"auth_domain" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
2018-05-17 21:47:34 +00:00
Computed : true ,
2018-05-10 22:01:22 +00:00
} ,
"location_id" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
Computed : true ,
ValidateFunc : validation . StringInSlice ( [ ] string {
"northamerica-northeast1" ,
"us-central" ,
"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 ,
2018-05-14 05:49:26 +00:00
Optional : true ,
ValidateFunc : validation . StringInSlice ( [ ] string {
"UNSPECIFIED" ,
"SERVING" ,
"USER_DISABLED" ,
"SYSTEM_DISABLED" ,
} , false ) ,
2018-05-10 22:01:22 +00:00
Computed : true ,
} ,
2018-06-08 01:09:14 +00:00
"feature_settings" : & schema . Schema {
Type : schema . TypeList ,
Optional : true ,
Computed : true ,
MaxItems : 1 ,
Elem : appEngineFeatureSettingsResource ( ) ,
} ,
"name" : & schema . Schema {
Type : schema . TypeString ,
Computed : true ,
} ,
"url_dispatch_rule" : & schema . Schema {
Type : schema . TypeList ,
Computed : true ,
Elem : appEngineURLDispatchRuleResource ( ) ,
} ,
"code_bucket" : & schema . Schema {
Type : schema . TypeString ,
Computed : true ,
} ,
2018-05-10 22:01:22 +00:00
"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 ,
2018-05-14 05:49:26 +00:00
Computed : true ,
2018-05-10 22:01:22 +00:00
} ,
} ,
}
}
func appEngineURLDispatchRuleResource ( ) * 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 ,
} ,
2016-08-10 04:44:53 +00:00
} ,
}
}
2018-05-10 22:01:22 +00:00
func appEngineFeatureSettingsResource ( ) * schema . Resource {
return & schema . Resource {
Schema : map [ string ] * schema . Schema {
"split_health_checks" : & schema . Schema {
Type : schema . TypeBool ,
Optional : true ,
} ,
2016-08-10 04:44:53 +00:00
} ,
}
}
2018-05-10 22:01:22 +00:00
func resourceGoogleProjectCustomizeDiff ( diff * schema . ResourceDiff , meta interface { } ) error {
2018-05-31 21:01:33 +00:00
if old , new := diff . GetChange ( "app_engine.#" ) ; old != nil && new != nil && old . ( int ) > 0 && new . ( int ) < 1 {
2018-06-07 22:37:30 +00:00
// if we're going from app_engine set to unset, we need to delete the project, app_engine has no delete
return diff . ForceNew ( "app_engine" )
} else if old , _ := diff . GetChange ( "app_engine.0.location_id" ) ; diff . HasChange ( "app_engine.0.location_id" ) && old != nil && old . ( string ) != "" {
// if location_id was already set, and has a new value, that forces a new app
// if location_id wasn't set, don't force a new value, as we're just enabling app engine
return diff . ForceNew ( "app_engine.0.location_id" )
2018-05-31 21:01:33 +00:00
}
2018-06-05 22:31:52 +00:00
return nil
}
2016-08-10 04:44:53 +00:00
func resourceGoogleProjectCreate ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
2016-11-23 06:55:40 +00:00
var pid string
var err error
pid = d . Get ( "project_id" ) . ( string )
log . Printf ( "[DEBUG]: Creating new project %q" , pid )
project := & cloudresourcemanager . Project {
ProjectId : pid ,
Name : d . Get ( "name" ) . ( string ) ,
}
2018-05-03 16:53:22 +00:00
if err := getParentResourceId ( d , project ) ; err != nil {
return err
}
2017-09-22 18:03:08 +00:00
2017-09-14 17:39:21 +00:00
if _ , ok := d . GetOk ( "labels" ) ; ok {
project . Labels = expandLabels ( d )
}
2016-11-23 06:55:40 +00:00
op , err := config . clientResourceManager . Projects . Create ( project ) . Do ( )
2016-08-10 04:44:53 +00:00
if err != nil {
2016-11-23 06:55:40 +00:00
return fmt . Errorf ( "Error creating project %s (%s): %s." , project . ProjectId , project . Name , err )
2016-08-10 04:44:53 +00:00
}
2016-11-23 06:55:40 +00:00
d . SetId ( pid )
// Wait for the operation to complete
2018-05-08 22:49:57 +00:00
waitErr := resourceManagerOperationWait ( config . clientResourceManager , op , "project to create" )
2016-11-23 06:55:40 +00:00
if waitErr != nil {
2017-04-13 23:16:47 +00:00
// The resource wasn't actually created
d . SetId ( "" )
2016-11-23 06:55:40 +00:00
return waitErr
2016-08-10 04:44:53 +00:00
}
2017-02-20 17:32:24 +00:00
// Set the billing account
2018-04-16 21:25:01 +00:00
if _ , ok := d . GetOk ( "billing_account" ) ; ok {
err = updateProjectBillingAccount ( d , config )
2017-02-20 17:32:24 +00:00
if err != nil {
2018-04-16 21:25:01 +00:00
return err
2017-02-20 17:32:24 +00:00
}
}
2018-05-14 05:49:26 +00:00
// 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" )
2018-05-31 21:01:33 +00:00
err = createAppEngineApp ( config , pid , app )
2018-05-14 05:49:26 +00:00
if err != nil {
2018-05-31 21:01:33 +00:00
return err
2018-05-14 05:49:26 +00:00
}
}
2018-04-10 22:23:04 +00:00
err = resourceGoogleProjectRead ( d , meta )
if err != nil {
return err
}
// There's no such thing as "don't auto-create network", only "delete the network
// post-creation" - but that's what it's called in the UI and let's not confuse
// people if we don't have to. The GCP Console is doing the same thing - creating
// a network and deleting it in the background.
if ! d . Get ( "auto_create_network" ) . ( bool ) {
2018-04-16 17:25:49 +00:00
// The compute API has to be enabled before we can delete a network.
if err = enableService ( "compute.googleapis.com" , project . ProjectId , config ) ; err != nil {
return fmt . Errorf ( "Error enabling the Compute Engine API required to delete the default network: %s" , err )
2018-04-10 22:23:04 +00:00
}
2018-04-16 17:25:49 +00:00
if err = forceDeleteComputeNetwork ( project . ProjectId , "default" , config ) ; err != nil {
return fmt . Errorf ( "Error deleting default network in project %s: %s" , project . ProjectId , err )
2018-04-10 22:23:04 +00:00
}
}
return nil
2016-08-10 04:44:53 +00:00
}
2018-05-31 21:01:33 +00:00
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
}
2016-08-10 04:44:53 +00:00
func resourceGoogleProjectRead ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
2016-11-23 06:55:40 +00:00
pid := d . Id ( )
2016-08-10 04:44:53 +00:00
2016-11-23 06:55:40 +00:00
// Read the project
p , err := config . clientResourceManager . Projects . Get ( pid ) . Do ( )
2016-08-10 04:44:53 +00:00
if err != nil {
2017-05-09 23:00:47 +00:00
return handleNotFoundError ( err , d , fmt . Sprintf ( "Project %q" , pid ) )
2016-08-10 04:44:53 +00:00
}
2017-09-26 23:55:32 +00:00
// If the project has been deleted from outside Terraform, remove it from state file.
if p . LifecycleState != "ACTIVE" {
log . Printf ( "[WARN] Removing project '%s' because its state is '%s' (requires 'ACTIVE')." , pid , p . LifecycleState )
d . SetId ( "" )
return nil
}
2016-11-23 06:55:40 +00:00
d . Set ( "project_id" , pid )
2016-08-10 04:44:53 +00:00
d . Set ( "number" , strconv . FormatInt ( int64 ( p . ProjectNumber ) , 10 ) )
d . Set ( "name" , p . Name )
2017-09-14 17:39:21 +00:00
d . Set ( "labels" , p . Labels )
2016-08-10 04:44:53 +00:00
2016-11-23 06:55:40 +00:00
if p . Parent != nil {
2017-09-22 18:03:08 +00:00
switch p . Parent . Type {
case "organization" :
d . Set ( "org_id" , p . Parent . Id )
d . Set ( "folder_id" , "" )
case "folder" :
d . Set ( "folder_id" , p . Parent . Id )
d . Set ( "org_id" , "" )
}
2016-11-23 06:55:40 +00:00
}
2017-02-20 17:32:24 +00:00
// Read the billing account
ba , err := config . clientBilling . Projects . GetBillingInfo ( prefixedProject ( pid ) ) . Do ( )
2018-05-22 23:59:33 +00:00
if err != nil && ! isApiNotEnabledError ( err ) {
2017-02-20 17:32:24 +00:00
return fmt . Errorf ( "Error reading billing account for project %q: %v" , prefixedProject ( pid ) , err )
2018-05-22 23:59:33 +00:00
} else if isApiNotEnabledError ( err ) {
log . Printf ( "[WARN] Billing info API not enabled, please enable it to read billing info about project %q: %s" , pid , err . Error ( ) )
} else if ba . BillingAccountName != "" {
2017-02-20 17:32:24 +00:00
// BillingAccountName is contains the resource name of the billing account
// associated with the project, if any. For example,
// `billingAccounts/012345-567890-ABCDEF`. We care about the ID and not
// the `billingAccounts/` prefix, so we need to remove that. If the
// prefix ever changes, we'll validate to make sure it's something we
// recognize.
_ba := strings . TrimPrefix ( ba . BillingAccountName , "billingAccounts/" )
if ba . BillingAccountName == _ba {
return fmt . Errorf ( "Error parsing billing account for project %q. Expected value to begin with 'billingAccounts/' but got %s" , prefixedProject ( pid ) , ba . BillingAccountName )
}
d . Set ( "billing_account" , _ba )
}
2018-05-10 22:01:22 +00:00
2018-05-17 21:47:34 +00:00
// 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 ( )
2018-05-22 23:59:33 +00:00
if err != nil && ! isGoogleApiErrorWithCode ( err , 404 ) && ! isApiNotEnabledError ( err ) {
2018-05-17 21:47:34 +00:00
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 { } { } )
2018-05-22 23:59:33 +00:00
} 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 ( ) )
2018-05-17 21:47:34 +00:00
} else {
2018-05-10 22:01:22 +00:00
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 {
2018-05-14 05:49:26 +00:00
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 ( ) )
2018-05-10 22:01:22 +00:00
}
}
2016-08-10 04:44:53 +00:00
return nil
}
2017-02-20 17:32:24 +00:00
func prefixedProject ( pid string ) string {
return "projects/" + pid
}
2017-03-13 23:19:53 +00:00
2017-09-22 18:03:08 +00:00
func getParentResourceId ( d * schema . ResourceData , p * cloudresourcemanager . Project ) error {
2018-05-03 16:53:22 +00:00
orgId := d . Get ( "org_id" ) . ( string )
folderId := d . Get ( "folder_id" ) . ( string )
if orgId != "" && folderId != "" {
return fmt . Errorf ( "'org_id' and 'folder_id' cannot be both set." )
}
if orgId != "" {
2017-09-22 18:03:08 +00:00
p . Parent = & cloudresourcemanager . ResourceId {
2018-05-03 16:53:22 +00:00
Id : orgId ,
2017-09-22 18:03:08 +00:00
Type : "organization" ,
}
}
2018-05-03 16:53:22 +00:00
if folderId != "" {
2017-09-22 18:03:08 +00:00
p . Parent = & cloudresourcemanager . ResourceId {
2018-05-03 16:53:22 +00:00
Id : parseFolderId ( folderId ) ,
2017-09-22 18:03:08 +00:00
Type : "folder" ,
}
}
2018-05-03 16:53:22 +00:00
2017-09-22 18:03:08 +00:00
return nil
}
func parseFolderId ( v interface { } ) string {
folderId := v . ( string )
if strings . HasPrefix ( folderId , "folders/" ) {
return folderId [ 8 : ]
}
return folderId
}
2016-08-10 04:44:53 +00:00
func resourceGoogleProjectUpdate ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
2016-11-23 06:55:40 +00:00
pid := d . Id ( )
2017-09-22 18:03:08 +00:00
project_name := d . Get ( "name" ) . ( string )
2016-11-23 06:55:40 +00:00
// Read the project
// we need the project even though refresh has already been called
// because the API doesn't support patch, so we need the actual object
p , err := config . clientResourceManager . Projects . Get ( pid ) . Do ( )
2016-08-10 04:44:53 +00:00
if err != nil {
2016-11-23 06:55:40 +00:00
if v , ok := err . ( * googleapi . Error ) ; ok && v . Code == http . StatusNotFound {
return fmt . Errorf ( "Project %q does not exist." , pid )
}
return fmt . Errorf ( "Error checking project %q: %s" , pid , err )
}
2017-09-22 18:03:08 +00:00
d . Partial ( true )
// Project display name has changed
2016-11-23 06:55:40 +00:00
if ok := d . HasChange ( "name" ) ; ok {
2017-09-22 18:03:08 +00:00
p . Name = project_name
2016-11-23 06:55:40 +00:00
// Do update on project
p , err = config . clientResourceManager . Projects . Update ( p . ProjectId , p ) . Do ( )
if err != nil {
2017-09-22 18:03:08 +00:00
return fmt . Errorf ( "Error updating project %q: %s" , project_name , err )
2016-11-23 06:55:40 +00:00
}
2017-09-22 18:03:08 +00:00
d . SetPartial ( "name" )
}
// Project parent has changed
if d . HasChange ( "org_id" ) || d . HasChange ( "folder_id" ) {
2018-05-03 16:53:22 +00:00
if err := getParentResourceId ( d , p ) ; err != nil {
return err
}
2017-09-22 18:03:08 +00:00
// Do update on project
p , err = config . clientResourceManager . Projects . Update ( p . ProjectId , p ) . Do ( )
if err != nil {
return fmt . Errorf ( "Error updating project %q: %s" , project_name , err )
}
d . SetPartial ( "org_id" )
d . SetPartial ( "folder_id" )
2016-11-23 06:55:40 +00:00
}
2017-02-20 17:32:24 +00:00
// Billing account has changed
if ok := d . HasChange ( "billing_account" ) ; ok {
2018-04-16 21:25:01 +00:00
err = updateProjectBillingAccount ( d , config )
2017-02-20 17:32:24 +00:00
if err != nil {
2018-04-16 21:25:01 +00:00
return err
2017-02-20 17:32:24 +00:00
}
}
2017-09-14 17:39:21 +00:00
// Project Labels have changed
if ok := d . HasChange ( "labels" ) ; ok {
p . Labels = expandLabels ( d )
// Do Update on project
p , err = config . clientResourceManager . Projects . Update ( p . ProjectId , p ) . Do ( )
if err != nil {
2018-01-27 21:55:39 +00:00
return fmt . Errorf ( "Error updating project %q: %s" , project_name , err )
2017-09-14 17:39:21 +00:00
}
2018-05-10 22:01:22 +00:00
d . SetPartial ( "labels" )
2017-09-14 17:39:21 +00:00
}
2018-05-10 22:01:22 +00:00
2018-05-31 21:01:33 +00:00
// 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" )
}
2018-06-08 01:11:07 +00:00
d . SetPartial ( "app_engine" )
2018-05-31 21:01:33 +00:00
}
}
2017-09-22 18:03:08 +00:00
d . Partial ( false )
2018-05-31 21:01:33 +00:00
return resourceGoogleProjectRead ( d , meta )
2016-11-23 06:55:40 +00:00
}
func resourceGoogleProjectDelete ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
// Only delete projects if skip_delete isn't set
if ! d . Get ( "skip_delete" ) . ( bool ) {
pid := d . Id ( )
_ , err := config . clientResourceManager . Projects . Delete ( pid ) . Do ( )
if err != nil {
return fmt . Errorf ( "Error deleting project %q: %s" , pid , err )
}
2016-08-10 04:44:53 +00:00
}
2016-11-23 06:55:40 +00:00
d . SetId ( "" )
return nil
}
2018-04-10 22:23:04 +00:00
func resourceProjectImportState ( d * schema . ResourceData , meta interface { } ) ( [ ] * schema . ResourceData , error ) {
// Explicitly set to default as a workaround for `ImportStateVerify` tests, and so that users
// don't see a diff immediately after import.
d . Set ( "auto_create_network" , true )
return [ ] * schema . ResourceData { d } , nil
}
2018-04-16 17:25:49 +00:00
// Delete a compute network along with the firewall rules inside it.
func forceDeleteComputeNetwork ( projectId , networkName string , config * Config ) error {
networkLink := fmt . Sprintf ( "https://www.googleapis.com/compute/v1/projects/%s/global/networks/%s" , projectId , networkName )
token := ""
for paginate := true ; paginate ; {
filter := fmt . Sprintf ( "network eq %s" , networkLink )
resp , err := config . clientCompute . Firewalls . List ( projectId ) . Filter ( filter ) . Do ( )
if err != nil {
return fmt . Errorf ( "Error listing firewall rules in proj: %s" , err )
}
log . Printf ( "[DEBUG] Found %d firewall rules in %q network" , len ( resp . Items ) , networkName )
for _ , firewall := range resp . Items {
op , err := config . clientCompute . Firewalls . Delete ( projectId , firewall . Name ) . Do ( )
if err != nil {
return fmt . Errorf ( "Error deleting firewall: %s" , err )
}
err = computeSharedOperationWait ( config . clientCompute , op , projectId , "Deleting Firewall" )
if err != nil {
return err
}
}
token = resp . NextPageToken
paginate = token != ""
}
return deleteComputeNetwork ( projectId , networkName , config )
}
2018-04-16 21:25:01 +00:00
func updateProjectBillingAccount ( d * schema . ResourceData , config * Config ) error {
pid := d . Id ( )
name := d . Get ( "billing_account" ) . ( string )
ba := cloudbilling . ProjectBillingInfo { }
// If we're unlinking an existing billing account, an empty request does that, not an empty-string billing account.
if name != "" {
ba . BillingAccountName = "billingAccounts/" + name
}
_ , err := config . clientBilling . Projects . UpdateBillingInfo ( prefixedProject ( pid ) , & ba ) . Do ( )
if err != nil {
d . Set ( "billing_account" , "" )
if _err , ok := err . ( * googleapi . Error ) ; ok {
return fmt . Errorf ( "Error setting billing account %q for project %q: %v" , name , prefixedProject ( pid ) , _err )
}
return fmt . Errorf ( "Error setting billing account %q for project %q: %v" , name , prefixedProject ( pid ) , err )
}
for retries := 0 ; retries < 3 ; retries ++ {
err = resourceGoogleProjectRead ( d , config )
if err != nil {
return err
}
if d . Get ( "billing_account" ) . ( string ) == name {
break
}
time . Sleep ( 3 )
}
if d . Get ( "billing_account" ) . ( string ) != name {
return fmt . Errorf ( "Timed out waiting for billing account to return correct value. Waiting for %s, got %s." ,
d . Get ( "billding_account" ) . ( string ) , name )
}
return nil
}
2018-05-10 22:01:22 +00:00
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 {
2018-05-14 05:49:26 +00:00
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 ) ,
2018-05-10 22:01:22 +00:00
}
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
}
2018-05-14 05:49:26 +00:00
result [ "url_dispatch_rule" ] = dispatchRules
2018-05-10 22:01:22 +00:00
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
}