Skip to content

Commit 65151a1

Browse files
authored
Merge pull request #90 from axiomhq/axm-11643-support-edge-based-dataset-creation-in-terraform
Support edge deployment for dataset resource
2 parents 3cf79c3 + 0a751f9 commit 65151a1

7 files changed

Lines changed: 305 additions & 53 deletions

File tree

axiom/data_source_dataset.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,11 @@ func (d *DatasetDataSource) Read(ctx context.Context, req datasource.ReadRequest
7171
return
7272
}
7373

74-
resp.Diagnostics.Append(resp.State.Set(ctx, flattenDataset(ds))...)
74+
state, err := flattenDatasetWithOrgDefault(ctx, d.client, ds)
75+
if err != nil {
76+
resp.Diagnostics.AddWarning("Unable to resolve default edge deployment", err.Error())
77+
state = flattenDataset(ds, "")
78+
}
79+
80+
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
7581
}

axiom/resource_dataset.go

Lines changed: 113 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type DatasetResourceModel struct {
4444
Name types.String `tfsdk:"name"`
4545
Kind types.String `tfsdk:"kind"`
4646
Description types.String `tfsdk:"description"`
47+
EdgeDeployment types.String `tfsdk:"edge_deployment"`
4748
ID types.String `tfsdk:"id"`
4849
UseRetentionPeriod types.Bool `tfsdk:"use_retention_period"`
4950
RetentionDays types.Int64 `tfsdk:"retention_days"`
@@ -90,6 +91,14 @@ func (r *DatasetResource) Schema(_ context.Context, _ resource.SchemaRequest, re
9091
stringplanmodifier.UseStateForUnknown(),
9192
},
9293
},
94+
"edge_deployment": schema.StringAttribute{
95+
Optional: true,
96+
Computed: true,
97+
MarkdownDescription: "Edge deployment for the dataset (for example, 'cloud.eu-central-1.aws')",
98+
PlanModifiers: []planmodifier.String{
99+
stringplanmodifier.UseStateForUnknown(),
100+
},
101+
},
93102
"id": schema.StringAttribute{
94103
Computed: true,
95104
MarkdownDescription: "Dataset identifier",
@@ -170,20 +179,20 @@ func (r *DatasetResource) Create(ctx context.Context, req resource.CreateRequest
170179
return
171180
}
172181

173-
ds, err := r.client.Datasets.Create(ctx, axiom.DatasetCreateRequest{
174-
Name: plan.Name.ValueString(),
175-
Kind: plan.Kind.ValueString(),
176-
Description: plan.Description.ValueString(),
177-
UseRetentionPeriod: plan.UseRetentionPeriod.ValueBool(),
178-
RetentionDays: int(plan.RetentionDays.ValueInt64()),
179-
})
182+
ds, err := r.client.Datasets.Create(ctx, datasetCreateRequestFromPlan(plan))
180183
if err != nil {
181184
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create dataset, got error: %s", err))
182185
return
183186
}
184187

188+
state, err := flattenDatasetWithOrgDefault(ctx, r.client, ds)
189+
if err != nil {
190+
resp.Diagnostics.AddWarning("Unable to resolve default edge deployment", err.Error())
191+
state = flattenDataset(ds, "")
192+
}
193+
185194
// Set state immediately after creation to avoid orphaned resources
186-
resp.Diagnostics.Append(resp.State.Set(ctx, flattenDataset(ds))...)
195+
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
187196
if resp.Diagnostics.HasError() {
188197
return
189198
}
@@ -203,7 +212,14 @@ func (r *DatasetResource) Create(ctx context.Context, req resource.CreateRequest
203212
}
204213

205214
ds.MapFields = resMapFields
206-
resp.Diagnostics.Append(resp.State.Set(ctx, flattenDataset(ds))...)
215+
216+
state, err := flattenDatasetWithOrgDefault(ctx, r.client, ds)
217+
if err != nil {
218+
resp.Diagnostics.AddWarning("Unable to resolve default edge deployment", err.Error())
219+
state = flattenDataset(ds, "")
220+
}
221+
222+
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
207223
}
208224
}
209225

