Configure GCP Network Connectivity Spokes

The gcp:networkconnectivity/spoke:Spoke resource, part of the Pulumi GCP provider, attaches network resources to Network Connectivity Center hubs for transitive routing. This guide focuses on three capabilities: VPC network attachment with route filtering, hybrid connectivity through VPN and Interconnect, and traffic inspection via router appliances and gateways.

Spokes reference existing hubs and network infrastructure such as VPC networks, VPN tunnels, Interconnect attachments, or Compute Engine instances. The examples are intentionally small. Combine them with your own hub topology and network architecture.

Connect a VPC network with route filtering

Most Network Connectivity Center deployments attach VPC networks to a hub, enabling transitive routing between spokes while controlling which IP ranges are advertised.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const network = new gcp.compute.Network("network", {
    name: "net",
    autoCreateSubnetworks: false,
});
const basicHub = new gcp.networkconnectivity.Hub("basic_hub", {
    name: "hub1",
    description: "A sample hub",
    labels: {
        "label-two": "value-one",
    },
});
const primary = new gcp.networkconnectivity.Spoke("primary", {
    name: "spoke1",
    location: "global",
    description: "A sample spoke with a linked router appliance instance",
    labels: {
        "label-one": "value-one",
    },
    hub: basicHub.id,
    linkedVpcNetwork: {
        excludeExportRanges: [
            "198.51.100.0/24",
            "10.10.0.0/16",
        ],
        includeExportRanges: [
            "198.51.100.0/23",
            "10.0.0.0/8",
        ],
        uri: network.selfLink,
    },
});
import pulumi
import pulumi_gcp as gcp

network = gcp.compute.Network("network",
    name="net",
    auto_create_subnetworks=False)
basic_hub = gcp.networkconnectivity.Hub("basic_hub",
    name="hub1",
    description="A sample hub",
    labels={
        "label-two": "value-one",
    })
