Skip to content

Commit 16b746c

Browse files
authored
Merge pull request #97 from axiomhq/reject-map-fields-on-metrics-datasets
dataset: reject `map_fields` on `otel:metrics:v1` datasets
2 parents 191d514 + 5b55a35 commit 16b746c

5 files changed

Lines changed: 160 additions & 3 deletions

File tree

axiom/provider_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,72 @@ func TestAccAxiomResources_dataset_map_fields(t *testing.T) {
747747
})
748748
}
749749

750+
func TestAccAxiomResources_dataset_map_fields_metrics_kind(t *testing.T) {
751+
client, err := ax.NewClient()
752+
assert.NoError(t, err)
753+
754+
datasetName := "new-dataset-metrics-mapfields-" + uuid.NewString()
755+
756+
resource.Test(t, resource.TestCase{
757+
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
758+
"axiom": providerserver.NewProtocol6WithError(NewAxiomProvider()),
759+
},
760+
CheckDestroy: testAccCheckAxiomResourcesDestroyed(client),
761+
Steps: []resource.TestStep{
762+
// map-fields are not supported for metrics datasets
763+
{
764+
Config: `
765+
provider "axiom" {
766+
api_token = "` + os.Getenv("AXIOM_TOKEN") + `"
767+
base_url = "` + os.Getenv("AXIOM_URL") + `"
768+
}
769+
770+
resource "axiom_dataset" "test" {
771+
name = "` + datasetName + `"
772+
kind = "otel:metrics:v1"
773+
map_fields = ["field1"]
774+
}
775+
`,
776+
ExpectError: regexp.MustCompile(`Error:\sInvalid\sAttribute\sCombination`),
777+
},
778+
// an empty list is still an explicit map-fields configuration
779+
{
780+
Config: `
781+
provider "axiom" {
782+
api_token = "` + os.Getenv("AXIOM_TOKEN") + `"
783+
base_url = "` + os.Getenv("AXIOM_URL") + `"
784+
}
785+
786+
resource "axiom_dataset" "test" {
787+
name = "` + datasetName + `"
788+
kind = "otel:metrics:v1"
789+
map_fields = []
790+
}
791+
`,
792+
ExpectError: regexp.MustCompile(`Error:\sInvalid\sAttribute\sCombination`),
793+
},
794+
// omitting map-fields is fine
795+
{
796+
Config: `
797+
provider "axiom" {
798+
api_token = "` + os.Getenv("AXIOM_TOKEN") + `"
799+
base_url = "` + os.Getenv("AXIOM_URL") + `"
800+
}
801+
802+
resource "axiom_dataset" "test" {
803+
name = "` + datasetName + `"
804+
kind = "otel:metrics:v1"
805+
}
806+
`,
807+
Check: resource.ComposeTestCheckFunc(
808+
resource.TestCheckResourceAttr("axiom_dataset.test", "kind", "otel:metrics:v1"),
809+
resource.TestCheckResourceAttr("axiom_dataset.test", "map_fields.#", "0"),
810+
),
811+
},
812+
},
813+
})
814+
}
815+
750816
func TestAccAxiomResources_dataset_map_kind(t *testing.T) {
751817
client, err := ax.NewClient()
752818
assert.NoError(t, err)

axiom/resource_dataset.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,14 @@ import (
2424

2525
var validMapFieldNameRe = regexp.MustCompile("^[a-zA-Z0-9]+([a-zA-Z0-9_.-]*[a-zA-Z0-9]+)?$")
2626

27+
// metricsDatasetKind is the dataset kind that does not support map fields.
28+
const metricsDatasetKind = "otel:metrics:v1"
29+
2730
// Ensure provider defined types fully satisfy framework interfaces.
2831
var (
2932
_ resource.Resource = &DatasetResource{}
3033
_ resource.ResourceWithImportState = &DatasetResource{}
34+
_ validator.List = unsupportedForKindValidator{}
3135
)
3236

3337
func NewDatasetResource() resource.Resource {
@@ -125,7 +129,7 @@ func (r *DatasetResource) Schema(_ context.Context, _ resource.SchemaRequest, re
125129
"map_fields": schema.ListAttribute{
126130
Optional: true,
127131
Computed: true,
128-
MarkdownDescription: "Map fields for the dataset",
132+
MarkdownDescription: "Map fields for the dataset. Not supported for datasets of kind 'otel:metrics:v1'",
129133
ElementType: types.StringType,
130134
Validators: []validator.List{
131135
listvalidator.All(
@@ -136,13 +140,57 @@ func (r *DatasetResource) Schema(_ context.Context, _ resource.SchemaRequest, re
136140
),
137141
),
138142
listvalidator.UniqueValues(),
143+
unsupportedForKindValidator{kind: metricsDatasetKind},
139144
),
140145
},
141146
},
142147
},
143148
}
144149
}
145150

