diff --git a/README.md b/README.md index ecd7b457..a231d9c0 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ The Random provider supports the use of randomness within Terraform configuratio provider resources can be used to generate a random [id](docs/resources/id.md), [integer](docs/resources/integer.md), [password](docs/resources/password.md), [pet](docs/resources/pet.md), [shuffle](docs/resources/shuffle.md) (random permutation -of a list of strings), [string](docs/resources/string.md) or -[uuid](docs/resources/uuid.md). +of a list of strings), [string](docs/resources/string.md), +[uuid](docs/resources/uuid.md) or [ip](docs/resources/ip.md). ## Documentation, questions and discussions diff --git a/docs/resources/ip.md b/docs/resources/ip.md new file mode 100644 index 00000000..f1bfb41e --- /dev/null +++ b/docs/resources/ip.md @@ -0,0 +1,76 @@ +--- +page_title: "random_ip Resource - terraform-provider-random" +subcategory: "" +description: |- + The random_ip resource generates a random IP address, either IPv4 or IPv6. By default, it randomly chooses between 0.0.0.0/0 (IPv4) and ::/0 (IPv6). You can influence the IP type by specifying a cidr_range. +--- + +# random_ip (Resource) + +The `random_ip` resource generates a random IP address, either IPv4 or IPv6. By default, it randomly chooses between 0.0.0.0/0 (IPv4) and ::/0 (IPv6). You can influence the IP type by specifying a `cidr_range`. + +## Example Usage + +```terraform +resource "random_ip" "example" {} +``` + +### IPv4 + +```terraform +resource "random_ip" "example" { + cidr_range = "10.1.0.0/16" +} +``` + +### IPv6 + +```terraform +resource "random_ip" "example" { + cidr_range = "2001:db8::/32" +} +``` + +### Setting a count + +It can be useful to generate a random number of IP addresses. You can achieve this by setting the [count](https://developer.hashicorp.com/terraform/language/meta-arguments/count) argument. + +```terraform +resource "random_ip" "example" { + count = 20 + cidr_range = "192.168.1.0/28" +} + +output "random_ipv4_addresses" { + value = random_ip.example[*].result +} +``` + +### Setting a count followed by a distinct + +Note that setting a [count](https://developer.hashicorp.com/terraform/language/meta-arguments/count) argument may result in duplicate IP addresses. +To prevent this, apply a [distinct](https://developer.hashicorp.com/terraform/language/functions/distinct) function to your list of IP addresses. + +```terraform +resource "random_ip" "example" { + count = 50 + cidr_range = "192.168.1.0/28" +} + +output "random_distinct_ipv4_addresses" { + value = distinct(random_ip.example[*].result) +} +``` + + +## Schema + +### Optional + +- `cidr_range` (String) A CIDR range from which to allocate the IP address. +- `keepers` (Map of String) Arbitrary map of values that, when changed, will trigger recreation of resource. See [the main provider documentation](../index.html) for more information. + +### Read-Only + +- `id` (String) A static value used internally by Terraform, this should not be referenced in configurations. +- `result` (String) The random IP address allocated from the CIDR range. diff --git a/examples/resources/random_ip/basic.tf b/examples/resources/random_ip/basic.tf new file mode 100644 index 00000000..e62c900a --- /dev/null +++ b/examples/resources/random_ip/basic.tf @@ -0,0 +1 @@ +resource "random_ip" "example" {} diff --git a/examples/resources/random_ip/count.tf b/examples/resources/random_ip/count.tf new file mode 100644 index 00000000..b23b6870 --- /dev/null +++ b/examples/resources/random_ip/count.tf @@ -0,0 +1,8 @@ +resource "random_ip" "example" { + count = 20 + cidr_range = "192.168.1.0/28" +} + +output "random_ipv4_addresses" { + value = random_ip.example[*].result +} diff --git a/examples/resources/random_ip/count_distinct.tf b/examples/resources/random_ip/count_distinct.tf new file mode 100644 index 00000000..02af23b0 --- /dev/null +++ b/examples/resources/random_ip/count_distinct.tf @@ -0,0 +1,8 @@ +resource "random_ip" "example" { + count = 50 + cidr_range = "192.168.1.0/28" +} + +output "random_distinct_ipv4_addresses" { + value = distinct(random_ip.example[*].result) +} diff --git a/examples/resources/random_ip/ipv4.tf b/examples/resources/random_ip/ipv4.tf new file mode 100644 index 00000000..8ac15fc4 --- /dev/null +++ b/examples/resources/random_ip/ipv4.tf @@ -0,0 +1,3 @@ +resource "random_ip" "example" { + cidr_range = "10.1.0.0/16" +} diff --git a/examples/resources/random_ip/ipv6.tf b/examples/resources/random_ip/ipv6.tf new file mode 100644 index 00000000..01dfcc7f --- /dev/null +++ b/examples/resources/random_ip/ipv6.tf @@ -0,0 +1,3 @@ +resource "random_ip" "example" { + cidr_range = "2001:db8::/32" +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1843c9ff..69f2f429 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -39,6 +39,7 @@ func (p *randomProvider) Resources(context.Context) []func() resource.Resource { NewShuffleResource, NewStringResource, NewUuidResource, + NewIPResource, } } diff --git a/internal/provider/random_ip.go b/internal/provider/random_ip.go new file mode 100644 index 00000000..bfe99c7d --- /dev/null +++ b/internal/provider/random_ip.go @@ -0,0 +1,184 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + "math/rand" + "net" + "time" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-random/internal/diagnostics" + mapplanmodifiers "github.com/terraform-providers/terraform-provider-random/internal/planmodifiers/map" +) + +var ( + _ resource.Resource = (*ipResource)(nil) +) + +func NewIPResource() resource.Resource { + return &ipResource{} +} + +type ipResource struct{} + +func (r *ipResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_ip" +} + +func (r *ipResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "The `random_ip` resource generates a random IP address, either IPv4 or IPv6. By default, it randomly chooses between 0.0.0.0/0 (IPv4) and ::/0 (IPv6). You can influence the IP type by specifying a `cidr_range`.", + Attributes: map[string]schema.Attribute{ + "keepers": schema.MapAttribute{ + Description: "Arbitrary map of values that, when changed, will trigger recreation of " + + "resource. See [the main provider documentation](../index.html) for more information.", + ElementType: types.StringType, + Optional: true, + PlanModifiers: []planmodifier.Map{ + mapplanmodifiers.RequiresReplaceIfValuesNotNull(), + }, + }, + "cidr_range": schema.StringAttribute{ + Description: "A CIDR range from which to allocate the IP address.", + Computed: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "result": schema.StringAttribute{ + Description: "The random IP address allocated from the CIDR range.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "id": schema.StringAttribute{ + Description: "A static value used internally by Terraform, this should not be referenced in configurations.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *ipResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan ipModelV0 + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + cidrRange := plan.CIDRRange.ValueString() + // Check if CIDR range is empty, and if so, set it to either 0.0.0.0/0 or ::/0 + if cidrRange == "" { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + if r.Intn(2) == 0 { + cidrRange = "0.0.0.0/0" + } else { + cidrRange = "::/0" + } + } + + // Generate a random IP address from a given CIDR range. + ip, err := getRandomIP(cidrRange) + if err != nil { + resp.Diagnostics.AddError( + "Create Random IP error", + "There was an error when generating the random IP address.\n\n"+ + diagnostics.RetryMsg+ + "Original Error: "+err.Error(), + ) + return + } + + u := &ipModelV0{ + ID: types.StringValue("-"), + Keepers: plan.Keepers, + CIDRRange: types.StringValue(cidrRange), + Result: types.StringValue(ip.String()), + } + + diags = resp.State.Set(ctx, u) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read does not need to perform any operations as the state in ReadResourceResponse is already populated. +func (r *ipResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +} + +// Update ensures the plan value is copied to the state to complete the update. +func (r *ipResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model ipModelV0 + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +// Delete does not need to explicitly call resp.State.RemoveResource() as this is automatically handled by the +// [framework](https://github.com/hashicorp/terraform-plugin-framework/pull/301). +func (r *ipResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +type ipModelV0 struct { + ID types.String `tfsdk:"id"` + Keepers types.Map `tfsdk:"keepers"` + CIDRRange types.String `tfsdk:"cidr_range"` + Result types.String `tfsdk:"result"` +} + +func getRandomIP(cidrRange string) (net.IP, error) { + // Parse the CIDR range to check for errors + // and to get the network address and netmask. + _, network, err := net.ParseCIDR(cidrRange) + if err != nil { + return nil, fmt.Errorf("error parsing CIDR range: %w", err) + } + + // Get the netmask and determine the IP type (IPv4 or IPv6) + netmaskBytes := network.Mask + addressBytes := network.IP + + // Check if the length of the netmask is valid (either IPv4 or IPv6 length). + if len(netmaskBytes) != net.IPv4len && len(netmaskBytes) != net.IPv6len { + return nil, fmt.Errorf("invalid netmask: must be either IPv4 or IPv6") + } + + // This typically occurs when the CIDR range is not of the same type as the address type. + if len(netmaskBytes) != len(addressBytes) { + return nil, fmt.Errorf("netmask byte length does not match IP address byte length") + } + + // Generate the random IP within the CIDR range. + picked := make([]byte, len(addressBytes)) + for i := 0; i < len(netmaskBytes); i++ { + // Combine the random bits with the original network address + picked[i] = ((255 ^ netmaskBytes[i]) & byte(rand.Intn(256))) | addressBytes[i] + } + + // Turn the randomized byte slice into an IP address. + pickedIP := net.IP(picked) + + return pickedIP, nil +} diff --git a/internal/provider/random_ip_test.go b/internal/provider/random_ip_test.go new file mode 100644 index 00000000..b2c94d20 --- /dev/null +++ b/internal/provider/random_ip_test.go @@ -0,0 +1,681 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccResourceIP_basic(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_ip" "test" {}`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("random_ip.test", "result"), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_ipv4(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_ip" "test" { + cidr_range = "10.0.0.0/16" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("random_ip.test", "cidr_range"), + resource.TestCheckResourceAttrSet("random_ip.test", "result"), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_ipv4_quadZero(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_ip" "test" { + cidr_range = "0.0.0.0/0" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("random_ip.test", "cidr_range"), + resource.TestCheckResourceAttrSet("random_ip.test", "result"), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_ipv4_largestPrefix(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_ip" "test" { + cidr_range = "0.0.0.0/0" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("random_ip.test", "cidr_range"), + resource.TestCheckResourceAttrSet("random_ip.test", "result"), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_ipv4_smallestPrefix(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_ip" "test" { + cidr_range = "192.168.1.1/32" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("random_ip.test", "cidr_range"), + resource.TestCheckResourceAttrSet("random_ip.test", "result"), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_ipv6(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_ip" "test" { + cidr_range = "2001:db8::/16" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("random_ip.test", "cidr_range"), + resource.TestCheckResourceAttrSet("random_ip.test", "result"), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_ipv6_zeroCompression(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_ip" "test" { + cidr_range = "::/0" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("random_ip.test", "cidr_range"), + resource.TestCheckResourceAttrSet("random_ip.test", "result"), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_ipv6_largestPrefix(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_ip" "test" { + cidr_range = "::/0" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("random_ip.test", "cidr_range"), + resource.TestCheckResourceAttrSet("random_ip.test", "result"), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_ipv6_smallestPrefix(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: `resource "random_ip" "test" { + cidr_range = "2001:db8::1/128" + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("random_ip.test", "cidr_range"), + resource.TestCheckResourceAttrSet("random_ip.test", "result"), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Keep_EmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = {} + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = {} + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Keep_EmptyMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = {} + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = null + } + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Keep_NullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Keep_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = null + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Keep_NullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = null + } + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = null + } + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Keep_NullValues(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key1" = null + "key2" = null + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key1" = null + "key2" = null + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Keep_Value(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = "123" + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = "123" + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Keep_Values(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key1" = "123" + "key2" = "456" + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Replace_EmptyMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = {} + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = "123" + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Replace_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = "123" + } + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Replace_NullValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = null + } + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = "123" + } + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Replace_ValueToEmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = "123" + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = {} + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Replace_ValueToNullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = "123" + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Replace_ValueToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = "123" + } + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = null + } + cidr_range = "2001:db8::/32" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResourceIP_Keepers_Replace_ValueToNewValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = "123" + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id1), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "random_ip" "test" { + keepers = { + "key" = "456" + } + cidr_range = "10.1.0.0/24" + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("random_ip.test", "id", &id2), + resource.TestCheckResourceAttr("random_ip.test", "keepers.%", "1"), + ), + }, + }, + }) +} diff --git a/templates/resources/ip.md.tmpl b/templates/resources/ip.md.tmpl new file mode 100644 index 00000000..04d96e3e --- /dev/null +++ b/templates/resources/ip.md.tmpl @@ -0,0 +1,37 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile (printf "examples/resources/%s/basic.tf" .Name)}} + +### IPv4 + +{{ tffile (printf "examples/resources/%s/ipv4.tf" .Name)}} + +### IPv6 + +{{ tffile (printf "examples/resources/%s/ipv6.tf" .Name)}} + +### Setting a count + +It can be useful to generate a random number of IP addresses. You can achieve this by setting the [count](https://developer.hashicorp.com/terraform/language/meta-arguments/count) argument. + +{{ tffile (printf "examples/resources/%s/count.tf" .Name)}} + +### Setting a count followed by a distinct + +Note that setting a [count](https://developer.hashicorp.com/terraform/language/meta-arguments/count) argument may result in duplicate IP addresses. +To prevent this, apply a [distinct](https://developer.hashicorp.com/terraform/language/functions/distinct) function to your list of IP addresses. + +{{ tffile (printf "examples/resources/%s/count_distinct.tf" .Name)}} + +{{ .SchemaMarkdown | trimspace }}