diff --git a/docs/resources/network.md b/docs/resources/network.md index 5304244..16e6d2b 100644 --- a/docs/resources/network.md +++ b/docs/resources/network.md @@ -88,4 +88,7 @@ terraform import unifi_network.mynetwork 5dc28e5e9106d105bdc87217 # import from another site terraform import unifi_network.mynetwork bfa2l6i7:5dc28e5e9106d105bdc87217 + +# import network by name +terraform import unifi_network.mynetwork name=LAN ``` diff --git a/examples/resources/unifi_network/import.sh b/examples/resources/unifi_network/import.sh index a3d04ee..3b071c3 100644 --- a/examples/resources/unifi_network/import.sh +++ b/examples/resources/unifi_network/import.sh @@ -3,3 +3,6 @@ terraform import unifi_network.mynetwork 5dc28e5e9106d105bdc87217 # import from another site terraform import unifi_network.mynetwork bfa2l6i7:5dc28e5e9106d105bdc87217 + +# import network by name +terraform import unifi_network.mynetwork name=LAN diff --git a/internal/provider/lazy_client.go b/internal/provider/lazy_client.go index 98f0502..2081a46 100644 --- a/internal/provider/lazy_client.go +++ b/internal/provider/lazy_client.go @@ -113,6 +113,12 @@ func (c *lazyClient) GetNetwork(ctx context.Context, site, id string) (*unifi.Ne } return c.inner.GetNetwork(ctx, site, id) } +func (c *lazyClient) ListNetwork(ctx context.Context, site string) ([]unifi.Network, error) { + if err := c.init(ctx); err != nil { + return nil, err + } + return c.inner.ListNetwork(ctx, site) +} func (c *lazyClient) UpdateNetwork(ctx context.Context, site string, d *unifi.Network) (*unifi.Network, error) { if err := c.init(ctx); err != nil { return nil, err diff --git a/internal/provider/provider.go b/internal/provider/provider.go index f745df1..3d8d18c 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -150,6 +150,7 @@ type unifiClient interface { DeleteNetwork(ctx context.Context, site, id, name string) error CreateNetwork(ctx context.Context, site string, d *unifi.Network) (*unifi.Network, error) GetNetwork(ctx context.Context, site, id string) (*unifi.Network, error) + ListNetwork(ctx context.Context, site string) ([]unifi.Network, error) UpdateNetwork(ctx context.Context, site string, d *unifi.Network) (*unifi.Network, error) DeleteWLAN(ctx context.Context, site, id string) error diff --git a/internal/provider/resource_network.go b/internal/provider/resource_network.go index 8ae76df..08dbe51 100644 --- a/internal/provider/resource_network.go +++ b/internal/provider/resource_network.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "regexp" + "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -33,7 +34,7 @@ func resourceNetwork() *schema.Resource { Update: resourceNetworkUpdate, Delete: resourceNetworkDelete, Importer: &schema.ResourceImporter{ - State: importSiteAndID, + State: importNetwork, }, Schema: map[string]*schema.Schema{ @@ -383,3 +384,59 @@ func resourceNetworkDelete(d *schema.ResourceData, meta interface{}) error { } return err } + +func importNetwork(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + c := meta.(*client) + id := d.Id() + site := d.Get("site").(string) + if site == "" { + site = c.site + } + + if strings.Contains(id, ":") { + importParts := strings.SplitN(id, ":", 2) + site = importParts[0] + id = importParts[1] + } + + if strings.HasPrefix(id, "name=") { + targetName := strings.TrimPrefix(id, "name=") + var err error + if id, err = getNetworkIDByName(c.c, targetName, site); err != nil { + return nil, err + } + } + + if id != "" { + d.SetId(id) + } + if site != "" { + d.Set("site", site) + } + + return []*schema.ResourceData{d}, nil +} + +func getNetworkIDByName(client unifiClient, networkName, site string) (string, error) { + networks, err := client.ListNetwork(context.TODO(), site) + if err != nil { + return "", err + } + + idMatchingName := "" + allNames := []string{} + for _, network := range networks { + allNames = append(allNames, network.Name) + if network.Name != networkName { + continue + } + if idMatchingName != "" { + return "", fmt.Errorf("Found multiple networks with name '%s'", networkName) + } + idMatchingName = network.ID + } + if idMatchingName == "" { + return "", fmt.Errorf("Found no networks with name '%s', found: %s", networkName, strings.Join(allNames, ", ")) + } + return idMatchingName, nil +} diff --git a/internal/provider/resource_network_test.go b/internal/provider/resource_network_test.go index 6c2f1c5..1eedfd1 100644 --- a/internal/provider/resource_network_test.go +++ b/internal/provider/resource_network_test.go @@ -2,6 +2,7 @@ package provider import ( "fmt" + "regexp" "strconv" "strings" "testing" @@ -203,6 +204,51 @@ func TestAccNetwork_differentSite(t *testing.T) { }) } +func TestAccNetwork_importByName(t *testing.T) { + vlanID1 := getTestVLAN(t) + vlanID2 := getTestVLAN(t) + vlanID3 := getTestVLAN(t) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { preCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + // Apply and import network by name. + { + Config: testAccNetworkConfig(vlanID1, true, nil), + }, + { + Config: testAccNetworkConfig(vlanID1, true, nil), + ResourceName: "unifi_network.test", + ImportState: true, + ImportStateVerify: true, + ImportStateId: "name=tfacc", + }, + // Apply and test errors. + { + Config: testAccNetworkWithDuplicateNames(vlanID2, vlanID3, "DUPLICATE_NAME"), + }, + // Test error on name that doesn't exist. + { + Config: testAccNetworkWithDuplicateNames(vlanID2, vlanID3, "DUPLICATE_NAME"), + ResourceName: "unifi_network.test1", + ImportState: true, + ImportStateVerify: true, + ImportStateId: "name=BAD_NAME", + ExpectError: regexp.MustCompile("BAD_NAME"), + }, + // Test error on multiple matches. + { + Config: testAccNetworkWithDuplicateNames(vlanID2, vlanID3, "DUPLICATE_NAME"), + ResourceName: "unifi_network.test1", + ImportState: true, + ImportStateVerify: true, + ImportStateId: "name=DUPLICATE_NAME", + ExpectError: regexp.MustCompile("DUPLICATE_NAME"), + }, + }, + }) +} + // TODO: ipv6 prefix delegation test func quoteStrings(src []string) []string { @@ -216,7 +262,7 @@ func quoteStrings(src []string) []string { func testAccNetworkConfig(vlan int, igmpSnoop bool, dhcpDNS []string) string { return fmt.Sprintf(` locals { - subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + subnet = cidrsubnet("10.0.0.0/8", 6, %[1]d) vlan_id = %[1]d } @@ -240,7 +286,7 @@ resource "unifi_network" "test" { func testAccNetworkConfigV6(vlan int, ipv6Type string, ipv6Subnet string) string { return fmt.Sprintf(` locals { - subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + subnet = cidrsubnet("10.0.0.0/8", 6, %[1]d) vlan_id = %[1]d } @@ -280,7 +326,7 @@ resource "unifi_network" "wan_test" { func testAccNetworkWithSiteConfig(vlan int) string { return fmt.Sprintf(` locals { - subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + subnet = cidrsubnet("10.0.0.0/8", 6, %[1]d) vlan_id = %[1]d } @@ -303,3 +349,30 @@ resource "unifi_network" "test" { } `, vlan) } + +func testAccNetworkWithDuplicateNames(vlan1, vlan2 int, networkName string) string { + return fmt.Sprintf(` +locals { + subnet1 = cidrsubnet("10.0.0.0/8", 6, %[1]d) + vlan_id1 = %[1]d + subnet2 = cidrsubnet("10.0.0.0/8", 6, %[2]d) + vlan_id2 = %[2]d +} + +resource "unifi_network" "test1" { + name = "%[3]s" + purpose = "corporate" + + subnet = local.subnet1 + vlan_id = local.vlan_id1 +} + +resource "unifi_network" "test2" { + name = "%[3]s" + purpose = "corporate" + + subnet = local.subnet2 + vlan_id = local.vlan_id2 +} +`, vlan1, vlan2, networkName) +} diff --git a/internal/provider/resource_wlan_test.go b/internal/provider/resource_wlan_test.go index 88c96dd..a730f82 100644 --- a/internal/provider/resource_wlan_test.go +++ b/internal/provider/resource_wlan_test.go @@ -270,7 +270,7 @@ resource "unifi_network" "test" { name = "tfacc" purpose = "corporate" - subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + subnet = cidrsubnet("10.0.0.0/8", 6, %[1]d) vlan_id = %[1]d } @@ -302,7 +302,7 @@ resource "unifi_network" "test" { name = "tfacc" purpose = "corporate" - subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + subnet = cidrsubnet("10.0.0.0/8", 6, %[1]d) vlan_id = %[1]d } @@ -331,7 +331,7 @@ resource "unifi_network" "test" { name = "tfacc" purpose = "corporate" - subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + subnet = cidrsubnet("10.0.0.0/8", 6, %[1]d) vlan_id = %[1]d } @@ -357,7 +357,7 @@ resource "unifi_network" "test" { name = "tfacc" purpose = "corporate" - subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + subnet = cidrsubnet("10.0.0.0/8", 6, %[1]d) vlan_id = %[1]d } @@ -395,7 +395,7 @@ resource "unifi_network" "test" { name = "tfacc" purpose = "corporate" - subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + subnet = cidrsubnet("10.0.0.0/8", 6, %[1]d) vlan_id = %[1]d } @@ -425,7 +425,7 @@ resource "unifi_network" "test" { name = "tfacc" purpose = "corporate" - subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + subnet = cidrsubnet("10.0.0.0/8", 6, %[1]d) vlan_id = %[1]d } @@ -455,7 +455,7 @@ resource "unifi_network" "test" { name = "tfacc" purpose = "corporate" - subnet = cidrsubnet("10.0.0.0/8", 4, %[1]d) + subnet = cidrsubnet("10.0.0.0/8", 6, %[1]d) vlan_id = %[1]d }