2016-02-14 22:27:17 +00:00
package google
import (
"fmt"
"log"
2016-04-10 16:59:57 +00:00
"strings"
2018-01-17 20:58:37 +00:00
"github.com/apparentlymart/go-cidr/cidr"
"github.com/hashicorp/terraform/helper/customdiff"
2016-02-14 22:27:17 +00:00
"github.com/hashicorp/terraform/helper/schema"
2017-12-08 21:11:09 +00:00
computeBeta "google.golang.org/api/compute/v0.beta"
2016-02-14 22:27:17 +00:00
"google.golang.org/api/compute/v1"
2018-01-17 20:58:37 +00:00
"net"
2017-12-18 17:47:11 +00:00
"time"
2016-02-14 22:27:17 +00:00
)
2017-12-08 21:11:09 +00:00
var (
SubnetworkBaseApiVersion = v1
SubnetworkVersionedFeatures = [ ] Feature {
{ Version : v0beta , Item : "secondary_ip_range" } ,
}
)
2016-02-14 22:27:17 +00:00
func resourceComputeSubnetwork ( ) * schema . Resource {
return & schema . Resource {
Create : resourceComputeSubnetworkCreate ,
Read : resourceComputeSubnetworkRead ,
2017-06-07 13:52:14 +00:00
Update : resourceComputeSubnetworkUpdate ,
2016-02-14 22:27:17 +00:00
Delete : resourceComputeSubnetworkDelete ,
2017-07-26 16:30:59 +00:00
Importer : & schema . ResourceImporter {
State : resourceComputeSubnetworkImportState ,
} ,
2016-02-14 22:27:17 +00:00
2017-12-18 17:47:11 +00:00
Timeouts : & schema . ResourceTimeout {
Create : schema . DefaultTimeout ( 6 * time . Minute ) ,
Update : schema . DefaultTimeout ( 6 * time . Minute ) ,
Delete : schema . DefaultTimeout ( 6 * time . Minute ) ,
} ,
2016-02-14 22:27:17 +00:00
Schema : map [ string ] * schema . Schema {
2016-04-10 21:34:15 +00:00
"ip_cidr_range" : & schema . Schema {
2018-01-17 20:58:37 +00:00
Type : schema . TypeString ,
Required : true ,
ValidateFunc : validateIpCidrRange ,
// ForceNew only if it shrinks the CIDR range, this is set in CustomizeDiff below.
2016-02-14 22:27:17 +00:00
} ,
2016-04-10 21:34:15 +00:00
"name" : & schema . Schema {
2016-02-14 22:27:17 +00:00
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
} ,
"network" : & schema . Schema {
2017-07-26 16:30:59 +00:00
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
2017-09-07 20:43:00 +00:00
DiffSuppressFunc : compareSelfLinkOrResourceName ,
2016-02-14 22:27:17 +00:00
} ,
"description" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
ForceNew : true ,
} ,
2017-12-08 21:11:09 +00:00
"fingerprint" : & schema . Schema {
Type : schema . TypeString ,
Computed : true ,
} ,
2016-02-14 22:27:17 +00:00
"gateway_address" : & schema . Schema {
Type : schema . TypeString ,
Computed : true ,
} ,
2016-04-10 21:34:15 +00:00
"project" : & schema . Schema {
2016-02-14 22:27:17 +00:00
Type : schema . TypeString ,
2016-04-10 21:34:15 +00:00
Optional : true ,
2017-11-28 00:32:20 +00:00
Computed : true ,
2016-04-10 21:34:15 +00:00
ForceNew : true ,
2016-02-14 22:27:17 +00:00
} ,
2016-04-10 16:59:57 +00:00
2016-04-10 21:34:15 +00:00
"region" : & schema . Schema {
2016-04-10 16:59:57 +00:00
Type : schema . TypeString ,
Optional : true ,
2017-11-28 00:32:20 +00:00
Computed : true ,
2016-04-10 16:59:57 +00:00
ForceNew : true ,
} ,
2016-04-10 21:34:15 +00:00
2017-05-18 20:35:02 +00:00
"private_ip_google_access" : & schema . Schema {
Type : schema . TypeBool ,
Optional : true ,
} ,
2017-08-09 22:02:54 +00:00
"secondary_ip_range" : & schema . Schema {
Type : schema . TypeList ,
Optional : true ,
Elem : & schema . Resource {
Schema : map [ string ] * schema . Schema {
"range_name" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
ValidateFunc : validateGCPName ,
} ,
"ip_cidr_range" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
} ,
} ,
} ,
} ,
2016-04-10 21:34:15 +00:00
"self_link" : & schema . Schema {
Type : schema . TypeString ,
Computed : true ,
} ,
2016-02-14 22:27:17 +00:00
} ,
2018-01-17 20:58:37 +00:00
CustomizeDiff : customdiff . All (
customdiff . ForceNewIfChange ( "ip_cidr_range" , isShrinkageIpCidr ) ,
) ,
2016-02-14 22:27:17 +00:00
}
}
func resourceComputeSubnetworkCreate ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
2017-10-10 16:53:57 +00:00
network , err := ParseNetworkFieldValue ( d . Get ( "network" ) . ( string ) , d , config )
if err != nil {
return err
}
2016-02-14 22:27:17 +00:00
2016-04-10 21:34:15 +00:00
region , err := getRegion ( d , config )
if err != nil {
return err
}
2016-04-10 16:59:57 +00:00
project , err := getProject ( d , config )
if err != nil {
return err
}
2016-02-14 22:27:17 +00:00
// Build the subnetwork parameters
2017-09-27 23:13:00 +00:00
subnetwork := & compute . Subnetwork {
2017-05-18 20:35:02 +00:00
Name : d . Get ( "name" ) . ( string ) ,
Description : d . Get ( "description" ) . ( string ) ,
IpCidrRange : d . Get ( "ip_cidr_range" ) . ( string ) ,
PrivateIpGoogleAccess : d . Get ( "private_ip_google_access" ) . ( bool ) ,
2017-08-09 22:02:54 +00:00
SecondaryIpRanges : expandSecondaryRanges ( d . Get ( "secondary_ip_range" ) . ( [ ] interface { } ) ) ,
2017-10-10 16:53:57 +00:00
Network : network . RelativeLink ( ) ,
2016-02-14 22:27:17 +00:00
}
log . Printf ( "[DEBUG] Subnetwork insert request: %#v" , subnetwork )
2017-08-09 22:02:54 +00:00
2017-09-27 23:13:00 +00:00
op , err := config . clientCompute . Subnetworks . Insert ( project , region , subnetwork ) . Do ( )
2016-02-14 22:27:17 +00:00
if err != nil {
return fmt . Errorf ( "Error creating subnetwork: %s" , err )
}
2016-02-16 03:04:44 +00:00
// It probably maybe worked, so store the ID now. ID is a combination of region + subnetwork
// name because subnetwork names are not unique in a project, per the Google docs:
// "When creating a new subnetwork, its name has to be unique in that project for that region, even across networks.
// The same name can appear twice in a project, as long as each one is in a different region."
// https://cloud.google.com/compute/docs/subnetworks
2016-02-14 22:27:17 +00:00
subnetwork . Region = region
2017-09-27 23:13:00 +00:00
d . SetId ( createSubnetID ( subnetwork ) )
2016-02-14 22:27:17 +00:00
2017-12-18 17:47:11 +00:00
err = computeSharedOperationWaitTime ( config . clientCompute , op , project , int ( d . Timeout ( schema . TimeoutCreate ) . Minutes ( ) ) , "Creating Subnetwork" )
2016-02-14 22:27:17 +00:00
if err != nil {
return err
}
return resourceComputeSubnetworkRead ( d , meta )
}
func resourceComputeSubnetworkRead ( d * schema . ResourceData , meta interface { } ) error {
2017-12-08 21:11:09 +00:00
computeApiVersion := getComputeApiVersion ( d , SubnetworkBaseApiVersion , SubnetworkVersionedFeatures )
if computeApiVersion == v0beta {
return resourceComputeSubnetworkReadV0Beta ( d , meta )
}
2016-02-14 22:27:17 +00:00
config := meta . ( * Config )
2016-04-10 16:59:57 +00:00
2016-04-10 21:34:15 +00:00
region , err := getRegion ( d , config )
if err != nil {
return err
}
2016-04-10 16:59:57 +00:00
project , err := getProject ( d , config )
if err != nil {
return err
}
2016-02-14 22:27:17 +00:00
name := d . Get ( "name" ) . ( string )
2017-09-27 23:13:00 +00:00
subnetwork , err := config . clientCompute . Subnetworks . Get ( project , region , name ) . Do ( )
if err != nil {
return handleNotFoundError ( err , d , fmt . Sprintf ( "Subnetwork %q" , name ) )
2016-02-14 22:27:17 +00:00
}
2017-07-26 16:30:59 +00:00
d . Set ( "name" , subnetwork . Name )
d . Set ( "ip_cidr_range" , subnetwork . IpCidrRange )
d . Set ( "network" , subnetwork . Network )
d . Set ( "description" , subnetwork . Description )
d . Set ( "private_ip_google_access" , subnetwork . PrivateIpGoogleAccess )
2016-02-14 22:27:17 +00:00
d . Set ( "gateway_address" , subnetwork . GatewayAddress )
2017-08-09 22:02:54 +00:00
d . Set ( "secondary_ip_range" , flattenSecondaryRanges ( subnetwork . SecondaryIpRanges ) )
2017-11-28 00:32:20 +00:00
d . Set ( "project" , project )
d . Set ( "region" , region )
2017-08-09 22:02:54 +00:00
d . Set ( "self_link" , ConvertSelfLinkToV1 ( subnetwork . SelfLink ) )
2016-02-14 22:27:17 +00:00
return nil
}
2017-12-08 21:11:09 +00:00
func resourceComputeSubnetworkReadV0Beta ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
region , err := getRegion ( d , config )
if err != nil {
return err
}
project , err := getProject ( d , config )
if err != nil {
return err
}
name := d . Get ( "name" ) . ( string )
subnetwork , err := config . clientComputeBeta . Subnetworks . Get ( project , region , name ) . Do ( )
if err != nil {
return handleNotFoundError ( err , d , fmt . Sprintf ( "Subnetwork %q" , name ) )
}
d . Set ( "name" , subnetwork . Name )
d . Set ( "ip_cidr_range" , subnetwork . IpCidrRange )
d . Set ( "network" , subnetwork . Network )
d . Set ( "description" , subnetwork . Description )
d . Set ( "private_ip_google_access" , subnetwork . PrivateIpGoogleAccess )
d . Set ( "gateway_address" , subnetwork . GatewayAddress )
d . Set ( "secondary_ip_range" , flattenSecondaryRangesV0Beta ( subnetwork . SecondaryIpRanges ) )
d . Set ( "project" , project )
d . Set ( "region" , region )
d . Set ( "self_link" , ConvertSelfLinkToV1 ( subnetwork . SelfLink ) )
d . Set ( "fingerprint" , subnetwork . Fingerprint )
return nil
}
2017-06-07 13:52:14 +00:00
func resourceComputeSubnetworkUpdate ( d * schema . ResourceData , meta interface { } ) error {
2017-12-08 21:11:09 +00:00
computeApiVersion := getComputeApiVersion ( d , SubnetworkBaseApiVersion , SubnetworkVersionedFeatures )
2017-06-07 13:52:14 +00:00
config := meta . ( * Config )
region , err := getRegion ( d , config )
if err != nil {
return err
}
project , err := getProject ( d , config )
if err != nil {
return err
}
d . Partial ( true )
if d . HasChange ( "private_ip_google_access" ) {
2017-09-27 23:13:00 +00:00
subnetworksSetPrivateIpGoogleAccessRequest := & compute . SubnetworksSetPrivateIpGoogleAccessRequest {
2017-06-07 13:52:14 +00:00
PrivateIpGoogleAccess : d . Get ( "private_ip_google_access" ) . ( bool ) ,
}
log . Printf ( "[DEBUG] Updating Subnetwork PrivateIpGoogleAccess %q: %#v" , d . Id ( ) , subnetworksSetPrivateIpGoogleAccessRequest )
2017-08-09 22:02:54 +00:00
2017-09-27 23:13:00 +00:00
op , err := config . clientCompute . Subnetworks . SetPrivateIpGoogleAccess (
project , region , d . Get ( "name" ) . ( string ) , subnetworksSetPrivateIpGoogleAccessRequest ) . Do ( )
2017-08-09 22:02:54 +00:00
2017-06-07 13:52:14 +00:00
if err != nil {
return fmt . Errorf ( "Error updating subnetwork PrivateIpGoogleAccess: %s" , err )
}
2017-12-18 17:47:11 +00:00
err = computeSharedOperationWaitTime ( config . clientCompute , op , project , int ( d . Timeout ( schema . TimeoutUpdate ) . Minutes ( ) ) , "Updating Subnetwork PrivateIpGoogleAccess" )
2017-06-07 13:52:14 +00:00
if err != nil {
return err
}
d . SetPartial ( "private_ip_google_access" )
}
2018-01-17 20:58:37 +00:00
if d . HasChange ( "ip_cidr_range" ) {
r := & compute . SubnetworksExpandIpCidrRangeRequest {
IpCidrRange : d . Get ( "ip_cidr_range" ) . ( string ) ,
}
op , err := config . clientCompute . Subnetworks . ExpandIpCidrRange ( project , region , d . Get ( "name" ) . ( string ) , r ) . Do ( )
if err != nil {
return fmt . Errorf ( "Error expanding the ip cidr range: %s" , err )
}
err = computeSharedOperationWaitTime ( config . clientCompute , op , project , int ( d . Timeout ( schema . TimeoutUpdate ) . Minutes ( ) ) , "Expanding Subnetwork IP CIDR range" )
if err != nil {
return err
}
d . SetPartial ( "ip_cidr_range" )
}
2017-12-08 21:11:09 +00:00
if d . HasChange ( "secondary_ip_range" ) && computeApiVersion == v0beta {
v0BetaSubnetwork := & computeBeta . Subnetwork {
SecondaryIpRanges : expandSecondaryRangesV0Beta ( d . Get ( "secondary_ip_range" ) . ( [ ] interface { } ) ) ,
Fingerprint : d . Get ( "fingerprint" ) . ( string ) ,
}
op , err := config . clientComputeBeta . Subnetworks . Patch (
project , region , d . Get ( "name" ) . ( string ) , v0BetaSubnetwork ) . Do ( )
if err != nil {
return fmt . Errorf ( "Error updating subnetwork SecondaryIpRanges: %s" , err )
}
2017-12-18 17:47:11 +00:00
err = computeSharedOperationWaitTime ( config . clientCompute , op , project , int ( d . Timeout ( schema . TimeoutUpdate ) . Minutes ( ) ) , "Updating Subnetwork SecondaryIpRanges" )
2017-12-08 21:11:09 +00:00
if err != nil {
return err
}
d . SetPartial ( "secondary_ip_range" )
}
2017-06-07 13:52:14 +00:00
d . Partial ( false )
return resourceComputeSubnetworkRead ( d , meta )
}
2016-02-14 22:27:17 +00:00
func resourceComputeSubnetworkDelete ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
2016-04-10 16:59:57 +00:00
2016-04-10 21:34:15 +00:00
region , err := getRegion ( d , config )
2016-04-10 16:59:57 +00:00
if err != nil {
return err
}
2016-04-10 21:34:15 +00:00
project , err := getProject ( d , config )
if err != nil {
return err
}
2016-02-14 22:27:17 +00:00
2016-02-16 03:04:44 +00:00
// Delete the subnetwork
2017-09-27 23:13:00 +00:00
op , err := config . clientCompute . Subnetworks . Delete (
project , region , d . Get ( "name" ) . ( string ) ) . Do ( )
2016-02-14 22:27:17 +00:00
if err != nil {
2016-02-16 03:04:44 +00:00
return fmt . Errorf ( "Error deleting subnetwork: %s" , err )
2016-02-14 22:27:17 +00:00
}
2017-12-18 17:47:11 +00:00
err = computeSharedOperationWaitTime ( config . clientCompute , op , project , int ( d . Timeout ( schema . TimeoutDelete ) . Minutes ( ) ) , "Deleting Subnetwork" )
2016-02-14 22:27:17 +00:00
if err != nil {
return err
}
d . SetId ( "" )
return nil
}
2017-07-26 16:30:59 +00:00
func resourceComputeSubnetworkImportState ( d * schema . ResourceData , meta interface { } ) ( [ ] * schema . ResourceData , error ) {
parts := strings . Split ( d . Id ( ) , "/" )
if len ( parts ) != 2 {
return nil , fmt . Errorf ( "Invalid compute subnetwork specifier. Expecting {region}/{name}" )
}
region , name := parts [ 0 ] , parts [ 1 ]
d . Set ( "region" , region )
d . Set ( "name" , name )
d . SetId ( createSubnetID ( & compute . Subnetwork {
Region : region ,
Name : name ,
} ) )
return [ ] * schema . ResourceData { d } , nil
}
2017-08-09 22:02:54 +00:00
func splitSubnetID ( id string ) ( region string , name string ) {
parts := strings . Split ( id , "/" )
region = parts [ 0 ]
name = parts [ 1 ]
return
}
2017-09-27 23:13:00 +00:00
func expandSecondaryRanges ( configured [ ] interface { } ) [ ] * compute . SubnetworkSecondaryRange {
secondaryRanges := make ( [ ] * compute . SubnetworkSecondaryRange , 0 , len ( configured ) )
2017-08-09 22:02:54 +00:00
for _ , raw := range configured {
data := raw . ( map [ string ] interface { } )
2017-09-27 23:13:00 +00:00
secondaryRange := compute . SubnetworkSecondaryRange {
2017-08-09 22:02:54 +00:00
RangeName : data [ "range_name" ] . ( string ) ,
IpCidrRange : data [ "ip_cidr_range" ] . ( string ) ,
}
secondaryRanges = append ( secondaryRanges , & secondaryRange )
}
return secondaryRanges
}
2017-12-08 21:11:09 +00:00
func expandSecondaryRangesV0Beta ( configured [ ] interface { } ) [ ] * computeBeta . SubnetworkSecondaryRange {
secondaryRanges := make ( [ ] * computeBeta . SubnetworkSecondaryRange , 0 , len ( configured ) )
for _ , raw := range configured {
data := raw . ( map [ string ] interface { } )
secondaryRange := computeBeta . SubnetworkSecondaryRange {
RangeName : data [ "range_name" ] . ( string ) ,
IpCidrRange : data [ "ip_cidr_range" ] . ( string ) ,
}
secondaryRanges = append ( secondaryRanges , & secondaryRange )
}
return secondaryRanges
}
func flattenSecondaryRangesV0Beta ( secondaryRanges [ ] * computeBeta . SubnetworkSecondaryRange ) [ ] map [ string ] interface { } {
secondaryRangesSchema := make ( [ ] map [ string ] interface { } , 0 , len ( secondaryRanges ) )
for _ , secondaryRange := range secondaryRanges {
data := map [ string ] interface { } {
"range_name" : secondaryRange . RangeName ,
"ip_cidr_range" : secondaryRange . IpCidrRange ,
}
secondaryRangesSchema = append ( secondaryRangesSchema , data )
}
return secondaryRangesSchema
}
2018-01-17 20:58:37 +00:00
// Whether the IP CIDR change shrinks the block.
func isShrinkageIpCidr ( old , new , _ interface { } ) bool {
_ , oldCidr , oldErr := net . ParseCIDR ( old . ( string ) )
_ , newCidr , newErr := net . ParseCIDR ( new . ( string ) )
if oldErr != nil || newErr != nil {
// This should never happen. The ValidateFunc on the field ensures it.
return false
}
oldStart , oldEnd := cidr . AddressRange ( oldCidr )
if newCidr . Contains ( oldStart ) && newCidr . Contains ( oldEnd ) {
// This is a CIDR range expansion, no need to ForceNew, we have an update method for it.
return false
}
return true
}