@@ -229,7 +245,13 @@ func (r *DatasetResource) Read(ctx context.Context, req resource.ReadRequest, re
229245
return
230246
}
231247

232-
resp.Diagnostics.Append(resp.State.Set(ctx, flattenDataset(ds))...)
248+
state, err := flattenDatasetWithOrgDefault(ctx, r.client, ds)
249+
if err != nil {
250+
resp.Diagnostics.AddWarning("Unable to resolve default edge deployment", err.Error())
251+
state = flattenDataset(ds, "")
252+
}
253+
254+
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
233255
}
234256

235257
func (r *DatasetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
@@ -260,8 +282,14 @@ func (r *DatasetResource) Update(ctx context.Context, req resource.UpdateRequest
260282
return
261283
}
262284

285+
state, err := flattenDatasetWithOrgDefault(ctx, r.client, ds)
286+
if err != nil {
287+
resp.Diagnostics.AddWarning("Unable to resolve default edge deployment", err.Error())
288+
state = flattenDataset(ds, "")
289+
}
290+
263291
// Set state immediately after update to preserve changes
264-
resp.Diagnostics.Append(resp.State.Set(ctx, flattenDataset(ds))...)
292+
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
265293
if resp.Diagnostics.HasError() {
266294
return
267295
}
@@ -281,7 +309,14 @@ func (r *DatasetResource) Update(ctx context.Context, req resource.UpdateRequest
281309
}
282310

283311
ds.MapFields = resMapFields
284-
resp.Diagnostics.Append(resp.State.Set(ctx, flattenDataset(ds))...)
312+
313+
state, err := flattenDatasetWithOrgDefault(ctx, r.client, ds)
314+
if err != nil {
315+
resp.Diagnostics.AddWarning("Unable to resolve default edge deployment", err.Error())
316+
state = flattenDataset(ds, "")
317+
}
318+
319+
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
285320
}
286321
}
287322

@@ -303,13 +338,20 @@ func (r *DatasetResource) ImportState(ctx context.Context, req resource.ImportSt
303338
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
304339
}
305340

