Configure GCP Network Connectivity Spokes

The gcp:networkconnectivity/spoke:Spoke resource, part of the Pulumi GCP provider, defines a spoke in Network Connectivity Center that connects networks to a central hub for transitive routing. This guide focuses on three capabilities: VPC network attachment with route filtering, router appliance and VPN tunnel connectivity, and gateway spokes for traffic inspection.

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

Connect a VPC network with route filtering

Most Network Connectivity Center deployments begin by attaching VPC networks to a hub, enabling transitive connectivity between spokes.

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 includeExportRanges property specifies which IP ranges this spoke advertises to other spokes, while excludeExportRanges removes specific subnets from advertisement. This controls which routes propagate through the hub, preventing unwanted connectivity or routing conflicts.

Route traffic through network virtual appliances

Organizations that need centralized security inspection deploy router appliance instances as spokes, directing traffic through virtual appliances before reaching other networks.

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

const network = new gcp.compute.Network("network", {
    name: "tf-test-network_11171",
    autoCreateSubnetworks: false,
});
const subnetwork = new gcp.compute.Subnetwork("subnetwork", {
    name: "tf-test-subnet_40472",
    ipCidrRange: "10.0.0.0/28",
    region: "us-central1",
    network: network.selfLink,
});
const instance = new gcp.compute.Instance("instance", {
    name: "tf-test-instance_44339",
    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_34599",
    description: "A sample hub",
    labels: {
        "label-two": "value-one",
    },
});
const primary = new gcp.networkconnectivity.Spoke("primary", {
    name: "tf-test-name_79513",
    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_11171",
    auto_create_subnetworks=False)
subnetwork = gcp.compute.Subnetwork("subnetwork",
    name="tf-test-subnet_40472",
    ip_cidr_range="10.0.0.0/28",
    region="us-central1",
    network=network.self_link)
instance = gcp.compute.Instance("instance",
    name="tf-test-instance_44339",
    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_34599",
    description="A sample hub",
    labels={
        "label-two": "value-one",
    })
primary = gcp.networkconnectivity.Spoke("primary",
    name="tf-test-name_79513",
    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_11171"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		subnetwork, err := compute.NewSubnetwork(ctx, "subnetwork", &compute.SubnetworkArgs{
			Name:        pulumi.String("tf-test-subnet_40472"),
			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_44339"),
			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_34599"),
			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_79513"),
			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_11171",
        AutoCreateSubnetworks = false,
    });

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

    var instance = new Gcp.Compute.Instance("instance", new()
    {
        Name = "tf-test-instance_44339",
        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_34599",
        Description = "A sample hub",
        Labels = 
        {
            { "label-two", "value-one" },
        },
    });

    var primary = new Gcp.NetworkConnectivity.Spoke("primary", new()
    {
        Name = "tf-test-name_79513",
        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_11171")
            .autoCreateSubnetworks(false)
            .build());

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

        var instance = new Instance("instance", InstanceArgs.builder()
            .name("tf-test-instance_44339")
            .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_34599")
            .description("A sample hub")
            .labels(Map.of("label-two", "value-one"))
            .build());

        var primary = new Spoke("primary", SpokeArgs.builder()
            .name("tf-test-name_79513")
            .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_11171
      autoCreateSubnetworks: false
  subnetwork:
    type: gcp:compute:Subnetwork
    properties:
      name: tf-test-subnet_40472
      ipCidrRange: 10.0.0.0/28
      region: us-central1
      network: ${network.selfLink}
  instance:
    type: gcp:compute:Instance
    properties:
      name: tf-test-instance_44339
      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_34599
      description: A sample hub
      labels:
        label-two: value-one
  primary:
    type: gcp:networkconnectivity:Spoke
    properties:
      name: tf-test-name_79513
      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 references Compute Engine instances configured for IP forwarding. The virtualMachine property points to the instance, and ipAddress specifies the internal IP that receives traffic. Setting siteToSiteDataTransfer to true enables data transfer between spokes through this appliance. The includeImportRanges property controls which routes the appliance advertises.

Connect on-premises networks via VPN tunnels

Hybrid cloud architectures use VPN tunnels to connect on-premises data centers, with Network Connectivity Center providing transitive routing between on-premises and cloud networks.

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 references existing VPN tunnel resources by their self-links. Multiple tunnels provide redundancy. The siteToSiteDataTransfer property enables traffic flow between this spoke and others, while includeImportRanges determines which routes are imported from the on-premises network. This configuration assumes BGP peering is already established on the Cloud Router.

Connect via dedicated or partner interconnect

Enterprise workloads requiring high bandwidth and low latency use Cloud Interconnect attachments, which Network Connectivity Center integrates for transitive routing.

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 references InterconnectAttachment resources. Like VPN spokes, siteToSiteDataTransfer enables transitive connectivity, and includeImportRanges controls route advertisement. Interconnect attachments provide higher bandwidth and lower latency than VPN tunnels, making them suitable for production workloads with strict performance requirements.

Deploy a gateway spoke for traffic inspection

Hybrid inspection topologies use gateway spokes to apply specialized processing to traffic flowing between networks.

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_49547",
    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_49547",
    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_49547"),
			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_49547",
        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_49547")
            .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_49547
      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 a spoke that processes traffic rather than connecting a network. The ipRangeReservations property allocates IP ranges for the gateway’s internal use, and capacity sets the throughput limit. Gateway spokes require a hub with presetTopology set to HYBRID_INSPECTION and must be assigned to the “gateways” group. Traffic between other spokes flows through gateway spokes for inspection or processing.

Beyond these examples

These snippets focus on specific spoke-level features: VPC network attachment with route filtering, router appliance, VPN, and interconnect connectivity, and gateway spokes for traffic inspection. They’re intentionally minimal rather than full network architectures.

The examples reference pre-existing infrastructure such as VPC networks, subnets, Cloud Routers, Compute Engine instances (for router appliances), VPN gateways, tunnels, BGP peering, interconnect attachments, 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:

  • Producer VPC network spokes (service networking)
  • Group-based spoke organization and auto-accept policies
  • IPv6 route advertisement (ALL_IPV6_RANGES)
  • Spoke lifecycle management (state transitions)

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

Spoke Configuration & Types
What types of resources can I link to a spoke?
You can link VPC networks (linkedVpcNetwork), router appliance instances (linkedRouterApplianceInstances), VPN tunnels (linkedVpnTunnels), interconnect attachments (linkedInterconnectAttachments), producer VPC networks (linkedProducerVpcNetwork), or create a gateway spoke (gateway).
What's the difference between a regular VPC spoke and a producer VPC spoke?
A regular VPC spoke (linkedVpcNetwork) connects your own VPC network to the hub. A producer VPC spoke (linkedProducerVpcNetwork) connects a service producer’s VPC network (like Cloud SQL or other managed services) that’s peered via Service Networking.
What hub topologies can I use with spokes?
Examples show STAR topology for standard hub-and-spoke configurations and HYBRID_INSPECTION topology for gateway-based traffic inspection.
Immutability & Updates
What spoke properties can't I change after creation?
The following properties are immutable: hub, location, name, group, project, gateway, and linkedProducerVpcNetwork. Changing any of these requires replacing the spoke resource.
Why does creating a producer VPC spoke require a dependency on another spoke?
A producer VPC spoke requires an existing VPC spoke in the same hub. Use dependsOn to ensure the VPC spoke is created first.
IP Range Management
How do I control which IP ranges are exported from my VPC spoke?
Use includeExportRanges to specify which CIDR ranges to export and excludeExportRanges to exclude specific ranges. You can use special values like ALL_IPV6_RANGES or ALL_PRIVATE_IPV4_RANGES.
What does ALL_IPV4_RANGES mean in includeImportRanges?
ALL_IPV4_RANGES is a special value that includes all IPv4 address ranges for import, commonly used with router appliances, VPN tunnels, and interconnect attachments.
Can I use IPv6 ranges with my spoke?
Yes, set includeExportRanges to ["ALL_IPV6_RANGES", "ALL_PRIVATE_IPV4_RANGES"] in your linkedVpcNetwork configuration.
What's the difference between includeExportRanges and includeImportRanges?
includeExportRanges (used with VPC spokes) controls which ranges your VPC advertises to the hub. includeImportRanges (used with router appliances, VPN tunnels, and interconnects) controls which ranges are imported from those resources.
Labels & Metadata
Why aren't all my spoke's labels showing up in my Pulumi configuration?
The labels field is non-authoritative and only manages labels defined in your configuration. To see all labels (including those set by other clients or GCP services), use the effectiveLabels output property.
Advanced Configuration
What does siteToSiteDataTransfer do?
siteToSiteDataTransfer enables direct data transfer between sites connected through the spoke, available for router appliances, VPN tunnels, and interconnect attachments.
How do I configure a gateway spoke for traffic inspection?
Set the gateway property with ipRangeReservations (IP ranges for the gateway) and capacity (e.g., CAPACITY_1_GBPS). Gateway spokes require a hub with HYBRID_INSPECTION topology.

Using a different cloud?

Explore networking guides for other cloud providers: