cli_tools/common/utils/param/network_resolver.go (76 lines of code) (raw):
// Copyright 2021 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package param
import (
"strings"
daisy "github.com/GoogleCloudPlatform/compute-daisy"
daisyCompute "github.com/GoogleCloudPlatform/compute-daisy/compute"
"google.golang.org/api/compute/v1"
"github.com/GoogleCloudPlatform/compute-image-import/cli_tools/common/utils/paramhelper"
)
// NetworkResolver standardizes and validates network and subnet fields. It follows the
// rules from the `networkInterfaces[].network` section of instances.insert:
//
// https://cloud.google.com/compute/docs/reference/rest/v1/instances/insert
//
// - When both subnet and network are empty, explicitly use the default network.
// - If the subnet is empty, leave it empty to allow the compute backend to infer it.
// - Similarly, if only the network is empty, leave it empty to allow inference.
// - Backfill project and region resource properties if they are omitted in the network and subnet URIs.
type NetworkResolver interface {
// Resolve returns the URI representation of network and subnet
// within a given region.
//
// There are two goals:
//
// a. Explicitly use the 'default' network only when
// network is omitted and subnet is empty.
// b. Convert bare identifiers to URIs.
Resolve(originalNetwork, originalSubnet, region, project string) (network, subnet string, err error)
}
// NewNetworkResolver returns a NetworkResolver implementation that uses the Compute API.
func NewNetworkResolver(client daisyCompute.Client) NetworkResolver {
return &computeNetworkResolver{client}
}
// computeNetworkResolver uses the Compute API to implement NetworkResolver.
type computeNetworkResolver struct {
client daisyCompute.Client
}
func (r *computeNetworkResolver) Resolve(
originalNetwork, originalSubnet, region, project string) (network, subnet string, err error) {
// 1. Segment the user's input into component fields such as network name, subnet name, project, and region.
// If the URI in originalNetwork or originalSubnet didn't specify project or region, then backfill
// those fields using region and project.
networkResource, subnetResource, err := parseNetworkAndSubnet(originalNetwork, originalSubnet, region, project)
if err != nil {
return "", "", err
}
if networkResource.String() == "" && subnetResource.String() == "" {
return "", "", nil
}
// 2. Query the Compute API to check whether the network and subnet exist.
var subnetResponse *compute.Subnetwork
var networkResponse *compute.Network
if subnetResource.String() != "" {
subnetResponse, err = r.client.GetSubnetwork(subnetResource.Project, subnetResource.Region, subnetResource.Name)
if err != nil {
return "", "", daisy.Errf("Validation of subnetwork %q failed: %s", subnetResource, err)
}
}
if networkResource.String() != "" {
networkResponse, err = r.client.GetNetwork(networkResource.Project, networkResource.Name)
if err != nil {
return "", "", daisy.Errf("Validation of network %q failed: %s", networkResource, err)
}
}
// 3. Check whether the subnet's network matches the user's specified network.
if subnetResponse != nil && networkResponse != nil && subnetResponse.Network != networkResponse.SelfLink {
return "", "", daisy.Errf("Network %q does not contain subnet %q", networkResource, subnetResource)
}
return networkResource.String(), subnetResource.String(), err
}
// parseNetworkAndSubnet parses the user's values into structs and backfills
// missing fields based on other values provided by the user.
func parseNetworkAndSubnet(originalNetwork, originalSubnet, region, project string) (
*paramhelper.NetworkResource, *paramhelper.SubnetResource, error) {
networkResource, err := paramhelper.SplitNetworkResource(strings.TrimSpace(originalNetwork))
if err != nil {
return nil, nil, err
}
subnetResource, err := paramhelper.SplitSubnetResource(strings.TrimSpace(originalSubnet))
if err != nil {
return nil, nil, err
}
if networkResource.String() == "" && subnetResource.String() == "" {
return ¶mhelper.NetworkResource{
Name: "default",
Project: project,
}, ¶mhelper.SubnetResource{}, nil
}
if networkResource.String() != "" {
if networkResource.Project == "" {
networkResource.Project = project
}
}
if subnetResource.String() != "" {
if subnetResource.Project == "" {
subnetResource.Project = project
}
if subnetResource.Region == "" {
subnetResource.Region = region
}
}
return networkResource, subnetResource, nil
}