github.com/GoogleCloudPlatform/compute-image-tools/cli_tools@v0.0.0-20240516224744-de2dabc4ed1b/common/utils/param/network_resolver.go (about) 1 // Copyright 2021 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package param 16 17 import ( 18 "strings" 19 20 daisy "github.com/GoogleCloudPlatform/compute-daisy" 21 daisyCompute "github.com/GoogleCloudPlatform/compute-daisy/compute" 22 "google.golang.org/api/compute/v1" 23 24 "github.com/GoogleCloudPlatform/compute-image-tools/cli_tools/common/utils/paramhelper" 25 ) 26 27 // NetworkResolver standardizes and validates network and subnet fields. It follows the 28 // rules from the `networkInterfaces[].network` section of instances.insert: 29 // 30 // https://cloud.google.com/compute/docs/reference/rest/v1/instances/insert 31 // 32 // - When both subnet and network are empty, explicitly use the default network. 33 // - If the subnet is empty, leave it empty to allow the compute backend to infer it. 34 // - Similarly, if only the network is empty, leave it empty to allow inference. 35 // - Backfill project and region resource properties if they are omitted in the network and subnet URIs. 36 type NetworkResolver interface { 37 // Resolve returns the URI representation of network and subnet 38 // within a given region. 39 // 40 // There are two goals: 41 // 42 // a. Explicitly use the 'default' network only when 43 // network is omitted and subnet is empty. 44 // b. Convert bare identifiers to URIs. 45 Resolve(originalNetwork, originalSubnet, region, project string) (network, subnet string, err error) 46 } 47 48 // NewNetworkResolver returns a NetworkResolver implementation that uses the Compute API. 49 func NewNetworkResolver(client daisyCompute.Client) NetworkResolver { 50 return &computeNetworkResolver{client} 51 } 52 53 // computeNetworkResolver uses the Compute API to implement NetworkResolver. 54 type computeNetworkResolver struct { 55 client daisyCompute.Client 56 } 57 58 func (r *computeNetworkResolver) Resolve( 59 originalNetwork, originalSubnet, region, project string) (network, subnet string, err error) { 60 61 // 1. Segment the user's input into component fields such as network name, subnet name, project, and region. 62 // If the URI in originalNetwork or originalSubnet didn't specify project or region, then backfill 63 // those fields using region and project. 64 networkResource, subnetResource, err := parseNetworkAndSubnet(originalNetwork, originalSubnet, region, project) 65 if err != nil { 66 return "", "", err 67 } 68 69 if networkResource.String() == "" && subnetResource.String() == "" { 70 return "", "", nil 71 } 72 73 // 2. Query the Compute API to check whether the network and subnet exist. 74 var subnetResponse *compute.Subnetwork 75 var networkResponse *compute.Network 76 if subnetResource.String() != "" { 77 subnetResponse, err = r.client.GetSubnetwork(subnetResource.Project, subnetResource.Region, subnetResource.Name) 78 if err != nil { 79 return "", "", daisy.Errf("Validation of subnetwork %q failed: %s", subnetResource, err) 80 } 81 } 82 if networkResource.String() != "" { 83 networkResponse, err = r.client.GetNetwork(networkResource.Project, networkResource.Name) 84 if err != nil { 85 return "", "", daisy.Errf("Validation of network %q failed: %s", networkResource, err) 86 } 87 } 88 89 // 3. Check whether the subnet's network matches the user's specified network. 90 if subnetResponse != nil && networkResponse != nil && subnetResponse.Network != networkResponse.SelfLink { 91 return "", "", daisy.Errf("Network %q does not contain subnet %q", networkResource, subnetResource) 92 } 93 return networkResource.String(), subnetResource.String(), err 94 } 95 96 // parseNetworkAndSubnet parses the user's values into structs and backfills 97 // missing fields based on other values provided by the user. 98 func parseNetworkAndSubnet(originalNetwork, originalSubnet, region, project string) ( 99 *paramhelper.NetworkResource, *paramhelper.SubnetResource, error) { 100 101 networkResource, err := paramhelper.SplitNetworkResource(strings.TrimSpace(originalNetwork)) 102 if err != nil { 103 return nil, nil, err 104 } 105 subnetResource, err := paramhelper.SplitSubnetResource(strings.TrimSpace(originalSubnet)) 106 if err != nil { 107 return nil, nil, err 108 } 109 if networkResource.String() == "" && subnetResource.String() == "" { 110 return ¶mhelper.NetworkResource{ 111 Name: "default", 112 Project: project, 113 }, ¶mhelper.SubnetResource{}, nil 114 } 115 if networkResource.String() != "" { 116 if networkResource.Project == "" { 117 networkResource.Project = project 118 } 119 } 120 if subnetResource.String() != "" { 121 if subnetResource.Project == "" { 122 subnetResource.Project = project 123 } 124 if subnetResource.Region == "" { 125 subnetResource.Region = region 126 } 127 } 128 return networkResource, subnetResource, nil 129 }