151+
// unsupportedForKindValidator rejects a list attribute that is set on a dataset of
152+
// an unsupported kind.
153+
type unsupportedForKindValidator struct {
154+
kind string
155+
}
156+
157+
func (v unsupportedForKindValidator) Description(_ context.Context) string {
158+
return fmt.Sprintf("must not be set when kind is %q", v.kind)
159+
}
160+
161+
func (v unsupportedForKindValidator) MarkdownDescription(ctx context.Context) string {
162+
return v.Description(ctx)
163+
}
164+
165+
func (v unsupportedForKindValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) {
166+
var kind types.String
167+
168+
resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path.Root("kind"), &kind)...)
169+
if resp.Diagnostics.HasError() {
170+
return
171+
}
172+
173+
if !attributeConflictsWithKind(kind, req.ConfigValue, v.kind) {
174+
return
175+
}
176+
177+
resp.Diagnostics.AddAttributeError(
178+
req.Path,
179+
"Invalid Attribute Combination",
180+
fmt.Sprintf("%s is not supported for datasets of kind %q, remove it from the configuration.", req.Path, v.kind),
181+
)
182+
}
183+
184+
// attributeConflictsWithKind reports whether the attribute is configured on a dataset
185+
// of the kind that does not support it.
186+
func attributeConflictsWithKind(kind types.String, value types.List, unsupportedKind string) bool {
187+
if kind.IsNull() || kind.IsUnknown() || kind.ValueString() != unsupportedKind {
188+
return false
189+
}
190+
191+
return !value.IsNull() && !value.IsUnknown()
192+
}
193+
146194
func (r *DatasetResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
147195
if req.ProviderData == nil {
148196
return

axiom/resource_dataset_unit_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package axiom
33
import (
44
"testing"
55

6+
"github.com/hashicorp/terraform-plugin-framework/attr"
67
"github.com/hashicorp/terraform-plugin-framework/types"
78
"github.com/stretchr/testify/assert"
89

@@ -121,6 +122,48 @@ func TestSelectDefaultEdgeDeployment(t *testing.T) {
121122
})
122123
}
123124

125+
func TestAttributeConflictsWithKind(t *testing.T) {
126+
t.Parallel()
127+
128+
mapFields := types.ListValueMust(types.StringType, []attr.Value{types.StringValue("field1")})
129+
130+
t.Run("conflicts when map fields are set on a metrics dataset", func(t *testing.T) {
131+
t.Parallel()
132+
133+
assert.True(t, attributeConflictsWithKind(types.StringValue("otel:metrics:v1"), mapFields, metricsDatasetKind))
134+
})
135+
136+
t.Run("conflicts when map fields are empty on a metrics dataset", func(t *testing.T) {
137+
t.Parallel()
138+
139+
empty := types.ListValueMust(types.StringType, []attr.Value{})
140+
141+
assert.True(t, attributeConflictsWithKind(types.StringValue("otel:metrics:v1"), empty, metricsDatasetKind))
142+
})
143+
144+
t.Run("does not conflict when map fields are absent on a metrics dataset", func(t *testing.T) {
145+
t.Parallel()
146+
147+
assert.False(t, attributeConflictsWithKind(types.StringValue("otel:metrics:v1"), types.ListNull(types.StringType), metricsDatasetKind))
148+
assert.False(t, attributeConflictsWithKind(types.StringValue("otel:metrics:v1"), types.ListUnknown(types.StringType), metricsDatasetKind))
149+
})
150+
151+
t.Run("does not conflict for other kinds", func(t *testing.T) {
152+
t.Parallel()
153+
154+
for _, kind := range []string{"axiom:events:v1", "otel:traces:v1", "otel:logs:v1"} {
155+
assert.False(t, attributeConflictsWithKind(types.StringValue(kind), mapFields, metricsDatasetKind), kind)
156+
}
157+
})
158+
159+
t.Run("does not conflict when kind is not known", func(t *testing.T) {
160+
t.Parallel()
161+
162+
assert.False(t, attributeConflictsWithKind(types.StringNull(), mapFields, metricsDatasetKind))
163+
assert.False(t, attributeConflictsWithKind(types.StringUnknown(), mapFields, metricsDatasetKind))
164+
})
165+
}
166+
124167
func TestEdgeDeploymentValue(t *testing.T) {
125168
t.Parallel()
126169

docs/data-sources/dataset.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ description: |-
2424
- `description` (String) Dataset description
2525
- `edge_deployment` (String) Edge deployment for the dataset (for example, 'cloud.eu-central-1.aws')
2626
- `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'
27-
- `map_fields` (List of String) Map fields for the dataset
27+
- `map_fields` (List of String) Map fields for the dataset. Not supported for datasets of kind 'otel:metrics:v1'
2828
- `name` (String) Dataset name
2929
- `retention_days` (Number) Retention days for the dataset
3030
- `use_retention_period` (Boolean) Use retention for the dataset

docs/resources/dataset.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ description: |-
2424
- `description` (String) Dataset description
2525
- `edge_deployment` (String) Edge deployment for the dataset (for example, 'cloud.eu-central-1.aws')
2626
- `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'
27-
- `map_fields` (List of String) Map fields for the dataset
27+
- `map_fields` (List of String) Map fields for the dataset. Not supported for datasets of kind 'otel:metrics:v1'
2828
- `retention_days` (Number) Retention days for the dataset
2929
- `use_retention_period` (Boolean) Use retention for the dataset
3030

0 commit comments

Comments
 (0)