@@ -19,7 +19,6 @@ import (
1919 "github.com/mitchellh/hashstructure"
2020 computeBeta "google.golang.org/api/compute/v0.beta"
2121 "google.golang.org/api/compute/v1"
22- "google.golang.org/api/googleapi"
2322)
2423
2524var (
5554 }
5655)
5756
57+ // network_interface.[d].network_ip can only change when subnet/network
58+ // is also changing. Validate that if network_ip is changing this scenario
59+ // holds up to par.
60+ func forceNewIfNetworkIPNotUpdatable (ctx context.Context , d * schema.ResourceDiff , meta interface {}) error {
61+ // separate func to allow unit testing
62+ return forceNewIfNetworkIPNotUpdatableFunc (d )
63+ }
64+
65+ func forceNewIfNetworkIPNotUpdatableFunc (d TerraformResourceDiff ) error {
66+ oldCount , newCount := d .GetChange ("network_interface.#" )
67+ if oldCount .(int ) != newCount .(int ) {
68+ return nil
69+ }
70+
71+ for i := 0 ; i < newCount .(int ); i ++ {
72+ prefix := fmt .Sprintf ("network_interface.%d" , i )
73+ networkKey := prefix + ".network"
74+ subnetworkKey := prefix + ".subnetwork"
75+ subnetworkProjectKey := prefix + ".subnetwork_project"
76+ networkIPKey := prefix + ".network_ip"
77+ if d .HasChange (networkIPKey ) {
78+ if ! d .HasChange (networkKey ) && ! d .HasChange (subnetworkKey ) && ! d .HasChange (subnetworkProjectKey ) {
79+ if err := d .ForceNew (networkIPKey ); err != nil {
80+ return err
81+ }
82+ }
83+ }
84+ }
85+
86+ return nil
87+ }
88+
5889func resourceComputeInstance () * schema.Resource {
5990 return & schema.Resource {
6091 Create : resourceComputeInstanceCreate ,
@@ -253,7 +284,6 @@ func resourceComputeInstance() *schema.Resource {
253284 "network_ip" : {
254285 Type : schema .TypeString ,
255286 Optional : true ,
256- ForceNew : true ,
257287 Computed : true ,
258288 Description : `The private IP address assigned to the instance.` ,
259289 },
@@ -710,6 +740,7 @@ func resourceComputeInstance() *schema.Resource {
710740 suppressEmptyGuestAcceleratorDiff ,
711741 ),
712742 desiredStatusDiff ,
743+ forceNewIfNetworkIPNotUpdatable ,
713744 ),
714745 }
715746}
@@ -1336,7 +1367,7 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
13361367 return fmt .Errorf ("Instance had unexpected number of network interfaces: %d" , len (instance .NetworkInterfaces ))
13371368 }
13381369
1339- var updatesToNIWhileStopped []func (... googleapi. CallOption ) ( * computeBeta.Operation , error )
1370+ var updatesToNIWhileStopped []func (inst * computeBeta.Instance ) error
13401371 for i := 0 ; i < len (networkInterfaces ); i ++ {
13411372 prefix := fmt .Sprintf ("network_interface.%d" , i )
13421373 networkInterface := networkInterfaces [i ]
@@ -1377,50 +1408,23 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
13771408 }
13781409 }
13791410
1380- if d .HasChange (prefix + ".access_config" ) {
1381-
1411+ if ! updateDuringStop && d .HasChange (prefix + ".access_config" ) {
13821412 // TODO: This code deletes then recreates accessConfigs. This is bad because it may
13831413 // leave the machine inaccessible from either ip if the creation part fails (network
13841414 // timeout etc). However right now there is a GCE limit of 1 accessConfig so it is
13851415 // the only way to do it. In future this should be revised to only change what is
13861416 // necessary, and also add before removing.
13871417
1388- // Delete any accessConfig that currently exists in instNetworkInterface
1389- for _ , ac := range instNetworkInterface .AccessConfigs {
1390- op , err := config .NewComputeClient (userAgent ).Instances .DeleteAccessConfig (
1391- project , zone , instance .Name , ac .Name , networkName ).Do ()
1392- if err != nil {
1393- return fmt .Errorf ("Error deleting old access_config: %s" , err )
1394- }
1395- opErr := computeOperationWaitTime (config , op , project , "old access_config to delete" , userAgent , d .Timeout (schema .TimeoutUpdate ))
1396- if opErr != nil {
1397- return opErr
1398- }
1418+ // Delete current access configs
1419+ err := computeInstanceDeleteAccessConfigs (d , config , instNetworkInterface , project , zone , userAgent , instance .Name )
1420+ if err != nil {
1421+ return err
13991422 }
14001423
14011424 // Create new ones
1402- accessConfigsCount := d .Get (prefix + ".access_config.#" ).(int )
1403- for j := 0 ; j < accessConfigsCount ; j ++ {
1404- acPrefix := fmt .Sprintf ("%s.access_config.%d" , prefix , j )
1405- ac := & computeBeta.AccessConfig {
1406- Type : "ONE_TO_ONE_NAT" ,
1407- NatIP : d .Get (acPrefix + ".nat_ip" ).(string ),
1408- NetworkTier : d .Get (acPrefix + ".network_tier" ).(string ),
1409- }
1410- if ptr , ok := d .GetOk (acPrefix + ".public_ptr_domain_name" ); ok && ptr != "" {
1411- ac .SetPublicPtr = true
1412- ac .PublicPtrDomainName = ptr .(string )
1413- }
1414-
1415- op , err := config .NewComputeBetaClient (userAgent ).Instances .AddAccessConfig (
1416- project , zone , instance .Name , networkName , ac ).Do ()
1417- if err != nil {
1418- return fmt .Errorf ("Error adding new access_config: %s" , err )
1419- }
1420- opErr := computeOperationWaitTime (config , op , project , "new access_config to add" , userAgent , d .Timeout (schema .TimeoutUpdate ))
1421- if opErr != nil {
1422- return opErr
1423- }
1425+ err = computeInstanceAddAccessConfigs (d , config , instNetworkInterface , networkInterface .AccessConfigs , project , zone , userAgent , instance .Name )
1426+ if err != nil {
1427+ return err
14241428 }
14251429
14261430 // re-read fingerprint
@@ -1431,10 +1435,6 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
14311435 instNetworkInterface = instance .NetworkInterfaces [i ]
14321436 }
14331437
1434- // Setting NetworkIP to empty and AccessConfigs to nil.
1435- // This will opt them out from being modified in the patch call.
1436- networkInterface .NetworkIP = ""
1437- networkInterface .AccessConfigs = nil
14381438 if ! updateDuringStop && d .HasChange (prefix + ".alias_ip_range" ) {
14391439 // Alias IP ranges cannot be updated; they must be removed and then added
14401440 // unless you are changing subnetwork/network
@@ -1459,8 +1459,11 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
14591459 instNetworkInterface = instance .NetworkInterfaces [i ]
14601460 }
14611461
1462- networkInterface .Fingerprint = instNetworkInterface .Fingerprint
1463- updateCall := config .NewComputeBetaClient (userAgent ).Instances .UpdateNetworkInterface (project , zone , instance .Name , networkName , networkInterface ).Do
1462+ networkInterfacePatchObj := & computeBeta.NetworkInterface {
1463+ AliasIpRanges : networkInterface .AliasIpRanges ,
1464+ Fingerprint : instNetworkInterface .Fingerprint ,
1465+ }
1466+ updateCall := config .NewComputeBetaClient (userAgent ).Instances .UpdateNetworkInterface (project , zone , instance .Name , networkName , networkInterfacePatchObj ).Do
14641467 op , err := updateCall ()
14651468 if err != nil {
14661469 return errwrap .Wrapf ("Error updating network interface: {{err}}" , err )
@@ -1469,9 +1472,30 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
14691472 if opErr != nil {
14701473 return opErr
14711474 }
1472- } else if updateDuringStop {
1473- networkInterface .Fingerprint = instNetworkInterface .Fingerprint
1474- updateCall := config .NewComputeBetaClient (userAgent ).Instances .UpdateNetworkInterface (project , zone , instance .Name , networkName , networkInterface ).Do
1475+ }
1476+
1477+ if updateDuringStop {
1478+ // Lets be explicit about what we are changing in the patch call
1479+ networkInterfacePatchObj := & computeBeta.NetworkInterface {
1480+ Network : networkInterface .Network ,
1481+ Subnetwork : networkInterface .Subnetwork ,
1482+ AliasIpRanges : networkInterface .AliasIpRanges ,
1483+ }
1484+
1485+ // network_ip can be inferred if not declared. Let's only patch if it's being changed by user
1486+ // otherwise this could fail if the network ip is not compatible with the new Subnetwork/Network.
1487+ if d .HasChange (prefix + ".network_ip" ) {
1488+ networkInterfacePatchObj .NetworkIP = networkInterface .NetworkIP
1489+ }
1490+
1491+ // Access config can run into some issues since we can't tell the difference between
1492+ // the users declared intent (config within their hcl file) and what we have inferred from the
1493+ // server (terraform state). Access configs contain an ip subproperty that can be incompatible
1494+ // with the subnetwork/network we are transitioning to. Due to this we only change access
1495+ // configs if we notice the configuration (user intent) changes.
1496+ accessConfigsHaveChanged := d .HasChange (prefix + ".access_config" )
1497+
1498+ updateCall := computeInstanceCreateUpdateWhileStoppedCall (d , config , networkInterfacePatchObj , networkInterface .AccessConfigs , accessConfigsHaveChanged , i , project , zone , userAgent , instance .Name )
14751499 updatesToNIWhileStopped = append (updatesToNIWhileStopped , updateCall )
14761500 }
14771501 }
@@ -1737,14 +1761,18 @@ func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) err
17371761 }
17381762 }
17391763
1740- for _ , updateCall := range updatesToNIWhileStopped {
1741- op , err := updateCall ()
1764+ // If the instance stops it can invalidate the fingerprint for network interface.
1765+ // refresh the instance to get a new fingerprint
1766+ if len (updatesToNIWhileStopped ) > 0 {
1767+ instance , err = config .NewComputeBetaClient (userAgent ).Instances .Get (project , zone , instance .Name ).Do ()
17421768 if err != nil {
1743- return errwrap . Wrapf ( "Error updating network interface: {{ err}}" , err )
1769+ return err
17441770 }
1745- opErr := computeOperationWaitTime (config , op , project , "network interface to update" , userAgent , d .Timeout (schema .TimeoutUpdate ))
1746- if opErr != nil {
1747- return opErr
1771+ }
1772+ for _ , patch := range updatesToNIWhileStopped {
1773+ err := patch (instance )
1774+ if err != nil {
1775+ return err
17481776 }
17491777 }
17501778
0 commit comments