primary = gcp.networkconnectivity.Spoke("primary",
    name="spoke1",
    location="global",
    description="A sample spoke with a linked router appliance instance",
    labels={
        "label-one": "value-one",
    },
    hub=basic_hub.id,
    linked_vpc_network={
        "exclude_export_ranges": [
            "198.51.100.0/24",
            "10.10.0.0/16",
        ],
        "include_export_ranges": [
            "198.51.100.0/23",
            "10.0.0.0/8",
        ],
        "uri": network.self_link,
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/networkconnectivity"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		network, err := compute.NewNetwork(ctx, "network", &compute.NetworkArgs{
			Name:                  pulumi.String("net"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		basicHub, err := networkconnectivity.NewHub(ctx, "basic_hub", &networkconnectivity.HubArgs{
			Name:        pulumi.String("hub1"),
			Description: pulumi.String("A sample hub"),
			Labels: pulumi.StringMap{
				"label-two": pulumi.String("value-one"),
			},
		})
		if err != nil {
			return err
		}
		_, err = networkconnectivity.NewSpoke(ctx, "primary", &networkconnectivity.SpokeArgs{
			Name:        pulumi.String("spoke1"),
			Location:    pulumi.String("global"),
			Description: pulumi.String("A sample spoke with a linked router appliance instance"),
			Labels: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
			Hub: basicHub.ID(),
			LinkedVpcNetwork: &networkconnectivity.SpokeLinkedVpcNetworkArgs{
				ExcludeExportRanges: pulumi.StringArray{
					pulumi.String("198.51.100.0/24"),
					pulumi.String("10.10.0.0/16"),
				},
				IncludeExportRanges: pulumi.StringArray{
					pulumi.String("198.51.100.0/23"),
					pulumi.String("10.0.0.0/8"),
				},
				Uri: network.SelfLink,
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var network = new Gcp.Compute.Network("network", new()
    {
        Name = "net",
        AutoCreateSubnetworks = false,
    });

    var basicHub = new Gcp.NetworkConnectivity.Hub("basic_hub", new()
    {
        Name = "hub1",
        Description = "A sample hub",
        Labels = 
        {
            { "label-two", "value-one" },
        },
    });

    var primary = new Gcp.NetworkConnectivity.Spoke("primary", new()
    {
        Name = "spoke1",
        Location = "global",
        Description = "A sample spoke with a linked router appliance instance",
        Labels = 
        {
            { "label-one", "value-one" },
        },
        Hub = basicHub.Id,
        LinkedVpcNetwork = new Gcp.NetworkConnectivity.Inputs.SpokeLinkedVpcNetworkArgs
        {
            ExcludeExportRanges = new[]
            {
                "198.51.100.0/24",
                "10.10.0.0/16",
            },
            IncludeExportRanges = new[]
            {
                "198.51.100.0/23",
                "10.0.0.0/8",
            },
            Uri = network.SelfLink,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.networkconnectivity.Hub;
import com.pulumi.gcp.networkconnectivity.HubArgs;
import com.pulumi.gcp.networkconnectivity.Spoke;
import com.pulumi.gcp.networkconnectivity.SpokeArgs;
import com.pulumi.gcp.networkconnectivity.inputs.SpokeLinkedVpcNetworkArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var network = new Network("network", NetworkArgs.builder()
            .name("net")
            .autoCreateSubnetworks(false)
            .build());

        var basicHub = new Hub("basicHub", HubArgs.builder()
            .name("hub1")
            .description("A sample hub")
            .labels(Map.of("label-two", "value-one"))
            .build());

        var primary = new Spoke("primary", SpokeArgs.builder()
            .name("spoke1")
            .location("global")
            .description("A sample spoke with a linked router appliance instance")
            .labels(Map.of("label-one", "value-one"))
            .hub(basicHub.id())
            .linkedVpcNetwork(SpokeLinkedVpcNetworkArgs.builder()
                .excludeExportRanges(                
                    "198.51.100.0/24",
                    "10.10.0.0/16")
                .includeExportRanges(                
                    "198.51.100.0/23",
                    "10.0.0.0/8")
                .uri(network.selfLink())
                .build())
            .build());

    }
}
resources:
  network:
    type: gcp:compute:Network
    properties:
      name: net
      autoCreateSubnetworks: false
  basicHub:
    type: gcp:networkconnectivity:Hub
    name: basic_hub
    properties:
      name: hub1
      description: A sample hub
      labels:
        label-two: value-one
  primary:
    type: gcp:networkconnectivity:Spoke
    properties:
      name: spoke1
      location: global
      description: A sample spoke with a linked router appliance instance
      labels:
        label-one: value-one
      hub: ${basicHub.id}
      linkedVpcNetwork:
        excludeExportRanges:
          - 198.51.100.0/24
          - 10.10.0.0/16
        includeExportRanges:
          - 198.51.100.0/23
          - 10.0.0.0/8
        uri: ${network.selfLink}

The linkedVpcNetwork property attaches a VPC to the hub. The uri references the VPC’s self-link. The includeExportRanges property specifies which CIDR blocks this spoke advertises to other spokes, while excludeExportRanges removes specific ranges from advertisement. This filtering prevents unwanted route propagation between spokes.

Route traffic through network virtual appliances

Organizations requiring centralized security inspection deploy router appliance instances as spokes, directing traffic through these virtual appliances before reaching destinations.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const network = new gcp.compute.Network("network", {
    name: "tf-test-network_85160",
    autoCreateSubnetworks: false,
});
const subnetwork = new gcp.compute.Subnetwork("subnetwork", {
    name: "tf-test-subnet_92130",
    ipCidrRange: "10.0.0.0/28",
    region: "us-central1",
    network: network.selfLink,
});
const instance = new gcp.compute.Instance("instance", {
    name: "tf-test-instance_16199",
    machineType: "e2-medium",
    canIpForward: true,
    zone: "us-central1-a",
    bootDisk: {
        initializeParams: {
            image: "projects/debian-cloud/global/images/debian-10-buster-v20210817",
        },
    },
    networkInterfaces: [{
        subnetwork: subnetwork.name,
        networkIp: "10.0.0.2",
        accessConfigs: [{
            networkTier: "PREMIUM",
        }],
    }],
});
const basicHub = new gcp.networkconnectivity.Hub("basic_hub", {
    name: "tf-test-hub_21563",
    description: "A sample hub",
    labels: {
        "label-two": "value-one",
    },
});
const primary = new gcp.networkconnectivity.Spoke("primary", {
    name: "tf-test-name_25141",
    location: "us-central1",
    description: "A sample spoke with a linked routher appliance instance",
    labels: {
        "label-one": "value-one",
    },
    hub: basicHub.id,
    linkedRouterApplianceInstances: {
        instances: [{
            virtualMachine: instance.selfLink,
            ipAddress: "10.0.0.2",
        }],
        siteToSiteDataTransfer: true,
        includeImportRanges: ["ALL_IPV4_RANGES"],
    },
});
import pulumi
import pulumi_gcp as gcp

network = gcp.compute.Network("network",
    name="tf-test-network_85160",
    auto_create_subnetworks=False)
subnetwork = gcp.compute.Subnetwork("subnetwork",
    name="tf-test-subnet_92130",
    ip_cidr_range="10.0.0.0/28",
    region="us-central1",
    network=network.self_link)
instance = gcp.compute.Instance("instance",
    name="tf-test-instance_16199",
    machine_type="e2-medium",
    can_ip_forward=True,
    zone="us-central1-a",
    boot_disk={
        "initialize_params": {
            "image": "projects/debian-cloud/global/images/debian-10-buster-v20210817",
        },
    },
    network_interfaces=[{
        "subnetwork": subnetwork.name,
        "network_ip": "10.0.0.2",
        "access_configs": [{
            "network_tier": "PREMIUM",
        }],
    }])
basic_hub = gcp.networkconnectivity.Hub("basic_hub",
    name="tf-test-hub_21563",
    description="A sample hub",
    labels={
        "label-two": "value-one",
    })
primary = gcp.networkconnectivity.Spoke("primary",
    name="tf-test-name_25141",
    location="us-central1",
    description="A sample spoke with a linked routher appliance instance",
    labels={
        "label-one": "value-one",
    },
    hub=basic_hub.id,
    linked_router_appliance_instances={
        "instances": [{
            "virtual_machine": instance.self_link,
            "ip_address": "10.0.0.2",
        }],
        "site_to_site_data_transfer": True,
        "include_import_ranges": ["ALL_IPV4_RANGES"],
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/networkconnectivity"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		network, err := compute.NewNetwork(ctx, "network", &compute.NetworkArgs{
			Name:                  pulumi.String("tf-test-network_85160"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		subnetwork, err := compute.NewSubnetwork(ctx, "subnetwork", &compute.SubnetworkArgs{
			Name:        pulumi.String("tf-test-subnet_92130"),
			IpCidrRange: pulumi.String("10.0.0.0/28"),
			Region:      pulumi.String("us-central1"),
			Network:     network.SelfLink,
		})
		if err != nil {
			return err
		}
		instance, err := compute.NewInstance(ctx, "instance", &compute.InstanceArgs{
			Name:         pulumi.String("tf-test-instance_16199"),
			MachineType:  pulumi.String("e2-medium"),
			CanIpForward: pulumi.Bool(true),
			Zone:         pulumi.String("us-central1-a"),
			BootDisk: &compute.InstanceBootDiskArgs{
				InitializeParams: &compute.InstanceBootDiskInitializeParamsArgs{
					Image: pulumi.String("projects/debian-cloud/global/images/debian-10-buster-v20210817"),
				},
			},
			NetworkInterfaces: compute.InstanceNetworkInterfaceArray{
				&compute.InstanceNetworkInterfaceArgs{
					Subnetwork: subnetwork.Name,
					NetworkIp:  pulumi.String("10.0.0.2"),
					AccessConfigs: compute.InstanceNetworkInterfaceAccessConfigArray{
						&compute.InstanceNetworkInterfaceAccessConfigArgs{
							NetworkTier: pulumi.String("PREMIUM"),
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		basicHub, err := networkconnectivity.NewHub(ctx, "basic_hub", &networkconnectivity.HubArgs{
			Name:        pulumi.String("tf-test-hub_21563"),
			Description: pulumi.String("A sample hub"),
			Labels: pulumi.StringMap{
				"label-two": pulumi.String("value-one"),
			},
		})
		if err != nil {
			return err
		}
		_, err = networkconnectivity.NewSpoke(ctx, "primary", &networkconnectivity.SpokeArgs{
			Name:        pulumi.String("tf-test-name_25141"),
			Location:    pulumi.String("us-central1"),
			Description: pulumi.String("A sample spoke with a linked routher appliance instance"),
			Labels: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
			Hub: basicHub.ID(),
			LinkedRouterApplianceInstances: &networkconnectivity.SpokeLinkedRouterApplianceInstancesArgs{
				Instances: networkconnectivity.SpokeLinkedRouterApplianceInstancesInstanceArray{
					&networkconnectivity.SpokeLinkedRouterApplianceInstancesInstanceArgs{
						VirtualMachine: instance.SelfLink,
						IpAddress:      pulumi.String("10.0.0.2"),
					},
				},
				SiteToSiteDataTransfer: pulumi.Bool(true),
				IncludeImportRanges: pulumi.StringArray{
					pulumi.String("ALL_IPV4_RANGES"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var network = new Gcp.Compute.Network("network", new()
    {
        Name = "tf-test-network_85160",
        AutoCreateSubnetworks = false,
    });

    var subnetwork = new Gcp.Compute.Subnetwork("subnetwork", new()
    {
        Name = "tf-test-subnet_92130",
        IpCidrRange = "10.0.0.0/28",
        Region = "us-central1",
        Network = network.SelfLink,
    });

    var instance = new Gcp.Compute.Instance("instance", new()
    {
        Name = "tf-test-instance_16199",
        MachineType = "e2-medium",
        CanIpForward = true,
        Zone = "us-central1-a",
        BootDisk = new Gcp.Compute.Inputs.InstanceBootDiskArgs
        {
            InitializeParams = new Gcp.Compute.Inputs.InstanceBootDiskInitializeParamsArgs
            {
                Image = "projects/debian-cloud/global/images/debian-10-buster-v20210817",
            },
        },
        NetworkInterfaces = new[]
        {
            new Gcp.Compute.Inputs.InstanceNetworkInterfaceArgs
            {
                Subnetwork = subnetwork.Name,
                NetworkIp = "10.0.0.2",
                AccessConfigs = new[]
                {
                    new Gcp.Compute.Inputs.InstanceNetworkInterfaceAccessConfigArgs
                    {
                        NetworkTier = "PREMIUM",
                    },
                },
            },
        },
    });

    var basicHub = new Gcp.NetworkConnectivity.Hub("basic_hub", new()
    {
        Name = "tf-test-hub_21563",
        Description = "A sample hub",
        Labels = 
        {
            { "label-two", "value-one" },
        },
    });

    var primary = new Gcp.NetworkConnectivity.Spoke("primary", new()
    {
        Name = "tf-test-name_25141",
        Location = "us-central1",
        Description = "A sample spoke with a linked routher appliance instance",
        Labels = 
        {
            { "label-one", "value-one" },
        },
        Hub = basicHub.Id,
        LinkedRouterApplianceInstances = new Gcp.NetworkConnectivity.Inputs.SpokeLinkedRouterApplianceInstancesArgs
        {
            Instances = new[]
            {
                new Gcp.NetworkConnectivity.Inputs.SpokeLinkedRouterApplianceInstancesInstanceArgs
                {
                    VirtualMachine = instance.SelfLink,
                    IpAddress = "10.0.0.2",
                },
            },
            SiteToSiteDataTransfer = true,
            IncludeImportRanges = new[]
            {
                "ALL_IPV4_RANGES",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
import com.pulumi.gcp.compute.Instance;
import com.pulumi.gcp.compute.InstanceArgs;
import com.pulumi.gcp.compute.inputs.InstanceBootDiskArgs;
import com.pulumi.gcp.compute.inputs.InstanceBootDiskInitializeParamsArgs;
import com.pulumi.gcp.compute.inputs.InstanceNetworkInterfaceArgs;
import com.pulumi.gcp.networkconnectivity.Hub;
import com.pulumi.gcp.networkconnectivity.HubArgs;
import com.pulumi.gcp.networkconnectivity.Spoke;
import com.pulumi.gcp.networkconnectivity.SpokeArgs;
import com.pulumi.gcp.networkconnectivity.inputs.SpokeLinkedRouterApplianceInstancesArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var network = new Network("network", NetworkArgs.builder()
            .name("tf-test-network_85160")
            .autoCreateSubnetworks(false)
            .build());

        var subnetwork = new Subnetwork("subnetwork", SubnetworkArgs.builder()
            .name("tf-test-subnet_92130")
            .ipCidrRange("10.0.0.0/28")
            .region("us-central1")
            .network(network.selfLink())
            .build());

        var instance = new Instance("instance", InstanceArgs.builder()
            .name("tf-test-instance_16199")
            .machineType("e2-medium")
            .canIpForward(true)
            .zone("us-central1-a")
            .bootDisk(InstanceBootDiskArgs.builder()
                .initializeParams(InstanceBootDiskInitializeParamsArgs.builder()
                    .image("projects/debian-cloud/global/images/debian-10-buster-v20210817")
                    .build())
                .build())
            .networkInterfaces(InstanceNetworkInterfaceArgs.builder()
                .subnetwork(subnetwork.name())
                .networkIp("10.0.0.2")
                .accessConfigs(InstanceNetworkInterfaceAccessConfigArgs.builder()
                    .networkTier("PREMIUM")
                    .build())
                .build())
            .build());

        var basicHub = new Hub("basicHub", HubArgs.builder()
            .name("tf-test-hub_21563")
            .description("A sample hub")
            .labels(Map.of("label-two", "value-one"))
            .build());

        var primary = new Spoke("primary", SpokeArgs.builder()
            .name("tf-test-name_25141")
            .location("us-central1")
            .description("A sample spoke with a linked routher appliance instance")
            .labels(Map.of("label-one", "value-one"))
            .hub(basicHub.id())
            .linkedRouterApplianceInstances(SpokeLinkedRouterApplianceInstancesArgs.builder()
                .instances(SpokeLinkedRouterApplianceInstancesInstanceArgs.builder()
                    .virtualMachine(instance.selfLink())
                    .ipAddress("10.0.0.2")
                    .build())
                .siteToSiteDataTransfer(true)
                .includeImportRanges("ALL_IPV4_RANGES")
                .build())
            .build());

    }
}
resources:
  network:
    type: gcp:compute:Network
    properties:
      name: tf-test-network_85160
      autoCreateSubnetworks: false
  subnetwork:
    type: gcp:compute:Subnetwork
    properties:
      name: tf-test-subnet_92130
      ipCidrRange: 10.0.0.0/28
      region: us-central1
      network: ${network.selfLink}
  instance:
    type: gcp:compute:Instance
    properties:
      name: tf-test-instance_16199
      machineType: e2-medium
      canIpForward: true
      zone: us-central1-a
      bootDisk:
        initializeParams:
          image: projects/debian-cloud/global/images/debian-10-buster-v20210817
      networkInterfaces:
        - subnetwork: ${subnetwork.name}
          networkIp: 10.0.0.2
          accessConfigs:
            - networkTier: PREMIUM
  basicHub:
    type: gcp:networkconnectivity:Hub
    name: basic_hub
    properties:
      name: tf-test-hub_21563
      description: A sample hub
      labels:
        label-two: value-one
  primary:
    type: gcp:networkconnectivity:Spoke
    properties:
      name: tf-test-name_25141
      location: us-central1
      description: A sample spoke with a linked routher appliance instance
      labels:
        label-one: value-one
      hub: ${basicHub.id}
      linkedRouterApplianceInstances:
        instances:
          - virtualMachine: ${instance.selfLink}
            ipAddress: 10.0.0.2
        siteToSiteDataTransfer: true
        includeImportRanges:
          - ALL_IPV4_RANGES

The linkedRouterApplianceInstances property defines one or more Compute Engine instances that act as routers. Each instance requires a virtualMachine reference and an ipAddress for routing. The siteToSiteDataTransfer property enables direct connectivity between spokes through the appliance. The instance must have IP forwarding enabled and appropriate routing software installed.

Connect on-premises networks via VPN tunnels

Hybrid cloud architectures connect on-premises data centers through VPN tunnels, with Network Connectivity Center managing routing between cloud and on-premises resources.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const basicHub = new gcp.networkconnectivity.Hub("basic_hub", {
    name: "basic-hub1",
    description: "A sample hub",
    labels: {
        "label-two": "value-one",
    },
});
const network = new gcp.compute.Network("network", {
    name: "basic-network",
    autoCreateSubnetworks: false,
});
const subnetwork = new gcp.compute.Subnetwork("subnetwork", {
    name: "basic-subnetwork",
    ipCidrRange: "10.0.0.0/28",
    region: "us-central1",
    network: network.selfLink,
});
const gateway = new gcp.compute.HaVpnGateway("gateway", {
    name: "vpn-gateway",
    network: network.id,
});
const externalVpnGw = new gcp.compute.ExternalVpnGateway("external_vpn_gw", {
    name: "external-vpn-gateway",
    redundancyType: "SINGLE_IP_INTERNALLY_REDUNDANT",
    description: "An externally managed VPN gateway",
    interfaces: [{
        id: 0,
        ipAddress: "8.8.8.8",
    }],
});
const router = new gcp.compute.Router("router", {
    name: "external-vpn-gateway",
    region: "us-central1",
    network: network.name,
    bgp: {
        asn: 64514,
    },
});
const tunnel1 = new gcp.compute.VPNTunnel("tunnel1", {
    name: "tunnel1",
    region: "us-central1",
    vpnGateway: gateway.id,
    peerExternalGateway: externalVpnGw.id,
    peerExternalGatewayInterface: 0,
    sharedSecret: "a secret message",
    router: router.id,
    vpnGatewayInterface: 0,
});
const tunnel2 = new gcp.compute.VPNTunnel("tunnel2", {
    name: "tunnel2",
    region: "us-central1",
    vpnGateway: gateway.id,
    peerExternalGateway: externalVpnGw.id,
    peerExternalGatewayInterface: 0,
    sharedSecret: "a secret message",
    router: pulumi.interpolate` ${router.id}`,
    vpnGatewayInterface: 1,
});
const routerInterface1 = new gcp.compute.RouterInterface("router_interface1", {
    name: "router-interface1",
    router: router.name,
    region: "us-central1",
    ipRange: "169.254.0.1/30",
    vpnTunnel: tunnel1.name,
});
const routerPeer1 = new gcp.compute.RouterPeer("router_peer1", {
    name: "router-peer1",
    router: router.name,
    region: "us-central1",
    peerIpAddress: "169.254.0.2",
    peerAsn: 64515,
    advertisedRoutePriority: 100,
    "interface": routerInterface1.name,
});
const routerInterface2 = new gcp.compute.RouterInterface("router_interface2", {
    name: "router-interface2",
    router: router.name,
    region: "us-central1",
    ipRange: "169.254.1.1/30",
    vpnTunnel: tunnel2.name,
});
const routerPeer2 = new gcp.compute.RouterPeer("router_peer2", {
    name: "router-peer2",
    router: router.name,
    region: "us-central1",
    peerIpAddress: "169.254.1.2",
    peerAsn: 64515,
    advertisedRoutePriority: 100,
    "interface": routerInterface2.name,
});
const tunnel1Spoke = new gcp.networkconnectivity.Spoke("tunnel1", {
    name: "vpn-tunnel-1-spoke",
    location: "us-central1",
    description: "A sample spoke with a linked VPN Tunnel",
    labels: {
        "label-one": "value-one",
    },
    hub: basicHub.id,
    linkedVpnTunnels: {
        uris: [tunnel1.selfLink],
        siteToSiteDataTransfer: true,
        includeImportRanges: ["ALL_IPV4_RANGES"],
    },
});
const tunnel2Spoke = new gcp.networkconnectivity.Spoke("tunnel2", {
    name: "vpn-tunnel-2-spoke",
    location: "us-central1",
    description: "A sample spoke with a linked VPN Tunnel",
    labels: {
        "label-one": "value-one",
    },
    hub: basicHub.id,
    linkedVpnTunnels: {
        uris: [tunnel2.selfLink],
        siteToSiteDataTransfer: true,
        includeImportRanges: ["ALL_IPV4_RANGES"],
    },
});
import pulumi
import pulumi_gcp as gcp

basic_hub = gcp.networkconnectivity.Hub("basic_hub",
    name="basic-hub1",
    description="A sample hub",
    labels={
        "label-two": "value-one",
    })
network = gcp.compute.Network("network",
    name="basic-network",
    auto_create_subnetworks=False)
subnetwork = gcp.compute.Subnetwork("subnetwork",
    name="basic-subnetwork",
    ip_cidr_range="10.0.0.0/28",
    region="us-central1",
    network=network.self_link)
gateway = gcp.compute.HaVpnGateway("gateway",
    name="vpn-gateway",
    network=network.id)
external_vpn_gw = gcp.compute.ExternalVpnGateway("external_vpn_gw",
    name="external-vpn-gateway",
    redundancy_type="SINGLE_IP_INTERNALLY_REDUNDANT",
    description="An externally managed VPN gateway",
    interfaces=[{
        "id": 0,
        "ip_address": "8.8.8.8",
    }])
router = gcp.compute.Router("router",
    name="external-vpn-gateway",
    region="us-central1",
    network=network.name,
    bgp={
        "asn": 64514,
    })
tunnel1 = gcp.compute.VPNTunnel("tunnel1",
    name="tunnel1",
    region="us-central1",
    vpn_gateway=gateway.id,
    peer_external_gateway=external_vpn_gw.id,
    peer_external_gateway_interface=0,
    shared_secret="a secret message",
    router=router.id,
    vpn_gateway_interface=0)
tunnel2 = gcp.compute.VPNTunnel("tunnel2",
    name="tunnel2",
    region="us-central1",
    vpn_gateway=gateway.id,
    peer_external_gateway=external_vpn_gw.id,
    peer_external_gateway_interface=0,
    shared_secret="a secret message",
    router=router.id.apply(lambda id: f" {id}"),
    vpn_gateway_interface=1)
router_interface1 = gcp.compute.RouterInterface("router_interface1",
    name="router-interface1",
    router=router.name,
    region="us-central1",
    ip_range="169.254.0.1/30",
    vpn_tunnel=tunnel1.name)
router_peer1 = gcp.compute.RouterPeer("router_peer1",
    name="router-peer1",
    router=router.name,
    region="us-central1",
    peer_ip_address="169.254.0.2",
    peer_asn=64515,
    advertised_route_priority=100,
    interface=router_interface1.name)
router_interface2 = gcp.compute.RouterInterface("router_interface2",
    name="router-interface2",
    router=router.name,
    region="us-central1",
    ip_range="169.254.1.1/30",
    vpn_tunnel=tunnel2.name)
router_peer2 = gcp.compute.RouterPeer("router_peer2",
    name="router-peer2",
    router=router.name,
    region="us-central1",
    peer_ip_address="169.254.1.2",
    peer_asn=64515,
    advertised_route_priority=100,
    interface=router_interface2.name)
tunnel1_spoke = gcp.networkconnectivity.Spoke("tunnel1",
    name="vpn-tunnel-1-spoke",
    location="us-central1",
    description="A sample spoke with a linked VPN Tunnel",
    labels={
        "label-one": "value-one",
    },
    hub=basic_hub.id,
    linked_vpn_tunnels={
        "uris": [tunnel1.self_link],
        "site_to_site_data_transfer": True,
        "include_import_ranges": ["ALL_IPV4_RANGES"],
    })
tunnel2_spoke = gcp.networkconnectivity.Spoke("tunnel2",
    name="vpn-tunnel-2-spoke",
    location="us-central1",
    description="A sample spoke with a linked VPN Tunnel",
    labels={
        "label-one": "value-one",
    },
    hub=basic_hub.id,
    linked_vpn_tunnels={
        "uris": [tunnel2.self_link],
        "site_to_site_data_transfer": True,
        "include_import_ranges": ["ALL_IPV4_RANGES"],
    })
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/networkconnectivity"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		basicHub, err := networkconnectivity.NewHub(ctx, "basic_hub", &networkconnectivity.HubArgs{
			Name:        pulumi.String("basic-hub1"),
			Description: pulumi.String("A sample hub"),
			Labels: pulumi.StringMap{
				"label-two": pulumi.String("value-one"),
			},
		})
		if err != nil {
			return err
		}
		network, err := compute.NewNetwork(ctx, "network", &compute.NetworkArgs{
			Name:                  pulumi.String("basic-network"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewSubnetwork(ctx, "subnetwork", &compute.SubnetworkArgs{
			Name:        pulumi.String("basic-subnetwork"),
			IpCidrRange: pulumi.String("10.0.0.0/28"),
			Region:      pulumi.String("us-central1"),
			Network:     network.SelfLink,
		})
		if err != nil {
			return err
		}
		gateway, err := compute.NewHaVpnGateway(ctx, "gateway", &compute.HaVpnGatewayArgs{
			Name:    pulumi.String("vpn-gateway"),
			Network: network.ID(),
		})
		if err != nil {
			return err
		}
		externalVpnGw, err := compute.NewExternalVpnGateway(ctx, "external_vpn_gw", &compute.ExternalVpnGatewayArgs{
			Name:           pulumi.String("external-vpn-gateway"),
			RedundancyType: pulumi.String("SINGLE_IP_INTERNALLY_REDUNDANT"),
			Description:    pulumi.String("An externally managed VPN gateway"),
			Interfaces: compute.ExternalVpnGatewayInterfaceArray{
				&compute.ExternalVpnGatewayInterfaceArgs{
					Id:        pulumi.Int(0),
					IpAddress: pulumi.String("8.8.8.8"),
				},
			},
		})
		if err != nil {
			return err
		}
		router, err := compute.NewRouter(ctx, "router", &compute.RouterArgs{
			Name:    pulumi.String("external-vpn-gateway"),
			Region:  pulumi.String("us-central1"),
			Network: network.Name,
			Bgp: &compute.RouterBgpArgs{
				Asn: pulumi.Int(64514),
			},
		})
		if err != nil {
			return err
		}
		tunnel1, err := compute.NewVPNTunnel(ctx, "tunnel1", &compute.VPNTunnelArgs{
			Name:                         pulumi.String("tunnel1"),
			Region:                       pulumi.String("us-central1"),
			VpnGateway:                   gateway.ID(),
			PeerExternalGateway:          externalVpnGw.ID(),
			PeerExternalGatewayInterface: pulumi.Int(0),
			SharedSecret:                 pulumi.String("a secret message"),
			Router:                       router.ID(),
			VpnGatewayInterface:          pulumi.Int(0),
		})
		if err != nil {
			return err
		}
		tunnel2, err := compute.NewVPNTunnel(ctx, "tunnel2", &compute.VPNTunnelArgs{
			Name:                         pulumi.String("tunnel2"),
			Region:                       pulumi.String("us-central1"),
			VpnGateway:                   gateway.ID(),
			PeerExternalGateway:          externalVpnGw.ID(),
			PeerExternalGatewayInterface: pulumi.Int(0),
			SharedSecret:                 pulumi.String("a secret message"),
			Router: router.ID().ApplyT(func(id string) (string, error) {
				return fmt.Sprintf(" %v", id), nil
			}).(pulumi.StringOutput),
			VpnGatewayInterface: pulumi.Int(1),
		})
		if err != nil {
			return err
		}
		routerInterface1, err := compute.NewRouterInterface(ctx, "router_interface1", &compute.RouterInterfaceArgs{
			Name:      pulumi.String("router-interface1"),
			Router:    router.Name,
			Region:    pulumi.String("us-central1"),
			IpRange:   pulumi.String("169.254.0.1/30"),
			VpnTunnel: tunnel1.Name,
		})
		if err != nil {
			return err
		}
		_, err = compute.NewRouterPeer(ctx, "router_peer1", &compute.RouterPeerArgs{
			Name:                    pulumi.String("router-peer1"),
			Router:                  router.Name,
			Region:                  pulumi.String("us-central1"),
			PeerIpAddress:           pulumi.String("169.254.0.2"),
			PeerAsn:                 pulumi.Int(64515),
			AdvertisedRoutePriority: pulumi.Int(100),
			Interface:               routerInterface1.Name,
		})
		if err != nil {
			return err
		}
		routerInterface2, err := compute.NewRouterInterface(ctx, "router_interface2", &compute.RouterInterfaceArgs{
			Name:      pulumi.String("router-interface2"),
			Router:    router.Name,
			Region:    pulumi.String("us-central1"),
			IpRange:   pulumi.String("169.254.1.1/30"),
			VpnTunnel: tunnel2.Name,
		})
		if err != nil {
			return err
		}
		_, err = compute.NewRouterPeer(ctx, "router_peer2", &compute.RouterPeerArgs{
			Name:                    pulumi.String("router-peer2"),
			Router:                  router.Name,
			Region:                  pulumi.String("us-central1"),
			PeerIpAddress:           pulumi.String("169.254.1.2"),
			PeerAsn:                 pulumi.Int(64515),
			AdvertisedRoutePriority: pulumi.Int(100),
			Interface:               routerInterface2.Name,
		})
		if err != nil {
			return err
		}
		_, err = networkconnectivity.NewSpoke(ctx, "tunnel1", &networkconnectivity.SpokeArgs{
			Name:        pulumi.String("vpn-tunnel-1-spoke"),
			Location:    pulumi.String("us-central1"),
			Description: pulumi.String("A sample spoke with a linked VPN Tunnel"),
			Labels: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
			Hub: basicHub.ID(),
			LinkedVpnTunnels: &networkconnectivity.SpokeLinkedVpnTunnelsArgs{
				Uris: pulumi.StringArray{
					tunnel1.SelfLink,
				},
				SiteToSiteDataTransfer: pulumi.Bool(true),
				IncludeImportRanges: pulumi.StringArray{
					pulumi.String("ALL_IPV4_RANGES"),
				},
			},
		})
		if err != nil {
			return err
		}
		_, err = networkconnectivity.NewSpoke(ctx, "tunnel2", &networkconnectivity.SpokeArgs{
			Name:        pulumi.String("vpn-tunnel-2-spoke"),
			Location:    pulumi.String("us-central1"),
			Description: pulumi.String("A sample spoke with a linked VPN Tunnel"),
			Labels: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
			Hub: basicHub.ID(),
			LinkedVpnTunnels: &networkconnectivity.SpokeLinkedVpnTunnelsArgs{
				Uris: pulumi.StringArray{
					tunnel2.SelfLink,
				},
				SiteToSiteDataTransfer: pulumi.Bool(true),
				IncludeImportRanges: pulumi.StringArray{
					pulumi.String("ALL_IPV4_RANGES"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var basicHub = new Gcp.NetworkConnectivity.Hub("basic_hub", new()
    {
        Name = "basic-hub1",
        Description = "A sample hub",
        Labels = 
        {
            { "label-two", "value-one" },
        },
    });

    var network = new Gcp.Compute.Network("network", new()
    {
        Name = "basic-network",
        AutoCreateSubnetworks = false,
    });

    var subnetwork = new Gcp.Compute.Subnetwork("subnetwork", new()
    {
        Name = "basic-subnetwork",
        IpCidrRange = "10.0.0.0/28",
        Region = "us-central1",
        Network = network.SelfLink,
    });

    var gateway = new Gcp.Compute.HaVpnGateway("gateway", new()
    {
        Name = "vpn-gateway",
        Network = network.Id,
    });

    var externalVpnGw = new Gcp.Compute.ExternalVpnGateway("external_vpn_gw", new()
    {
        Name = "external-vpn-gateway",
        RedundancyType = "SINGLE_IP_INTERNALLY_REDUNDANT",
        Description = "An externally managed VPN gateway",
        Interfaces = new[]
        {
            new Gcp.Compute.Inputs.ExternalVpnGatewayInterfaceArgs
            {
                Id = 0,
                IpAddress = "8.8.8.8",
            },
        },
    });

    var router = new Gcp.Compute.Router("router", new()
    {
        Name = "external-vpn-gateway",
        Region = "us-central1",
        Network = network.Name,
        Bgp = new Gcp.Compute.Inputs.RouterBgpArgs
        {
            Asn = 64514,
        },
    });

    var tunnel1 = new Gcp.Compute.VPNTunnel("tunnel1", new()
    {
        Name = "tunnel1",
        Region = "us-central1",
        VpnGateway = gateway.Id,
        PeerExternalGateway = externalVpnGw.Id,
        PeerExternalGatewayInterface = 0,
        SharedSecret = "a secret message",
        Router = router.Id,
        VpnGatewayInterface = 0,
    });

    var tunnel2 = new Gcp.Compute.VPNTunnel("tunnel2", new()
    {
        Name = "tunnel2",
        Region = "us-central1",
        VpnGateway = gateway.Id,
        PeerExternalGateway = externalVpnGw.Id,
        PeerExternalGatewayInterface = 0,
        SharedSecret = "a secret message",
        Router = router.Id.Apply(id => $" {id}"),
        VpnGatewayInterface = 1,
    });

    var routerInterface1 = new Gcp.Compute.RouterInterface("router_interface1", new()
    {
        Name = "router-interface1",
        Router = router.Name,
        Region = "us-central1",
        IpRange = "169.254.0.1/30",
        VpnTunnel = tunnel1.Name,
    });

    var routerPeer1 = new Gcp.Compute.RouterPeer("router_peer1", new()
    {
        Name = "router-peer1",
        Router = router.Name,
        Region = "us-central1",
        PeerIpAddress = "169.254.0.2",
        PeerAsn = 64515,
        AdvertisedRoutePriority = 100,
        Interface = routerInterface1.Name,
    });

    var routerInterface2 = new Gcp.Compute.RouterInterface("router_interface2", new()
    {
        Name = "router-interface2",
        Router = router.Name,
        Region = "us-central1",
        IpRange = "169.254.1.1/30",
        VpnTunnel = tunnel2.Name,
    });

    var routerPeer2 = new Gcp.Compute.RouterPeer("router_peer2", new()
    {
        Name = "router-peer2",
        Router = router.Name,
        Region = "us-central1",
        PeerIpAddress = "169.254.1.2",
        PeerAsn = 64515,
        AdvertisedRoutePriority = 100,
        Interface = routerInterface2.Name,
    });

    var tunnel1Spoke = new Gcp.NetworkConnectivity.Spoke("tunnel1", new()
    {
        Name = "vpn-tunnel-1-spoke",
        Location = "us-central1",
        Description = "A sample spoke with a linked VPN Tunnel",
        Labels = 
        {
            { "label-one", "value-one" },
        },
        Hub = basicHub.Id,
        LinkedVpnTunnels = new Gcp.NetworkConnectivity.Inputs.SpokeLinkedVpnTunnelsArgs
        {
            Uris = new[]
            {
                tunnel1.SelfLink,
            },
            SiteToSiteDataTransfer = true,
            IncludeImportRanges = new[]
            {
                "ALL_IPV4_RANGES",
            },
        },
    });

    var tunnel2Spoke = new Gcp.NetworkConnectivity.Spoke("tunnel2", new()
    {
        Name = "vpn-tunnel-2-spoke",
        Location = "us-central1",
        Description = "A sample spoke with a linked VPN Tunnel",
        Labels = 
        {
            { "label-one", "value-one" },
        },
        Hub = basicHub.Id,
        LinkedVpnTunnels = new Gcp.NetworkConnectivity.Inputs.SpokeLinkedVpnTunnelsArgs
        {
            Uris = new[]
            {
                tunnel2.SelfLink,
            },
            SiteToSiteDataTransfer = true,
            IncludeImportRanges = new[]
            {
                "ALL_IPV4_RANGES",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.networkconnectivity.Hub;
import com.pulumi.gcp.networkconnectivity.HubArgs;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
import com.pulumi.gcp.compute.HaVpnGateway;
import com.pulumi.gcp.compute.HaVpnGatewayArgs;
import com.pulumi.gcp.compute.ExternalVpnGateway;
import com.pulumi.gcp.compute.ExternalVpnGatewayArgs;
import com.pulumi.gcp.compute.inputs.ExternalVpnGatewayInterfaceArgs;
import com.pulumi.gcp.compute.Router;
import com.pulumi.gcp.compute.RouterArgs;
import com.pulumi.gcp.compute.inputs.RouterBgpArgs;
import com.pulumi.gcp.compute.VPNTunnel;
import com.pulumi.gcp.compute.VPNTunnelArgs;
import com.pulumi.gcp.compute.RouterInterface;
import com.pulumi.gcp.compute.RouterInterfaceArgs;
import com.pulumi.gcp.compute.RouterPeer;
import com.pulumi.gcp.compute.RouterPeerArgs;
import com.pulumi.gcp.networkconnectivity.Spoke;
import com.pulumi.gcp.networkconnectivity.SpokeArgs;
import com.pulumi.gcp.networkconnectivity.inputs.SpokeLinkedVpnTunnelsArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var basicHub = new Hub("basicHub", HubArgs.builder()
            .name("basic-hub1")
            .description("A sample hub")
            .labels(Map.of("label-two", "value-one"))
            .build());

        var network = new Network("network", NetworkArgs.builder()
            .name("basic-network")
            .autoCreateSubnetworks(false)
            .build());

        var subnetwork = new Subnetwork("subnetwork", SubnetworkArgs.builder()
            .name("basic-subnetwork")
            .ipCidrRange("10.0.0.0/28")
            .region("us-central1")
            .network(network.selfLink())
            .build());

        var gateway = new HaVpnGateway("gateway", HaVpnGatewayArgs.builder()
            .name("vpn-gateway")
            .network(network.id())
            .build());

        var externalVpnGw = new ExternalVpnGateway("externalVpnGw", ExternalVpnGatewayArgs.builder()
            .name("external-vpn-gateway")
            .redundancyType("SINGLE_IP_INTERNALLY_REDUNDANT")
            .description("An externally managed VPN gateway")
            .interfaces(ExternalVpnGatewayInterfaceArgs.builder()
                .id(0)
                .ipAddress("8.8.8.8")
                .build())
            .build());

        var router = new Router("router", RouterArgs.builder()
            .name("external-vpn-gateway")
            .region("us-central1")
            .network(network.name())
            .bgp(RouterBgpArgs.builder()
                .asn(64514)
                .build())
            .build());

        var tunnel1 = new VPNTunnel("tunnel1", VPNTunnelArgs.builder()
            .name("tunnel1")
            .region("us-central1")
            .vpnGateway(gateway.id())
            .peerExternalGateway(externalVpnGw.id())
            .peerExternalGatewayInterface(0)
            .sharedSecret("a secret message")
            .router(router.id())
            .vpnGatewayInterface(0)
            .build());

        var tunnel2 = new VPNTunnel("tunnel2", VPNTunnelArgs.builder()
            .name("tunnel2")
            .region("us-central1")
            .vpnGateway(gateway.id())
            .peerExternalGateway(externalVpnGw.id())
            .peerExternalGatewayInterface(0)
            .sharedSecret("a secret message")
            .router(router.id().applyValue(_id -> String.format(" %s", _id)))
            .vpnGatewayInterface(1)
            .build());

        var routerInterface1 = new RouterInterface("routerInterface1", RouterInterfaceArgs.builder()
            .name("router-interface1")
            .router(router.name())
            .region("us-central1")
            .ipRange("169.254.0.1/30")
            .vpnTunnel(tunnel1.name())
            .build());

        var routerPeer1 = new RouterPeer("routerPeer1", RouterPeerArgs.builder()
            .name("router-peer1")
            .router(router.name())
            .region("us-central1")
            .peerIpAddress("169.254.0.2")
            .peerAsn(64515)
            .advertisedRoutePriority(100)
            .interface_(routerInterface1.name())
            .build());

        var routerInterface2 = new RouterInterface("routerInterface2", RouterInterfaceArgs.builder()
            .name("router-interface2")
            .router(router.name())
            .region("us-central1")
            .ipRange("169.254.1.1/30")
            .vpnTunnel(tunnel2.name())
            .build());

        var routerPeer2 = new RouterPeer("routerPeer2", RouterPeerArgs.builder()
            .name("router-peer2")
            .router(router.name())
            .region("us-central1")
            .peerIpAddress("169.254.1.2")
            .peerAsn(64515)
            .advertisedRoutePriority(100)
            .interface_(routerInterface2.name())
            .build());

        var tunnel1Spoke = new Spoke("tunnel1Spoke", SpokeArgs.builder()
            .name("vpn-tunnel-1-spoke")
            .location("us-central1")
            .description("A sample spoke with a linked VPN Tunnel")
            .labels(Map.of("label-one", "value-one"))
            .hub(basicHub.id())
            .linkedVpnTunnels(SpokeLinkedVpnTunnelsArgs.builder()
                .uris(tunnel1.selfLink())
                .siteToSiteDataTransfer(true)
                .includeImportRanges("ALL_IPV4_RANGES")
                .build())
            .build());

        var tunnel2Spoke = new Spoke("tunnel2Spoke", SpokeArgs.builder()
            .name("vpn-tunnel-2-spoke")
            .location("us-central1")
            .description("A sample spoke with a linked VPN Tunnel")
            .labels(Map.of("label-one", "value-one"))
            .hub(basicHub.id())
            .linkedVpnTunnels(SpokeLinkedVpnTunnelsArgs.builder()
                .uris(tunnel2.selfLink())
                .siteToSiteDataTransfer(true)
                .includeImportRanges("ALL_IPV4_RANGES")
                .build())
            .build());

    }
}
resources:
  basicHub:
    type: gcp:networkconnectivity:Hub
    name: basic_hub
    properties:
      name: basic-hub1
      description: A sample hub
      labels:
        label-two: value-one
  network:
    type: gcp:compute:Network
    properties:
      name: basic-network
      autoCreateSubnetworks: false
  subnetwork:
    type: gcp:compute:Subnetwork
    properties:
      name: basic-subnetwork
      ipCidrRange: 10.0.0.0/28
      region: us-central1
      network: ${network.selfLink}
  gateway:
    type: gcp:compute:HaVpnGateway
    properties:
      name: vpn-gateway
      network: ${network.id}
  externalVpnGw:
    type: gcp:compute:ExternalVpnGateway
    name: external_vpn_gw
    properties:
      name: external-vpn-gateway
      redundancyType: SINGLE_IP_INTERNALLY_REDUNDANT
      description: An externally managed VPN gateway
      interfaces:
        - id: 0
          ipAddress: 8.8.8.8
  router:
    type: gcp:compute:Router
    properties:
      name: external-vpn-gateway
      region: us-central1
      network: ${network.name}
      bgp:
        asn: 64514
  tunnel1:
    type: gcp:compute:VPNTunnel
    properties:
      name: tunnel1
      region: us-central1
      vpnGateway: ${gateway.id}
      peerExternalGateway: ${externalVpnGw.id}
      peerExternalGatewayInterface: 0
      sharedSecret: a secret message
      router: ${router.id}
      vpnGatewayInterface: 0
  tunnel2:
    type: gcp:compute:VPNTunnel
    properties:
      name: tunnel2
      region: us-central1
      vpnGateway: ${gateway.id}
      peerExternalGateway: ${externalVpnGw.id}
      peerExternalGatewayInterface: 0
      sharedSecret: a secret message
      router: ' ${router.id}'
      vpnGatewayInterface: 1
  routerInterface1:
    type: gcp:compute:RouterInterface
    name: router_interface1
    properties:
      name: router-interface1
      router: ${router.name}
      region: us-central1
      ipRange: 169.254.0.1/30
      vpnTunnel: ${tunnel1.name}
  routerPeer1:
    type: gcp:compute:RouterPeer
    name: router_peer1
    properties:
      name: router-peer1
      router: ${router.name}
      region: us-central1
      peerIpAddress: 169.254.0.2
      peerAsn: 64515
      advertisedRoutePriority: 100
      interface: ${routerInterface1.name}
  routerInterface2:
    type: gcp:compute:RouterInterface
    name: router_interface2
    properties:
      name: router-interface2
      router: ${router.name}
      region: us-central1
      ipRange: 169.254.1.1/30
      vpnTunnel: ${tunnel2.name}
  routerPeer2:
    type: gcp:compute:RouterPeer
    name: router_peer2
    properties:
      name: router-peer2
      router: ${router.name}
      region: us-central1
      peerIpAddress: 169.254.1.2
      peerAsn: 64515
      advertisedRoutePriority: 100
      interface: ${routerInterface2.name}
  tunnel1Spoke:
    type: gcp:networkconnectivity:Spoke
    name: tunnel1
    properties:
      name: vpn-tunnel-1-spoke
      location: us-central1
      description: A sample spoke with a linked VPN Tunnel
      labels:
        label-one: value-one
      hub: ${basicHub.id}
      linkedVpnTunnels:
        uris:
          - ${tunnel1.selfLink}
        siteToSiteDataTransfer: true
        includeImportRanges:
          - ALL_IPV4_RANGES
  tunnel2Spoke:
    type: gcp:networkconnectivity:Spoke
    name: tunnel2
    properties:
      name: vpn-tunnel-2-spoke
      location: us-central1
      description: A sample spoke with a linked VPN Tunnel
      labels:
        label-one: value-one
      hub: ${basicHub.id}
      linkedVpnTunnels:
        uris:
          - ${tunnel2.selfLink}
        siteToSiteDataTransfer: true
        includeImportRanges:
          - ALL_IPV4_RANGES

The linkedVpnTunnels property attaches one or more VPN tunnels to the hub. The uris array references tunnel self-links. The includeImportRanges property controls which routes from the tunnel are imported into the hub; ALL_IPV4_RANGES accepts all advertised routes. BGP configuration on the Cloud Router handles route exchange with the on-premises network.

Connect via Cloud Interconnect attachments

Enterprises with dedicated physical connections use Cloud Interconnect attachments for higher bandwidth and lower latency than VPN tunnels.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const basicHub = new gcp.networkconnectivity.Hub("basic_hub", {
    name: "basic-hub1",
    description: "A sample hub",
    labels: {
        "label-two": "value-one",
    },
});
const network = new gcp.compute.Network("network", {
    name: "basic-network",
    autoCreateSubnetworks: false,
});
const router = new gcp.compute.Router("router", {
    name: "external-vpn-gateway",
    region: "us-central1",
    network: network.name,
    bgp: {
        asn: 16550,
    },
});
const interconnect_attachment = new gcp.compute.InterconnectAttachment("interconnect-attachment", {
    name: "partner-interconnect1",
    edgeAvailabilityDomain: "AVAILABILITY_DOMAIN_1",
    type: "PARTNER",
    router: router.id,
    mtu: "1500",
    region: "us-central1",
});
const primary = new gcp.networkconnectivity.Spoke("primary", {
    name: "interconnect-attachment-spoke",
    location: "us-central1",
    description: "A sample spoke with a linked Interconnect Attachment",
    labels: {
        "label-one": "value-one",
    },
    hub: basicHub.id,
    linkedInterconnectAttachments: {
        uris: [interconnect_attachment.selfLink],
        siteToSiteDataTransfer: true,
        includeImportRanges: ["ALL_IPV4_RANGES"],
    },
});
import pulumi
import pulumi_gcp as gcp

basic_hub = gcp.networkconnectivity.Hub("basic_hub",
    name="basic-hub1",
    description="A sample hub",
    labels={
        "label-two": "value-one",
    })
network = gcp.compute.Network("network",
    name="basic-network",
    auto_create_subnetworks=False)
router = gcp.compute.Router("router",
    name="external-vpn-gateway",
    region="us-central1",
    network=network.name,
    bgp={
        "asn": 16550,
    })
interconnect_attachment = gcp.compute.InterconnectAttachment("interconnect-attachment",
    name="partner-interconnect1",
    edge_availability_domain="AVAILABILITY_DOMAIN_1",
    type="PARTNER",
    router=router.id,
    mtu="1500",
    region="us-central1")
primary = gcp.networkconnectivity.Spoke("primary",
    name="interconnect-attachment-spoke",
    location="us-central1",
    description="A sample spoke with a linked Interconnect Attachment",
    labels={
        "label-one": "value-one",
    },
    hub=basic_hub.id,
    linked_interconnect_attachments={
        "uris": [interconnect_attachment.self_link],
        "site_to_site_data_transfer": True,
        "include_import_ranges": ["ALL_IPV4_RANGES"],
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/networkconnectivity"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		basicHub, err := networkconnectivity.NewHub(ctx, "basic_hub", &networkconnectivity.HubArgs{
			Name:        pulumi.String("basic-hub1"),
			Description: pulumi.String("A sample hub"),
			Labels: pulumi.StringMap{
				"label-two": pulumi.String("value-one"),
			},
		})
		if err != nil {
			return err
		}
		network, err := compute.NewNetwork(ctx, "network", &compute.NetworkArgs{
			Name:                  pulumi.String("basic-network"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		router, err := compute.NewRouter(ctx, "router", &compute.RouterArgs{
			Name:    pulumi.String("external-vpn-gateway"),
			Region:  pulumi.String("us-central1"),
			Network: network.Name,
			Bgp: &compute.RouterBgpArgs{
				Asn: pulumi.Int(16550),
			},
		})
		if err != nil {
			return err
		}
		interconnect_attachment, err := compute.NewInterconnectAttachment(ctx, "interconnect-attachment", &compute.InterconnectAttachmentArgs{
			Name:                   pulumi.String("partner-interconnect1"),
			EdgeAvailabilityDomain: pulumi.String("AVAILABILITY_DOMAIN_1"),
			Type:                   pulumi.String("PARTNER"),
			Router:                 router.ID(),
			Mtu:                    pulumi.String("1500"),
			Region:                 pulumi.String("us-central1"),
		})
		if err != nil {
			return err
		}
		_, err = networkconnectivity.NewSpoke(ctx, "primary", &networkconnectivity.SpokeArgs{
			Name:        pulumi.String("interconnect-attachment-spoke"),
			Location:    pulumi.String("us-central1"),
			Description: pulumi.String("A sample spoke with a linked Interconnect Attachment"),
			Labels: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
			Hub: basicHub.ID(),
			LinkedInterconnectAttachments: &networkconnectivity.SpokeLinkedInterconnectAttachmentsArgs{
				Uris: pulumi.StringArray{
					interconnect_attachment.SelfLink,
				},
				SiteToSiteDataTransfer: pulumi.Bool(true),
				IncludeImportRanges: pulumi.StringArray{
					pulumi.String("ALL_IPV4_RANGES"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var basicHub = new Gcp.NetworkConnectivity.Hub("basic_hub", new()
    {
        Name = "basic-hub1",
        Description = "A sample hub",
        Labels = 
        {
            { "label-two", "value-one" },
        },
    });

    var network = new Gcp.Compute.Network("network", new()
    {
        Name = "basic-network",
        AutoCreateSubnetworks = false,
    });

    var router = new Gcp.Compute.Router("router", new()
    {
        Name = "external-vpn-gateway",
        Region = "us-central1",
        Network = network.Name,
        Bgp = new Gcp.Compute.Inputs.RouterBgpArgs
        {
            Asn = 16550,
        },
    });

    var interconnect_attachment = new Gcp.Compute.InterconnectAttachment("interconnect-attachment", new()
    {
        Name = "partner-interconnect1",
        EdgeAvailabilityDomain = "AVAILABILITY_DOMAIN_1",
        Type = "PARTNER",
        Router = router.Id,
        Mtu = "1500",
        Region = "us-central1",
    });

    var primary = new Gcp.NetworkConnectivity.Spoke("primary", new()
    {
        Name = "interconnect-attachment-spoke",
        Location = "us-central1",
        Description = "A sample spoke with a linked Interconnect Attachment",
        Labels = 
        {
            { "label-one", "value-one" },
        },
        Hub = basicHub.Id,
        LinkedInterconnectAttachments = new Gcp.NetworkConnectivity.Inputs.SpokeLinkedInterconnectAttachmentsArgs
        {
            Uris = new[]
            {
                interconnect_attachment.SelfLink,
            },
            SiteToSiteDataTransfer = true,
            IncludeImportRanges = new[]
            {
                "ALL_IPV4_RANGES",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.networkconnectivity.Hub;
import com.pulumi.gcp.networkconnectivity.HubArgs;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Router;
import com.pulumi.gcp.compute.RouterArgs;
import com.pulumi.gcp.compute.inputs.RouterBgpArgs;
import com.pulumi.gcp.compute.InterconnectAttachment;
import com.pulumi.gcp.compute.InterconnectAttachmentArgs;
import com.pulumi.gcp.networkconnectivity.Spoke;
import com.pulumi.gcp.networkconnectivity.SpokeArgs;
import com.pulumi.gcp.networkconnectivity.inputs.SpokeLinkedInterconnectAttachmentsArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var basicHub = new Hub("basicHub", HubArgs.builder()
            .name("basic-hub1")
            .description("A sample hub")
            .labels(Map.of("label-two", "value-one"))
            .build());

        var network = new Network("network", NetworkArgs.builder()
            .name("basic-network")
            .autoCreateSubnetworks(false)
            .build());

        var router = new Router("router", RouterArgs.builder()
            .name("external-vpn-gateway")
            .region("us-central1")
            .network(network.name())
            .bgp(RouterBgpArgs.builder()
                .asn(16550)
                .build())
            .build());

        var interconnect_attachment = new InterconnectAttachment("interconnect-attachment", InterconnectAttachmentArgs.builder()
            .name("partner-interconnect1")
            .edgeAvailabilityDomain("AVAILABILITY_DOMAIN_1")
            .type("PARTNER")
            .router(router.id())
            .mtu("1500")
            .region("us-central1")
            .build());

        var primary = new Spoke("primary", SpokeArgs.builder()
            .name("interconnect-attachment-spoke")
            .location("us-central1")
            .description("A sample spoke with a linked Interconnect Attachment")
            .labels(Map.of("label-one", "value-one"))
            .hub(basicHub.id())
            .linkedInterconnectAttachments(SpokeLinkedInterconnectAttachmentsArgs.builder()
                .uris(interconnect_attachment.selfLink())
                .siteToSiteDataTransfer(true)
                .includeImportRanges("ALL_IPV4_RANGES")
                .build())
            .build());

    }
}
resources:
  basicHub:
    type: gcp:networkconnectivity:Hub
    name: basic_hub
    properties:
      name: basic-hub1
      description: A sample hub
      labels:
        label-two: value-one
  network:
    type: gcp:compute:Network
    properties:
      name: basic-network
      autoCreateSubnetworks: false
  router:
    type: gcp:compute:Router
    properties:
      name: external-vpn-gateway
      region: us-central1
      network: ${network.name}
      bgp:
        asn: 16550
  interconnect-attachment:
    type: gcp:compute:InterconnectAttachment
    properties:
      name: partner-interconnect1
      edgeAvailabilityDomain: AVAILABILITY_DOMAIN_1
      type: PARTNER
      router: ${router.id}
      mtu: 1500
      region: us-central1
  primary:
    type: gcp:networkconnectivity:Spoke
    properties:
      name: interconnect-attachment-spoke
      location: us-central1
      description: A sample spoke with a linked Interconnect Attachment
      labels:
        label-one: value-one
      hub: ${basicHub.id}
      linkedInterconnectAttachments:
        uris:
          - ${["interconnect-attachment"].selfLink}
        siteToSiteDataTransfer: true
        includeImportRanges:
          - ALL_IPV4_RANGES

The linkedInterconnectAttachments property attaches VLAN attachments to the hub. Like VPN tunnels, these attachments use BGP for route exchange. The siteToSiteDataTransfer property enables direct connectivity between spokes through the Interconnect. Interconnect attachments provide dedicated bandwidth and consistent latency for production workloads.

Connect service producer VPC networks

Managed services that use VPC Service Controls create producer VPC networks through VPC peering. Network Connectivity Center can attach these producer networks to enable routing to other resources.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const network = new gcp.compute.Network("network", {
    name: "net-spoke",
    autoCreateSubnetworks: false,
});
const address = new gcp.compute.GlobalAddress("address", {
    name: "test-address",
    purpose: "VPC_PEERING",
    addressType: "INTERNAL",
    prefixLength: 16,
    network: network.id,
});
const peering = new gcp.servicenetworking.Connection("peering", {
    network: network.id,
    service: "servicenetworking.googleapis.com",
    reservedPeeringRanges: [address.name],
});
const basicHub = new gcp.networkconnectivity.Hub("basic_hub", {name: "hub-basic"});
const linkedVpcSpoke = new gcp.networkconnectivity.Spoke("linked_vpc_spoke", {
    name: "vpc-spoke",
    location: "global",
    hub: basicHub.id,
    linkedVpcNetwork: {
        uri: network.selfLink,
    },
});
const primary = new gcp.networkconnectivity.Spoke("primary", {
    name: "producer-spoke",
    location: "global",
    description: "A sample spoke with a linked router appliance instance",
    labels: {
        "label-one": "value-one",
    },
    hub: basicHub.id,
    linkedProducerVpcNetwork: {
        network: network.name,
        peering: peering.peering,
        excludeExportRanges: [
            "198.51.100.0/24",
            "10.10.0.0/16",
        ],
    },
}, {
    dependsOn: [linkedVpcSpoke],
});
import pulumi
import pulumi_gcp as gcp

network = gcp.compute.Network("network",
    name="net-spoke",
    auto_create_subnetworks=False)
address = gcp.compute.GlobalAddress("address",
    name="test-address",
    purpose="VPC_PEERING",
    address_type="INTERNAL",
    prefix_length=16,
    network=network.id)
peering = gcp.servicenetworking.Connection("peering",
    network=network.id,
    service="servicenetworking.googleapis.com",
    reserved_peering_ranges=[address.name])
basic_hub = gcp.networkconnectivity.Hub("basic_hub", name="hub-basic")
linked_vpc_spoke = gcp.networkconnectivity.Spoke("linked_vpc_spoke",
    name="vpc-spoke",
    location="global",
    hub=basic_hub.id,
    linked_vpc_network={
        "uri": network.self_link,
    })
primary = gcp.networkconnectivity.Spoke("primary",
    name="producer-spoke",
    location="global",
    description="A sample spoke with a linked router appliance instance",
    labels={
        "label-one": "value-one",
    },
    hub=basic_hub.id,
    linked_producer_vpc_network={
        "network": network.name,
        "peering": peering.peering,
        "exclude_export_ranges": [
            "198.51.100.0/24",
            "10.10.0.0/16",
        ],
    },
    opts = pulumi.ResourceOptions(depends_on=[linked_vpc_spoke]))
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/networkconnectivity"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/servicenetworking"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		network, err := compute.NewNetwork(ctx, "network", &compute.NetworkArgs{
			Name:                  pulumi.String("net-spoke"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		address, err := compute.NewGlobalAddress(ctx, "address", &compute.GlobalAddressArgs{
			Name:         pulumi.String("test-address"),
			Purpose:      pulumi.String("VPC_PEERING"),
			AddressType:  pulumi.String("INTERNAL"),
			PrefixLength: pulumi.Int(16),
			Network:      network.ID(),
		})
		if err != nil {
			return err
		}
		peering, err := servicenetworking.NewConnection(ctx, "peering", &servicenetworking.ConnectionArgs{
			Network: network.ID(),
			Service: pulumi.String("servicenetworking.googleapis.com"),
			ReservedPeeringRanges: pulumi.StringArray{
				address.Name,
			},
		})
		if err != nil {
			return err
		}
		basicHub, err := networkconnectivity.NewHub(ctx, "basic_hub", &networkconnectivity.HubArgs{
			Name: pulumi.String("hub-basic"),
		})
		if err != nil {
			return err
		}
		linkedVpcSpoke, err := networkconnectivity.NewSpoke(ctx, "linked_vpc_spoke", &networkconnectivity.SpokeArgs{
			Name:     pulumi.String("vpc-spoke"),
			Location: pulumi.String("global"),
			Hub:      basicHub.ID(),
			LinkedVpcNetwork: &networkconnectivity.SpokeLinkedVpcNetworkArgs{
				Uri: network.SelfLink,
			},
		})
		if err != nil {
			return err
		}
		_, err = networkconnectivity.NewSpoke(ctx, "primary", &networkconnectivity.SpokeArgs{
			Name:        pulumi.String("producer-spoke"),
			Location:    pulumi.String("global"),
			Description: pulumi.String("A sample spoke with a linked router appliance instance"),
			Labels: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
			Hub: basicHub.ID(),
			LinkedProducerVpcNetwork: &networkconnectivity.SpokeLinkedProducerVpcNetworkArgs{
				Network: network.Name,
				Peering: peering.Peering,
				ExcludeExportRanges: pulumi.StringArray{
					pulumi.String("198.51.100.0/24"),
					pulumi.String("10.10.0.0/16"),
				},
			},
		}, pulumi.DependsOn([]pulumi.Resource{
			linkedVpcSpoke,
		}))
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var network = new Gcp.Compute.Network("network", new()
    {
        Name = "net-spoke",
        AutoCreateSubnetworks = false,
    });

    var address = new Gcp.Compute.GlobalAddress("address", new()
    {
        Name = "test-address",
        Purpose = "VPC_PEERING",
        AddressType = "INTERNAL",
        PrefixLength = 16,
        Network = network.Id,
    });

    var peering = new Gcp.ServiceNetworking.Connection("peering", new()
    {
        Network = network.Id,
        Service = "servicenetworking.googleapis.com",
        ReservedPeeringRanges = new[]
        {
            address.Name,
        },
    });

    var basicHub = new Gcp.NetworkConnectivity.Hub("basic_hub", new()
    {
        Name = "hub-basic",
    });

    var linkedVpcSpoke = new Gcp.NetworkConnectivity.Spoke("linked_vpc_spoke", new()
    {
        Name = "vpc-spoke",
        Location = "global",
        Hub = basicHub.Id,
        LinkedVpcNetwork = new Gcp.NetworkConnectivity.Inputs.SpokeLinkedVpcNetworkArgs
        {
            Uri = network.SelfLink,
        },
    });

    var primary = new Gcp.NetworkConnectivity.Spoke("primary", new()
    {
        Name = "producer-spoke",
        Location = "global",
        Description = "A sample spoke with a linked router appliance instance",
        Labels = 
        {
            { "label-one", "value-one" },
        },
        Hub = basicHub.Id,
        LinkedProducerVpcNetwork = new Gcp.NetworkConnectivity.Inputs.SpokeLinkedProducerVpcNetworkArgs
        {
            Network = network.Name,
            Peering = peering.Peering,
            ExcludeExportRanges = new[]
            {
                "198.51.100.0/24",
                "10.10.0.0/16",
            },
        },
    }, new CustomResourceOptions
    {
        DependsOn =
        {
            linkedVpcSpoke,
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.GlobalAddress;
import com.pulumi.gcp.compute.GlobalAddressArgs;
import com.pulumi.gcp.servicenetworking.Connection;
import com.pulumi.gcp.servicenetworking.ConnectionArgs;
import com.pulumi.gcp.networkconnectivity.Hub;
import com.pulumi.gcp.networkconnectivity.HubArgs;
import com.pulumi.gcp.networkconnectivity.Spoke;
import com.pulumi.gcp.networkconnectivity.SpokeArgs;
import com.pulumi.gcp.networkconnectivity.inputs.SpokeLinkedVpcNetworkArgs;
import com.pulumi.gcp.networkconnectivity.inputs.SpokeLinkedProducerVpcNetworkArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var network = new Network("network", NetworkArgs.builder()
            .name("net-spoke")
            .autoCreateSubnetworks(false)
            .build());

        var address = new GlobalAddress("address", GlobalAddressArgs.builder()
            .name("test-address")
            .purpose("VPC_PEERING")
            .addressType("INTERNAL")
            .prefixLength(16)
            .network(network.id())
            .build());

        var peering = new Connection("peering", ConnectionArgs.builder()
            .network(network.id())
            .service("servicenetworking.googleapis.com")
            .reservedPeeringRanges(address.name())
            .build());

        var basicHub = new Hub("basicHub", HubArgs.builder()
            .name("hub-basic")
            .build());

        var linkedVpcSpoke = new Spoke("linkedVpcSpoke", SpokeArgs.builder()
            .name("vpc-spoke")
            .location("global")
            .hub(basicHub.id())
            .linkedVpcNetwork(SpokeLinkedVpcNetworkArgs.builder()
                .uri(network.selfLink())
                .build())
            .build());

        var primary = new Spoke("primary", SpokeArgs.builder()
            .name("producer-spoke")
            .location("global")
            .description("A sample spoke with a linked router appliance instance")
            .labels(Map.of("label-one", "value-one"))
            .hub(basicHub.id())
            .linkedProducerVpcNetwork(SpokeLinkedProducerVpcNetworkArgs.builder()
                .network(network.name())
                .peering(peering.peering())
                .excludeExportRanges(                
                    "198.51.100.0/24",
                    "10.10.0.0/16")
                .build())
            .build(), CustomResourceOptions.builder()
                .dependsOn(linkedVpcSpoke)
                .build());

    }
}
resources:
  network:
    type: gcp:compute:Network
    properties:
      name: net-spoke
      autoCreateSubnetworks: false
  address:
    type: gcp:compute:GlobalAddress
    properties:
      name: test-address
      purpose: VPC_PEERING
      addressType: INTERNAL
      prefixLength: 16
      network: ${network.id}
  peering:
    type: gcp:servicenetworking:Connection
    properties:
      network: ${network.id}
      service: servicenetworking.googleapis.com
      reservedPeeringRanges:
        - ${address.name}
  basicHub:
    type: gcp:networkconnectivity:Hub
    name: basic_hub
    properties:
      name: hub-basic
  linkedVpcSpoke:
    type: gcp:networkconnectivity:Spoke
    name: linked_vpc_spoke
    properties:
      name: vpc-spoke
      location: global
      hub: ${basicHub.id}
      linkedVpcNetwork:
        uri: ${network.selfLink}
  primary:
    type: gcp:networkconnectivity:Spoke
    properties:
      name: producer-spoke
      location: global
      description: A sample spoke with a linked router appliance instance
      labels:
        label-one: value-one
      hub: ${basicHub.id}
      linkedProducerVpcNetwork:
        network: ${network.name}
        peering: ${peering.peering}
        excludeExportRanges:
          - 198.51.100.0/24
          - 10.10.0.0/16
    options:
      dependsOn:
        - ${linkedVpcSpoke}

The linkedProducerVpcNetwork property attaches a service producer network. The network property specifies the VPC name, and the peering property identifies the VPC peering connection created by the managed service. The excludeExportRanges property prevents specific ranges from being advertised. This spoke type requires an existing linked VPC spoke, shown in the dependsOn relationship.

Deploy traffic inspection gateways

Organizations requiring centralized security inspection deploy gateway spokes that process traffic between other spokes, applying firewall rules or deep packet inspection.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const network = new gcp.compute.Network("network", {
    name: "net-spoke",
    autoCreateSubnetworks: false,
});
const subnetwork = new gcp.compute.Subnetwork("subnetwork", {
    name: "tf-test-subnet_16178",
    ipCidrRange: "10.0.0.0/28",
    region: "us-central1",
    network: network.selfLink,
});
const basicHub = new gcp.networkconnectivity.Hub("basic_hub", {
    name: "hub",
    description: "A sample hub",
    labels: {
        "label-two": "value-one",
    },
    presetTopology: "HYBRID_INSPECTION",
});
const primary = new gcp.networkconnectivity.Spoke("primary", {
    name: "gateway",
    location: "us-central1",
    description: "A sample spoke of type Gateway",
    labels: {
        "label-one": "value-one",
    },
    hub: basicHub.id,
    gateway: {
        ipRangeReservations: [{
            ipRange: "10.0.0.0/23",
        }],
        capacity: "CAPACITY_1_GBPS",
    },
    group: "gateways",
});
import pulumi
import pulumi_gcp as gcp

network = gcp.compute.Network("network",
    name="net-spoke",
    auto_create_subnetworks=False)
subnetwork = gcp.compute.Subnetwork("subnetwork",
    name="tf-test-subnet_16178",
    ip_cidr_range="10.0.0.0/28",
    region="us-central1",
    network=network.self_link)
basic_hub = gcp.networkconnectivity.Hub("basic_hub",
    name="hub",
    description="A sample hub",
    labels={
        "label-two": "value-one",
    },
    preset_topology="HYBRID_INSPECTION")
primary = gcp.networkconnectivity.Spoke("primary",
    name="gateway",
    location="us-central1",
    description="A sample spoke of type Gateway",
    labels={
        "label-one": "value-one",
    },
    hub=basic_hub.id,
    gateway={
        "ip_range_reservations": [{
            "ip_range": "10.0.0.0/23",
        }],
        "capacity": "CAPACITY_1_GBPS",
    },
    group="gateways")
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/networkconnectivity"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		network, err := compute.NewNetwork(ctx, "network", &compute.NetworkArgs{
			Name:                  pulumi.String("net-spoke"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewSubnetwork(ctx, "subnetwork", &compute.SubnetworkArgs{
			Name:        pulumi.String("tf-test-subnet_16178"),
			IpCidrRange: pulumi.String("10.0.0.0/28"),
			Region:      pulumi.String("us-central1"),
			Network:     network.SelfLink,
		})
		if err != nil {
			return err
		}
		basicHub, err := networkconnectivity.NewHub(ctx, "basic_hub", &networkconnectivity.HubArgs{
			Name:        pulumi.String("hub"),
			Description: pulumi.String("A sample hub"),
			Labels: pulumi.StringMap{
				"label-two": pulumi.String("value-one"),
			},
			PresetTopology: pulumi.String("HYBRID_INSPECTION"),
		})
		if err != nil {
			return err
		}
		_, err = networkconnectivity.NewSpoke(ctx, "primary", &networkconnectivity.SpokeArgs{
			Name:        pulumi.String("gateway"),
			Location:    pulumi.String("us-central1"),
			Description: pulumi.String("A sample spoke of type Gateway"),
			Labels: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
			Hub: basicHub.ID(),
			Gateway: &networkconnectivity.SpokeGatewayArgs{
				IpRangeReservations: networkconnectivity.SpokeGatewayIpRangeReservationArray{
					&networkconnectivity.SpokeGatewayIpRangeReservationArgs{
						IpRange: pulumi.String("10.0.0.0/23"),
					},
				},
				Capacity: pulumi.String("CAPACITY_1_GBPS"),
			},
			Group: pulumi.String("gateways"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var network = new Gcp.Compute.Network("network", new()
    {
        Name = "net-spoke",
        AutoCreateSubnetworks = false,
    });

    var subnetwork = new Gcp.Compute.Subnetwork("subnetwork", new()
    {
        Name = "tf-test-subnet_16178",
        IpCidrRange = "10.0.0.0/28",
        Region = "us-central1",
        Network = network.SelfLink,
    });

    var basicHub = new Gcp.NetworkConnectivity.Hub("basic_hub", new()
    {
        Name = "hub",
        Description = "A sample hub",
        Labels = 
        {
            { "label-two", "value-one" },
        },
        PresetTopology = "HYBRID_INSPECTION",
    });

    var primary = new Gcp.NetworkConnectivity.Spoke("primary", new()
    {
        Name = "gateway",
        Location = "us-central1",
        Description = "A sample spoke of type Gateway",
        Labels = 
        {
            { "label-one", "value-one" },
        },
        Hub = basicHub.Id,
        Gateway = new Gcp.NetworkConnectivity.Inputs.SpokeGatewayArgs
        {
            IpRangeReservations = new[]
            {
                new Gcp.NetworkConnectivity.Inputs.SpokeGatewayIpRangeReservationArgs
                {
                    IpRange = "10.0.0.0/23",
                },
            },
            Capacity = "CAPACITY_1_GBPS",
        },
        Group = "gateways",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
import com.pulumi.gcp.networkconnectivity.Hub;
import com.pulumi.gcp.networkconnectivity.HubArgs;
import com.pulumi.gcp.networkconnectivity.Spoke;
import com.pulumi.gcp.networkconnectivity.SpokeArgs;
import com.pulumi.gcp.networkconnectivity.inputs.SpokeGatewayArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var network = new Network("network", NetworkArgs.builder()
            .name("net-spoke")
            .autoCreateSubnetworks(false)
            .build());

        var subnetwork = new Subnetwork("subnetwork", SubnetworkArgs.builder()
            .name("tf-test-subnet_16178")
            .ipCidrRange("10.0.0.0/28")
            .region("us-central1")
            .network(network.selfLink())
            .build());

        var basicHub = new Hub("basicHub", HubArgs.builder()
            .name("hub")
            .description("A sample hub")
            .labels(Map.of("label-two", "value-one"))
            .presetTopology("HYBRID_INSPECTION")
            .build());

        var primary = new Spoke("primary", SpokeArgs.builder()
            .name("gateway")
            .location("us-central1")
            .description("A sample spoke of type Gateway")
            .labels(Map.of("label-one", "value-one"))
            .hub(basicHub.id())
            .gateway(SpokeGatewayArgs.builder()
                .ipRangeReservations(SpokeGatewayIpRangeReservationArgs.builder()
                    .ipRange("10.0.0.0/23")
                    .build())
                .capacity("CAPACITY_1_GBPS")
                .build())
            .group("gateways")
            .build());

    }
}
resources:
  network:
    type: gcp:compute:Network
    properties:
      name: net-spoke
      autoCreateSubnetworks: false
  subnetwork:
    type: gcp:compute:Subnetwork
    properties:
      name: tf-test-subnet_16178
      ipCidrRange: 10.0.0.0/28
      region: us-central1
      network: ${network.selfLink}
  basicHub:
    type: gcp:networkconnectivity:Hub
    name: basic_hub
    properties:
      name: hub
      description: A sample hub
      labels:
        label-two: value-one
      presetTopology: HYBRID_INSPECTION
  primary:
    type: gcp:networkconnectivity:Spoke
    properties:
      name: gateway
      location: us-central1
      description: A sample spoke of type Gateway
      labels:
        label-one: value-one
      hub: ${basicHub.id}
      gateway:
        ipRangeReservations:
          - ipRange: 10.0.0.0/23
        capacity: CAPACITY_1_GBPS
      group: gateways

The gateway property defines an inspection gateway. The ipRangeReservations property allocates IP ranges for gateway processing. The capacity property sets throughput limits (CAPACITY_1_GBPS, CAPACITY_10_GBPS). Gateway spokes require a hub with HYBRID_INSPECTION preset topology and are assigned to the “gateways” group for routing policy application.

Beyond these examples

These snippets focus on specific spoke-level features: VPC network attachment with route filtering, hybrid connectivity through VPN and Interconnect, and traffic inspection via router appliances and gateways. They’re intentionally minimal rather than full network architectures.

The examples reference pre-existing infrastructure such as VPC networks, subnets, Cloud Routers, VPN tunnels, gateways, Interconnect attachments, Compute Engine instances configured for routing, and Network Connectivity Center hubs. They focus on configuring the spoke rather than provisioning the underlying network infrastructure.

To keep things focused, common spoke patterns are omitted, including:

  • Hub creation and topology configuration (STAR, MESH, HYBRID_INSPECTION)
  • Group membership and auto-accept policies
  • IPv6 route advertisement (ALL_IPV6_RANGES)
  • Import range filtering beyond ALL_IPV4_RANGES

These omissions are intentional: the goal is to illustrate how each spoke type is wired, not provide drop-in network modules. See the Network Connectivity Spoke resource reference for all available configuration options.

Let's configure GCP Network Connectivity Spokes

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration & Immutability
What properties can't I change after creating a spoke?
The hub, location, name, group, gateway, and linkedProducerVpcNetwork properties are immutable and cannot be changed after spoke creation. Changes to these properties require replacing the resource.
Why aren't my label changes showing up on the spoke?
The labels field is non-authoritative and only manages labels present in your configuration. To see all labels on the resource (including those set by other clients), use the effectiveLabels output property.
Linking Resources
What types of resources can I link to a spoke?

You can link five types of resources to a spoke:

  1. VPC networks - Use linkedVpcNetwork
  2. Router appliance instances - Use linkedRouterApplianceInstances
  3. VPN tunnels - Use linkedVpnTunnels
  4. Interconnect attachments - Use linkedInterconnectAttachments
  5. Producer VPC networks - Use linkedProducerVpcNetwork (for service networking)
What's the difference between a regular VPC spoke and a producer VPC spoke?
A linkedVpcNetwork spoke connects your own VPC network, while a linkedProducerVpcNetwork spoke connects a VPC from a service producer (like Cloud SQL) via VPC peering. Producer VPC spokes require an existing VPC spoke in the same hub and should use dependsOn to ensure proper creation order.
Do I need to create spokes in a specific order?
Yes, when creating a linkedProducerVpcNetwork spoke, you must first create a linkedVpcNetwork spoke in the same hub. Use Pulumi’s dependsOn option to enforce this dependency.
Network Routing & Ranges
How do I control which IP ranges are exported or imported?
For VPC network spokes, use includeExportRanges and excludeExportRanges to control exported routes. For router appliances, VPN tunnels, and interconnect attachments, use includeImportRanges to control imported routes. You can specify CIDR blocks (e.g., 198.51.100.0/24) or special values like ALL_IPV4_RANGES, ALL_IPV6_RANGES, or ALL_PRIVATE_IPV4_RANGES.
What does the siteToSiteDataTransfer flag do?
The siteToSiteDataTransfer boolean enables direct data transfer between sites connected through the spoke (for router appliances, VPN tunnels, and interconnect attachments). When set to true, traffic can flow directly between connected sites without routing through Google Cloud.
Advanced Features
How do I use groups with spokes?
Set the group property to associate a spoke with a hub group. For STAR topology hubs, you can create a center group with autoAccept configuration to automatically accept spokes from specified projects.
What's a gateway spoke used for?
A gateway spoke applies specialized processing to traffic going through it, typically used with HYBRID_INSPECTION topology hubs. Configure it with capacity (e.g., CAPACITY_1_GBPS) and ipRangeReservations to reserve IP ranges for the gateway.

Using a different cloud?

Explore networking guides for other cloud providers: