-
Notifications
You must be signed in to change notification settings - Fork 120
TPT-4297: terraform: Implement linode_tag Data Source with Reserved IPv4 Support #2330
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
Merged
mgwoj
merged 9 commits into
linode:proj/reserved-ips
from
mgwoj:feature/TPT-4297-terraform-update-linode_tag-resource-and-data-source-for-reserved-ip-for-ipv4
May 12, 2026
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
fe8f6cf
TPT-4297: terraform: Implement linode_tag Data Source with Reserved Iβ¦
mgwoj 2eed1c2
TPT-4297: terraform: Implement linode_tag Data Source with Reserved Iβ¦
mgwoj 10be5cd
TPT-4297: terraform: Implement linode_tag Data Source with Reserved Iβ¦
mgwoj 22f94f3
Merge branch 'dev' into feature/TPT-4297-terraform-update-linode_tag-β¦
mgwoj 7e73cac
TPT-4297: terraform: Implement linode_tag Data Source with Reserved Iβ¦
mgwoj 8d7fadd
TPT-4297: terraform: Implement linode_tag Data Source with Reserved Iβ¦
mgwoj 97818a4
Merge remote-tracking branch 'upstream/proj/reserved-ips' into featurβ¦
mgwoj 2e5803f
TPT-4297: terraform: Implement linode_tag Data Source with Reserved Iβ¦
mgwoj ef479d2
Merge branch 'proj/reserved-ips' into feature/TPT-4297-terraform-updaβ¦
mgwoj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| --- | ||
| page_title: "Linode: linode_tag" | ||
| description: |- | ||
| Provides details about a Linode Tag. | ||
| --- | ||
|
|
||
| # Data Source: linode\_tag | ||
|
|
||
| Provides information about a Linode Tag, including the objects associated with it. | ||
|
|
||
| For more information, see the [Linode APIv4 documentation](https://techdocs.akamai.com/linode-api/reference/get-tagged-objects). | ||
|
|
||
| ## Example Usage | ||
|
|
||
| ```hcl | ||
| data "linode_tag" "example" { | ||
| label = "my-tag" | ||
| } | ||
| ``` | ||
|
|
||
| ## Argument Reference | ||
|
|
||
| * `label` - (Required) The label of the tag to look up. | ||
|
|
||
| ## Attributes Reference | ||
|
|
||
| * `id` - The label of the tag. | ||
|
|
||
| * `objects` - A list of objects associated with this tag. Each object has the following attributes: | ||
|
|
||
| * `type` - The type of the tagged object (e.g. `linode`, `domain`, `volume`, `nodebalancer`, `reserved_ipv4_address`). | ||
|
|
||
| * `id` - The ID of the tagged object. For `reserved_ipv4_address` objects, this is the IP address string. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| //go:build integration || tag | ||
|
|
||
| package tag_test | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/hashicorp/terraform-plugin-testing/helper/acctest" | ||
| "github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
| "github.com/linode/linodego" | ||
| "github.com/linode/terraform-provider-linode/v3/linode/acceptance" | ||
| "github.com/linode/terraform-provider-linode/v3/linode/tag/tmpl" | ||
| ) | ||
|
|
||
| func init() { | ||
| resource.AddTestSweepers("linode_tag", &resource.Sweeper{ | ||
| Name: "linode_tag", | ||
| F: sweep, | ||
| }) | ||
| } | ||
|
|
||
| func sweep(prefix string) error { | ||
| client, err := acceptance.GetTestClient() | ||
| if err != nil { | ||
| return fmt.Errorf("Error getting client: %s", err) | ||
| } | ||
|
|
||
| tags, err := client.ListTags(context.Background(), nil) | ||
| if err != nil { | ||
| return fmt.Errorf("Error getting tags: %s", err) | ||
| } | ||
|
|
||
| for _, tag := range tags { | ||
| if !acceptance.ShouldSweep(prefix, tag.Label) { | ||
| continue | ||
| } | ||
| if err := client.DeleteTag(context.Background(), tag.Label); err != nil { | ||
| return fmt.Errorf("Error destroying tag %q during sweep: %s", tag.Label, err) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func TestAccDataSourceTag_basic(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| dsName := "data.linode_tag.test" | ||
| tagLabel := acctest.RandomWithPrefix("tf_test") | ||
|
|
||
| resource.Test(t, resource.TestCase{ | ||
| PreCheck: func() { acceptance.PreCheck(t) }, | ||
| ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, | ||
| Steps: []resource.TestStep{ | ||
| { | ||
| PreConfig: func() { | ||
| client, err := acceptance.GetTestClient() | ||
| if err != nil { | ||
| t.Fatalf("Error getting client: %s", err) | ||
| } | ||
| t.Cleanup(func() { | ||
| _ = client.DeleteTag(context.Background(), tagLabel) | ||
| }) | ||
| if _, err := client.CreateTag(context.Background(), linodego.TagCreateOptions{Label: tagLabel}); err != nil { | ||
| t.Fatalf("Error creating tag: %s", err) | ||
| } | ||
| }, | ||
| Config: tmpl.DataSource(t, tagLabel), | ||
| Check: resource.ComposeTestCheckFunc( | ||
| resource.TestCheckResourceAttr(dsName, "label", tagLabel), | ||
| resource.TestCheckResourceAttr(dsName, "id", tagLabel), | ||
| ), | ||
| }, | ||
| }, | ||
| }) | ||
| } | ||
|
|
||
| func TestAccDataSourceTag_reservedIP(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| dsName := "data.linode_tag.test" | ||
| tagLabel := acctest.RandomWithPrefix("tf_test") | ||
| var reservedIPAddress string | ||
|
|
||
| resource.Test(t, resource.TestCase{ | ||
| PreCheck: func() { acceptance.PreCheck(t) }, | ||
| ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, | ||
| Steps: []resource.TestStep{ | ||
| { | ||
| PreConfig: func() { | ||
| region, err := acceptance.GetRandomRegionWithCaps(nil, "core") | ||
| if err != nil { | ||
| t.Fatalf("Error finding region: %s", err) | ||
| } | ||
| client, err := acceptance.GetTestClient() | ||
| if err != nil { | ||
| t.Fatalf("Error getting client: %s", err) | ||
| } | ||
| ip, err := client.AllocateReserveIP(context.Background(), linodego.AllocateReserveIPOptions{ | ||
| Type: "ipv4", | ||
| Public: true, | ||
| Reserved: true, | ||
| Region: region, | ||
| }) | ||
| if err != nil { | ||
| t.Fatalf("Error reserving IP: %s", err) | ||
| } | ||
| reservedIPAddress = ip.Address | ||
| t.Cleanup(func() { | ||
| _ = client.DeleteReservedIPAddress(context.Background(), ip.Address) | ||
| _ = client.DeleteTag(context.Background(), tagLabel) | ||
| }) | ||
| if _, err := client.CreateTag(context.Background(), linodego.TagCreateOptions{ | ||
| Label: tagLabel, | ||
| ReservedIPv4Addresses: []string{ip.Address}, | ||
| }); err != nil { | ||
| t.Fatalf("Error creating tag: %s", err) | ||
| } | ||
|
mgwoj marked this conversation as resolved.
|
||
|
|
||
| // Poll until the reserved IP association is visible in the API | ||
| // before letting Terraform proceed (eventual consistency). | ||
| // Skip if the API environment does not support reserved_ipv4_addresses | ||
| // in POST /tags (feature may not be rolled out yet). | ||
| deadline := time.Now().Add(30 * time.Second) | ||
| visible := false | ||
| for time.Now().Before(deadline) { | ||
| objects, err := client.ListTaggedObjects(context.Background(), tagLabel, nil) | ||
| if err == nil && len(objects) > 0 { | ||
| visible = true | ||
| break | ||
| } | ||
| time.Sleep(2 * time.Second) | ||
| } | ||
| if !visible { | ||
| t.Skip("reserved_ipv4_addresses tag association not visible via API; skipping (feature may not be available in this environment)") | ||
| } | ||
| }, | ||
| Config: tmpl.DataSource(t, tagLabel), | ||
| Check: resource.ComposeTestCheckFunc( | ||
| resource.TestCheckResourceAttr(dsName, "label", tagLabel), | ||
| resource.TestCheckResourceAttr(dsName, "id", tagLabel), | ||
| resource.TestCheckResourceAttr(dsName, "objects.#", "1"), | ||
| resource.TestCheckResourceAttr(dsName, "objects.0.type", "reserved_ipv4_address"), | ||
| resource.TestCheckResourceAttrWith(dsName, "objects.0.id", func(val string) error { | ||
| if val != reservedIPAddress { | ||
| return fmt.Errorf("expected objects.0.id to be %q, got %q", reservedIPAddress, val) | ||
| } | ||
| return nil | ||
| }), | ||
| ), | ||
|
mgwoj marked this conversation as resolved.
|
||
| }, | ||
| }, | ||
| }) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package tag | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| "github.com/hashicorp/terraform-plugin-framework/datasource" | ||
| "github.com/hashicorp/terraform-plugin-framework/types" | ||
| "github.com/hashicorp/terraform-plugin-log/tflog" | ||
| "github.com/linode/linodego" | ||
| "github.com/linode/terraform-provider-linode/v3/linode/helper" | ||
| ) | ||
|
|
||
| func NewDataSource() datasource.DataSource { | ||
| return &DataSource{ | ||
| BaseDataSource: helper.NewBaseDataSource( | ||
| helper.BaseDataSourceConfig{ | ||
| Name: "linode_tag", | ||
| Schema: &frameworkDatasourceSchema, | ||
| }, | ||
| ), | ||
| } | ||
| } | ||
|
|
||
| type DataSource struct { | ||
| helper.BaseDataSource | ||
| } | ||
|
|
||
| func (d *DataSource) Read( | ||
| ctx context.Context, | ||
| req datasource.ReadRequest, | ||
| resp *datasource.ReadResponse, | ||
| ) { | ||
| tflog.Debug(ctx, "Read data."+d.Config.Name) | ||
|
|
||
| var data DataSourceModel | ||
|
|
||
| resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) | ||
| if resp.Diagnostics.HasError() { | ||
| return | ||
| } | ||
|
|
||
| label := data.Label.ValueString() | ||
| ctx = tflog.SetField(ctx, "tag_label", label) | ||
|
|
||
| objects, err := d.Meta.Client.ListTaggedObjects(ctx, label, nil) | ||
| if err != nil { | ||
| if linodego.IsNotFound(err) { | ||
| resp.Diagnostics.AddError( | ||
| fmt.Sprintf("Tag %q not found", label), | ||
| err.Error(), | ||
| ) | ||
| return | ||
| } | ||
| resp.Diagnostics.AddError( | ||
| fmt.Sprintf("Failed to list objects for Tag %q", label), | ||
| err.Error(), | ||
| ) | ||
| return | ||
| } | ||
|
|
||
| data.ID = types.StringValue(label) | ||
| data.FlattenTaggedObjects(ctx, objects, &resp.Diagnostics) | ||
| if resp.Diagnostics.HasError() { | ||
| return | ||
| } | ||
|
|
||
| resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| package tag | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "strconv" | ||
|
|
||
| "github.com/hashicorp/terraform-plugin-framework/diag" | ||
| "github.com/hashicorp/terraform-plugin-framework/types" | ||
| "github.com/linode/linodego" | ||
| ) | ||
|
|
||
| type DataSourceModel struct { | ||
| ID types.String `tfsdk:"id"` | ||
| Label types.String `tfsdk:"label"` | ||
| Objects types.List `tfsdk:"objects"` | ||
| } | ||
|
|
||
| type TaggedObjectModel struct { | ||
| Type types.String `tfsdk:"type"` | ||
| ID types.String `tfsdk:"id"` | ||
| } | ||
|
|
||
| func (data *DataSourceModel) FlattenTaggedObjects( | ||
| ctx context.Context, | ||
| objects linodego.TaggedObjectList, | ||
| diags *diag.Diagnostics, | ||
| ) { | ||
| models := make([]TaggedObjectModel, 0, len(objects)) | ||
|
|
||
| for _, obj := range objects { | ||
| m := TaggedObjectModel{ | ||
| Type: types.StringValue(obj.Type), | ||
| } | ||
|
|
||
| switch obj.Type { | ||
| case "linode": | ||
| if inst, ok := obj.Data.(linodego.Instance); ok { | ||
| m.ID = types.StringValue(strconv.Itoa(inst.ID)) | ||
| } | ||
|
mgwoj marked this conversation as resolved.
|
||
| case "domain": | ||
| if d, ok := obj.Data.(linodego.Domain); ok { | ||
| m.ID = types.StringValue(strconv.Itoa(d.ID)) | ||
| } | ||
| case "volume": | ||
| if v, ok := obj.Data.(linodego.Volume); ok { | ||
| m.ID = types.StringValue(strconv.Itoa(v.ID)) | ||
| } | ||
| case "nodebalancer": | ||
| if n, ok := obj.Data.(linodego.NodeBalancer); ok { | ||
| m.ID = types.StringValue(strconv.Itoa(n.ID)) | ||
| } | ||
| case "reserved_ipv4_address": | ||
| if ip, ok := obj.Data.(linodego.InstanceIP); ok { | ||
| m.ID = types.StringValue(ip.Address) | ||
| } | ||
| default: | ||
| diags.AddWarning("Unknown tagged object type", | ||
| fmt.Sprintf("tagged object type %q is not recognised; ID will be empty", obj.Type)) | ||
| m.ID = types.StringValue("") | ||
| } | ||
|
mgwoj marked this conversation as resolved.
|
||
|
|
||
| models = append(models, m) | ||
| } | ||
|
|
||
| listVal, d := types.ListValueFrom( | ||
| ctx, | ||
| types.ObjectType{AttrTypes: tagObjectAttrTypes}, | ||
| models, | ||
| ) | ||
| diags.Append(d...) | ||
| if !d.HasError() { | ||
| data.Objects = listVal | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package tag | ||
|
|
||
| import ( | ||
| "github.com/hashicorp/terraform-plugin-framework/attr" | ||
| "github.com/hashicorp/terraform-plugin-framework/datasource/schema" | ||
| "github.com/hashicorp/terraform-plugin-framework/types" | ||
| ) | ||
|
|
||
| var tagObjectAttrTypes = map[string]attr.Type{ | ||
| "type": types.StringType, | ||
| "id": types.StringType, | ||
| } | ||
|
|
||
| var frameworkDatasourceSchema = schema.Schema{ | ||
| Attributes: map[string]schema.Attribute{ | ||
| "id": schema.StringAttribute{ | ||
| Description: "The label of this Tag.", | ||
| Computed: true, | ||
| }, | ||
| "label": schema.StringAttribute{ | ||
| Description: "A label used to categorize resources. For display purposes only.", | ||
| Required: true, | ||
| }, | ||
| "objects": schema.ListNestedAttribute{ | ||
| Description: "The objects associated with this tag.", | ||
| Computed: true, | ||
| NestedObject: schema.NestedAttributeObject{ | ||
| Attributes: map[string]schema.Attribute{ | ||
| "type": schema.StringAttribute{ | ||
| Description: "The type of the tagged object " + | ||
| "(e.g. linode, domain, volume, nodebalancer, reserved_ipv4_address).", | ||
| Computed: true, | ||
| }, | ||
| "id": schema.StringAttribute{ | ||
| Description: "The ID (or address for reserved_ipv4_address) of the tagged object.", | ||
| Computed: true, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.