Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement random_ip resource #519

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e250bc5
feat: implement `random_ip` resource
bschaatsbergen Jan 20, 2024
9a8afd6
chore: register `NewIPResource` to the provider
bschaatsbergen Jan 20, 2024
0665205
chore: parse given `cidr_range`
bschaatsbergen Jan 20, 2024
1b82d3c
chore: implement getRandomIP func
bschaatsbergen Jan 21, 2024
ea7baf3
chore: create first test for the `random_ip` resource
bschaatsbergen Jan 21, 2024
9ac350a
chore: resource type should have a `_` in the name
bschaatsbergen Jan 21, 2024
b526c42
chore: fix an issue in the netmask check
bschaatsbergen Jan 21, 2024
1948ac7
chore: add ipv6 test for `random_ip`
bschaatsbergen Jan 21, 2024
bef50ad
chore: require replace if `address_type` or `cidr_range` are changed
bschaatsbergen Jan 21, 2024
b4adc21
chore: add keepers tests
bschaatsbergen Jan 21, 2024
96a364a
chore: assert whether the length of the netmask (in byte form) is equ…
bschaatsbergen Jan 21, 2024
1368a04
chore: improve error message
bschaatsbergen Jan 21, 2024
688a9db
chore: change some tests to use IPv6
bschaatsbergen Jan 21, 2024
649dc2c
chore: generate documentation and add examples
bschaatsbergen Jan 21, 2024
ff1cf55
chore: improve documentation by adding links
bschaatsbergen Jan 21, 2024
4ee00e7
chore: add edge case tests for CIDR ranges
bschaatsbergen Jan 21, 2024
f48c718
chore: remove `testCheckAttributeValuesDiffer`
bschaatsbergen Jan 21, 2024
3760ad9
chore: put `IP` in front of "addresses"
bschaatsbergen Jan 21, 2024
86e08f9
Merge branch 'main' into f/add-random-ip-resource
bschaatsbergen Jan 22, 2024
51a39a2
chore: update README
bschaatsbergen Jan 22, 2024
8b8aca2
Merge branch 'main' into f/add-random-ip-resource
bschaatsbergen Jan 25, 2024
7cad1de
Merge branch 'main' into f/add-random-ip-resource
bschaatsbergen Jan 29, 2024
f4618b2
Merge branch 'main' into f/add-random-ip-resource
bschaatsbergen Feb 6, 2024
4a3f374
Merge branch 'main' into f/add-random-ip-resource
bschaatsbergen Feb 11, 2024
fb73ba0
Merge branch 'main' into f/add-random-ip-resource
bschaatsbergen Feb 13, 2024
dbc9e38
Merge branch 'main' into f/add-random-ip-resource
bschaatsbergen Aug 9, 2024
415eab5
chore: remove `address_type` and make `cidr_range` optional.
bschaatsbergen Aug 9, 2024
582f62e
chore: reference the right example tfconfig
bschaatsbergen Aug 9, 2024
2e02ea0
chore: fix typo in resource type
bschaatsbergen Aug 9, 2024
ee6cc2b
docs: improve wording
bschaatsbergen Aug 9, 2024
f0d6dc1
Merge branch 'main' into f/add-random-ip-resource
bschaatsbergen Aug 23, 2024
314e650
Merge branch 'main' into f/add-random-ip-resource
bschaatsbergen Oct 25, 2024
aecf658
Merge branch 'main' into f/add-random-ip-resource
bschaatsbergen Nov 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
76 changes: 76 additions & 0 deletions docs/resources/ip.md
Original file line number Diff line number Diff line change
@@ -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 generated by tfplugindocs -->
## 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.
1 change: 1 addition & 0 deletions examples/resources/random_ip/basic.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
resource "random_ip" "example" {}
8 changes: 8 additions & 0 deletions examples/resources/random_ip/count.tf
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 8 additions & 0 deletions examples/resources/random_ip/count_distinct.tf
Original file line number Diff line number Diff line change
@@ -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)
}
3 changes: 3 additions & 0 deletions examples/resources/random_ip/ipv4.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resource "random_ip" "example" {
cidr_range = "10.1.0.0/16"
}
3 changes: 3 additions & 0 deletions examples/resources/random_ip/ipv6.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resource "random_ip" "example" {
cidr_range = "2001:db8::/32"
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func (p *randomProvider) Resources(context.Context) []func() resource.Resource {
NewShuffleResource,
NewStringResource,
NewUuidResource,
NewIPResource,
}
}

Expand Down
184 changes: 184 additions & 0 deletions internal/provider/random_ip.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading