Skip to content

Commit 739b3c4

Browse files
authored
feat: add support for tags in endpoints (#61)
Using the updated api, we can now add tags to our endpoints (at the cost of a lot more api calls).
1 parent c18f27d commit 739b3c4

3 files changed

Lines changed: 158 additions & 8 deletions

File tree

docs/resources/endpoint.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Endpoint resource
2323
### Optional
2424

2525
- `label` (String) Label to decorate an endpoint with
26+
- `tags` (List of String) Tags to associate with the endpoint
2627

2728
### Read-Only
2829

internal/provider/endpoint_resource.go

Lines changed: 152 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"net/url"
23+
"strconv"
2324
"strings"
2425

2526
"github.com/circlefin/terraform-provider-quicknode/api/quicknode"
@@ -74,6 +75,7 @@ type EndpointResourceModel struct {
7475
Url types.String `tfsdk:"url"`
7576
Id types.String `tfsdk:"id"`
7677
Security types.Object `tfsdk:"security"`
78+
Tags types.List `tfsdk:"tags"`
7779
}
7880

7981
type EndpointResourceSecurityToken struct {
@@ -151,6 +153,11 @@ func (r *EndpointResource) Schema(ctx context.Context, req resource.SchemaReques
151153
objectplanmodifier.UseStateForUnknown(),
152154
},
153155
},
156+
"tags": schema.ListAttribute{
157+
ElementType: types.StringType,
158+
Optional: true,
159+
MarkdownDescription: "Tags to associate with the endpoint",
160+
},
154161
},
155162
}
156163
}
@@ -236,7 +243,6 @@ func (r *EndpointResource) Create(ctx context.Context, req resource.CreateReques
236243
Network: data.Network.ValueStringPointer(),
237244
},
238245
)
239-
240246
if err != nil {
241247
resp.Diagnostics.AddError(
242248
fmt.Sprintf("%s - Creating Endpoint", utils.ClientErrorSummary),
@@ -315,6 +321,36 @@ func (r *EndpointResource) Create(ctx context.Context, req resource.CreateReques
315321
}
316322
}
317323

324+
var tags []string
325+
data.Tags.ElementsAs(ctx, &tags, false)
326+
for _, tag := range tags {
327+
tagResp, err := r.client.CreateTagWithResponse(
328+
ctx,
329+
data.Id.ValueString(),
330+
quicknode.CreateTagJSONRequestBody{
331+
Label: &tag,
332+
},
333+
)
334+
if err != nil {
335+
resp.Diagnostics.AddError(
336+
fmt.Sprintf("%s - Creating Tag: %s", utils.ClientErrorSummary, tag),
337+
utils.BuildClientErrorMessage(err),
338+
)
339+
return
340+
} else if tagResp.StatusCode() != 200 {
341+
m, err := utils.BuildRequestErrorMessage(tagResp.Status(), tagResp.Body)
342+
if err != nil {
343+
resp.Diagnostics.AddWarning(fmt.Sprintf("%s - Creating Tag", utils.InternalErrorSummary), utils.BuildInternalErrorMessage(err))
344+
}
345+
346+
resp.Diagnostics.AddError(
347+
fmt.Sprintf("%s - Creating Tag: %s", utils.RequestErrorSummary, tag),
348+
m,
349+
)
350+
return
351+
}
352+
}
353+
318354
tflog.Trace(ctx, "created a resource")
319355
// Save data into Terraform state
320356
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -334,7 +370,6 @@ func (r *EndpointResource) Read(ctx context.Context, req resource.ReadRequest, r
334370
ctx,
335371
data.Id.ValueString(),
336372
)
337-
338373
if err != nil {
339374
resp.Diagnostics.AddError(
340375
fmt.Sprintf("%s - Reading Endpoint", utils.ClientErrorSummary),
@@ -389,6 +424,19 @@ func (r *EndpointResource) Read(ctx context.Context, req resource.ReadRequest, r
389424
data.Security = securityValueObject
390425
}
391426

427+
data.Tags = types.ListNull(types.StringType)
428+
if endpoint.Tags != nil && len(*endpoint.Tags) > 0 {
429+
var tags []string
430+
for _, tag := range *endpoint.Tags {
431+
if tag.Label != nil {
432+
tags = append(tags, *tag.Label)
433+
}
434+
}
435+
t, diags := types.ListValueFrom(ctx, types.StringType, tags)
436+
resp.Diagnostics.Append(diags...)
437+
data.Tags = t
438+
}
439+
392440
// Save updated data into Terraform state
393441
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
394442
}
@@ -412,7 +460,6 @@ func (r *EndpointResource) Update(ctx context.Context, req resource.UpdateReques
412460
Label: &l,
413461
},
414462
)
415-
416463
if err != nil {
417464
resp.Diagnostics.AddError(
418465
fmt.Sprintf("%s - Patching Endpoint", utils.ClientErrorSummary),
@@ -434,6 +481,108 @@ func (r *EndpointResource) Update(ctx context.Context, req resource.UpdateReques
434481
return
435482
}
436483

484+
// Fetch current endpoint state to get tag IDs
485+
currentEndpointResp, err := r.client.ShowEndpointWithResponse(ctx, data.Id.ValueString())
486+
if err != nil {
487+
resp.Diagnostics.AddError(
488+
fmt.Sprintf("%s - Reading Endpoint for Tags", utils.ClientErrorSummary),
489+
utils.BuildClientErrorMessage(err),
490+
)
491+
return
492+
}
493+
if currentEndpointResp.StatusCode() != 200 {
494+
m, err := utils.BuildRequestErrorMessage(currentEndpointResp.Status(), currentEndpointResp.Body)
495+
if err != nil {
496+
resp.Diagnostics.AddWarning(fmt.Sprintf("%s - Reading Endpoint for Tags", utils.InternalErrorSummary), utils.BuildInternalErrorMessage(err))
497+
}
498+
resp.Diagnostics.AddError(
499+
fmt.Sprintf("%s - Reading Endpoint for Tags", utils.RequestErrorSummary),
500+
m,
501+
)
502+
return
503+
}
504+
505+
currentTags := make(map[string]int)
506+
if currentEndpointResp.JSON200.Data.Tags != nil {
507+
for _, tag := range *currentEndpointResp.JSON200.Data.Tags {
508+
if tag.Label != nil && tag.TagId != nil {
509+
currentTags[*tag.Label] = *tag.TagId
510+
}
511+
}
512+
}
513+
514+
var planTags []string
515+
resp.Diagnostics.Append(data.Tags.ElementsAs(ctx, &planTags, false)...)
516+
if resp.Diagnostics.HasError() {
517+
return
518+
}
519+
520+
// Create map of plan tags for easy lookup
521+
planTagsMap := make(map[string]bool)
522+
for _, tag := range planTags {
523+
planTagsMap[tag] = true
524+
}
525+
526+
// Add new tags
527+
for _, tag := range planTags {
528+
if _, exists := currentTags[tag]; !exists {
529+
tagResp, err := r.client.CreateTagWithResponse(
530+
ctx,
531+
data.Id.ValueString(),
532+
quicknode.CreateTagJSONRequestBody{
533+
Label: &tag,
534+
},
535+
)
536+
if err != nil {
537+
resp.Diagnostics.AddError(
538+
fmt.Sprintf("%s - Creating Tag", utils.ClientErrorSummary),
539+
utils.BuildClientErrorMessage(err),
540+
)
541+
return
542+
}
543+
if tagResp.StatusCode() != 200 {
544+
m, err := utils.BuildRequestErrorMessage(tagResp.Status(), tagResp.Body)
545+
if err != nil {
546+
resp.Diagnostics.AddWarning(fmt.Sprintf("%s - Creating Tag", utils.InternalErrorSummary), utils.BuildInternalErrorMessage(err))
547+
}
548+
resp.Diagnostics.AddError(
549+
fmt.Sprintf("%s - Creating Tag", utils.RequestErrorSummary),
550+
m,
551+
)
552+
return
553+
}
554+
}
555+
}
556+
557+
// Remove deleted tags
558+
for label, id := range currentTags {
559+
if _, exists := planTagsMap[label]; !exists {
560+
delResp, err := r.client.DeleteTagWithResponse(
561+
ctx,
562+
data.Id.ValueString(),
563+
strconv.Itoa(id),
564+
)
565+
if err != nil {
566+
resp.Diagnostics.AddError(
567+
fmt.Sprintf("%s - Deleting Tag", utils.ClientErrorSummary),
568+
utils.BuildClientErrorMessage(err),
569+
)
570+
return
571+
}
572+
if delResp.StatusCode() != 200 {
573+
m, err := utils.BuildRequestErrorMessage(delResp.Status(), delResp.Body)
574+
if err != nil {
575+
resp.Diagnostics.AddWarning(fmt.Sprintf("%s - Deleting Tag", utils.InternalErrorSummary), utils.BuildInternalErrorMessage(err))
576+
}
577+
resp.Diagnostics.AddError(
578+
fmt.Sprintf("%s - Deleting Tag", utils.RequestErrorSummary),
579+
m,
580+
)
581+
return
582+
}
583+
}
584+
}
585+
437586
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
438587
}
439588

@@ -451,7 +600,6 @@ func (r *EndpointResource) Delete(ctx context.Context, req resource.DeleteReques
451600
ctx,
452601
data.Id.ValueString(),
453602
)
454-
455603
if err != nil {
456604
resp.Diagnostics.AddError(
457605
fmt.Sprintf("%s - Deleting Endpoint", utils.ClientErrorSummary),

internal/provider/endpoint_resource_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func TestAccMinimalQuicknodeEndpointResource(t *testing.T) {
3636
Steps: []resource.TestStep{
3737
// Create and Read testing
3838
{
39-
Config: testAccQuickNodeResource(rName, "created-by-terraform"),
39+
Config: testAccQuickNodeResource(rName, "created-by-terraform", "tag1", "tag2"),
4040
Check: resource.ComposeAggregateTestCheckFunc(
4141
resource.TestCheckResourceAttrSet("quicknode_endpoint.main", "id"),
4242
),
@@ -49,7 +49,7 @@ func TestAccMinimalQuicknodeEndpointResource(t *testing.T) {
4949
},
5050
// Update and Read testing
5151
{
52-
Config: testAccQuickNodeResource(rName, "updated-by-terraform"),
52+
Config: testAccQuickNodeResource(rName, "updated-by-terraform", "tag1", "tag3"),
5353
Check: resource.ComposeAggregateTestCheckFunc(),
5454
},
5555
// Delete testing automatically occurs in TestCase
@@ -75,11 +75,12 @@ func TestAccMinimalQuicknodeEndpointResource(t *testing.T) {
7575
})
7676
}
7777

78-
func testAccQuickNodeResource(name string, label string) string {
78+
func testAccQuickNodeResource(name, label, tag1, tag2 string) string {
7979
return providerConfig + fmt.Sprintf(`
8080
resource "quicknode_endpoint" "main" {
8181
network = "mainnet"
8282
chain = "eth"
8383
label = "%s-%s"
84-
}`, name, label)
84+
tags = [%q, %q]
85+
}`, name, label, tag1, tag2)
8586
}

0 commit comments

Comments
 (0)