306-
func flattenDataset(dataset *axiom.Dataset) DatasetResourceModel {
341+
func flattenDataset(dataset *axiom.Dataset, defaultEdgeDeployment string) DatasetResourceModel {
307342
var description types.String
343+
var edgeDeployment types.String
308344

309345
if dataset.Description != "" {
310346
description = types.StringValue(dataset.Description)
311347
}
312348

349+
if dataset.EdgeDeployment != "" {
350+
edgeDeployment = types.StringValue(dataset.EdgeDeployment)
351+
} else if defaultEdgeDeployment != "" {
352+
edgeDeployment = types.StringValue(defaultEdgeDeployment)
353+
}
354+
313355
mapFields := make([]attr.Value, 0, len(dataset.MapFields))
314356
for _, fieldName := range dataset.MapFields {
315357
mapFields = append(mapFields, types.StringValue(fieldName))
@@ -320,8 +362,66 @@ func flattenDataset(dataset *axiom.Dataset) DatasetResourceModel {
320362
Name: types.StringValue(dataset.Name),
321363
Kind: types.StringValue(dataset.Kind),
322364
Description: description,
365+
EdgeDeployment: edgeDeployment,
323366
UseRetentionPeriod: types.BoolValue(dataset.UseRetentionPeriod),
324367
RetentionDays: types.Int64Value(int64(dataset.RetentionDays)),
325368
MapFields: types.ListValueMust(types.StringType, mapFields),
326369
}
327370
}
371+
372+
func datasetCreateRequestFromPlan(plan DatasetResourceModel) axiom.DatasetCreateRequest {
373+
return axiom.DatasetCreateRequest{
374+
Name: plan.Name.ValueString(),
375+
Kind: plan.Kind.ValueString(),
376+
Description: plan.Description.ValueString(),
377+
EdgeDeployment: edgeDeploymentValue(plan.EdgeDeployment),
378+
UseRetentionPeriod: plan.UseRetentionPeriod.ValueBool(),
379+
RetentionDays: int(plan.RetentionDays.ValueInt64()),
380+
}
381+
}
382+
383+
func edgeDeploymentValue(edgeDeployment types.String) string {
384+
if edgeDeployment.IsNull() || edgeDeployment.IsUnknown() {
385+
return ""
386+
}
387+
388+
return edgeDeployment.ValueString()
389+
}
390+
391+
func flattenDatasetWithOrgDefault(ctx context.Context, client *axiom.Client, dataset *axiom.Dataset) (DatasetResourceModel, error) {
392+
if dataset.EdgeDeployment != "" {
393+
return flattenDataset(dataset, ""), nil
394+
}
395+
396+
defaultEdgeDeployment, err := fetchDefaultEdgeDeployment(ctx, client)
397+
if err != nil {
398+
return DatasetResourceModel{}, err
399+
}
400+
401+
return flattenDataset(dataset, defaultEdgeDeployment), nil
402+
}
403+
404+
func fetchDefaultEdgeDeployment(ctx context.Context, client *axiom.Client) (string, error) {
405+
if client == nil {
406+
return "", fmt.Errorf("client is not set")
407+
}
408+
409+
organizations, err := client.Organizations.List(ctx)
410+
if err != nil {
411+
return "", fmt.Errorf("could not fetch organizations while resolving edge deployment: %w", err)
412+
}
413+
414+
return selectDefaultEdgeDeployment(organizations), nil
415+
}
416+
417+
func selectDefaultEdgeDeployment(organizations []*axiom.Organization) string {
418+
for _, organization := range organizations {
419+
if organization == nil || organization.DefaultEdgeDeployment == "" {
420+
continue
421+
}
422+
423+
return organization.DefaultEdgeDeployment
424+
}
425+
426+
return ""
427+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package axiom
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform-plugin-framework/types"
7+
"github.com/stretchr/testify/assert"
8+
9+
"github.com/axiomhq/axiom-go/axiom"
10+
)
11+
12+
func TestDatasetCreateRequestFromPlan(t *testing.T) {
13+
t.Parallel()
14+
15+
request := datasetCreateRequestFromPlan(DatasetResourceModel{
16+
Name: types.StringValue("dataset-a"),
17+
Kind: types.StringValue("axiom:events:v1"),
18+
Description: types.StringValue("dataset description"),
19+
EdgeDeployment: types.StringValue("cloud.eu-central-1.aws"),
20+
UseRetentionPeriod: types.BoolValue(true),
21+
RetentionDays: types.Int64Value(30),
22+
})
23+
24+
assert.Equal(t, axiom.DatasetCreateRequest{
25+
Name: "dataset-a",
26+
Kind: "axiom:events:v1",
27+
Description: "dataset description",
28+
EdgeDeployment: "cloud.eu-central-1.aws",
29+
UseRetentionPeriod: true,
30+
RetentionDays: 30,
31+
}, request)
32+
}
33+
34+
func TestDatasetCreateRequestFromPlan_NullEdgeDeployment(t *testing.T) {
35+
t.Parallel()
36+
37+
request := datasetCreateRequestFromPlan(DatasetResourceModel{
38+
Name: types.StringValue("dataset-a"),
39+
Kind: types.StringValue("axiom:events:v1"),
40+
EdgeDeployment: types.StringNull(),
41+
})
42+
43+
assert.Empty(t, request.EdgeDeployment)
44+
}
45+
46+
func TestFlattenDataset_EdgeDeployment(t *testing.T) {
47+
t.Parallel()
48+
49+
t.Run("sets edge deployment in state", func(t *testing.T) {
50+
t.Parallel()
51+
52+
state := flattenDataset(&axiom.Dataset{
53+
ID: "dataset-a",
54+
Name: "dataset-a",
55+
Kind: "axiom:events:v1",
56+
EdgeDeployment: "cloud.eu-central-1.aws",
57+
}, "")
58+
59+
assert.Equal(t, "cloud.eu-central-1.aws", state.EdgeDeployment.ValueString())
60+
})
61+
62+
t.Run("uses org default edge deployment when dataset omits it", func(t *testing.T) {
63+
t.Parallel()
64+
65+
state := flattenDataset(&axiom.Dataset{
66+
ID: "dataset-a",
67+
Name: "dataset-a",
68+
Kind: "axiom:events:v1",
69+
}, "cloud.us-east-1.aws")
70+
71+
assert.Equal(t, "cloud.us-east-1.aws", state.EdgeDeployment.ValueString())
72+
})
73+
74+
t.Run("keeps edge deployment null when absent", func(t *testing.T) {
75+
t.Parallel()
76+
77+
state := flattenDataset(&axiom.Dataset{
78+
ID: "dataset-a",
79+
Name: "dataset-a",
80+
Kind: "axiom:events:v1",
81+
}, "")
82+
83+
assert.True(t, state.EdgeDeployment.IsNull())
84+
})
85+
}
86+
87+
func TestSelectDefaultEdgeDeployment(t *testing.T) {
88+
t.Parallel()
89+
90+
t.Run("returns first default edge deployment", func(t *testing.T) {
91+
t.Parallel()
92+
93+
selected := selectDefaultEdgeDeployment([]*axiom.Organization{
94+
{DefaultEdgeDeployment: "cloud.eu-central-1.aws"},
95+
{DefaultEdgeDeployment: "cloud.us-east-1.aws"},
96+
})
97+
98+
assert.Equal(t, "cloud.eu-central-1.aws", selected)
99+
})
100+
101+
t.Run("skips nil and empty organizations", func(t *testing.T) {
102+
t.Parallel()
103+
104+
selected := selectDefaultEdgeDeployment([]*axiom.Organization{
105+
nil,
106+
{},
107+
{DefaultEdgeDeployment: "cloud.us-east-1.aws"},
108+
})
109+
110+
assert.Equal(t, "cloud.us-east-1.aws", selected)
111+
})
112+
113+
t.Run("returns empty when none configured", func(t *testing.T) {
114+
t.Parallel()
115+
116+
selected := selectDefaultEdgeDeployment([]*axiom.Organization{
117+
{},
118+
})
119+
120+
assert.Empty(t, selected)
121+
})
122+
}
123+
124+
func TestEdgeDeploymentValue(t *testing.T) {
125+
t.Parallel()
126+
127+
t.Run("returns empty string for null", func(t *testing.T) {
128+
t.Parallel()
129+
130+
assert.Empty(t, edgeDeploymentValue(types.StringNull()))
131+
})
132+
133+
t.Run("returns empty string for unknown", func(t *testing.T) {
134+
t.Parallel()
135+
136+
assert.Empty(t, edgeDeploymentValue(types.StringUnknown()))
137+
})
138+
139+
t.Run("returns value when known", func(t *testing.T) {
140+
t.Parallel()
141+
142+
assert.Equal(t, "cloud.eu-central-1.aws", edgeDeploymentValue(types.StringValue("cloud.eu-central-1.aws")))
143+
})
144+
}

docs/data-sources/dataset.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ description: |-
2222
### Read-Only
2323

2424
- `description` (String) Dataset description
25+
- `edge_deployment` (String) Edge deployment for the dataset (for example, 'cloud.eu-central-1.aws')
2526
- `kind` (String) Dataset kind. Must be one of: 'axiom:events:v1', 'otel:metrics:v1', 'otel:traces:v1', 'otel:logs:v1'. Defaults to 'axiom:events:v1'
2627
- `map_fields` (List of String) Map fields for the dataset
2728
- `name` (String) Dataset name

docs/resources/dataset.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ description: |-
2222
### Optional
2323

2424
- `description` (String) Dataset description
25+
- `edge_deployment` (String) Edge deployment for the dataset (for example, 'cloud.eu-central-1.aws')
2526
- `kind` (String) Dataset kind. Must be one of: 'axiom:events:v1', 'otel:metrics:v1', 'otel:traces:v1', 'otel:logs:v1'. Defaults to 'axiom:events:v1'
2627
- `map_fields` (List of String) Map fields for the dataset
2728
- `retention_days` (Number) Retention days for the dataset

0 commit comments

Comments
 (0)