From ddf0c6d83ab5877c9a41d31de33f2fc92bca7391 Mon Sep 17 00:00:00 2001 From: Claus Matzinger Date: Mon, 20 Oct 2025 16:01:10 +0200 Subject: [PATCH 01/10] update SDK --- go.mod | 2 +- go.sum | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index f2bc2b692..f8e84f97b 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ replace github.com/grpc-ecosystem/grpc-gateway/v2 => github.com/coralogix/grpc-g require ( github.com/ahmetalpbalkan/go-linq v3.0.0+incompatible - github.com/coralogix/coralogix-management-sdk v1.9.3-0.20251016142149-cfbf6680f3aa + github.com/coralogix/coralogix-management-sdk v1.9.3-0.20251020091618-075a0c2c07d9 github.com/google/uuid v1.6.0 github.com/grafana/grafana-api-golang-client v0.27.0 github.com/hashicorp/terraform-plugin-docs v0.20.1 diff --git a/go.sum b/go.sum index 4369a3072..d521a1bc7 100644 --- a/go.sum +++ b/go.sum @@ -38,12 +38,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/coralogix/coralogix-management-sdk v1.9.3-0.20251013075013-a70c0bbaecaf h1:TnlB5YHvpQctMhhh2/eqCFnxz8qr9nfzOPI31ESeuUM= -github.com/coralogix/coralogix-management-sdk v1.9.3-0.20251013075013-a70c0bbaecaf/go.mod h1:rxWZfvnH6aXq2zZildOH98EzTLrzQIPSKffz4kf8bkY= -github.com/coralogix/coralogix-management-sdk v1.9.3-0.20251015105058-3e0ead18dfb8 h1:tdoMTI+K5c79TPh7TaMQ1EPJeK66p7zSZIMaIf5f1eU= -github.com/coralogix/coralogix-management-sdk v1.9.3-0.20251015105058-3e0ead18dfb8/go.mod h1:rxWZfvnH6aXq2zZildOH98EzTLrzQIPSKffz4kf8bkY= -github.com/coralogix/coralogix-management-sdk v1.9.3-0.20251016142149-cfbf6680f3aa h1:e4ZtOfkzOtY4+ZkRA2wmTG5+GgyNm40VxTW8N+nbYV0= -github.com/coralogix/coralogix-management-sdk v1.9.3-0.20251016142149-cfbf6680f3aa/go.mod h1:Q7eSBDvWFb6JNfigQXGoWO3XIs6ap1CXfElaZeUrZec= +github.com/coralogix/coralogix-management-sdk v1.9.3-0.20251020091618-075a0c2c07d9 h1:aPHnQzRcxUfg8A8ch9bbhl6EA7TAO8ckPivp/Lw25lA= +github.com/coralogix/coralogix-management-sdk v1.9.3-0.20251020091618-075a0c2c07d9/go.mod h1:Q7eSBDvWFb6JNfigQXGoWO3XIs6ap1CXfElaZeUrZec= github.com/coralogix/grpc-gateway/v2 v2.0.0-20251015134251-4d8694a21a7c h1:aOfG9Pwe7Fp/m+tdybObODwgeK5st/+9df/QUw0dzBQ= github.com/coralogix/grpc-gateway/v2 v2.0.0-20251015134251-4d8694a21a7c/go.mod h1:bqGO/kNOHTFiDIfPmXLQcox10aWQs5Q3b9sXUFoaFFk= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= From 87b2ebda3a531fc8b2a989ca379126e7cde91b46 Mon Sep 17 00:00:00 2001 From: Or Novogroder Date: Thu, 26 Jun 2025 10:00:45 +0300 Subject: [PATCH 02/10] bookmark --- internal/clientset/clientset.go | 6 + internal/provider/resource_coralogix_view.go | 2603 ++++++++++++++++++ 2 files changed, 2609 insertions(+) create mode 100644 internal/provider/resource_coralogix_view.go diff --git a/internal/clientset/clientset.go b/internal/clientset/clientset.go index 2d0209475..63084883f 100644 --- a/internal/clientset/clientset.go +++ b/internal/clientset/clientset.go @@ -51,6 +51,7 @@ type ClientSet struct { groupGrpc *cxsdk.GroupsClient notifications *cxsdk.NotificationsClient ipaccess *ipaccess.IPAccessServiceAPIService + views *cxsdk.ViewsClient grafana *GrafanaClient groups *GroupsClient @@ -164,6 +165,10 @@ func (c *ClientSet) LegacySLOs() *cxsdk.LegacySLOsClient { return c.legacySlos } +func (c *ClientSet) Views() *cxsdk.ViewsClient { + return c.views +} + func NewClientSet(region string, apiKey string, targetUrl string) *ClientSet { apiKeySdk := cxsdk.NewSDKCallPropertiesCreatorTerraform(strings.ToLower(region), cxsdk.NewAuthContext(apiKey, apiKey), TF_PROVIDER_VERSION) apikeyCPC := NewCallPropertiesCreator(targetUrl, apiKey) @@ -197,6 +202,7 @@ func NewClientSet(region string, apiKey string, targetUrl string) *ClientSet { groupGrpc: cxsdk.NewGroupsClient(apiKeySdk), notifications: cxsdk.NewNotificationsClient(apiKeySdk), ipaccess: cxsdkOpenapi.NewIPAccessClient(oasTfCPC), + views: cxsdk.NewViewsClient(apiKeySdk), grafana: NewGrafanaClient(apikeyCPC), groups: NewGroupsClient(apikeyCPC), diff --git a/internal/provider/resource_coralogix_view.go b/internal/provider/resource_coralogix_view.go new file mode 100644 index 000000000..abeeea085 --- /dev/null +++ b/internal/provider/resource_coralogix_view.go @@ -0,0 +1,2603 @@ +package coralogix + +import ( + "context" + "fmt" + "log" + "regexp" + "strings" + + cxsdk "github.com/coralogix/coralogix-management-sdk/go" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "google.golang.org/protobuf/encoding/protojson" + "terraform-provider-coralogix/coralogix/clientset" + "terraform-provider-coralogix/coralogix/utils" +) + +var _ resource.Resource = (*ViewsResource)(nil) + +func NewViewsResource() resource.Resource { + return &ViewsResource{} +} + +type ViewsResource struct { + client *cxsdk.ViewsClient +} + +func (r *ViewsResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_views" +} + +func (r *ViewsResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + clientSet, ok := req.ProviderData.(*clientset.ClientSet) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *clientset.ClientSet, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = clientSet.Views() +} + +func (r *ViewsResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = ViewResourceSchema(ctx) +} + +func (r *ViewsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *ViewModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create API call logic + createViewRequest, diags := extractCreateView(ctx, data) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + viewStr := protojson.Format(createViewRequest) + log.Printf("[INFO] Creating new view: %s", viewStr) + createViewResponse, err := r.client.Create(ctx, createViewRequest) + if err != nil { + log.Printf("[ERROR] Received error: %s", err) + resp.Diagnostics.AddError("Error creating View", + utils.FormatRpcErrors(err, cxsdk.CreateActionRPC, viewStr), + ) + return + } + log.Printf("[INFO] View created successfully: %s", protojson.Format(createViewResponse.View)) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func extractCreateView(ctx context.Context, data *ViewModel) (*cxsdk.CreateViewRequest, diag.Diagnostics) { + return &cxsdk.CreateViewRequest{ + Name: utils.TypeStringToWrapperspbString(data.Name), + SearchQuery: expandViewSearchQuery(ctx, data.SearchQuery), + TimeSelection: expandViewTimeSelection(ctx, data.TimeSelection), + }, nil +} + +func expandViewSearchQuery(ctx context.Context, query SearchQueryValue) *cxsdk.SearchQuery { + if query.IsNull() || query.IsUnknown() { + return nil + } + + return &cxsdk.SearchQuery{ + Query: utils.TypeStringToWrapperspbString(query.Query), + } +} + +func expandViewTimeSelection(ctx context.Context, selection TimeSelectionValue) *cxsdk.TimeSelection { + if selection.IsNull() || selection.IsUnknown() { + return nil + } + return nil +} + +func (r *ViewsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *ViewModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Read API call logic + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ViewsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *ViewModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Update API call logic + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ViewsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *ViewModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete API call logic +} + +func ViewResourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "filters": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "filters": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Description: "Filter name", + MarkdownDescription: "Filter name", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "selected_values": schema.MapAttribute{ + ElementType: types.BoolType, + Required: true, + Description: "Filter selected values", + MarkdownDescription: "Filter selected values", + }, + }, + CustomType: InnerFiltersType{ + ObjectType: types.ObjectType{ + AttrTypes: FiltersValue{}.AttributeTypes(ctx), + }, + }, + }, + Optional: true, + Computed: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + }, + }, + CustomType: FiltersType{ + ObjectType: types.ObjectType{ + AttrTypes: FiltersValue{}.AttributeTypes(ctx), + }, + }, + Optional: true, + Computed: true, + }, + "folder_id": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Unique identifier for folders", + MarkdownDescription: "Unique identifier for folders", + Validators: []validator.String{ + stringvalidator.LengthBetween(36, 36), + stringvalidator.RegexMatches(regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), ""), + }, + }, + "id": schema.Int64Attribute{ + Computed: true, + Description: "id", + MarkdownDescription: "id", + }, + "is_compact_mode": schema.BoolAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "View name", + MarkdownDescription: "View name", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "search_query": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "query": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + }, + CustomType: SearchQueryType{ + ObjectType: types.ObjectType{ + AttrTypes: SearchQueryValue{}.AttributeTypes(ctx), + }, + }, + Optional: true, + Computed: true, + }, + "time_selection": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "custom_selection": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "from_time": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "to_time": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + }, + CustomType: CustomSelectionType{ + ObjectType: types.ObjectType{ + AttrTypes: CustomSelectionValue{}.AttributeTypes(ctx), + }, + }, + Optional: true, + Validators: []validator.Object{ + objectvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("quick_selection")), + }, + }, + "quick_selection": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "caption": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Folder name", + MarkdownDescription: "Folder name", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "seconds": schema.Int64Attribute{ + Required: true, + Description: "Folder name", + MarkdownDescription: "Folder name", + }, + }, + CustomType: QuickSelectionType{ + ObjectType: types.ObjectType{ + AttrTypes: QuickSelectionValue{}.AttributeTypes(ctx), + }, + }, + Optional: true, + }, + }, + CustomType: TimeSelectionType{ + ObjectType: types.ObjectType{ + AttrTypes: TimeSelectionValue{}.AttributeTypes(ctx), + }, + }, + Required: true, + }, + }, + } +} + +type ViewModel struct { + Filters FiltersValue `tfsdk:"filters"` + FolderId types.String `tfsdk:"folder_id"` + Id types.Int64 `tfsdk:"id"` + IsCompactMode types.Bool `tfsdk:"is_compact_mode"` + Name types.String `tfsdk:"name"` + SearchQuery SearchQueryValue `tfsdk:"search_query"` + TimeSelection TimeSelectionValue `tfsdk:"time_selection"` +} + +var _ basetypes.ObjectTypable = FiltersType{} + +type FiltersType struct { + basetypes.ObjectType +} + +func (t FiltersType) Equal(o attr.Type) bool { + other, ok := o.(FiltersType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t FiltersType) String() string { + return "FiltersType" +} + +func (t FiltersType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + filtersAttribute, ok := attributes["filters"] + + if !ok { + diags.AddError( + "Attribute Missing", + `filters is missing from object`) + + return nil, diags + } + + filtersVal, ok := filtersAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`filters expected to be basetypes.ListValue, was: %T`, filtersAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return FiltersValue{ + Filters: filtersVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewFiltersValueNull() FiltersValue { + return FiltersValue{ + state: attr.ValueStateNull, + } +} + +func NewFiltersValueUnknown() FiltersValue { + return FiltersValue{ + state: attr.ValueStateUnknown, + } +} + +func NewFiltersValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (FiltersValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing FiltersValue Attribute Value", + "While creating a FiltersValue value, a missing attribute value was detected. "+ + "A FiltersValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("FiltersValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid FiltersValue Attribute Type", + "While creating a FiltersValue value, an invalid attribute value was detected. "+ + "A FiltersValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("FiltersValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("FiltersValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra FiltersValue Attribute Value", + "While creating a FiltersValue value, an extra attribute value was detected. "+ + "A FiltersValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra FiltersValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewFiltersValueUnknown(), diags + } + + filtersAttribute, ok := attributes["filters"] + + if !ok { + diags.AddError( + "Attribute Missing", + `filters is missing from object`) + + return NewFiltersValueUnknown(), diags + } + + filtersVal, ok := filtersAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`filters expected to be basetypes.ListValue, was: %T`, filtersAttribute)) + } + + if diags.HasError() { + return NewFiltersValueUnknown(), diags + } + + return FiltersValue{ + Filters: filtersVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewFiltersValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) FiltersValue { + object, diags := NewFiltersValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewFiltersValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t FiltersType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewFiltersValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewFiltersValueUnknown(), nil + } + + if in.IsNull() { + return NewFiltersValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewFiltersValueMust(FiltersValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t FiltersType) ValueType(ctx context.Context) attr.Value { + return FiltersValue{} +} + +var _ basetypes.ObjectValuable = FiltersValue{} + +type FiltersValue struct { + Filters basetypes.ListValue `tfsdk:"filters"` + state attr.ValueState +} + +func (v FiltersValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 1) + + var val tftypes.Value + var err error + + attrTypes["filters"] = basetypes.ListType{ + ElemType: FiltersValue{}.Type(ctx), + }.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 1) + + val, err = v.Filters.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["filters"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v FiltersValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v FiltersValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v FiltersValue) String() string { + return "FiltersValue" +} + +func (v FiltersValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + filters := types.ListValueMust( + FiltersType{ + basetypes.ObjectType{ + AttrTypes: FiltersValue{}.AttributeTypes(ctx), + }, + }, + v.Filters.Elements(), + ) + + if v.Filters.IsNull() { + filters = types.ListNull( + FiltersType{ + basetypes.ObjectType{ + AttrTypes: FiltersValue{}.AttributeTypes(ctx), + }, + }, + ) + } + + if v.Filters.IsUnknown() { + filters = types.ListUnknown( + FiltersType{ + basetypes.ObjectType{ + AttrTypes: FiltersValue{}.AttributeTypes(ctx), + }, + }, + ) + } + + attributeTypes := map[string]attr.Type{ + "filters": basetypes.ListType{ + ElemType: FiltersValue{}.Type(ctx), + }, + } + + if v.IsNull() { + return types.ObjectNull(attributeTypes), diags + } + + if v.IsUnknown() { + return types.ObjectUnknown(attributeTypes), diags + } + + objVal, diags := types.ObjectValue( + attributeTypes, + map[string]attr.Value{ + "filters": filters, + }) + + return objVal, diags +} + +func (v FiltersValue) Equal(o attr.Value) bool { + other, ok := o.(FiltersValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Filters.Equal(other.Filters) { + return false + } + + return true +} + +func (v FiltersValue) Type(ctx context.Context) attr.Type { + return FiltersType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v FiltersValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "filters": basetypes.ListType{ + ElemType: FiltersValue{}.Type(ctx), + }, + } +} + +var _ basetypes.ObjectTypable = FiltersType{} + +type InnerFiltersType struct { + basetypes.ObjectType +} + +func (t InnerFiltersType) Equal(o attr.Type) bool { + other, ok := o.(InnerFiltersType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t InnerFiltersType) String() string { + return "InnerFiltersType" +} + +func (t InnerFiltersType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + nameAttribute, ok := attributes["name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `name is missing from object`) + + return nil, diags + } + + nameVal, ok := nameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute)) + } + + selectedValuesAttribute, ok := attributes["selected_values"] + + if !ok { + diags.AddError( + "Attribute Missing", + `selected_values is missing from object`) + + return nil, diags + } + + selectedValuesVal, ok := selectedValuesAttribute.(basetypes.MapValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`selected_values expected to be basetypes.MapValue, was: %T`, selectedValuesAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return InnerFiltersValue{ + Name: nameVal, + SelectedValues: selectedValuesVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewInnerFiltersValueNull() InnerFiltersValue { + return InnerFiltersValue{ + state: attr.ValueStateNull, + } +} + +func NewInnerFiltersValueUnknown() InnerFiltersValue { + return InnerFiltersValue{ + state: attr.ValueStateUnknown, + } +} + +func NewInnerFiltersValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (InnerFiltersValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing FiltersValue Attribute Value", + "While creating a FiltersValue value, a missing attribute value was detected. "+ + "A FiltersValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("FiltersValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid FiltersValue Attribute Type", + "While creating a FiltersValue value, an invalid attribute value was detected. "+ + "A FiltersValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("FiltersValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("FiltersValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra FiltersValue Attribute Value", + "While creating a FiltersValue value, an extra attribute value was detected. "+ + "A FiltersValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra FiltersValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewInnerFiltersValueUnknown(), diags + } + + nameAttribute, ok := attributes["name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `name is missing from object`) + + return NewInnerFiltersValueUnknown(), diags + } + + nameVal, ok := nameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute)) + } + + selectedValuesAttribute, ok := attributes["selected_values"] + + if !ok { + diags.AddError( + "Attribute Missing", + `selected_values is missing from object`) + + return NewInnerFiltersValueUnknown(), diags + } + + selectedValuesVal, ok := selectedValuesAttribute.(basetypes.MapValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`selected_values expected to be basetypes.MapValue, was: %T`, selectedValuesAttribute)) + } + + if diags.HasError() { + return NewInnerFiltersValueUnknown(), diags + } + + return InnerFiltersValue{ + Name: nameVal, + SelectedValues: selectedValuesVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewInnerFiltersValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) InnerFiltersValue { + object, diags := NewInnerFiltersValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewFiltersValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t InnerFiltersType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewInnerFiltersValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewInnerFiltersValueUnknown(), nil + } + + if in.IsNull() { + return NewInnerFiltersValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewInnerFiltersValueMust(FiltersValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t InnerFiltersType) ValueType(ctx context.Context) attr.Value { + return FiltersValue{} +} + +var _ basetypes.ObjectValuable = InnerFiltersValue{} + +type InnerFiltersValue struct { + Name basetypes.StringValue `tfsdk:"name"` + SelectedValues basetypes.MapValue `tfsdk:"selected_values"` + state attr.ValueState +} + +func (v InnerFiltersValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 2) + + var val tftypes.Value + var err error + + attrTypes["name"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["selected_values"] = basetypes.MapType{ + ElemType: types.BoolType, + }.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 2) + + val, err = v.Name.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["name"] = val + + val, err = v.SelectedValues.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["selected_values"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v InnerFiltersValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v InnerFiltersValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v InnerFiltersValue) String() string { + return "InnerFiltersValue" +} + +func (v InnerFiltersValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + var selectedValuesVal basetypes.MapValue + switch { + case v.SelectedValues.IsUnknown(): + selectedValuesVal = types.MapUnknown(types.BoolType) + case v.SelectedValues.IsNull(): + selectedValuesVal = types.MapNull(types.BoolType) + default: + var d diag.Diagnostics + selectedValuesVal, d = types.MapValue(types.BoolType, v.SelectedValues.Elements()) + diags.Append(d...) + } + + if diags.HasError() { + return types.ObjectUnknown(map[string]attr.Type{ + "name": basetypes.StringType{}, + "selected_values": basetypes.MapType{ + ElemType: types.BoolType, + }, + }), diags + } + + attributeTypes := map[string]attr.Type{ + "name": basetypes.StringType{}, + "selected_values": basetypes.MapType{ + ElemType: types.BoolType, + }, + } + + if v.IsNull() { + return types.ObjectNull(attributeTypes), diags + } + + if v.IsUnknown() { + return types.ObjectUnknown(attributeTypes), diags + } + + objVal, diags := types.ObjectValue( + attributeTypes, + map[string]attr.Value{ + "name": v.Name, + "selected_values": selectedValuesVal, + }) + + return objVal, diags +} + +func (v InnerFiltersValue) Equal(o attr.Value) bool { + other, ok := o.(InnerFiltersValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Name.Equal(other.Name) { + return false + } + + if !v.SelectedValues.Equal(other.SelectedValues) { + return false + } + + return true +} + +func (v InnerFiltersValue) Type(ctx context.Context) attr.Type { + return InnerFiltersType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v InnerFiltersValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "name": basetypes.StringType{}, + "selected_values": basetypes.MapType{ + ElemType: types.BoolType, + }, + } +} + +var _ basetypes.ObjectTypable = SearchQueryType{} + +type SearchQueryType struct { + basetypes.ObjectType +} + +func (t SearchQueryType) Equal(o attr.Type) bool { + other, ok := o.(SearchQueryType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t SearchQueryType) String() string { + return "SearchQueryType" +} + +func (t SearchQueryType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + queryAttribute, ok := attributes["query"] + + if !ok { + diags.AddError( + "Attribute Missing", + `query is missing from object`) + + return nil, diags + } + + queryVal, ok := queryAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`query expected to be basetypes.StringValue, was: %T`, queryAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return SearchQueryValue{ + Query: queryVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewSearchQueryValueNull() SearchQueryValue { + return SearchQueryValue{ + state: attr.ValueStateNull, + } +} + +func NewSearchQueryValueUnknown() SearchQueryValue { + return SearchQueryValue{ + state: attr.ValueStateUnknown, + } +} + +func NewSearchQueryValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (SearchQueryValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing SearchQueryValue Attribute Value", + "While creating a SearchQueryValue value, a missing attribute value was detected. "+ + "A SearchQueryValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("SearchQueryValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid SearchQueryValue Attribute Type", + "While creating a SearchQueryValue value, an invalid attribute value was detected. "+ + "A SearchQueryValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("SearchQueryValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("SearchQueryValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra SearchQueryValue Attribute Value", + "While creating a SearchQueryValue value, an extra attribute value was detected. "+ + "A SearchQueryValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra SearchQueryValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewSearchQueryValueUnknown(), diags + } + + queryAttribute, ok := attributes["query"] + + if !ok { + diags.AddError( + "Attribute Missing", + `query is missing from object`) + + return NewSearchQueryValueUnknown(), diags + } + + queryVal, ok := queryAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`query expected to be basetypes.StringValue, was: %T`, queryAttribute)) + } + + if diags.HasError() { + return NewSearchQueryValueUnknown(), diags + } + + return SearchQueryValue{ + Query: queryVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewSearchQueryValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) SearchQueryValue { + object, diags := NewSearchQueryValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewSearchQueryValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t SearchQueryType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewSearchQueryValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewSearchQueryValueUnknown(), nil + } + + if in.IsNull() { + return NewSearchQueryValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewSearchQueryValueMust(SearchQueryValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t SearchQueryType) ValueType(ctx context.Context) attr.Value { + return SearchQueryValue{} +} + +var _ basetypes.ObjectValuable = SearchQueryValue{} + +type SearchQueryValue struct { + Query basetypes.StringValue `tfsdk:"query"` + state attr.ValueState +} + +func (v SearchQueryValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 1) + + var val tftypes.Value + var err error + + attrTypes["query"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 1) + + val, err = v.Query.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["query"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v SearchQueryValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v SearchQueryValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v SearchQueryValue) String() string { + return "SearchQueryValue" +} + +func (v SearchQueryValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + attributeTypes := map[string]attr.Type{ + "query": basetypes.StringType{}, + } + + if v.IsNull() { + return types.ObjectNull(attributeTypes), diags + } + + if v.IsUnknown() { + return types.ObjectUnknown(attributeTypes), diags + } + + objVal, diags := types.ObjectValue( + attributeTypes, + map[string]attr.Value{ + "query": v.Query, + }) + + return objVal, diags +} + +func (v SearchQueryValue) Equal(o attr.Value) bool { + other, ok := o.(SearchQueryValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Query.Equal(other.Query) { + return false + } + + return true +} + +func (v SearchQueryValue) Type(ctx context.Context) attr.Type { + return SearchQueryType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v SearchQueryValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "query": basetypes.StringType{}, + } +} + +var _ basetypes.ObjectTypable = TimeSelectionType{} + +type TimeSelectionType struct { + basetypes.ObjectType +} + +func (t TimeSelectionType) Equal(o attr.Type) bool { + other, ok := o.(TimeSelectionType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t TimeSelectionType) String() string { + return "TimeSelectionType" +} + +func (t TimeSelectionType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + customSelectionAttribute, ok := attributes["custom_selection"] + + if !ok { + diags.AddError( + "Attribute Missing", + `custom_selection is missing from object`) + + return nil, diags + } + + customSelectionVal, ok := customSelectionAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`custom_selection expected to be basetypes.ObjectValue, was: %T`, customSelectionAttribute)) + } + + quickSelectionAttribute, ok := attributes["quick_selection"] + + if !ok { + diags.AddError( + "Attribute Missing", + `quick_selection is missing from object`) + + return nil, diags + } + + quickSelectionVal, ok := quickSelectionAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`quick_selection expected to be basetypes.ObjectValue, was: %T`, quickSelectionAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return TimeSelectionValue{ + CustomSelection: customSelectionVal, + QuickSelection: quickSelectionVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewTimeSelectionValueNull() TimeSelectionValue { + return TimeSelectionValue{ + state: attr.ValueStateNull, + } +} + +func NewTimeSelectionValueUnknown() TimeSelectionValue { + return TimeSelectionValue{ + state: attr.ValueStateUnknown, + } +} + +func NewTimeSelectionValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (TimeSelectionValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing TimeSelectionValue Attribute Value", + "While creating a TimeSelectionValue value, a missing attribute value was detected. "+ + "A TimeSelectionValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("TimeSelectionValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid TimeSelectionValue Attribute Type", + "While creating a TimeSelectionValue value, an invalid attribute value was detected. "+ + "A TimeSelectionValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("TimeSelectionValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("TimeSelectionValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra TimeSelectionValue Attribute Value", + "While creating a TimeSelectionValue value, an extra attribute value was detected. "+ + "A TimeSelectionValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra TimeSelectionValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewTimeSelectionValueUnknown(), diags + } + + customSelectionAttribute, ok := attributes["custom_selection"] + + if !ok { + diags.AddError( + "Attribute Missing", + `custom_selection is missing from object`) + + return NewTimeSelectionValueUnknown(), diags + } + + customSelectionVal, ok := customSelectionAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`custom_selection expected to be basetypes.ObjectValue, was: %T`, customSelectionAttribute)) + } + + quickSelectionAttribute, ok := attributes["quick_selection"] + + if !ok { + diags.AddError( + "Attribute Missing", + `quick_selection is missing from object`) + + return NewTimeSelectionValueUnknown(), diags + } + + quickSelectionVal, ok := quickSelectionAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`quick_selection expected to be basetypes.ObjectValue, was: %T`, quickSelectionAttribute)) + } + + if diags.HasError() { + return NewTimeSelectionValueUnknown(), diags + } + + return TimeSelectionValue{ + CustomSelection: customSelectionVal, + QuickSelection: quickSelectionVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewTimeSelectionValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) TimeSelectionValue { + object, diags := NewTimeSelectionValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewTimeSelectionValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t TimeSelectionType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewTimeSelectionValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewTimeSelectionValueUnknown(), nil + } + + if in.IsNull() { + return NewTimeSelectionValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewTimeSelectionValueMust(TimeSelectionValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t TimeSelectionType) ValueType(ctx context.Context) attr.Value { + return TimeSelectionValue{} +} + +var _ basetypes.ObjectValuable = TimeSelectionValue{} + +type TimeSelectionValue struct { + CustomSelection basetypes.ObjectValue `tfsdk:"custom_selection"` + QuickSelection basetypes.ObjectValue `tfsdk:"quick_selection"` + state attr.ValueState +} + +func (v TimeSelectionValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 2) + + var val tftypes.Value + var err error + + attrTypes["custom_selection"] = basetypes.ObjectType{ + AttrTypes: CustomSelectionValue{}.AttributeTypes(ctx), + }.TerraformType(ctx) + attrTypes["quick_selection"] = basetypes.ObjectType{ + AttrTypes: QuickSelectionValue{}.AttributeTypes(ctx), + }.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 2) + + val, err = v.CustomSelection.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["custom_selection"] = val + + val, err = v.QuickSelection.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["quick_selection"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v TimeSelectionValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v TimeSelectionValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v TimeSelectionValue) String() string { + return "TimeSelectionValue" +} + +func (v TimeSelectionValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + var customSelection basetypes.ObjectValue + + if v.CustomSelection.IsNull() { + customSelection = types.ObjectNull( + CustomSelectionValue{}.AttributeTypes(ctx), + ) + } + + if v.CustomSelection.IsUnknown() { + customSelection = types.ObjectUnknown( + CustomSelectionValue{}.AttributeTypes(ctx), + ) + } + + if !v.CustomSelection.IsNull() && !v.CustomSelection.IsUnknown() { + customSelection = types.ObjectValueMust( + CustomSelectionValue{}.AttributeTypes(ctx), + v.CustomSelection.Attributes(), + ) + } + + var quickSelection basetypes.ObjectValue + + if v.QuickSelection.IsNull() { + quickSelection = types.ObjectNull( + QuickSelectionValue{}.AttributeTypes(ctx), + ) + } + + if v.QuickSelection.IsUnknown() { + quickSelection = types.ObjectUnknown( + QuickSelectionValue{}.AttributeTypes(ctx), + ) + } + + if !v.QuickSelection.IsNull() && !v.QuickSelection.IsUnknown() { + quickSelection = types.ObjectValueMust( + QuickSelectionValue{}.AttributeTypes(ctx), + v.QuickSelection.Attributes(), + ) + } + + attributeTypes := map[string]attr.Type{ + "custom_selection": basetypes.ObjectType{ + AttrTypes: CustomSelectionValue{}.AttributeTypes(ctx), + }, + "quick_selection": basetypes.ObjectType{ + AttrTypes: QuickSelectionValue{}.AttributeTypes(ctx), + }, + } + + if v.IsNull() { + return types.ObjectNull(attributeTypes), diags + } + + if v.IsUnknown() { + return types.ObjectUnknown(attributeTypes), diags + } + + objVal, diags := types.ObjectValue( + attributeTypes, + map[string]attr.Value{ + "custom_selection": customSelection, + "quick_selection": quickSelection, + }) + + return objVal, diags +} + +func (v TimeSelectionValue) Equal(o attr.Value) bool { + other, ok := o.(TimeSelectionValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.CustomSelection.Equal(other.CustomSelection) { + return false + } + + if !v.QuickSelection.Equal(other.QuickSelection) { + return false + } + + return true +} + +func (v TimeSelectionValue) Type(ctx context.Context) attr.Type { + return TimeSelectionType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v TimeSelectionValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "custom_selection": basetypes.ObjectType{ + AttrTypes: CustomSelectionValue{}.AttributeTypes(ctx), + }, + "quick_selection": basetypes.ObjectType{ + AttrTypes: QuickSelectionValue{}.AttributeTypes(ctx), + }, + } +} + +var _ basetypes.ObjectTypable = CustomSelectionType{} + +type CustomSelectionType struct { + basetypes.ObjectType +} + +func (t CustomSelectionType) Equal(o attr.Type) bool { + other, ok := o.(CustomSelectionType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t CustomSelectionType) String() string { + return "CustomSelectionType" +} + +func (t CustomSelectionType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + fromTimeAttribute, ok := attributes["from_time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `from_time is missing from object`) + + return nil, diags + } + + fromTimeVal, ok := fromTimeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`from_time expected to be basetypes.StringValue, was: %T`, fromTimeAttribute)) + } + + toTimeAttribute, ok := attributes["to_time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `to_time is missing from object`) + + return nil, diags + } + + toTimeVal, ok := toTimeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`to_time expected to be basetypes.StringValue, was: %T`, toTimeAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return CustomSelectionValue{ + FromTime: fromTimeVal, + ToTime: toTimeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewCustomSelectionValueNull() CustomSelectionValue { + return CustomSelectionValue{ + state: attr.ValueStateNull, + } +} + +func NewCustomSelectionValueUnknown() CustomSelectionValue { + return CustomSelectionValue{ + state: attr.ValueStateUnknown, + } +} + +func NewCustomSelectionValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (CustomSelectionValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing CustomSelectionValue Attribute Value", + "While creating a CustomSelectionValue value, a missing attribute value was detected. "+ + "A CustomSelectionValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("CustomSelectionValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid CustomSelectionValue Attribute Type", + "While creating a CustomSelectionValue value, an invalid attribute value was detected. "+ + "A CustomSelectionValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("CustomSelectionValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("CustomSelectionValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra CustomSelectionValue Attribute Value", + "While creating a CustomSelectionValue value, an extra attribute value was detected. "+ + "A CustomSelectionValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra CustomSelectionValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewCustomSelectionValueUnknown(), diags + } + + fromTimeAttribute, ok := attributes["from_time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `from_time is missing from object`) + + return NewCustomSelectionValueUnknown(), diags + } + + fromTimeVal, ok := fromTimeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`from_time expected to be basetypes.StringValue, was: %T`, fromTimeAttribute)) + } + + toTimeAttribute, ok := attributes["to_time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `to_time is missing from object`) + + return NewCustomSelectionValueUnknown(), diags + } + + toTimeVal, ok := toTimeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`to_time expected to be basetypes.StringValue, was: %T`, toTimeAttribute)) + } + + if diags.HasError() { + return NewCustomSelectionValueUnknown(), diags + } + + return CustomSelectionValue{ + FromTime: fromTimeVal, + ToTime: toTimeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewCustomSelectionValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) CustomSelectionValue { + object, diags := NewCustomSelectionValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewCustomSelectionValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t CustomSelectionType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewCustomSelectionValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewCustomSelectionValueUnknown(), nil + } + + if in.IsNull() { + return NewCustomSelectionValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewCustomSelectionValueMust(CustomSelectionValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t CustomSelectionType) ValueType(ctx context.Context) attr.Value { + return CustomSelectionValue{} +} + +var _ basetypes.ObjectValuable = CustomSelectionValue{} + +type CustomSelectionValue struct { + FromTime basetypes.StringValue `tfsdk:"from_time"` + ToTime basetypes.StringValue `tfsdk:"to_time"` + state attr.ValueState +} + +func (v CustomSelectionValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 2) + + var val tftypes.Value + var err error + + attrTypes["from_time"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["to_time"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 2) + + val, err = v.FromTime.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["from_time"] = val + + val, err = v.ToTime.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["to_time"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v CustomSelectionValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v CustomSelectionValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v CustomSelectionValue) String() string { + return "CustomSelectionValue" +} + +func (v CustomSelectionValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + attributeTypes := map[string]attr.Type{ + "from_time": basetypes.StringType{}, + "to_time": basetypes.StringType{}, + } + + if v.IsNull() { + return types.ObjectNull(attributeTypes), diags + } + + if v.IsUnknown() { + return types.ObjectUnknown(attributeTypes), diags + } + + objVal, diags := types.ObjectValue( + attributeTypes, + map[string]attr.Value{ + "from_time": v.FromTime, + "to_time": v.ToTime, + }) + + return objVal, diags +} + +func (v CustomSelectionValue) Equal(o attr.Value) bool { + other, ok := o.(CustomSelectionValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.FromTime.Equal(other.FromTime) { + return false + } + + if !v.ToTime.Equal(other.ToTime) { + return false + } + + return true +} + +func (v CustomSelectionValue) Type(ctx context.Context) attr.Type { + return CustomSelectionType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v CustomSelectionValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "from_time": basetypes.StringType{}, + "to_time": basetypes.StringType{}, + } +} + +var _ basetypes.ObjectTypable = QuickSelectionType{} + +type QuickSelectionType struct { + basetypes.ObjectType +} + +func (t QuickSelectionType) Equal(o attr.Type) bool { + other, ok := o.(QuickSelectionType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t QuickSelectionType) String() string { + return "QuickSelectionType" +} + +func (t QuickSelectionType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + captionAttribute, ok := attributes["caption"] + + if !ok { + diags.AddError( + "Attribute Missing", + `caption is missing from object`) + + return nil, diags + } + + captionVal, ok := captionAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`caption expected to be basetypes.StringValue, was: %T`, captionAttribute)) + } + + secondsAttribute, ok := attributes["seconds"] + + if !ok { + diags.AddError( + "Attribute Missing", + `seconds is missing from object`) + + return nil, diags + } + + secondsVal, ok := secondsAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`seconds expected to be basetypes.Int64Value, was: %T`, secondsAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return QuickSelectionValue{ + Caption: captionVal, + Seconds: secondsVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewQuickSelectionValueNull() QuickSelectionValue { + return QuickSelectionValue{ + state: attr.ValueStateNull, + } +} + +func NewQuickSelectionValueUnknown() QuickSelectionValue { + return QuickSelectionValue{ + state: attr.ValueStateUnknown, + } +} + +func NewQuickSelectionValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (QuickSelectionValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing QuickSelectionValue Attribute Value", + "While creating a QuickSelectionValue value, a missing attribute value was detected. "+ + "A QuickSelectionValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("QuickSelectionValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid QuickSelectionValue Attribute Type", + "While creating a QuickSelectionValue value, an invalid attribute value was detected. "+ + "A QuickSelectionValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("QuickSelectionValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("QuickSelectionValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra QuickSelectionValue Attribute Value", + "While creating a QuickSelectionValue value, an extra attribute value was detected. "+ + "A QuickSelectionValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra QuickSelectionValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewQuickSelectionValueUnknown(), diags + } + + captionAttribute, ok := attributes["caption"] + + if !ok { + diags.AddError( + "Attribute Missing", + `caption is missing from object`) + + return NewQuickSelectionValueUnknown(), diags + } + + captionVal, ok := captionAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`caption expected to be basetypes.StringValue, was: %T`, captionAttribute)) + } + + secondsAttribute, ok := attributes["seconds"] + + if !ok { + diags.AddError( + "Attribute Missing", + `seconds is missing from object`) + + return NewQuickSelectionValueUnknown(), diags + } + + secondsVal, ok := secondsAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`seconds expected to be basetypes.Int64Value, was: %T`, secondsAttribute)) + } + + if diags.HasError() { + return NewQuickSelectionValueUnknown(), diags + } + + return QuickSelectionValue{ + Caption: captionVal, + Seconds: secondsVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewQuickSelectionValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) QuickSelectionValue { + object, diags := NewQuickSelectionValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewQuickSelectionValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t QuickSelectionType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewQuickSelectionValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewQuickSelectionValueUnknown(), nil + } + + if in.IsNull() { + return NewQuickSelectionValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewQuickSelectionValueMust(QuickSelectionValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t QuickSelectionType) ValueType(ctx context.Context) attr.Value { + return QuickSelectionValue{} +} + +var _ basetypes.ObjectValuable = QuickSelectionValue{} + +type QuickSelectionValue struct { + Caption basetypes.StringValue `tfsdk:"caption"` + Seconds basetypes.Int64Value `tfsdk:"seconds"` + state attr.ValueState +} + +func (v QuickSelectionValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 2) + + var val tftypes.Value + var err error + + attrTypes["caption"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["seconds"] = basetypes.Int64Type{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 2) + + val, err = v.Caption.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["caption"] = val + + val, err = v.Seconds.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["seconds"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v QuickSelectionValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v QuickSelectionValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v QuickSelectionValue) String() string { + return "QuickSelectionValue" +} + +func (v QuickSelectionValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + attributeTypes := map[string]attr.Type{ + "caption": basetypes.StringType{}, + "seconds": basetypes.Int64Type{}, + } + + if v.IsNull() { + return types.ObjectNull(attributeTypes), diags + } + + if v.IsUnknown() { + return types.ObjectUnknown(attributeTypes), diags + } + + objVal, diags := types.ObjectValue( + attributeTypes, + map[string]attr.Value{ + "caption": v.Caption, + "seconds": v.Seconds, + }) + + return objVal, diags +} + +func (v QuickSelectionValue) Equal(o attr.Value) bool { + other, ok := o.(QuickSelectionValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Caption.Equal(other.Caption) { + return false + } + + if !v.Seconds.Equal(other.Seconds) { + return false + } + + return true +} + +func (v QuickSelectionValue) Type(ctx context.Context) attr.Type { + return QuickSelectionType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v QuickSelectionValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "caption": basetypes.StringType{}, + "seconds": basetypes.Int64Type{}, + } +} From 236636624f7f61c18dda86f7a0d200d13cdb2ccd Mon Sep 17 00:00:00 2001 From: Or Novogroder Date: Mon, 30 Jun 2025 15:57:40 +0300 Subject: [PATCH 03/10] initial commit --- docs/data-sources/view.md | 78 + docs/resources/view.md | 82 + examples/resources/coralogix_views/main.tf | 45 + .../provider/data_source_coralogix_view.go | 109 + .../data_source_coralogix_view_test.go | 44 + internal/provider/resource_coralogix_view.go | 2909 ++++------------- .../provider/resource_coralogix_view_test.go | 107 + 7 files changed, 1053 insertions(+), 2321 deletions(-) create mode 100644 docs/data-sources/view.md create mode 100644 docs/resources/view.md create mode 100644 examples/resources/coralogix_views/main.tf create mode 100644 internal/provider/data_source_coralogix_view.go create mode 100644 internal/provider/data_source_coralogix_view_test.go create mode 100644 internal/provider/resource_coralogix_view_test.go diff --git a/docs/data-sources/view.md b/docs/data-sources/view.md new file mode 100644 index 000000000..696105968 --- /dev/null +++ b/docs/data-sources/view.md @@ -0,0 +1,78 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coralogix_view Data Source - terraform-provider-coralogix" +subcategory: "" +description: |- + +--- + +# coralogix_view (Data Source) + + + + + + +## Schema + +### Required + +- `id` (String) id + +### Read-Only + +- `filters` (Attributes) (see [below for nested schema](#nestedatt--filters)) +- `folder_id` (String) Unique identifier for folders +- `is_compact_mode` (Boolean) +- `name` (String) View name +- `search_query` (Attributes) (see [below for nested schema](#nestedatt--search_query)) +- `time_selection` (Attributes) (see [below for nested schema](#nestedatt--time_selection)) + + +### Nested Schema for `filters` + +Read-Only: + +- `filters` (Attributes List) (see [below for nested schema](#nestedatt--filters--filters)) + + +### Nested Schema for `filters.filters` + +Read-Only: + +- `name` (String) Filter name +- `selected_values` (Map of Boolean) Filter selected values + + + + +### Nested Schema for `search_query` + +Read-Only: + +- `query` (String) + + + +### Nested Schema for `time_selection` + +Read-Only: + +- `custom_selection` (Attributes) (see [below for nested schema](#nestedatt--time_selection--custom_selection)) +- `quick_selection` (Attributes) (see [below for nested schema](#nestedatt--time_selection--quick_selection)) + + +### Nested Schema for `time_selection.custom_selection` + +Read-Only: + +- `from_time` (String) +- `to_time` (String) + + + +### Nested Schema for `time_selection.quick_selection` + +Read-Only: + +- `seconds` (Number) Folder name diff --git a/docs/resources/view.md b/docs/resources/view.md new file mode 100644 index 000000000..f3facc9d5 --- /dev/null +++ b/docs/resources/view.md @@ -0,0 +1,82 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coralogix_view Resource - terraform-provider-coralogix" +subcategory: "" +description: |- + +--- + +# coralogix_view (Resource) + + + + + + +## Schema + +### Required + +- `name` (String) View name +- `time_selection` (Attributes) (see [below for nested schema](#nestedatt--time_selection)) + +### Optional + +- `filters` (Attributes) (see [below for nested schema](#nestedatt--filters)) +- `folder_id` (String) Unique identifier for folders +- `search_query` (Attributes) (see [below for nested schema](#nestedatt--search_query)) + +### Read-Only + +- `id` (Number) id +- `is_compact_mode` (Boolean) + + +### Nested Schema for `time_selection` + +Optional: + +- `custom_selection` (Attributes) (see [below for nested schema](#nestedatt--time_selection--custom_selection)) +- `quick_selection` (Attributes) (see [below for nested schema](#nestedatt--time_selection--quick_selection)) + + +### Nested Schema for `time_selection.custom_selection` + +Required: + +- `from_time` (String) +- `to_time` (String) + + + +### Nested Schema for `time_selection.quick_selection` + +Required: + +- `seconds` (Number) Folder name + + + + +### Nested Schema for `filters` + +Optional: + +- `filters` (Attributes List) (see [below for nested schema](#nestedatt--filters--filters)) + + +### Nested Schema for `filters.filters` + +Required: + +- `name` (String) Filter name +- `selected_values` (Map of Boolean) Filter selected values + + + + +### Nested Schema for `search_query` + +Required: + +- `query` (String) diff --git a/examples/resources/coralogix_views/main.tf b/examples/resources/coralogix_views/main.tf new file mode 100644 index 000000000..197490e4d --- /dev/null +++ b/examples/resources/coralogix_views/main.tf @@ -0,0 +1,45 @@ +terraform { + required_providers { + coralogix = { + version = "~> 2.0" + source = "coralogix/coralogix" + } + } +} + +provider "coralogix" { + #api_key = "" + #env = "" +} + +resource "coralogix_view" "example_view" { + name = "Example View" + time_selection = { + custom_selection = { + from_time = "2023-01-01T00:00:00Z" + to_time = "2023-01-02T00:00:00Z" + } + } + search_query = { + query = "error OR warning" + + } + filters = { + filters = [ + { + name = "severity" + selected_values = { + "ERROR" = true + "WARNING" = true + } + }, + { + name = "application" + selected_values = { + "my-app" = true + "another-app" = true + } + } + ] + } +} \ No newline at end of file diff --git a/internal/provider/data_source_coralogix_view.go b/internal/provider/data_source_coralogix_view.go new file mode 100644 index 000000000..4e4503017 --- /dev/null +++ b/internal/provider/data_source_coralogix_view.go @@ -0,0 +1,109 @@ +// Copyright 2024 Coralogix Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package coralogix + +import ( + "context" + "encoding/json" + "fmt" + "log" + + "terraform-provider-coralogix/coralogix/clientset" + "terraform-provider-coralogix/coralogix/utils" + + cxsdk "github.com/coralogix/coralogix-management-sdk/go" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/resource" + "google.golang.org/grpc/codes" +) + +var _ datasource.DataSourceWithConfigure = &ViewDataSource{} + +func NewViewDataSource() datasource.DataSource { + return &ViewDataSource{} +} + +type ViewDataSource struct { + client *cxsdk.ViewsClient +} + +func (d *ViewDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_view" +} + +func (d *ViewDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + clientSet, ok := req.ProviderData.(*clientset.ClientSet) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *clientset.ClientSet, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = clientSet.Views() +} + +func (d *ViewDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + var r ViewResource + var resourceResp resource.SchemaResponse + r.Schema(ctx, resource.SchemaRequest{}, &resourceResp) + + resp.Schema = utils.FrameworkDatasourceSchemaFromFrameworkResourceSchema(resourceResp.Schema) +} + +func (d *ViewDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data *ViewModel + diags := req.Config.Get(ctx, &data) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + //Get refreshed View value from Coralogix + id := data.Id.ValueInt64() + log.Printf("[INFO] Reading view: %d", id) + getViewResp, err := d.client.Get(ctx, &cxsdk.GetViewRequest{Id: utils.TypeInt64ToWrappedInt32(data.Id)}) + if err != nil { + log.Printf("[ERROR] Received error: %s", err.Error()) + if cxsdk.Code(err) == codes.NotFound { + resp.Diagnostics.AddWarning( + err.Error(), + fmt.Sprintf("View %q is in state, but no longer exists in Coralogix backend", id), + ) + } else { + resp.Diagnostics.AddError( + "Error reading View", + utils.FormatRpcErrors(err, fmt.Sprintf("%s/%d", cxsdk.GetViewRPC, id), ""), + ) + } + return + } + respStr, _ := json.Marshal(getViewResp) + log.Printf("[INFO] Received View: %s", string(respStr)) + + data, diags = flattenView(ctx, getViewResp.View) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + // Set state to fully populated data + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/provider/data_source_coralogix_view_test.go b/internal/provider/data_source_coralogix_view_test.go new file mode 100644 index 000000000..a324a5157 --- /dev/null +++ b/internal/provider/data_source_coralogix_view_test.go @@ -0,0 +1,44 @@ +// Copyright 2024 Coralogix Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package coralogix + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccCoralogixDataSourceView_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccCoralogixResourceView() + + testAccCoralogixDataSourceView_read(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.coralogix_view.test", "name", "Example View"), + ), + }, + }, + }) +} + +func testAccCoralogixDataSourceView_read() string { + return `data "coralogix_view" "test" { + id = coralogix_view.test.id + } +` +} diff --git a/internal/provider/resource_coralogix_view.go b/internal/provider/resource_coralogix_view.go index abeeea085..9b1bd0521 100644 --- a/internal/provider/resource_coralogix_view.go +++ b/internal/provider/resource_coralogix_view.go @@ -1,3 +1,16 @@ +// Copyright 2024 Coralogix Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package coralogix import ( @@ -5,7 +18,7 @@ import ( "fmt" "log" "regexp" - "strings" + "time" cxsdk "github.com/coralogix/coralogix-management-sdk/go" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" @@ -16,30 +29,37 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - "github.com/hashicorp/terraform-plugin-go/tftypes" + "google.golang.org/grpc/codes" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" "terraform-provider-coralogix/coralogix/clientset" "terraform-provider-coralogix/coralogix/utils" ) -var _ resource.Resource = (*ViewsResource)(nil) +var _ resource.Resource = (*ViewResource)(nil) -func NewViewsResource() resource.Resource { - return &ViewsResource{} +func NewViewResource() resource.Resource { + return &ViewResource{} } -type ViewsResource struct { +type ViewResource struct { client *cxsdk.ViewsClient } -func (r *ViewsResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_views" +func (r *ViewResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_view" } -func (r *ViewsResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func (r *ViewResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { if req.ProviderData == nil { return } @@ -56,11 +76,11 @@ func (r *ViewsResource) Configure(_ context.Context, req resource.ConfigureReque r.client = clientSet.Views() } -func (r *ViewsResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *ViewResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = ViewResourceSchema(ctx) } -func (r *ViewsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { +func (r *ViewResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var data *ViewModel // Read Terraform plan data into the model @@ -89,77 +109,527 @@ func (r *ViewsResource) Create(ctx context.Context, req resource.CreateRequest, log.Printf("[INFO] View created successfully: %s", protojson.Format(createViewResponse.View)) // Save data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + data, diags = flattenView(ctx, createViewResponse.View) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func flattenView(ctx context.Context, view *cxsdk.View) (*ViewModel, diag.Diagnostics) { + filters, diags := flattenViewFilter(ctx, view.Filters) + if diags.HasError() { + return nil, diags + } + + searchQuery, diags := flattenSearchQuery(ctx, view.SearchQuery) + if diags.HasError() { + return nil, diags + } + + timeSelection, diags := flattenViewTimeSelection(ctx, view.TimeSelection) + if diags.HasError() { + return nil, diags + } + + return &ViewModel{ + Filters: filters, + FolderId: utils.WrapperspbStringToTypeString(view.FolderId), + Id: utils.WrapperspbInt32ToTypeInt64(view.Id), + IsCompactMode: utils.WrapperspbBoolToTypeBool(view.IsCompactMode), + Name: utils.WrapperspbStringToTypeString(view.Name), + SearchQuery: searchQuery, + TimeSelection: timeSelection, + }, nil +} + +func flattenViewTimeSelection(ctx context.Context, selection *cxsdk.TimeSelection) (types.Object, diag.Diagnostics) { + if selection == nil { + return TimeSelectionModel{ + CustomSelection: types.ObjectNull(CustomSelectionModel{}.AttributeTypes(ctx)), + QuickSelection: types.ObjectNull(QuickSelectionModel{}.AttributeTypes(ctx)), + }.ToObjectValue(ctx) + } + + if quickSelection := selection.GetQuickSelection(); quickSelection != nil { + qs, diags := flattenQuickSelection(ctx, quickSelection) + if diags.HasError() { + return types.ObjectNull(TimeSelectionModel{}.AttributeTypes(ctx)), diags + } + + return TimeSelectionModel{ + QuickSelection: qs, + CustomSelection: types.ObjectNull(CustomSelectionModel{}.AttributeTypes(ctx)), + }.ToObjectValue(ctx) + } + + if customSelection := selection.GetCustomSelection(); customSelection != nil { + cs, diags := flattenCustomSelection(ctx, customSelection) + if diags.HasError() { + return types.ObjectNull(TimeSelectionModel{}.AttributeTypes(ctx)), diags + } + return TimeSelectionModel{ + CustomSelection: cs, + QuickSelection: types.ObjectNull(QuickSelectionModel{}.AttributeTypes(ctx)), + }.ToObjectValue(ctx) + } + + return types.ObjectNull(TimeSelectionModel{}.AttributeTypes(ctx)), diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Time Selection", + "Time selection must have either quick selection or custom selection defined.", + ), + } +} + +func flattenCustomSelection(ctx context.Context, selection *cxsdk.CustomTimeSelection) (types.Object, diag.Diagnostics) { + if selection == nil { + return types.ObjectNull(CustomSelectionModel{}.AttributeTypes(ctx)), nil + } + + customSelectionModel := CustomSelectionModel{ + FromTime: types.StringValue(selection.FromTime.AsTime().Format(time.RFC3339)), + ToTime: types.StringValue(selection.ToTime.AsTime().Format(time.RFC3339)), + } + + return customSelectionModel.ToObjectValue(ctx) +} + +func flattenQuickSelection(ctx context.Context, selection *cxsdk.QuickTimeSelection) (types.Object, diag.Diagnostics) { + if selection == nil { + return types.ObjectNull(QuickSelectionModel{}.AttributeTypes(ctx)), nil + } + + quickSelectionModel := QuickSelectionModel{ + Seconds: types.Int64Value(int64(selection.Seconds)), + } + + return quickSelectionModel.ToObjectValue(ctx) +} + +func flattenSearchQuery(ctx context.Context, query *cxsdk.SearchQuery) (types.Object, diag.Diagnostics) { + if query == nil { + return types.ObjectNull(SearchQueryModel{}.AttributeTypes(ctx)), nil + } + + return SearchQueryModel{ + Query: utils.WrapperspbStringToTypeString(query.Query), + }.ToObjectValue(ctx) +} + +func flattenViewFilter(ctx context.Context, filters *cxsdk.SelectedFilters) (types.Object, diag.Diagnostics) { + if filters == nil { + return types.ObjectNull(FiltersModel{}.AttributeTypes()), nil + } + + innerFilters, diags := flattenInnerViewFilters(ctx, filters.Filters) + if diags.HasError() { + return types.ObjectNull(FiltersModel{}.AttributeTypes()), diags + } + + return FiltersModel{ + Filters: innerFilters, + }.ToObjectValue(ctx) +} + +func flattenInnerViewFilters(ctx context.Context, filters []*cxsdk.ViewFilter) (basetypes.ListValue, diag.Diagnostics) { + if filters == nil { + return types.ListNull(types.ObjectType{AttrTypes: InnerFiltersModel{}.AttributeTypes()}), nil + } + + innerFilters := make([]InnerFiltersModel, 0, len(filters)) + var diags diag.Diagnostics + for i := range filters { + var selectedValues basetypes.MapValue + if filters[i].SelectedValues == nil { + selectedValues = types.MapNull(types.BoolType) + } else { + var dgs diag.Diagnostics + selectedValues, dgs = types.MapValueFrom(ctx, types.BoolType, filters[i].SelectedValues) + if dgs.HasError() { + diags.Append(dgs...) + continue + } + } + + innerFilters = append(innerFilters, InnerFiltersModel{ + Name: utils.WrapperspbStringToTypeString(filters[i].Name), + SelectedValues: selectedValues, + }) + } + + if diags.HasError() { + return types.ListNull(types.ObjectType{AttrTypes: InnerFiltersModel{}.AttributeTypes()}), diags + } + + return types.ListValueFrom(ctx, types.ObjectType{AttrTypes: InnerFiltersModel{}.AttributeTypes()}, innerFilters) } func extractCreateView(ctx context.Context, data *ViewModel) (*cxsdk.CreateViewRequest, diag.Diagnostics) { + filters, diags := expandSelectedFilters(ctx, data.Filters) + if diags.HasError() { + return nil, diags + } + + timeSelection, diags := expandViewTimeSelection(ctx, data.TimeSelection) + if diags.HasError() { + return nil, diags + } + + searchQuery, diags := expandViewSearchQuery(ctx, data.SearchQuery) + if diags.HasError() { + return nil, diags + } + return &cxsdk.CreateViewRequest{ Name: utils.TypeStringToWrapperspbString(data.Name), - SearchQuery: expandViewSearchQuery(ctx, data.SearchQuery), - TimeSelection: expandViewTimeSelection(ctx, data.TimeSelection), + SearchQuery: searchQuery, + TimeSelection: timeSelection, + Filters: filters, + FolderId: utils.TypeStringToWrapperspbString(data.FolderId), + }, nil +} + +func extractUpdateView(ctx context.Context, data *ViewModel) (*cxsdk.ReplaceViewRequest, diag.Diagnostics) { + filters, diags := expandSelectedFilters(ctx, data.Filters) + if diags.HasError() { + return nil, diags + } + + timeSelection, diags := expandViewTimeSelection(ctx, data.TimeSelection) + if diags.HasError() { + return nil, diags + } + + searchQuery, diags := expandViewSearchQuery(ctx, data.SearchQuery) + if diags.HasError() { + return nil, diags + } + + return &cxsdk.ReplaceViewRequest{ + View: &cxsdk.View{ + Id: wrapperspb.Int32(int32(data.Id.ValueInt64())), + Name: utils.TypeStringToWrapperspbString(data.Name), + SearchQuery: searchQuery, + TimeSelection: timeSelection, + Filters: filters, + FolderId: utils.TypeStringToWrapperspbString(data.FolderId), + }, + }, nil +} + +func expandSelectedFilters(ctx context.Context, filtersObject types.Object) (*cxsdk.SelectedFilters, diag.Diagnostics) { + if filtersObject.IsNull() || filtersObject.IsUnknown() { + return nil, nil + } + + ov, _ := filtersObject.ToObjectValue(ctx) + var filters FiltersModel + if dg := ov.As(ctx, &filters, basetypes.ObjectAsOptions{}); dg.HasError() { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Filters Object", + fmt.Sprintf("Expected FiltersModel, got: %T. Please report this issue to the provider developers.", filtersObject), + ), + } + } + innerFilters, diags := expandViewFilters(ctx, filters.Filters) + if diags.HasError() { + return nil, diags + } + + return &cxsdk.SelectedFilters{ + Filters: innerFilters, }, nil + +} + +func expandViewFilters(ctx context.Context, filters basetypes.ListValue) ([]*cxsdk.ViewFilter, diag.Diagnostics) { + if filters.IsNull() || filters.IsUnknown() { + return nil, nil + } + + var diags diag.Diagnostics + var filtersObjects []types.Object + diags = filters.ElementsAs(ctx, &filtersObjects, true) + innerFilters := make([]*cxsdk.ViewFilter, 0, len(filtersObjects)) + for _, fo := range filtersObjects { + var innerFilterValue InnerFiltersModel + if dg := fo.As(ctx, &innerFilterValue, basetypes.ObjectAsOptions{}); dg.HasError() { + diags.Append(dg...) + continue + } + + var selectedValues map[string]bool + if dg := innerFilterValue.SelectedValues.ElementsAs(ctx, &selectedValues, true); dg.HasError() { + diags.Append(dg...) + continue + } + innerFilters = append(innerFilters, &cxsdk.ViewFilter{ + Name: utils.TypeStringToWrapperspbString(innerFilterValue.Name), + SelectedValues: selectedValues, + }) + } + + if diags.HasError() { + return nil, diags + } + + return innerFilters, nil } -func expandViewSearchQuery(ctx context.Context, query SearchQueryValue) *cxsdk.SearchQuery { - if query.IsNull() || query.IsUnknown() { - return nil +func expandViewSearchQuery(ctx context.Context, queryObject types.Object) (*cxsdk.SearchQuery, diag.Diagnostics) { + if queryObject.IsNull() || queryObject.IsUnknown() { + return nil, nil + } + + ov, _ := queryObject.ToObjectValue(ctx) + var query SearchQueryModel + if dg := ov.As(ctx, &query, basetypes.ObjectAsOptions{}); dg.HasError() { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Search Query Object", + fmt.Sprintf("Expected SearchQueryModel, got: %T. Please report this issue to the provider developers.", queryObject), + ), + } } return &cxsdk.SearchQuery{ Query: utils.TypeStringToWrapperspbString(query.Query), + }, nil +} + +func expandViewTimeSelection(ctx context.Context, selectionObject types.Object) (*cxsdk.TimeSelection, diag.Diagnostics) { + if selectionObject.IsNull() || selectionObject.IsUnknown() { + return nil, nil + } + + var selection TimeSelectionModel + if dg := selectionObject.As(ctx, &selection, basetypes.ObjectAsOptions{}); dg.HasError() { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Time Selection Object", + fmt.Sprintf("Expected TimeSelectionModel, got: %T. Please report this issue to the provider developers.", selectionObject), + ), + } + } + + if quickSelection := selection.QuickSelection; !(quickSelection.IsNull() || quickSelection.IsUnknown()) { + qs, diags := expandQuickSelection(ctx, selection.QuickSelection) + if diags.HasError() { + return nil, diags + } + return &cxsdk.TimeSelection{ + SelectionType: &cxsdk.ViewTimeSelectionQuick{ + QuickSelection: qs, + }, + }, nil + } else if customSelection := selection.CustomSelection; !(customSelection.IsNull() || customSelection.IsUnknown()) { + cs, diags := expandCustomSelection(ctx, selection.CustomSelection) + if diags.HasError() { + return nil, diags + } + return &cxsdk.TimeSelection{ + SelectionType: &cxsdk.ViewTimeSelectionCustom{ + CustomSelection: cs, + }, + }, nil + } + + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Time Selection", + "Time selection must have either quick selection or custom selection defined.", + ), + } +} + +func expandCustomSelection(ctx context.Context, selection types.Object) (*cxsdk.CustomTimeSelection, diag.Diagnostics) { + if selection.IsNull() || selection.IsUnknown() { + return nil, nil + } + + attributes := selection.Attributes() + fromTimeAttr, ok := attributes["from_time"] + if !ok { + return nil, nil + } + toTimeAttr, ok := attributes["to_time"] + if !ok { + return nil, nil + } + + fromTime, ok := fromTimeAttr.(types.String) + if !ok || fromTime.IsNull() || fromTime.IsUnknown() { + return nil, nil + } + toTime, ok := toTimeAttr.(types.String) + if !ok || toTime.IsNull() || toTime.IsUnknown() { + return nil, nil + } + + ft, err := time.Parse(time.RFC3339, fromTime.ValueString()) + if err != nil { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid From Time Format", + fmt.Sprintf("From time '%s' is not in RFC3339 format: %s", fromTime.ValueString(), err.Error()), + ), + } + } + tt, err := time.Parse(time.RFC3339, toTime.ValueString()) + if err != nil { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid To Time Format", + fmt.Sprintf("To time '%s' is not in RFC3339 format: %s", toTime.ValueString(), err.Error()), + ), + } } + + return &cxsdk.CustomTimeSelection{ + FromTime: timestamppb.New(ft), + ToTime: timestamppb.New(tt), + }, nil } -func expandViewTimeSelection(ctx context.Context, selection TimeSelectionValue) *cxsdk.TimeSelection { +func expandQuickSelection(ctx context.Context, selection types.Object) (*cxsdk.QuickTimeSelection, diag.Diagnostics) { if selection.IsNull() || selection.IsUnknown() { - return nil + return nil, nil } - return nil + + attributes := selection.Attributes() + secondsAttr, ok := attributes["seconds"] + if !ok { + return nil, nil + } + + seconds, ok := secondsAttr.(types.Int64) + if !ok || seconds.IsNull() || seconds.IsUnknown() { + return nil, nil + } + + if seconds.ValueInt64() < 0 { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Seconds Value", + fmt.Sprintf("Seconds value '%d' cannot be negative.", seconds.ValueInt64()), + ), + } + } + + return &cxsdk.QuickTimeSelection{ + Seconds: uint32(seconds.ValueInt64()), + }, nil } -func (r *ViewsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +func (r *ViewResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var data *ViewModel // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { return } - // Read API call logic + id := data.Id.ValueInt64() + readReq := &cxsdk.GetViewRequest{ + Id: wrapperspb.Int32(int32(id)), + } + log.Printf("[INFO] Reading view with ID: %d", id) + readResp, err := r.client.Get(ctx, readReq) + if err != nil { + log.Printf("[ERROR] Received error: %s", err.Error()) + if cxsdk.Code(err) == codes.NotFound { + resp.Diagnostics.AddWarning( + fmt.Sprintf("View %q is in state, but no longer exists in Coralogix backend", id), + fmt.Sprintf("%d will be recreated when you apply", id), + ) + resp.State.RemoveResource(ctx) + } else { + resp.Diagnostics.AddError( + "Error reading view", + utils.FormatRpcErrors(err, cxsdk.GetViewRPC, protojson.Format(readReq)), + ) + } + return + } + log.Printf("[INFO] View read successfully: %s", protojson.Format(readResp.View)) + + // Flatten the response into the model + data, diags := flattenView(ctx, readResp.View) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } - // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r *ViewsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (r *ViewResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var data *ViewModel // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - if resp.Diagnostics.HasError() { return } - // Update API call logic + // Save updated data into Terraform state + updateReq, diags := extractUpdateView(ctx, data) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + log.Printf("[INFO] Updating view in state: %s", protojson.Format(updateReq)) + updateResp, err := r.client.Replace(ctx, updateReq) + if err != nil { + log.Printf("[ERROR] Received error: %s", err.Error()) + resp.Diagnostics.AddError( + "Error updating view in state", + utils.FormatRpcErrors(err, cxsdk.ReplaceViewRPC, protojson.Format(updateReq)), + ) + return + } + log.Printf("[INFO] View updated in state successfully: %s", protojson.Format(updateResp.View)) + // Flatten the response into the model + data, diags = flattenView(ctx, updateResp.View) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func (r *ViewsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +func (r *ViewResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var data *ViewModel // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + _, err := r.client.Delete(ctx, &cxsdk.DeleteViewRequest{Id: wrapperspb.Int32(int32(data.Id.ValueInt64()))}) + if err != nil { + log.Printf("[ERROR] Received error: %s", err.Error()) + if cxsdk.Code(err) == codes.NotFound { + resp.Diagnostics.AddWarning( + fmt.Sprintf("View %q is in state, but no longer exists in Coralogix backend", data.Id.ValueInt64()), + fmt.Sprintf("%d will be removed from state", data.Id.ValueInt64()), + ) + resp.State.RemoveResource(ctx) + } else { + resp.Diagnostics.AddError( + "Error deleting view", + utils.FormatRpcErrors(err, cxsdk.DeleteViewRPC, fmt.Sprintf("ID: %d", data.Id.ValueInt64())), + ) + } + return + } if resp.Diagnostics.HasError() { return } - - // Delete API call logic } func ViewResourceSchema(ctx context.Context) schema.Schema { @@ -185,30 +655,25 @@ func ViewResourceSchema(ctx context.Context) schema.Schema { MarkdownDescription: "Filter selected values", }, }, - CustomType: InnerFiltersType{ - ObjectType: types.ObjectType{ - AttrTypes: FiltersValue{}.AttributeTypes(ctx), - }, - }, }, Optional: true, Computed: true, Validators: []validator.List{ listvalidator.SizeAtLeast(1), }, - }, - }, - CustomType: FiltersType{ - ObjectType: types.ObjectType{ - AttrTypes: FiltersValue{}.AttributeTypes(ctx), + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, }, }, Optional: true, Computed: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, }, "folder_id": schema.StringAttribute{ Optional: true, - Computed: true, Description: "Unique identifier for folders", MarkdownDescription: "Unique identifier for folders", Validators: []validator.String{ @@ -220,9 +685,15 @@ func ViewResourceSchema(ctx context.Context) schema.Schema { Computed: true, Description: "id", MarkdownDescription: "id", + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, }, "is_compact_mode": schema.BoolAttribute{ Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, }, "name": schema.StringAttribute{ Required: true, @@ -241,13 +712,11 @@ func ViewResourceSchema(ctx context.Context) schema.Schema { }, }, }, - CustomType: SearchQueryType{ - ObjectType: types.ObjectType{ - AttrTypes: SearchQueryValue{}.AttributeTypes(ctx), - }, - }, Optional: true, Computed: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, }, "time_selection": schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ @@ -266,46 +735,24 @@ func ViewResourceSchema(ctx context.Context) schema.Schema { }, }, }, - CustomType: CustomSelectionType{ - ObjectType: types.ObjectType{ - AttrTypes: CustomSelectionValue{}.AttributeTypes(ctx), - }, - }, - Optional: true, Validators: []validator.Object{ - objectvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("quick_selection")), + objectvalidator.ExactlyOneOf( + path.MatchRoot("time_selection").AtName("quick_selection"), + ), }, + Optional: true, }, "quick_selection": schema.SingleNestedAttribute{ Attributes: map[string]schema.Attribute{ - "caption": schema.StringAttribute{ - Optional: true, - Computed: true, - Description: "Folder name", - MarkdownDescription: "Folder name", - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - }, "seconds": schema.Int64Attribute{ Required: true, Description: "Folder name", MarkdownDescription: "Folder name", }, }, - CustomType: QuickSelectionType{ - ObjectType: types.ObjectType{ - AttrTypes: QuickSelectionValue{}.AttributeTypes(ctx), - }, - }, Optional: true, }, }, - CustomType: TimeSelectionType{ - ObjectType: types.ObjectType{ - AttrTypes: TimeSelectionValue{}.AttributeTypes(ctx), - }, - }, Required: true, }, }, @@ -313,2291 +760,111 @@ func ViewResourceSchema(ctx context.Context) schema.Schema { } type ViewModel struct { - Filters FiltersValue `tfsdk:"filters"` - FolderId types.String `tfsdk:"folder_id"` - Id types.Int64 `tfsdk:"id"` - IsCompactMode types.Bool `tfsdk:"is_compact_mode"` - Name types.String `tfsdk:"name"` - SearchQuery SearchQueryValue `tfsdk:"search_query"` - TimeSelection TimeSelectionValue `tfsdk:"time_selection"` + Filters types.Object `tfsdk:"filters"` //FiltersModel + FolderId types.String `tfsdk:"folder_id"` + Id types.Int64 `tfsdk:"id"` + IsCompactMode types.Bool `tfsdk:"is_compact_mode"` + Name types.String `tfsdk:"name"` + SearchQuery types.Object `tfsdk:"search_query"` //SearchQueryModel + TimeSelection types.Object `tfsdk:"time_selection"` // TimeSelectionModel } -var _ basetypes.ObjectTypable = FiltersType{} - -type FiltersType struct { - basetypes.ObjectType +type QuickSelectionModel struct { + Seconds types.Int64 `tfsdk:"seconds"` } -func (t FiltersType) Equal(o attr.Type) bool { - other, ok := o.(FiltersType) +func (v QuickSelectionModel) ToObjectValue(ctx context.Context) (types.Object, diag.Diagnostics) { + return types.ObjectValueFrom(ctx, v.AttributeTypes(ctx), v) +} - if !ok { - return false +func (v QuickSelectionModel) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "seconds": basetypes.Int64Type{}, } - - return t.ObjectType.Equal(other.ObjectType) } -func (t FiltersType) String() string { - return "FiltersType" +type FiltersModel struct { + Filters types.List `tfsdk:"filters"` // InnerFiltersModel } -func (t FiltersType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { - var diags diag.Diagnostics +func (v FiltersModel) ToObjectValue(ctx context.Context) (types.Object, diag.Diagnostics) { + return types.ObjectValueFrom(ctx, v.AttributeTypes(), v) +} - attributes := in.Attributes() +func (v FiltersModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "filters": basetypes.ListType{ + ElemType: basetypes.ObjectType{ + AttrTypes: InnerFiltersModel{}.AttributeTypes(), + }, + }, + } +} - filtersAttribute, ok := attributes["filters"] +type InnerFiltersModel struct { + Name types.String `tfsdk:"name"` + SelectedValues types.Map `tfsdk:"selected_values"` +} - if !ok { - diags.AddError( - "Attribute Missing", - `filters is missing from object`) +func (v InnerFiltersModel) ToObjectValue(ctx context.Context) (types.Object, diag.Diagnostics) { + return types.ObjectValueFrom(ctx, v.AttributeTypes(), v) +} - return nil, diags +func (v InnerFiltersModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "name": basetypes.StringType{}, + "selected_values": basetypes.MapType{ + ElemType: types.BoolType, + }, } +} - filtersVal, ok := filtersAttribute.(basetypes.ListValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`filters expected to be basetypes.ListValue, was: %T`, filtersAttribute)) - } - - if diags.HasError() { - return nil, diags - } - - return FiltersValue{ - Filters: filtersVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewFiltersValueNull() FiltersValue { - return FiltersValue{ - state: attr.ValueStateNull, - } -} - -func NewFiltersValueUnknown() FiltersValue { - return FiltersValue{ - state: attr.ValueStateUnknown, - } -} - -func NewFiltersValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (FiltersValue, diag.Diagnostics) { - var diags diag.Diagnostics - - // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 - ctx := context.Background() - - for name, attributeType := range attributeTypes { - attribute, ok := attributes[name] - - if !ok { - diags.AddError( - "Missing FiltersValue Attribute Value", - "While creating a FiltersValue value, a missing attribute value was detected. "+ - "A FiltersValue must contain values for all attributes, even if null or unknown. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("FiltersValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), - ) - - continue - } - - if !attributeType.Equal(attribute.Type(ctx)) { - diags.AddError( - "Invalid FiltersValue Attribute Type", - "While creating a FiltersValue value, an invalid attribute value was detected. "+ - "A FiltersValue must use a matching attribute type for the value. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("FiltersValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ - fmt.Sprintf("FiltersValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), - ) - } - } - - for name := range attributes { - _, ok := attributeTypes[name] - - if !ok { - diags.AddError( - "Extra FiltersValue Attribute Value", - "While creating a FiltersValue value, an extra attribute value was detected. "+ - "A FiltersValue must not contain values beyond the expected attribute types. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("Extra FiltersValue Attribute Name: %s", name), - ) - } - } - - if diags.HasError() { - return NewFiltersValueUnknown(), diags - } - - filtersAttribute, ok := attributes["filters"] - - if !ok { - diags.AddError( - "Attribute Missing", - `filters is missing from object`) - - return NewFiltersValueUnknown(), diags - } - - filtersVal, ok := filtersAttribute.(basetypes.ListValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`filters expected to be basetypes.ListValue, was: %T`, filtersAttribute)) - } - - if diags.HasError() { - return NewFiltersValueUnknown(), diags - } - - return FiltersValue{ - Filters: filtersVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewFiltersValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) FiltersValue { - object, diags := NewFiltersValue(attributeTypes, attributes) - - if diags.HasError() { - // This could potentially be added to the diag package. - diagsStrings := make([]string, 0, len(diags)) - - for _, diagnostic := range diags { - diagsStrings = append(diagsStrings, fmt.Sprintf( - "%s | %s | %s", - diagnostic.Severity(), - diagnostic.Summary(), - diagnostic.Detail())) - } - - panic("NewFiltersValueMust received error(s): " + strings.Join(diagsStrings, "\n")) - } - - return object -} - -func (t FiltersType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { - if in.Type() == nil { - return NewFiltersValueNull(), nil - } - - if !in.Type().Equal(t.TerraformType(ctx)) { - return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) - } - - if !in.IsKnown() { - return NewFiltersValueUnknown(), nil - } - - if in.IsNull() { - return NewFiltersValueNull(), nil - } - - attributes := map[string]attr.Value{} - - val := map[string]tftypes.Value{} - - err := in.As(&val) - - if err != nil { - return nil, err - } - - for k, v := range val { - a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) - - if err != nil { - return nil, err - } - - attributes[k] = a - } - - return NewFiltersValueMust(FiltersValue{}.AttributeTypes(ctx), attributes), nil -} - -func (t FiltersType) ValueType(ctx context.Context) attr.Value { - return FiltersValue{} -} - -var _ basetypes.ObjectValuable = FiltersValue{} - -type FiltersValue struct { - Filters basetypes.ListValue `tfsdk:"filters"` - state attr.ValueState -} - -func (v FiltersValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { - attrTypes := make(map[string]tftypes.Type, 1) - - var val tftypes.Value - var err error - - attrTypes["filters"] = basetypes.ListType{ - ElemType: FiltersValue{}.Type(ctx), - }.TerraformType(ctx) - - objectType := tftypes.Object{AttributeTypes: attrTypes} - - switch v.state { - case attr.ValueStateKnown: - vals := make(map[string]tftypes.Value, 1) - - val, err = v.Filters.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["filters"] = val - - if err := tftypes.ValidateValue(objectType, vals); err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - return tftypes.NewValue(objectType, vals), nil - case attr.ValueStateNull: - return tftypes.NewValue(objectType, nil), nil - case attr.ValueStateUnknown: - return tftypes.NewValue(objectType, tftypes.UnknownValue), nil - default: - panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) - } -} - -func (v FiltersValue) IsNull() bool { - return v.state == attr.ValueStateNull -} - -func (v FiltersValue) IsUnknown() bool { - return v.state == attr.ValueStateUnknown -} - -func (v FiltersValue) String() string { - return "FiltersValue" -} - -func (v FiltersValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { - var diags diag.Diagnostics - - filters := types.ListValueMust( - FiltersType{ - basetypes.ObjectType{ - AttrTypes: FiltersValue{}.AttributeTypes(ctx), - }, - }, - v.Filters.Elements(), - ) - - if v.Filters.IsNull() { - filters = types.ListNull( - FiltersType{ - basetypes.ObjectType{ - AttrTypes: FiltersValue{}.AttributeTypes(ctx), - }, - }, - ) - } - - if v.Filters.IsUnknown() { - filters = types.ListUnknown( - FiltersType{ - basetypes.ObjectType{ - AttrTypes: FiltersValue{}.AttributeTypes(ctx), - }, - }, - ) - } - - attributeTypes := map[string]attr.Type{ - "filters": basetypes.ListType{ - ElemType: FiltersValue{}.Type(ctx), - }, - } - - if v.IsNull() { - return types.ObjectNull(attributeTypes), diags - } - - if v.IsUnknown() { - return types.ObjectUnknown(attributeTypes), diags - } - - objVal, diags := types.ObjectValue( - attributeTypes, - map[string]attr.Value{ - "filters": filters, - }) - - return objVal, diags -} - -func (v FiltersValue) Equal(o attr.Value) bool { - other, ok := o.(FiltersValue) - - if !ok { - return false - } - - if v.state != other.state { - return false - } - - if v.state != attr.ValueStateKnown { - return true - } - - if !v.Filters.Equal(other.Filters) { - return false - } - - return true -} - -func (v FiltersValue) Type(ctx context.Context) attr.Type { - return FiltersType{ - basetypes.ObjectType{ - AttrTypes: v.AttributeTypes(ctx), - }, - } -} - -func (v FiltersValue) AttributeTypes(ctx context.Context) map[string]attr.Type { - return map[string]attr.Type{ - "filters": basetypes.ListType{ - ElemType: FiltersValue{}.Type(ctx), - }, - } -} - -var _ basetypes.ObjectTypable = FiltersType{} - -type InnerFiltersType struct { - basetypes.ObjectType -} - -func (t InnerFiltersType) Equal(o attr.Type) bool { - other, ok := o.(InnerFiltersType) - - if !ok { - return false - } - - return t.ObjectType.Equal(other.ObjectType) -} - -func (t InnerFiltersType) String() string { - return "InnerFiltersType" -} - -func (t InnerFiltersType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { - var diags diag.Diagnostics - - attributes := in.Attributes() - - nameAttribute, ok := attributes["name"] - - if !ok { - diags.AddError( - "Attribute Missing", - `name is missing from object`) - - return nil, diags - } - - nameVal, ok := nameAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute)) - } - - selectedValuesAttribute, ok := attributes["selected_values"] - - if !ok { - diags.AddError( - "Attribute Missing", - `selected_values is missing from object`) - - return nil, diags - } - - selectedValuesVal, ok := selectedValuesAttribute.(basetypes.MapValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`selected_values expected to be basetypes.MapValue, was: %T`, selectedValuesAttribute)) - } - - if diags.HasError() { - return nil, diags - } - - return InnerFiltersValue{ - Name: nameVal, - SelectedValues: selectedValuesVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewInnerFiltersValueNull() InnerFiltersValue { - return InnerFiltersValue{ - state: attr.ValueStateNull, - } -} - -func NewInnerFiltersValueUnknown() InnerFiltersValue { - return InnerFiltersValue{ - state: attr.ValueStateUnknown, - } -} - -func NewInnerFiltersValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (InnerFiltersValue, diag.Diagnostics) { - var diags diag.Diagnostics - - // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 - ctx := context.Background() - - for name, attributeType := range attributeTypes { - attribute, ok := attributes[name] - - if !ok { - diags.AddError( - "Missing FiltersValue Attribute Value", - "While creating a FiltersValue value, a missing attribute value was detected. "+ - "A FiltersValue must contain values for all attributes, even if null or unknown. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("FiltersValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), - ) - - continue - } - - if !attributeType.Equal(attribute.Type(ctx)) { - diags.AddError( - "Invalid FiltersValue Attribute Type", - "While creating a FiltersValue value, an invalid attribute value was detected. "+ - "A FiltersValue must use a matching attribute type for the value. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("FiltersValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ - fmt.Sprintf("FiltersValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), - ) - } - } - - for name := range attributes { - _, ok := attributeTypes[name] - - if !ok { - diags.AddError( - "Extra FiltersValue Attribute Value", - "While creating a FiltersValue value, an extra attribute value was detected. "+ - "A FiltersValue must not contain values beyond the expected attribute types. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("Extra FiltersValue Attribute Name: %s", name), - ) - } - } - - if diags.HasError() { - return NewInnerFiltersValueUnknown(), diags - } - - nameAttribute, ok := attributes["name"] - - if !ok { - diags.AddError( - "Attribute Missing", - `name is missing from object`) - - return NewInnerFiltersValueUnknown(), diags - } - - nameVal, ok := nameAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute)) - } - - selectedValuesAttribute, ok := attributes["selected_values"] - - if !ok { - diags.AddError( - "Attribute Missing", - `selected_values is missing from object`) - - return NewInnerFiltersValueUnknown(), diags - } - - selectedValuesVal, ok := selectedValuesAttribute.(basetypes.MapValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`selected_values expected to be basetypes.MapValue, was: %T`, selectedValuesAttribute)) - } - - if diags.HasError() { - return NewInnerFiltersValueUnknown(), diags - } - - return InnerFiltersValue{ - Name: nameVal, - SelectedValues: selectedValuesVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewInnerFiltersValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) InnerFiltersValue { - object, diags := NewInnerFiltersValue(attributeTypes, attributes) - - if diags.HasError() { - // This could potentially be added to the diag package. - diagsStrings := make([]string, 0, len(diags)) - - for _, diagnostic := range diags { - diagsStrings = append(diagsStrings, fmt.Sprintf( - "%s | %s | %s", - diagnostic.Severity(), - diagnostic.Summary(), - diagnostic.Detail())) - } - - panic("NewFiltersValueMust received error(s): " + strings.Join(diagsStrings, "\n")) - } - - return object -} - -func (t InnerFiltersType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { - if in.Type() == nil { - return NewInnerFiltersValueNull(), nil - } - - if !in.Type().Equal(t.TerraformType(ctx)) { - return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) - } - - if !in.IsKnown() { - return NewInnerFiltersValueUnknown(), nil - } - - if in.IsNull() { - return NewInnerFiltersValueNull(), nil - } - - attributes := map[string]attr.Value{} - - val := map[string]tftypes.Value{} - - err := in.As(&val) - - if err != nil { - return nil, err - } - - for k, v := range val { - a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) - - if err != nil { - return nil, err - } - - attributes[k] = a - } - - return NewInnerFiltersValueMust(FiltersValue{}.AttributeTypes(ctx), attributes), nil -} - -func (t InnerFiltersType) ValueType(ctx context.Context) attr.Value { - return FiltersValue{} -} - -var _ basetypes.ObjectValuable = InnerFiltersValue{} - -type InnerFiltersValue struct { - Name basetypes.StringValue `tfsdk:"name"` - SelectedValues basetypes.MapValue `tfsdk:"selected_values"` - state attr.ValueState -} - -func (v InnerFiltersValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { - attrTypes := make(map[string]tftypes.Type, 2) - - var val tftypes.Value - var err error - - attrTypes["name"] = basetypes.StringType{}.TerraformType(ctx) - attrTypes["selected_values"] = basetypes.MapType{ - ElemType: types.BoolType, - }.TerraformType(ctx) - - objectType := tftypes.Object{AttributeTypes: attrTypes} - - switch v.state { - case attr.ValueStateKnown: - vals := make(map[string]tftypes.Value, 2) - - val, err = v.Name.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["name"] = val - - val, err = v.SelectedValues.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["selected_values"] = val - - if err := tftypes.ValidateValue(objectType, vals); err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - return tftypes.NewValue(objectType, vals), nil - case attr.ValueStateNull: - return tftypes.NewValue(objectType, nil), nil - case attr.ValueStateUnknown: - return tftypes.NewValue(objectType, tftypes.UnknownValue), nil - default: - panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) - } -} - -func (v InnerFiltersValue) IsNull() bool { - return v.state == attr.ValueStateNull -} - -func (v InnerFiltersValue) IsUnknown() bool { - return v.state == attr.ValueStateUnknown -} - -func (v InnerFiltersValue) String() string { - return "InnerFiltersValue" -} - -func (v InnerFiltersValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { - var diags diag.Diagnostics - - var selectedValuesVal basetypes.MapValue - switch { - case v.SelectedValues.IsUnknown(): - selectedValuesVal = types.MapUnknown(types.BoolType) - case v.SelectedValues.IsNull(): - selectedValuesVal = types.MapNull(types.BoolType) - default: - var d diag.Diagnostics - selectedValuesVal, d = types.MapValue(types.BoolType, v.SelectedValues.Elements()) - diags.Append(d...) - } - - if diags.HasError() { - return types.ObjectUnknown(map[string]attr.Type{ - "name": basetypes.StringType{}, - "selected_values": basetypes.MapType{ - ElemType: types.BoolType, - }, - }), diags - } - - attributeTypes := map[string]attr.Type{ - "name": basetypes.StringType{}, - "selected_values": basetypes.MapType{ - ElemType: types.BoolType, - }, - } - - if v.IsNull() { - return types.ObjectNull(attributeTypes), diags - } - - if v.IsUnknown() { - return types.ObjectUnknown(attributeTypes), diags - } - - objVal, diags := types.ObjectValue( - attributeTypes, - map[string]attr.Value{ - "name": v.Name, - "selected_values": selectedValuesVal, - }) - - return objVal, diags -} - -func (v InnerFiltersValue) Equal(o attr.Value) bool { - other, ok := o.(InnerFiltersValue) - - if !ok { - return false - } - - if v.state != other.state { - return false - } - - if v.state != attr.ValueStateKnown { - return true - } - - if !v.Name.Equal(other.Name) { - return false - } - - if !v.SelectedValues.Equal(other.SelectedValues) { - return false - } - - return true -} - -func (v InnerFiltersValue) Type(ctx context.Context) attr.Type { - return InnerFiltersType{ - basetypes.ObjectType{ - AttrTypes: v.AttributeTypes(ctx), - }, - } -} - -func (v InnerFiltersValue) AttributeTypes(ctx context.Context) map[string]attr.Type { - return map[string]attr.Type{ - "name": basetypes.StringType{}, - "selected_values": basetypes.MapType{ - ElemType: types.BoolType, - }, - } -} - -var _ basetypes.ObjectTypable = SearchQueryType{} - -type SearchQueryType struct { - basetypes.ObjectType -} - -func (t SearchQueryType) Equal(o attr.Type) bool { - other, ok := o.(SearchQueryType) - - if !ok { - return false - } - - return t.ObjectType.Equal(other.ObjectType) -} - -func (t SearchQueryType) String() string { - return "SearchQueryType" -} - -func (t SearchQueryType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { - var diags diag.Diagnostics - - attributes := in.Attributes() - - queryAttribute, ok := attributes["query"] - - if !ok { - diags.AddError( - "Attribute Missing", - `query is missing from object`) - - return nil, diags - } - - queryVal, ok := queryAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`query expected to be basetypes.StringValue, was: %T`, queryAttribute)) - } - - if diags.HasError() { - return nil, diags - } - - return SearchQueryValue{ - Query: queryVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewSearchQueryValueNull() SearchQueryValue { - return SearchQueryValue{ - state: attr.ValueStateNull, - } -} - -func NewSearchQueryValueUnknown() SearchQueryValue { - return SearchQueryValue{ - state: attr.ValueStateUnknown, - } -} - -func NewSearchQueryValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (SearchQueryValue, diag.Diagnostics) { - var diags diag.Diagnostics - - // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 - ctx := context.Background() - - for name, attributeType := range attributeTypes { - attribute, ok := attributes[name] - - if !ok { - diags.AddError( - "Missing SearchQueryValue Attribute Value", - "While creating a SearchQueryValue value, a missing attribute value was detected. "+ - "A SearchQueryValue must contain values for all attributes, even if null or unknown. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("SearchQueryValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), - ) - - continue - } - - if !attributeType.Equal(attribute.Type(ctx)) { - diags.AddError( - "Invalid SearchQueryValue Attribute Type", - "While creating a SearchQueryValue value, an invalid attribute value was detected. "+ - "A SearchQueryValue must use a matching attribute type for the value. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("SearchQueryValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ - fmt.Sprintf("SearchQueryValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), - ) - } - } - - for name := range attributes { - _, ok := attributeTypes[name] - - if !ok { - diags.AddError( - "Extra SearchQueryValue Attribute Value", - "While creating a SearchQueryValue value, an extra attribute value was detected. "+ - "A SearchQueryValue must not contain values beyond the expected attribute types. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("Extra SearchQueryValue Attribute Name: %s", name), - ) - } - } - - if diags.HasError() { - return NewSearchQueryValueUnknown(), diags - } - - queryAttribute, ok := attributes["query"] - - if !ok { - diags.AddError( - "Attribute Missing", - `query is missing from object`) - - return NewSearchQueryValueUnknown(), diags - } - - queryVal, ok := queryAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`query expected to be basetypes.StringValue, was: %T`, queryAttribute)) - } - - if diags.HasError() { - return NewSearchQueryValueUnknown(), diags - } - - return SearchQueryValue{ - Query: queryVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewSearchQueryValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) SearchQueryValue { - object, diags := NewSearchQueryValue(attributeTypes, attributes) - - if diags.HasError() { - // This could potentially be added to the diag package. - diagsStrings := make([]string, 0, len(diags)) - - for _, diagnostic := range diags { - diagsStrings = append(diagsStrings, fmt.Sprintf( - "%s | %s | %s", - diagnostic.Severity(), - diagnostic.Summary(), - diagnostic.Detail())) - } - - panic("NewSearchQueryValueMust received error(s): " + strings.Join(diagsStrings, "\n")) - } - - return object -} - -func (t SearchQueryType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { - if in.Type() == nil { - return NewSearchQueryValueNull(), nil - } - - if !in.Type().Equal(t.TerraformType(ctx)) { - return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) - } - - if !in.IsKnown() { - return NewSearchQueryValueUnknown(), nil - } - - if in.IsNull() { - return NewSearchQueryValueNull(), nil - } - - attributes := map[string]attr.Value{} - - val := map[string]tftypes.Value{} - - err := in.As(&val) - - if err != nil { - return nil, err - } - - for k, v := range val { - a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) - - if err != nil { - return nil, err - } - - attributes[k] = a - } - - return NewSearchQueryValueMust(SearchQueryValue{}.AttributeTypes(ctx), attributes), nil -} - -func (t SearchQueryType) ValueType(ctx context.Context) attr.Value { - return SearchQueryValue{} -} - -var _ basetypes.ObjectValuable = SearchQueryValue{} - -type SearchQueryValue struct { - Query basetypes.StringValue `tfsdk:"query"` - state attr.ValueState -} - -func (v SearchQueryValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { - attrTypes := make(map[string]tftypes.Type, 1) - - var val tftypes.Value - var err error - - attrTypes["query"] = basetypes.StringType{}.TerraformType(ctx) - - objectType := tftypes.Object{AttributeTypes: attrTypes} - - switch v.state { - case attr.ValueStateKnown: - vals := make(map[string]tftypes.Value, 1) - - val, err = v.Query.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["query"] = val - - if err := tftypes.ValidateValue(objectType, vals); err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - return tftypes.NewValue(objectType, vals), nil - case attr.ValueStateNull: - return tftypes.NewValue(objectType, nil), nil - case attr.ValueStateUnknown: - return tftypes.NewValue(objectType, tftypes.UnknownValue), nil - default: - panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) - } -} - -func (v SearchQueryValue) IsNull() bool { - return v.state == attr.ValueStateNull -} - -func (v SearchQueryValue) IsUnknown() bool { - return v.state == attr.ValueStateUnknown -} - -func (v SearchQueryValue) String() string { - return "SearchQueryValue" -} - -func (v SearchQueryValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { - var diags diag.Diagnostics - - attributeTypes := map[string]attr.Type{ - "query": basetypes.StringType{}, - } - - if v.IsNull() { - return types.ObjectNull(attributeTypes), diags - } - - if v.IsUnknown() { - return types.ObjectUnknown(attributeTypes), diags - } - - objVal, diags := types.ObjectValue( - attributeTypes, - map[string]attr.Value{ - "query": v.Query, - }) - - return objVal, diags -} - -func (v SearchQueryValue) Equal(o attr.Value) bool { - other, ok := o.(SearchQueryValue) - - if !ok { - return false - } - - if v.state != other.state { - return false - } - - if v.state != attr.ValueStateKnown { - return true - } - - if !v.Query.Equal(other.Query) { - return false - } - - return true -} - -func (v SearchQueryValue) Type(ctx context.Context) attr.Type { - return SearchQueryType{ - basetypes.ObjectType{ - AttrTypes: v.AttributeTypes(ctx), - }, - } -} - -func (v SearchQueryValue) AttributeTypes(ctx context.Context) map[string]attr.Type { - return map[string]attr.Type{ - "query": basetypes.StringType{}, - } -} - -var _ basetypes.ObjectTypable = TimeSelectionType{} - -type TimeSelectionType struct { - basetypes.ObjectType -} - -func (t TimeSelectionType) Equal(o attr.Type) bool { - other, ok := o.(TimeSelectionType) - - if !ok { - return false - } - - return t.ObjectType.Equal(other.ObjectType) -} - -func (t TimeSelectionType) String() string { - return "TimeSelectionType" -} - -func (t TimeSelectionType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { - var diags diag.Diagnostics - - attributes := in.Attributes() - - customSelectionAttribute, ok := attributes["custom_selection"] - - if !ok { - diags.AddError( - "Attribute Missing", - `custom_selection is missing from object`) - - return nil, diags - } - - customSelectionVal, ok := customSelectionAttribute.(basetypes.ObjectValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`custom_selection expected to be basetypes.ObjectValue, was: %T`, customSelectionAttribute)) - } - - quickSelectionAttribute, ok := attributes["quick_selection"] - - if !ok { - diags.AddError( - "Attribute Missing", - `quick_selection is missing from object`) - - return nil, diags - } - - quickSelectionVal, ok := quickSelectionAttribute.(basetypes.ObjectValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`quick_selection expected to be basetypes.ObjectValue, was: %T`, quickSelectionAttribute)) - } - - if diags.HasError() { - return nil, diags - } - - return TimeSelectionValue{ - CustomSelection: customSelectionVal, - QuickSelection: quickSelectionVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewTimeSelectionValueNull() TimeSelectionValue { - return TimeSelectionValue{ - state: attr.ValueStateNull, - } -} - -func NewTimeSelectionValueUnknown() TimeSelectionValue { - return TimeSelectionValue{ - state: attr.ValueStateUnknown, - } -} - -func NewTimeSelectionValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (TimeSelectionValue, diag.Diagnostics) { - var diags diag.Diagnostics - - // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 - ctx := context.Background() - - for name, attributeType := range attributeTypes { - attribute, ok := attributes[name] - - if !ok { - diags.AddError( - "Missing TimeSelectionValue Attribute Value", - "While creating a TimeSelectionValue value, a missing attribute value was detected. "+ - "A TimeSelectionValue must contain values for all attributes, even if null or unknown. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("TimeSelectionValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), - ) - - continue - } - - if !attributeType.Equal(attribute.Type(ctx)) { - diags.AddError( - "Invalid TimeSelectionValue Attribute Type", - "While creating a TimeSelectionValue value, an invalid attribute value was detected. "+ - "A TimeSelectionValue must use a matching attribute type for the value. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("TimeSelectionValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ - fmt.Sprintf("TimeSelectionValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), - ) - } - } - - for name := range attributes { - _, ok := attributeTypes[name] - - if !ok { - diags.AddError( - "Extra TimeSelectionValue Attribute Value", - "While creating a TimeSelectionValue value, an extra attribute value was detected. "+ - "A TimeSelectionValue must not contain values beyond the expected attribute types. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("Extra TimeSelectionValue Attribute Name: %s", name), - ) - } - } - - if diags.HasError() { - return NewTimeSelectionValueUnknown(), diags - } - - customSelectionAttribute, ok := attributes["custom_selection"] - - if !ok { - diags.AddError( - "Attribute Missing", - `custom_selection is missing from object`) - - return NewTimeSelectionValueUnknown(), diags - } - - customSelectionVal, ok := customSelectionAttribute.(basetypes.ObjectValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`custom_selection expected to be basetypes.ObjectValue, was: %T`, customSelectionAttribute)) - } - - quickSelectionAttribute, ok := attributes["quick_selection"] - - if !ok { - diags.AddError( - "Attribute Missing", - `quick_selection is missing from object`) - - return NewTimeSelectionValueUnknown(), diags - } - - quickSelectionVal, ok := quickSelectionAttribute.(basetypes.ObjectValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`quick_selection expected to be basetypes.ObjectValue, was: %T`, quickSelectionAttribute)) - } - - if diags.HasError() { - return NewTimeSelectionValueUnknown(), diags - } - - return TimeSelectionValue{ - CustomSelection: customSelectionVal, - QuickSelection: quickSelectionVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewTimeSelectionValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) TimeSelectionValue { - object, diags := NewTimeSelectionValue(attributeTypes, attributes) - - if diags.HasError() { - // This could potentially be added to the diag package. - diagsStrings := make([]string, 0, len(diags)) - - for _, diagnostic := range diags { - diagsStrings = append(diagsStrings, fmt.Sprintf( - "%s | %s | %s", - diagnostic.Severity(), - diagnostic.Summary(), - diagnostic.Detail())) - } - - panic("NewTimeSelectionValueMust received error(s): " + strings.Join(diagsStrings, "\n")) - } - - return object -} - -func (t TimeSelectionType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { - if in.Type() == nil { - return NewTimeSelectionValueNull(), nil - } - - if !in.Type().Equal(t.TerraformType(ctx)) { - return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) - } - - if !in.IsKnown() { - return NewTimeSelectionValueUnknown(), nil - } - - if in.IsNull() { - return NewTimeSelectionValueNull(), nil - } - - attributes := map[string]attr.Value{} - - val := map[string]tftypes.Value{} - - err := in.As(&val) - - if err != nil { - return nil, err - } - - for k, v := range val { - a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) - - if err != nil { - return nil, err - } - - attributes[k] = a - } - - return NewTimeSelectionValueMust(TimeSelectionValue{}.AttributeTypes(ctx), attributes), nil -} - -func (t TimeSelectionType) ValueType(ctx context.Context) attr.Value { - return TimeSelectionValue{} -} - -var _ basetypes.ObjectValuable = TimeSelectionValue{} - -type TimeSelectionValue struct { - CustomSelection basetypes.ObjectValue `tfsdk:"custom_selection"` - QuickSelection basetypes.ObjectValue `tfsdk:"quick_selection"` - state attr.ValueState -} - -func (v TimeSelectionValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { - attrTypes := make(map[string]tftypes.Type, 2) - - var val tftypes.Value - var err error - - attrTypes["custom_selection"] = basetypes.ObjectType{ - AttrTypes: CustomSelectionValue{}.AttributeTypes(ctx), - }.TerraformType(ctx) - attrTypes["quick_selection"] = basetypes.ObjectType{ - AttrTypes: QuickSelectionValue{}.AttributeTypes(ctx), - }.TerraformType(ctx) - - objectType := tftypes.Object{AttributeTypes: attrTypes} - - switch v.state { - case attr.ValueStateKnown: - vals := make(map[string]tftypes.Value, 2) - - val, err = v.CustomSelection.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["custom_selection"] = val - - val, err = v.QuickSelection.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["quick_selection"] = val - - if err := tftypes.ValidateValue(objectType, vals); err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - return tftypes.NewValue(objectType, vals), nil - case attr.ValueStateNull: - return tftypes.NewValue(objectType, nil), nil - case attr.ValueStateUnknown: - return tftypes.NewValue(objectType, tftypes.UnknownValue), nil - default: - panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) - } -} - -func (v TimeSelectionValue) IsNull() bool { - return v.state == attr.ValueStateNull -} - -func (v TimeSelectionValue) IsUnknown() bool { - return v.state == attr.ValueStateUnknown -} - -func (v TimeSelectionValue) String() string { - return "TimeSelectionValue" -} - -func (v TimeSelectionValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { - var diags diag.Diagnostics - - var customSelection basetypes.ObjectValue - - if v.CustomSelection.IsNull() { - customSelection = types.ObjectNull( - CustomSelectionValue{}.AttributeTypes(ctx), - ) - } - - if v.CustomSelection.IsUnknown() { - customSelection = types.ObjectUnknown( - CustomSelectionValue{}.AttributeTypes(ctx), - ) - } - - if !v.CustomSelection.IsNull() && !v.CustomSelection.IsUnknown() { - customSelection = types.ObjectValueMust( - CustomSelectionValue{}.AttributeTypes(ctx), - v.CustomSelection.Attributes(), - ) - } - - var quickSelection basetypes.ObjectValue - - if v.QuickSelection.IsNull() { - quickSelection = types.ObjectNull( - QuickSelectionValue{}.AttributeTypes(ctx), - ) - } - - if v.QuickSelection.IsUnknown() { - quickSelection = types.ObjectUnknown( - QuickSelectionValue{}.AttributeTypes(ctx), - ) - } - - if !v.QuickSelection.IsNull() && !v.QuickSelection.IsUnknown() { - quickSelection = types.ObjectValueMust( - QuickSelectionValue{}.AttributeTypes(ctx), - v.QuickSelection.Attributes(), - ) - } - - attributeTypes := map[string]attr.Type{ - "custom_selection": basetypes.ObjectType{ - AttrTypes: CustomSelectionValue{}.AttributeTypes(ctx), - }, - "quick_selection": basetypes.ObjectType{ - AttrTypes: QuickSelectionValue{}.AttributeTypes(ctx), - }, - } - - if v.IsNull() { - return types.ObjectNull(attributeTypes), diags - } - - if v.IsUnknown() { - return types.ObjectUnknown(attributeTypes), diags - } - - objVal, diags := types.ObjectValue( - attributeTypes, - map[string]attr.Value{ - "custom_selection": customSelection, - "quick_selection": quickSelection, - }) - - return objVal, diags -} - -func (v TimeSelectionValue) Equal(o attr.Value) bool { - other, ok := o.(TimeSelectionValue) - - if !ok { - return false - } - - if v.state != other.state { - return false - } - - if v.state != attr.ValueStateKnown { - return true - } - - if !v.CustomSelection.Equal(other.CustomSelection) { - return false - } - - if !v.QuickSelection.Equal(other.QuickSelection) { - return false - } - - return true -} - -func (v TimeSelectionValue) Type(ctx context.Context) attr.Type { - return TimeSelectionType{ - basetypes.ObjectType{ - AttrTypes: v.AttributeTypes(ctx), - }, - } -} - -func (v TimeSelectionValue) AttributeTypes(ctx context.Context) map[string]attr.Type { - return map[string]attr.Type{ - "custom_selection": basetypes.ObjectType{ - AttrTypes: CustomSelectionValue{}.AttributeTypes(ctx), - }, - "quick_selection": basetypes.ObjectType{ - AttrTypes: QuickSelectionValue{}.AttributeTypes(ctx), - }, - } -} - -var _ basetypes.ObjectTypable = CustomSelectionType{} - -type CustomSelectionType struct { - basetypes.ObjectType -} - -func (t CustomSelectionType) Equal(o attr.Type) bool { - other, ok := o.(CustomSelectionType) - - if !ok { - return false - } - - return t.ObjectType.Equal(other.ObjectType) -} - -func (t CustomSelectionType) String() string { - return "CustomSelectionType" -} - -func (t CustomSelectionType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { - var diags diag.Diagnostics - - attributes := in.Attributes() - - fromTimeAttribute, ok := attributes["from_time"] - - if !ok { - diags.AddError( - "Attribute Missing", - `from_time is missing from object`) - - return nil, diags - } - - fromTimeVal, ok := fromTimeAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`from_time expected to be basetypes.StringValue, was: %T`, fromTimeAttribute)) - } - - toTimeAttribute, ok := attributes["to_time"] - - if !ok { - diags.AddError( - "Attribute Missing", - `to_time is missing from object`) - - return nil, diags - } - - toTimeVal, ok := toTimeAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`to_time expected to be basetypes.StringValue, was: %T`, toTimeAttribute)) - } - - if diags.HasError() { - return nil, diags - } - - return CustomSelectionValue{ - FromTime: fromTimeVal, - ToTime: toTimeVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewCustomSelectionValueNull() CustomSelectionValue { - return CustomSelectionValue{ - state: attr.ValueStateNull, - } -} - -func NewCustomSelectionValueUnknown() CustomSelectionValue { - return CustomSelectionValue{ - state: attr.ValueStateUnknown, - } -} - -func NewCustomSelectionValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (CustomSelectionValue, diag.Diagnostics) { - var diags diag.Diagnostics - - // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 - ctx := context.Background() - - for name, attributeType := range attributeTypes { - attribute, ok := attributes[name] - - if !ok { - diags.AddError( - "Missing CustomSelectionValue Attribute Value", - "While creating a CustomSelectionValue value, a missing attribute value was detected. "+ - "A CustomSelectionValue must contain values for all attributes, even if null or unknown. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("CustomSelectionValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), - ) - - continue - } - - if !attributeType.Equal(attribute.Type(ctx)) { - diags.AddError( - "Invalid CustomSelectionValue Attribute Type", - "While creating a CustomSelectionValue value, an invalid attribute value was detected. "+ - "A CustomSelectionValue must use a matching attribute type for the value. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("CustomSelectionValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ - fmt.Sprintf("CustomSelectionValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), - ) - } - } - - for name := range attributes { - _, ok := attributeTypes[name] - - if !ok { - diags.AddError( - "Extra CustomSelectionValue Attribute Value", - "While creating a CustomSelectionValue value, an extra attribute value was detected. "+ - "A CustomSelectionValue must not contain values beyond the expected attribute types. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("Extra CustomSelectionValue Attribute Name: %s", name), - ) - } - } - - if diags.HasError() { - return NewCustomSelectionValueUnknown(), diags - } - - fromTimeAttribute, ok := attributes["from_time"] - - if !ok { - diags.AddError( - "Attribute Missing", - `from_time is missing from object`) - - return NewCustomSelectionValueUnknown(), diags - } - - fromTimeVal, ok := fromTimeAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`from_time expected to be basetypes.StringValue, was: %T`, fromTimeAttribute)) - } - - toTimeAttribute, ok := attributes["to_time"] - - if !ok { - diags.AddError( - "Attribute Missing", - `to_time is missing from object`) - - return NewCustomSelectionValueUnknown(), diags - } - - toTimeVal, ok := toTimeAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`to_time expected to be basetypes.StringValue, was: %T`, toTimeAttribute)) - } - - if diags.HasError() { - return NewCustomSelectionValueUnknown(), diags - } - - return CustomSelectionValue{ - FromTime: fromTimeVal, - ToTime: toTimeVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewCustomSelectionValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) CustomSelectionValue { - object, diags := NewCustomSelectionValue(attributeTypes, attributes) - - if diags.HasError() { - // This could potentially be added to the diag package. - diagsStrings := make([]string, 0, len(diags)) - - for _, diagnostic := range diags { - diagsStrings = append(diagsStrings, fmt.Sprintf( - "%s | %s | %s", - diagnostic.Severity(), - diagnostic.Summary(), - diagnostic.Detail())) - } - - panic("NewCustomSelectionValueMust received error(s): " + strings.Join(diagsStrings, "\n")) - } - - return object -} - -func (t CustomSelectionType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { - if in.Type() == nil { - return NewCustomSelectionValueNull(), nil - } - - if !in.Type().Equal(t.TerraformType(ctx)) { - return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) - } - - if !in.IsKnown() { - return NewCustomSelectionValueUnknown(), nil - } - - if in.IsNull() { - return NewCustomSelectionValueNull(), nil - } - - attributes := map[string]attr.Value{} - - val := map[string]tftypes.Value{} - - err := in.As(&val) - - if err != nil { - return nil, err - } - - for k, v := range val { - a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) - - if err != nil { - return nil, err - } - - attributes[k] = a - } - - return NewCustomSelectionValueMust(CustomSelectionValue{}.AttributeTypes(ctx), attributes), nil +type SearchQueryModel struct { + Query types.String `tfsdk:"query"` } -func (t CustomSelectionType) ValueType(ctx context.Context) attr.Value { - return CustomSelectionValue{} +func (v SearchQueryModel) ToObjectValue(ctx context.Context) (types.Object, diag.Diagnostics) { + return types.ObjectValueFrom(ctx, v.AttributeTypes(ctx), v) } -var _ basetypes.ObjectValuable = CustomSelectionValue{} - -type CustomSelectionValue struct { - FromTime basetypes.StringValue `tfsdk:"from_time"` - ToTime basetypes.StringValue `tfsdk:"to_time"` - state attr.ValueState -} - -func (v CustomSelectionValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { - attrTypes := make(map[string]tftypes.Type, 2) - - var val tftypes.Value - var err error - - attrTypes["from_time"] = basetypes.StringType{}.TerraformType(ctx) - attrTypes["to_time"] = basetypes.StringType{}.TerraformType(ctx) - - objectType := tftypes.Object{AttributeTypes: attrTypes} - - switch v.state { - case attr.ValueStateKnown: - vals := make(map[string]tftypes.Value, 2) - - val, err = v.FromTime.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["from_time"] = val - - val, err = v.ToTime.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["to_time"] = val - - if err := tftypes.ValidateValue(objectType, vals); err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - return tftypes.NewValue(objectType, vals), nil - case attr.ValueStateNull: - return tftypes.NewValue(objectType, nil), nil - case attr.ValueStateUnknown: - return tftypes.NewValue(objectType, tftypes.UnknownValue), nil - default: - panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) +func (v SearchQueryModel) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "query": basetypes.StringType{}, } } -func (v CustomSelectionValue) IsNull() bool { - return v.state == attr.ValueStateNull +type TimeSelectionModel struct { + CustomSelection types.Object `tfsdk:"custom_selection"` //CustomSelectionModel + QuickSelection types.Object `tfsdk:"quick_selection"` //QuickSelectionModel } -func (v CustomSelectionValue) IsUnknown() bool { - return v.state == attr.ValueStateUnknown +func (v TimeSelectionModel) ToObjectValue(ctx context.Context) (types.Object, diag.Diagnostics) { + return types.ObjectValueFrom(ctx, v.AttributeTypes(ctx), v) } -func (v CustomSelectionValue) String() string { - return "CustomSelectionValue" -} - -func (v CustomSelectionValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { - var diags diag.Diagnostics - - attributeTypes := map[string]attr.Type{ - "from_time": basetypes.StringType{}, - "to_time": basetypes.StringType{}, - } - - if v.IsNull() { - return types.ObjectNull(attributeTypes), diags - } - - if v.IsUnknown() { - return types.ObjectUnknown(attributeTypes), diags +func (v TimeSelectionModel) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "custom_selection": basetypes.ObjectType{ + AttrTypes: CustomSelectionModel{}.AttributeTypes(ctx), + }, + "quick_selection": basetypes.ObjectType{ + AttrTypes: QuickSelectionModel{}.AttributeTypes(ctx), + }, } - - objVal, diags := types.ObjectValue( - attributeTypes, - map[string]attr.Value{ - "from_time": v.FromTime, - "to_time": v.ToTime, - }) - - return objVal, diags } -func (v CustomSelectionValue) Equal(o attr.Value) bool { - other, ok := o.(CustomSelectionValue) - - if !ok { - return false - } - - if v.state != other.state { - return false - } - - if v.state != attr.ValueStateKnown { - return true - } - - if !v.FromTime.Equal(other.FromTime) { - return false - } - - if !v.ToTime.Equal(other.ToTime) { - return false - } - - return true +type CustomSelectionModel struct { + FromTime types.String `tfsdk:"from_time"` + ToTime types.String `tfsdk:"to_time"` } -func (v CustomSelectionValue) Type(ctx context.Context) attr.Type { - return CustomSelectionType{ - basetypes.ObjectType{ - AttrTypes: v.AttributeTypes(ctx), - }, - } +func (v CustomSelectionModel) ToObjectValue(ctx context.Context) (types.Object, diag.Diagnostics) { + return types.ObjectValueFrom(ctx, v.AttributeTypes(ctx), v) } -func (v CustomSelectionValue) AttributeTypes(ctx context.Context) map[string]attr.Type { +func (v CustomSelectionModel) AttributeTypes(context.Context) map[string]attr.Type { return map[string]attr.Type{ "from_time": basetypes.StringType{}, "to_time": basetypes.StringType{}, } } - -var _ basetypes.ObjectTypable = QuickSelectionType{} - -type QuickSelectionType struct { - basetypes.ObjectType -} - -func (t QuickSelectionType) Equal(o attr.Type) bool { - other, ok := o.(QuickSelectionType) - - if !ok { - return false - } - - return t.ObjectType.Equal(other.ObjectType) -} - -func (t QuickSelectionType) String() string { - return "QuickSelectionType" -} - -func (t QuickSelectionType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { - var diags diag.Diagnostics - - attributes := in.Attributes() - - captionAttribute, ok := attributes["caption"] - - if !ok { - diags.AddError( - "Attribute Missing", - `caption is missing from object`) - - return nil, diags - } - - captionVal, ok := captionAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`caption expected to be basetypes.StringValue, was: %T`, captionAttribute)) - } - - secondsAttribute, ok := attributes["seconds"] - - if !ok { - diags.AddError( - "Attribute Missing", - `seconds is missing from object`) - - return nil, diags - } - - secondsVal, ok := secondsAttribute.(basetypes.Int64Value) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`seconds expected to be basetypes.Int64Value, was: %T`, secondsAttribute)) - } - - if diags.HasError() { - return nil, diags - } - - return QuickSelectionValue{ - Caption: captionVal, - Seconds: secondsVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewQuickSelectionValueNull() QuickSelectionValue { - return QuickSelectionValue{ - state: attr.ValueStateNull, - } -} - -func NewQuickSelectionValueUnknown() QuickSelectionValue { - return QuickSelectionValue{ - state: attr.ValueStateUnknown, - } -} - -func NewQuickSelectionValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (QuickSelectionValue, diag.Diagnostics) { - var diags diag.Diagnostics - - // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 - ctx := context.Background() - - for name, attributeType := range attributeTypes { - attribute, ok := attributes[name] - - if !ok { - diags.AddError( - "Missing QuickSelectionValue Attribute Value", - "While creating a QuickSelectionValue value, a missing attribute value was detected. "+ - "A QuickSelectionValue must contain values for all attributes, even if null or unknown. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("QuickSelectionValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), - ) - - continue - } - - if !attributeType.Equal(attribute.Type(ctx)) { - diags.AddError( - "Invalid QuickSelectionValue Attribute Type", - "While creating a QuickSelectionValue value, an invalid attribute value was detected. "+ - "A QuickSelectionValue must use a matching attribute type for the value. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("QuickSelectionValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ - fmt.Sprintf("QuickSelectionValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), - ) - } - } - - for name := range attributes { - _, ok := attributeTypes[name] - - if !ok { - diags.AddError( - "Extra QuickSelectionValue Attribute Value", - "While creating a QuickSelectionValue value, an extra attribute value was detected. "+ - "A QuickSelectionValue must not contain values beyond the expected attribute types. "+ - "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - fmt.Sprintf("Extra QuickSelectionValue Attribute Name: %s", name), - ) - } - } - - if diags.HasError() { - return NewQuickSelectionValueUnknown(), diags - } - - captionAttribute, ok := attributes["caption"] - - if !ok { - diags.AddError( - "Attribute Missing", - `caption is missing from object`) - - return NewQuickSelectionValueUnknown(), diags - } - - captionVal, ok := captionAttribute.(basetypes.StringValue) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`caption expected to be basetypes.StringValue, was: %T`, captionAttribute)) - } - - secondsAttribute, ok := attributes["seconds"] - - if !ok { - diags.AddError( - "Attribute Missing", - `seconds is missing from object`) - - return NewQuickSelectionValueUnknown(), diags - } - - secondsVal, ok := secondsAttribute.(basetypes.Int64Value) - - if !ok { - diags.AddError( - "Attribute Wrong Type", - fmt.Sprintf(`seconds expected to be basetypes.Int64Value, was: %T`, secondsAttribute)) - } - - if diags.HasError() { - return NewQuickSelectionValueUnknown(), diags - } - - return QuickSelectionValue{ - Caption: captionVal, - Seconds: secondsVal, - state: attr.ValueStateKnown, - }, diags -} - -func NewQuickSelectionValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) QuickSelectionValue { - object, diags := NewQuickSelectionValue(attributeTypes, attributes) - - if diags.HasError() { - // This could potentially be added to the diag package. - diagsStrings := make([]string, 0, len(diags)) - - for _, diagnostic := range diags { - diagsStrings = append(diagsStrings, fmt.Sprintf( - "%s | %s | %s", - diagnostic.Severity(), - diagnostic.Summary(), - diagnostic.Detail())) - } - - panic("NewQuickSelectionValueMust received error(s): " + strings.Join(diagsStrings, "\n")) - } - - return object -} - -func (t QuickSelectionType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { - if in.Type() == nil { - return NewQuickSelectionValueNull(), nil - } - - if !in.Type().Equal(t.TerraformType(ctx)) { - return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) - } - - if !in.IsKnown() { - return NewQuickSelectionValueUnknown(), nil - } - - if in.IsNull() { - return NewQuickSelectionValueNull(), nil - } - - attributes := map[string]attr.Value{} - - val := map[string]tftypes.Value{} - - err := in.As(&val) - - if err != nil { - return nil, err - } - - for k, v := range val { - a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) - - if err != nil { - return nil, err - } - - attributes[k] = a - } - - return NewQuickSelectionValueMust(QuickSelectionValue{}.AttributeTypes(ctx), attributes), nil -} - -func (t QuickSelectionType) ValueType(ctx context.Context) attr.Value { - return QuickSelectionValue{} -} - -var _ basetypes.ObjectValuable = QuickSelectionValue{} - -type QuickSelectionValue struct { - Caption basetypes.StringValue `tfsdk:"caption"` - Seconds basetypes.Int64Value `tfsdk:"seconds"` - state attr.ValueState -} - -func (v QuickSelectionValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { - attrTypes := make(map[string]tftypes.Type, 2) - - var val tftypes.Value - var err error - - attrTypes["caption"] = basetypes.StringType{}.TerraformType(ctx) - attrTypes["seconds"] = basetypes.Int64Type{}.TerraformType(ctx) - - objectType := tftypes.Object{AttributeTypes: attrTypes} - - switch v.state { - case attr.ValueStateKnown: - vals := make(map[string]tftypes.Value, 2) - - val, err = v.Caption.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["caption"] = val - - val, err = v.Seconds.ToTerraformValue(ctx) - - if err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - vals["seconds"] = val - - if err := tftypes.ValidateValue(objectType, vals); err != nil { - return tftypes.NewValue(objectType, tftypes.UnknownValue), err - } - - return tftypes.NewValue(objectType, vals), nil - case attr.ValueStateNull: - return tftypes.NewValue(objectType, nil), nil - case attr.ValueStateUnknown: - return tftypes.NewValue(objectType, tftypes.UnknownValue), nil - default: - panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) - } -} - -func (v QuickSelectionValue) IsNull() bool { - return v.state == attr.ValueStateNull -} - -func (v QuickSelectionValue) IsUnknown() bool { - return v.state == attr.ValueStateUnknown -} - -func (v QuickSelectionValue) String() string { - return "QuickSelectionValue" -} - -func (v QuickSelectionValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { - var diags diag.Diagnostics - - attributeTypes := map[string]attr.Type{ - "caption": basetypes.StringType{}, - "seconds": basetypes.Int64Type{}, - } - - if v.IsNull() { - return types.ObjectNull(attributeTypes), diags - } - - if v.IsUnknown() { - return types.ObjectUnknown(attributeTypes), diags - } - - objVal, diags := types.ObjectValue( - attributeTypes, - map[string]attr.Value{ - "caption": v.Caption, - "seconds": v.Seconds, - }) - - return objVal, diags -} - -func (v QuickSelectionValue) Equal(o attr.Value) bool { - other, ok := o.(QuickSelectionValue) - - if !ok { - return false - } - - if v.state != other.state { - return false - } - - if v.state != attr.ValueStateKnown { - return true - } - - if !v.Caption.Equal(other.Caption) { - return false - } - - if !v.Seconds.Equal(other.Seconds) { - return false - } - - return true -} - -func (v QuickSelectionValue) Type(ctx context.Context) attr.Type { - return QuickSelectionType{ - basetypes.ObjectType{ - AttrTypes: v.AttributeTypes(ctx), - }, - } -} - -func (v QuickSelectionValue) AttributeTypes(ctx context.Context) map[string]attr.Type { - return map[string]attr.Type{ - "caption": basetypes.StringType{}, - "seconds": basetypes.Int64Type{}, - } -} diff --git a/internal/provider/resource_coralogix_view_test.go b/internal/provider/resource_coralogix_view_test.go new file mode 100644 index 000000000..184c3ae43 --- /dev/null +++ b/internal/provider/resource_coralogix_view_test.go @@ -0,0 +1,107 @@ +package coralogix + +import ( + "context" + "fmt" + "strconv" + "testing" + + cxsdk "github.com/coralogix/coralogix-management-sdk/go" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "google.golang.org/protobuf/types/known/wrapperspb" + "terraform-provider-coralogix/coralogix/clientset" +) + +func TestAccCoralogixResourceView(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckViewDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCoralogixResourceView(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("coralogix_view.test", "id"), + resource.TestCheckResourceAttr("coralogix_view.test", "name", "Example View"), + resource.TestCheckResourceAttr("coralogix_view.test", "time_selection.0.custom_selection.0.from_time", "2023-01-01T00:00:00Z"), + resource.TestCheckResourceAttr("coralogix_view.test", "time_selection.0.custom_selection.0.to_time", "2023-01-02T00:00:00Z"), + resource.TestCheckResourceAttr("coralogix_view.test", "search_query.0.query", "error OR warning"), + ), + }, + { + ResourceName: "coralogix_view.test", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCoralogixResourceUpdatedView(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("coralogix_view.test", "id"), + resource.TestCheckResourceAttr("coralogix_view.test", "name", "Example View Updated"), + resource.TestCheckResourceAttr("coralogix_view.test", "time_selection.0.quick_selection.0.seconds", "86400"), // 24 hours in seconds + resource.TestCheckResourceAttr("coralogix_view.test", "search_query.0.query", "error OR warning"), + ), + }, + }, + }) +} + +func testAccCheckViewDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*clientset.ClientSet).Views() + ctx := context.TODO() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "coralogix_view" { + continue + } + + if rs.Primary.ID == "" { + return nil + } + + intID, err := strconv.Atoi(rs.Primary.ID) + if err != nil { + return fmt.Errorf("invalid ID format: %s", rs.Primary.ID) + } + + resp, err := client.Get(ctx, &cxsdk.GetViewRequest{ + Id: wrapperspb.Int32(int32(intID)), + }) + if err == nil && resp != nil && resp.View != nil { + return fmt.Errorf("view still exists: %v", rs.Primary.ID) + } + } + return nil +} + +func testAccCoralogixResourceView() string { + return `resource "coralogix_view" "test" { + name = "Example View" + time_selection = { + custom_selection = { + from_time = "2023-01-01T00:00:00Z" + to_time = "2023-01-02T00:00:00Z" + } + } + search_query = { + query = "error OR warning" + } +} + ` +} + +func testAccCoralogixResourceUpdatedView() string { + return `resource "coralogix_view" "test" { + name = "Example View Updated" + time_selection = { + quick_selection = { + seconds = 86400 # 24 hours in seconds + } + } + search_query = { + query = "error OR warning" + } +} + ` +} From 29ec5fef84cf20dcaed001e1f30d929b05efd913 Mon Sep 17 00:00:00 2001 From: Or Novogroder Date: Mon, 30 Jun 2025 17:56:49 +0300 Subject: [PATCH 04/10] fixing id type bug --- docs/data-sources/view.md | 2 +- .../provider/data_source_coralogix_view.go | 23 ++++++++++++++--- internal/provider/resource_coralogix_view.go | 25 ++++++++++--------- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/docs/data-sources/view.md b/docs/data-sources/view.md index 696105968..5d3be36d2 100644 --- a/docs/data-sources/view.md +++ b/docs/data-sources/view.md @@ -17,7 +17,7 @@ description: |- ### Required -- `id` (String) id +- `id` (Number) id ### Read-Only diff --git a/internal/provider/data_source_coralogix_view.go b/internal/provider/data_source_coralogix_view.go index 4e4503017..3f3fe9e63 100644 --- a/internal/provider/data_source_coralogix_view.go +++ b/internal/provider/data_source_coralogix_view.go @@ -20,6 +20,8 @@ import ( "fmt" "log" + datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "google.golang.org/protobuf/types/known/wrapperspb" "terraform-provider-coralogix/coralogix/clientset" "terraform-provider-coralogix/coralogix/utils" @@ -65,7 +67,22 @@ func (d *ViewDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, var resourceResp resource.SchemaResponse r.Schema(ctx, resource.SchemaRequest{}, &resourceResp) - resp.Schema = utils.FrameworkDatasourceSchemaFromFrameworkResourceSchema(resourceResp.Schema) + attributes := utils.ConvertAttributes(resourceResp.Schema.Attributes) + if idSchema, ok := resourceResp.Schema.Attributes["id"]; ok { + attributes["id"] = datasourceschema.Int32Attribute{ + Required: true, + Description: idSchema.GetDescription(), + MarkdownDescription: idSchema.GetMarkdownDescription(), + } + } + + resp.Schema = datasourceschema.Schema{ + Attributes: attributes, + //Blocks: convertBlocks(rs.Blocks), + Description: resourceResp.Schema.Description, + MarkdownDescription: resourceResp.Schema.MarkdownDescription, + DeprecationMessage: resourceResp.Schema.DeprecationMessage, + } } func (d *ViewDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { @@ -76,9 +93,9 @@ func (d *ViewDataSource) Read(ctx context.Context, req datasource.ReadRequest, r return } //Get refreshed View value from Coralogix - id := data.Id.ValueInt64() + id := data.Id.ValueInt32() log.Printf("[INFO] Reading view: %d", id) - getViewResp, err := d.client.Get(ctx, &cxsdk.GetViewRequest{Id: utils.TypeInt64ToWrappedInt32(data.Id)}) + getViewResp, err := d.client.Get(ctx, &cxsdk.GetViewRequest{Id: wrapperspb.Int32(id)}) if err != nil { log.Printf("[ERROR] Received error: %s", err.Error()) if cxsdk.Code(err) == codes.NotFound { diff --git a/internal/provider/resource_coralogix_view.go b/internal/provider/resource_coralogix_view.go index 9b1bd0521..07b76caac 100644 --- a/internal/provider/resource_coralogix_view.go +++ b/internal/provider/resource_coralogix_view.go @@ -30,7 +30,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -136,7 +136,7 @@ func flattenView(ctx context.Context, view *cxsdk.View) (*ViewModel, diag.Diagno return &ViewModel{ Filters: filters, FolderId: utils.WrapperspbStringToTypeString(view.FolderId), - Id: utils.WrapperspbInt32ToTypeInt64(view.Id), + Id: utils.WrapperspbInt32ToTypeInt32(view.Id), IsCompactMode: utils.WrapperspbBoolToTypeBool(view.IsCompactMode), Name: utils.WrapperspbStringToTypeString(view.Name), SearchQuery: searchQuery, @@ -309,7 +309,7 @@ func extractUpdateView(ctx context.Context, data *ViewModel) (*cxsdk.ReplaceView return &cxsdk.ReplaceViewRequest{ View: &cxsdk.View{ - Id: wrapperspb.Int32(int32(data.Id.ValueInt64())), + Id: wrapperspb.Int32(data.Id.ValueInt32()), Name: utils.TypeStringToWrapperspbString(data.Name), SearchQuery: searchQuery, TimeSelection: timeSelection, @@ -533,7 +533,7 @@ func (r *ViewResource) Read(ctx context.Context, req resource.ReadRequest, resp return } - id := data.Id.ValueInt64() + id := data.Id.ValueInt32() readReq := &cxsdk.GetViewRequest{ Id: wrapperspb.Int32(int32(id)), } @@ -610,19 +610,20 @@ func (r *ViewResource) Delete(ctx context.Context, req resource.DeleteRequest, r // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - _, err := r.client.Delete(ctx, &cxsdk.DeleteViewRequest{Id: wrapperspb.Int32(int32(data.Id.ValueInt64()))}) + id := data.Id.ValueInt32() + _, err := r.client.Delete(ctx, &cxsdk.DeleteViewRequest{Id: wrapperspb.Int32(id)}) if err != nil { log.Printf("[ERROR] Received error: %s", err.Error()) if cxsdk.Code(err) == codes.NotFound { resp.Diagnostics.AddWarning( - fmt.Sprintf("View %q is in state, but no longer exists in Coralogix backend", data.Id.ValueInt64()), - fmt.Sprintf("%d will be removed from state", data.Id.ValueInt64()), + fmt.Sprintf("View %q is in state, but no longer exists in Coralogix backend", id), + fmt.Sprintf("%d will be removed from state", id), ) resp.State.RemoveResource(ctx) } else { resp.Diagnostics.AddError( "Error deleting view", - utils.FormatRpcErrors(err, cxsdk.DeleteViewRPC, fmt.Sprintf("ID: %d", data.Id.ValueInt64())), + utils.FormatRpcErrors(err, cxsdk.DeleteViewRPC, fmt.Sprintf("ID: %d", id)), ) } return @@ -681,12 +682,12 @@ func ViewResourceSchema(ctx context.Context) schema.Schema { stringvalidator.RegexMatches(regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), ""), }, }, - "id": schema.Int64Attribute{ + "id": schema.Int32Attribute{ Computed: true, Description: "id", MarkdownDescription: "id", - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.UseStateForUnknown(), + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.UseStateForUnknown(), }, }, "is_compact_mode": schema.BoolAttribute{ @@ -762,7 +763,7 @@ func ViewResourceSchema(ctx context.Context) schema.Schema { type ViewModel struct { Filters types.Object `tfsdk:"filters"` //FiltersModel FolderId types.String `tfsdk:"folder_id"` - Id types.Int64 `tfsdk:"id"` + Id types.Int32 `tfsdk:"id"` IsCompactMode types.Bool `tfsdk:"is_compact_mode"` Name types.String `tfsdk:"name"` SearchQuery types.Object `tfsdk:"search_query"` //SearchQueryModel From ec91f2aa14db32261e2dd3ab7d7b1d53be862c3b Mon Sep 17 00:00:00 2001 From: Or Novogroder Date: Tue, 1 Jul 2025 11:13:32 +0300 Subject: [PATCH 05/10] fixing tests and remove is_compact_mode --- docs/data-sources/view.md | 1 - docs/resources/view.md | 1 - internal/provider/data_source_coralogix_view_test.go | 2 +- internal/provider/resource_coralogix_view.go | 9 --------- internal/provider/resource_coralogix_view_test.go | 10 +++++----- 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/docs/data-sources/view.md b/docs/data-sources/view.md index 5d3be36d2..44da4e556 100644 --- a/docs/data-sources/view.md +++ b/docs/data-sources/view.md @@ -23,7 +23,6 @@ description: |- - `filters` (Attributes) (see [below for nested schema](#nestedatt--filters)) - `folder_id` (String) Unique identifier for folders -- `is_compact_mode` (Boolean) - `name` (String) View name - `search_query` (Attributes) (see [below for nested schema](#nestedatt--search_query)) - `time_selection` (Attributes) (see [below for nested schema](#nestedatt--time_selection)) diff --git a/docs/resources/view.md b/docs/resources/view.md index f3facc9d5..faaa02afd 100644 --- a/docs/resources/view.md +++ b/docs/resources/view.md @@ -29,7 +29,6 @@ description: |- ### Read-Only - `id` (Number) id -- `is_compact_mode` (Boolean) ### Nested Schema for `time_selection` diff --git a/internal/provider/data_source_coralogix_view_test.go b/internal/provider/data_source_coralogix_view_test.go index a324a5157..008a23e8c 100644 --- a/internal/provider/data_source_coralogix_view_test.go +++ b/internal/provider/data_source_coralogix_view_test.go @@ -38,7 +38,7 @@ func TestAccCoralogixDataSourceView_basic(t *testing.T) { func testAccCoralogixDataSourceView_read() string { return `data "coralogix_view" "test" { - id = coralogix_view.test.id + id = coralogix_view.test.id } ` } diff --git a/internal/provider/resource_coralogix_view.go b/internal/provider/resource_coralogix_view.go index 07b76caac..64e782360 100644 --- a/internal/provider/resource_coralogix_view.go +++ b/internal/provider/resource_coralogix_view.go @@ -29,7 +29,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" @@ -137,7 +136,6 @@ func flattenView(ctx context.Context, view *cxsdk.View) (*ViewModel, diag.Diagno Filters: filters, FolderId: utils.WrapperspbStringToTypeString(view.FolderId), Id: utils.WrapperspbInt32ToTypeInt32(view.Id), - IsCompactMode: utils.WrapperspbBoolToTypeBool(view.IsCompactMode), Name: utils.WrapperspbStringToTypeString(view.Name), SearchQuery: searchQuery, TimeSelection: timeSelection, @@ -690,12 +688,6 @@ func ViewResourceSchema(ctx context.Context) schema.Schema { int32planmodifier.UseStateForUnknown(), }, }, - "is_compact_mode": schema.BoolAttribute{ - Computed: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.UseStateForUnknown(), - }, - }, "name": schema.StringAttribute{ Required: true, Description: "View name", @@ -764,7 +756,6 @@ type ViewModel struct { Filters types.Object `tfsdk:"filters"` //FiltersModel FolderId types.String `tfsdk:"folder_id"` Id types.Int32 `tfsdk:"id"` - IsCompactMode types.Bool `tfsdk:"is_compact_mode"` Name types.String `tfsdk:"name"` SearchQuery types.Object `tfsdk:"search_query"` //SearchQueryModel TimeSelection types.Object `tfsdk:"time_selection"` // TimeSelectionModel diff --git a/internal/provider/resource_coralogix_view_test.go b/internal/provider/resource_coralogix_view_test.go index 184c3ae43..ed972aa52 100644 --- a/internal/provider/resource_coralogix_view_test.go +++ b/internal/provider/resource_coralogix_view_test.go @@ -24,9 +24,9 @@ func TestAccCoralogixResourceView(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet("coralogix_view.test", "id"), resource.TestCheckResourceAttr("coralogix_view.test", "name", "Example View"), - resource.TestCheckResourceAttr("coralogix_view.test", "time_selection.0.custom_selection.0.from_time", "2023-01-01T00:00:00Z"), - resource.TestCheckResourceAttr("coralogix_view.test", "time_selection.0.custom_selection.0.to_time", "2023-01-02T00:00:00Z"), - resource.TestCheckResourceAttr("coralogix_view.test", "search_query.0.query", "error OR warning"), + resource.TestCheckResourceAttr("coralogix_view.test", "time_selection.custom_selection.from_time", "2023-01-01T00:00:00Z"), + resource.TestCheckResourceAttr("coralogix_view.test", "time_selection.custom_selection.to_time", "2023-01-02T00:00:00Z"), + resource.TestCheckResourceAttr("coralogix_view.test", "search_query.query", "error OR warning"), ), }, { @@ -39,8 +39,8 @@ func TestAccCoralogixResourceView(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet("coralogix_view.test", "id"), resource.TestCheckResourceAttr("coralogix_view.test", "name", "Example View Updated"), - resource.TestCheckResourceAttr("coralogix_view.test", "time_selection.0.quick_selection.0.seconds", "86400"), // 24 hours in seconds - resource.TestCheckResourceAttr("coralogix_view.test", "search_query.0.query", "error OR warning"), + resource.TestCheckResourceAttr("coralogix_view.test", "time_selection.quick_selection.seconds", "86400"), // 24 hours in seconds + resource.TestCheckResourceAttr("coralogix_view.test", "search_query.query", "error OR warning"), ), }, }, From def119cd038c0fadc300f8a843ebc70ac4c6e366 Mon Sep 17 00:00:00 2001 From: Or Novogroder Date: Tue, 1 Jul 2025 12:58:10 +0300 Subject: [PATCH 06/10] adding views_folder --- .../main.tf | 1 - .../resources/coralogix_views_folder/main.tf | 27 ++ internal/clientset/clientset.go | 6 + .../data_source_coralogix_views_folder.go | 105 ++++++++ ...data_source_coralogix_views_folder_test.go | 44 ++++ internal/provider/resource_coralogix_view.go | 6 +- .../resource_coralogix_views_folder.go | 246 ++++++++++++++++++ .../resource_coralogix_views_folder_test.go | 90 +++++++ 8 files changed, 523 insertions(+), 2 deletions(-) rename examples/resources/{coralogix_views => coralogix_view}/main.tf (99%) create mode 100644 examples/resources/coralogix_views_folder/main.tf create mode 100644 internal/provider/data_source_coralogix_views_folder.go create mode 100644 internal/provider/data_source_coralogix_views_folder_test.go create mode 100644 internal/provider/resource_coralogix_views_folder.go create mode 100644 internal/provider/resource_coralogix_views_folder_test.go diff --git a/examples/resources/coralogix_views/main.tf b/examples/resources/coralogix_view/main.tf similarity index 99% rename from examples/resources/coralogix_views/main.tf rename to examples/resources/coralogix_view/main.tf index 197490e4d..93e4376fa 100644 --- a/examples/resources/coralogix_views/main.tf +++ b/examples/resources/coralogix_view/main.tf @@ -22,7 +22,6 @@ resource "coralogix_view" "example_view" { } search_query = { query = "error OR warning" - } filters = { filters = [ diff --git a/examples/resources/coralogix_views_folder/main.tf b/examples/resources/coralogix_views_folder/main.tf new file mode 100644 index 000000000..dcd3ee6d9 --- /dev/null +++ b/examples/resources/coralogix_views_folder/main.tf @@ -0,0 +1,27 @@ +terraform { + required_providers { + coralogix = { + version = "~> 2.0" + source = "coralogix/coralogix" + } + } +} + +provider "coralogix" { + #api_key = "" + #env = "" +} + +resource "coralogix_views_folder" "example_view_folder" { + name = "Example View Folder" +} + +resource "coralogix_view" "example_view" { + name = "Example View" + time_selection = { + quick_selection = { + seconds = 3600 + } + } + folder_id = coralogix_views_folder.example_view_folder.id +} \ No newline at end of file diff --git a/internal/clientset/clientset.go b/internal/clientset/clientset.go index 63084883f..35e631cb6 100644 --- a/internal/clientset/clientset.go +++ b/internal/clientset/clientset.go @@ -52,6 +52,7 @@ type ClientSet struct { notifications *cxsdk.NotificationsClient ipaccess *ipaccess.IPAccessServiceAPIService views *cxsdk.ViewsClient + viewsFolders *cxsdk.ViewFoldersClient grafana *GrafanaClient groups *GroupsClient @@ -169,6 +170,10 @@ func (c *ClientSet) Views() *cxsdk.ViewsClient { return c.views } +func (c *ClientSet) ViewsFolders() *cxsdk.ViewFoldersClient { + return c.viewsFolders +} + func NewClientSet(region string, apiKey string, targetUrl string) *ClientSet { apiKeySdk := cxsdk.NewSDKCallPropertiesCreatorTerraform(strings.ToLower(region), cxsdk.NewAuthContext(apiKey, apiKey), TF_PROVIDER_VERSION) apikeyCPC := NewCallPropertiesCreator(targetUrl, apiKey) @@ -203,6 +208,7 @@ func NewClientSet(region string, apiKey string, targetUrl string) *ClientSet { notifications: cxsdk.NewNotificationsClient(apiKeySdk), ipaccess: cxsdkOpenapi.NewIPAccessClient(oasTfCPC), views: cxsdk.NewViewsClient(apiKeySdk), + viewsFolders: cxsdk.NewViewFoldersClient(apiKeySdk), grafana: NewGrafanaClient(apikeyCPC), groups: NewGroupsClient(apikeyCPC), diff --git a/internal/provider/data_source_coralogix_views_folder.go b/internal/provider/data_source_coralogix_views_folder.go new file mode 100644 index 000000000..e69cdcca3 --- /dev/null +++ b/internal/provider/data_source_coralogix_views_folder.go @@ -0,0 +1,105 @@ +// Copyright 2024 Coralogix Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package coralogix + +import ( + "context" + "encoding/json" + "fmt" + "log" + + "google.golang.org/protobuf/types/known/wrapperspb" + "terraform-provider-coralogix/coralogix/clientset" + "terraform-provider-coralogix/coralogix/utils" + + cxsdk "github.com/coralogix/coralogix-management-sdk/go" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/resource" + "google.golang.org/grpc/codes" +) + +var _ datasource.DataSourceWithConfigure = &ViewsFolderDataSource{} + +func NewViewsFolderDataSource() datasource.DataSource { + return &ViewsFolderDataSource{} +} + +type ViewsFolderDataSource struct { + client *cxsdk.ViewFoldersClient +} + +func (d *ViewsFolderDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_views_folder" +} + +func (d *ViewsFolderDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + clientSet, ok := req.ProviderData.(*clientset.ClientSet) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *clientset.ClientSet, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = clientSet.ViewsFolders() +} + +func (d *ViewsFolderDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + var r ViewsFolderResource + var resourceResp resource.SchemaResponse + r.Schema(ctx, resource.SchemaRequest{}, &resourceResp) + + resp.Schema = utils.FrameworkDatasourceSchemaFromFrameworkResourceSchema(resourceResp.Schema) +} + +func (d *ViewsFolderDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data *ViewsFolderModel + diags := req.Config.Get(ctx, &data) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + //Get refreshed Views-Folder value from Coralogix + id := data.Id.ValueString() + log.Printf("[INFO] Reading views-folder: %s", id) + getViewsFolderResp, err := d.client.Get(ctx, &cxsdk.GetViewFolderRequest{Id: wrapperspb.String(id)}) + if err != nil { + log.Printf("[ERROR] Received error: %s", err.Error()) + if cxsdk.Code(err) == codes.NotFound { + resp.Diagnostics.AddWarning( + err.Error(), + fmt.Sprintf("Views-Folder %q is in state, but no longer exists in Coralogix backend", id), + ) + } else { + resp.Diagnostics.AddError( + "Error reading Views-Folder", + utils.FormatRpcErrors(err, fmt.Sprintf("%s/%s", cxsdk.GetViewFolderRPC, id), ""), + ) + } + return + } + respStr, _ := json.Marshal(getViewsFolderResp) + log.Printf("[INFO] Received View: %s", string(respStr)) + + data = flattenViewsFolder(getViewsFolderResp.Folder) + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/data_source_coralogix_views_folder_test.go b/internal/provider/data_source_coralogix_views_folder_test.go new file mode 100644 index 000000000..73863ce12 --- /dev/null +++ b/internal/provider/data_source_coralogix_views_folder_test.go @@ -0,0 +1,44 @@ +// Copyright 2024 Coralogix Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package coralogix + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccCoralogixDataSourceViewsFolder_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccCoralogixResourceViewsFolder() + + testAccCoralogixDataSourceViewsFolder_read(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.coralogix_views_folder.test", "name", "Example Views Folder"), + ), + }, + }, + }) +} + +func testAccCoralogixDataSourceViewsFolder_read() string { + return `data "coralogix_views_folder" "test" { + id = coralogix_views_folder.test.id + } +` +} diff --git a/internal/provider/resource_coralogix_view.go b/internal/provider/resource_coralogix_view.go index 64e782360..c49fa66fd 100644 --- a/internal/provider/resource_coralogix_view.go +++ b/internal/provider/resource_coralogix_view.go @@ -79,6 +79,10 @@ func (r *ViewResource) Schema(ctx context.Context, req resource.SchemaRequest, r resp.Schema = ViewResourceSchema(ctx) } +func (r *ViewResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + func (r *ViewResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var data *ViewModel @@ -533,7 +537,7 @@ func (r *ViewResource) Read(ctx context.Context, req resource.ReadRequest, resp id := data.Id.ValueInt32() readReq := &cxsdk.GetViewRequest{ - Id: wrapperspb.Int32(int32(id)), + Id: wrapperspb.Int32(id), } log.Printf("[INFO] Reading view with ID: %d", id) readResp, err := r.client.Get(ctx, readReq) diff --git a/internal/provider/resource_coralogix_views_folder.go b/internal/provider/resource_coralogix_views_folder.go new file mode 100644 index 000000000..be717b686 --- /dev/null +++ b/internal/provider/resource_coralogix_views_folder.go @@ -0,0 +1,246 @@ +// Copyright 2024 Coralogix Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package coralogix + +import ( + "context" + "fmt" + "log" + + cxsdk "github.com/coralogix/coralogix-management-sdk/go" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "google.golang.org/grpc/codes" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/wrapperspb" + "terraform-provider-coralogix/coralogix/clientset" + "terraform-provider-coralogix/coralogix/utils" +) + +var _ resource.Resource = (*ViewsFolderResource)(nil) + +func NewViewsFolderResource() resource.Resource { + return &ViewsFolderResource{} +} + +type ViewsFolderResource struct { + client *cxsdk.ViewFoldersClient +} + +func (r *ViewsFolderResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_views_folder" +} + +func (r *ViewsFolderResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + clientSet, ok := req.ProviderData.(*clientset.ClientSet) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *clientset.ClientSet, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = clientSet.ViewsFolders() +} + +func (r *ViewsFolderResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = ViewsFolderResourceSchema(ctx) +} + +func (r *ViewsFolderResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func (r *ViewsFolderResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *ViewsFolderModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create API call logic + createRequest := &cxsdk.CreateViewFolderRequest{ + Name: utils.TypeStringToWrapperspbString(data.Name), + } + viewFolderStr := protojson.Format(createRequest) + log.Printf("[INFO] Creating new views-folder: %s", viewFolderStr) + createResponse, err := r.client.Create(ctx, createRequest) + if err != nil { + log.Printf("[ERROR] Received error: %s", err) + resp.Diagnostics.AddError("Error creating Views-Folder", + utils.FormatRpcErrors(err, cxsdk.CreateActionRPC, viewFolderStr), + ) + return + } + log.Printf("[INFO] Views-Folder created successfully: %s", protojson.Format(createResponse)) + + // Save data into Terraform state + data = flattenViewsFolder(createResponse.Folder) + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func extractViewsFolder(data *ViewsFolderModel) *cxsdk.ViewFolder { + return &cxsdk.ViewFolder{ + Id: utils.TypeStringToWrapperspbString(data.Id), + Name: utils.TypeStringToWrapperspbString(data.Name), + } +} + +func flattenViewsFolder(viewsFolder *cxsdk.ViewFolder) *ViewsFolderModel { + return &ViewsFolderModel{ + Id: utils.WrapperspbStringToTypeString(viewsFolder.Id), + Name: utils.WrapperspbStringToTypeString(viewsFolder.Name), + } +} + +func (r *ViewsFolderResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *ViewsFolderModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + id := data.Id.ValueString() + readReq := &cxsdk.GetViewFolderRequest{ + Id: wrapperspb.String(id), + } + log.Printf("[INFO] Reading views-folder with ID: %s", id) + readResp, err := r.client.Get(ctx, readReq) + if err != nil { + log.Printf("[ERROR] Received error: %s", err.Error()) + if cxsdk.Code(err) == codes.NotFound { + resp.Diagnostics.AddWarning( + fmt.Sprintf("Views-Folder %q is in state, but no longer exists in Coralogix backend", id), + fmt.Sprintf("%s will be recreated when you apply", id), + ) + resp.State.RemoveResource(ctx) + } else { + resp.Diagnostics.AddError( + "Error reading views-folder", + utils.FormatRpcErrors(err, cxsdk.GetViewFolderRPC, protojson.Format(readReq)), + ) + } + return + } + log.Printf("[INFO] Views-Folder read successfully: %s", protojson.Format(readResp.Folder)) + + // Flatten the response into the model + data = flattenViewsFolder(readResp.Folder) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ViewsFolderResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *ViewsFolderModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Save updated data into Terraform state + updateReq := &cxsdk.ReplaceViewFolderRequest{ + Folder: extractViewsFolder(data), + } + + log.Printf("[INFO] Updating views-folder in state: %s", protojson.Format(updateReq)) + updateResp, err := r.client.Replace(ctx, updateReq) + if err != nil { + log.Printf("[ERROR] Received error: %s", err.Error()) + resp.Diagnostics.AddError( + "Error updating views-folder in state", + utils.FormatRpcErrors(err, cxsdk.ReplaceViewFolderRPC, protojson.Format(updateReq)), + ) + return + } + log.Printf("[INFO] Views-Folder updated in state successfully: %s", protojson.Format(updateResp.Folder)) + + // Flatten the response into the model + data = flattenViewsFolder(updateResp.Folder) + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ViewsFolderResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *ViewsFolderModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + id := data.Id.ValueString() + _, err := r.client.Delete(ctx, &cxsdk.DeleteViewFolderRequest{Id: wrapperspb.String(id)}) + if err != nil { + log.Printf("[ERROR] Received error: %s", err.Error()) + if cxsdk.Code(err) == codes.NotFound { + resp.Diagnostics.AddWarning( + fmt.Sprintf("Views-Folder %q is in state, but no longer exists in Coralogix backend", id), + fmt.Sprintf("%s will be removed from state", id), + ) + resp.State.RemoveResource(ctx) + } else { + resp.Diagnostics.AddError( + "Error deleting views-folder", + utils.FormatRpcErrors(err, cxsdk.DeleteViewFolderRPC, id), + ) + } + return + } + if resp.Diagnostics.HasError() { + return + } +} + +func ViewsFolderResourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "id", + MarkdownDescription: "id", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Name of the views-folder", + MarkdownDescription: "Name of the views-folder", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + }, + } +} + +type ViewsFolderModel struct { + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` +} diff --git a/internal/provider/resource_coralogix_views_folder_test.go b/internal/provider/resource_coralogix_views_folder_test.go new file mode 100644 index 000000000..97d841ef0 --- /dev/null +++ b/internal/provider/resource_coralogix_views_folder_test.go @@ -0,0 +1,90 @@ +// Copyright 2024 Coralogix Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package coralogix + +import ( + "context" + "fmt" + "testing" + + cxsdk "github.com/coralogix/coralogix-management-sdk/go" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "google.golang.org/protobuf/types/known/wrapperspb" + "terraform-provider-coralogix/coralogix/clientset" +) + +func TestAccCoralogixResourceViewsFolder(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckViewsFolderDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCoralogixResourceViewsFolder(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("coralogix_views_folder.test", "id"), + resource.TestCheckResourceAttr("coralogix_views_folder.test", "name", "Example Views Folder"), + ), + }, + { + ResourceName: "coralogix_views_folder.test", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCoralogixResourceUpdatedViewsFolder(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("coralogix_views_folder.test", "id"), + resource.TestCheckResourceAttr("coralogix_views_folder.test", "name", "Example Views Folder Updated"), + ), + }, + }, + }) +} + +func testAccCheckViewsFolderDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*clientset.ClientSet).ViewsFolders() + ctx := context.TODO() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "coralogix_views_folder" { + continue + } + + if rs.Primary.ID == "" { + return nil + } + + resp, err := client.Get(ctx, &cxsdk.GetViewFolderRequest{Id: wrapperspb.String(rs.Primary.ID)}) + if err == nil && resp != nil && resp.Folder != nil { + return fmt.Errorf("views-folder still exists: %v", rs.Primary.ID) + } + } + return nil +} + +func testAccCoralogixResourceViewsFolder() string { + return `resource "coralogix_views_folder" "test" { + name = "Example Views Folder" +} + ` +} + +func testAccCoralogixResourceUpdatedViewsFolder() string { + return `resource "coralogix_views_folder" "test" { + name = "Example Views Folder Updated" +} + ` +} From 7f12c89e28d5607aca8e25277ad9c0f0c9a9b1d5 Mon Sep 17 00:00:00 2001 From: Or Novogroder Date: Tue, 1 Jul 2025 12:59:58 +0300 Subject: [PATCH 07/10] adding docs --- docs/data-sources/views_folder.md | 24 +++++++++++++++++++ docs/resources/views_folder.md | 24 +++++++++++++++++++ .../resource_coralogix_views_folder_test.go | 2 +- 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 docs/data-sources/views_folder.md create mode 100644 docs/resources/views_folder.md diff --git a/docs/data-sources/views_folder.md b/docs/data-sources/views_folder.md new file mode 100644 index 000000000..aff8d4ee2 --- /dev/null +++ b/docs/data-sources/views_folder.md @@ -0,0 +1,24 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coralogix_views_folder Data Source - terraform-provider-coralogix" +subcategory: "" +description: |- + +--- + +# coralogix_views_folder (Data Source) + + + + + + +## Schema + +### Required + +- `id` (String) id + +### Read-Only + +- `name` (String) Name of the views-folder diff --git a/docs/resources/views_folder.md b/docs/resources/views_folder.md new file mode 100644 index 000000000..bb4058963 --- /dev/null +++ b/docs/resources/views_folder.md @@ -0,0 +1,24 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coralogix_views_folder Resource - terraform-provider-coralogix" +subcategory: "" +description: |- + +--- + +# coralogix_views_folder (Resource) + + + + + + +## Schema + +### Required + +- `name` (String) Name of the views-folder + +### Read-Only + +- `id` (String) id diff --git a/internal/provider/resource_coralogix_views_folder_test.go b/internal/provider/resource_coralogix_views_folder_test.go index 97d841ef0..f38a8e797 100644 --- a/internal/provider/resource_coralogix_views_folder_test.go +++ b/internal/provider/resource_coralogix_views_folder_test.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// https://www.apache.org/licenses/LICENSE-2.0 +// https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, From 7fc50f08f26df68ecf039936361cdb53b4a116bd Mon Sep 17 00:00:00 2001 From: Or Novogroder Date: Tue, 1 Jul 2025 14:33:19 +0300 Subject: [PATCH 08/10] changing view id to string --- docs/data-sources/view.md | 2 +- docs/resources/view.md | 2 +- .../provider/data_source_coralogix_view.go | 32 +++++------ internal/provider/resource_coralogix_view.go | 57 ++++++++++++++----- 4 files changed, 58 insertions(+), 35 deletions(-) diff --git a/docs/data-sources/view.md b/docs/data-sources/view.md index 44da4e556..aa03569c2 100644 --- a/docs/data-sources/view.md +++ b/docs/data-sources/view.md @@ -17,7 +17,7 @@ description: |- ### Required -- `id` (Number) id +- `id` (String) id ### Read-Only diff --git a/docs/resources/view.md b/docs/resources/view.md index faaa02afd..f03f867bd 100644 --- a/docs/resources/view.md +++ b/docs/resources/view.md @@ -28,7 +28,7 @@ description: |- ### Read-Only -- `id` (Number) id +- `id` (String) id ### Nested Schema for `time_selection` diff --git a/internal/provider/data_source_coralogix_view.go b/internal/provider/data_source_coralogix_view.go index 3f3fe9e63..225e53b70 100644 --- a/internal/provider/data_source_coralogix_view.go +++ b/internal/provider/data_source_coralogix_view.go @@ -19,8 +19,8 @@ import ( "encoding/json" "fmt" "log" + "strconv" - datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "google.golang.org/protobuf/types/known/wrapperspb" "terraform-provider-coralogix/coralogix/clientset" "terraform-provider-coralogix/coralogix/utils" @@ -67,22 +67,7 @@ func (d *ViewDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, var resourceResp resource.SchemaResponse r.Schema(ctx, resource.SchemaRequest{}, &resourceResp) - attributes := utils.ConvertAttributes(resourceResp.Schema.Attributes) - if idSchema, ok := resourceResp.Schema.Attributes["id"]; ok { - attributes["id"] = datasourceschema.Int32Attribute{ - Required: true, - Description: idSchema.GetDescription(), - MarkdownDescription: idSchema.GetMarkdownDescription(), - } - } - - resp.Schema = datasourceschema.Schema{ - Attributes: attributes, - //Blocks: convertBlocks(rs.Blocks), - Description: resourceResp.Schema.Description, - MarkdownDescription: resourceResp.Schema.MarkdownDescription, - DeprecationMessage: resourceResp.Schema.DeprecationMessage, - } + resp.Schema = utils.FrameworkDatasourceSchemaFromFrameworkResourceSchema(resourceResp.Schema) } func (d *ViewDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { @@ -93,9 +78,18 @@ func (d *ViewDataSource) Read(ctx context.Context, req datasource.ReadRequest, r return } //Get refreshed View value from Coralogix - id := data.Id.ValueInt32() + idStr := data.Id.ValueString() + id, err := strconv.Atoi(idStr) + if err != nil { + resp.Diagnostics.AddError( + "Invalid View ID", + fmt.Sprintf("ID '%s' is not a valid integer: %s", idStr, err.Error()), + ) + return + } + log.Printf("[INFO] Reading view: %d", id) - getViewResp, err := d.client.Get(ctx, &cxsdk.GetViewRequest{Id: wrapperspb.Int32(id)}) + getViewResp, err := d.client.Get(ctx, &cxsdk.GetViewRequest{Id: wrapperspb.Int32(int32(id))}) if err != nil { log.Printf("[ERROR] Received error: %s", err.Error()) if cxsdk.Code(err) == codes.NotFound { diff --git a/internal/provider/resource_coralogix_view.go b/internal/provider/resource_coralogix_view.go index c49fa66fd..a92fb1f2e 100644 --- a/internal/provider/resource_coralogix_view.go +++ b/internal/provider/resource_coralogix_view.go @@ -18,6 +18,7 @@ import ( "fmt" "log" "regexp" + "strconv" "time" cxsdk "github.com/coralogix/coralogix-management-sdk/go" @@ -29,10 +30,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -139,7 +140,7 @@ func flattenView(ctx context.Context, view *cxsdk.View) (*ViewModel, diag.Diagno return &ViewModel{ Filters: filters, FolderId: utils.WrapperspbStringToTypeString(view.FolderId), - Id: utils.WrapperspbInt32ToTypeInt32(view.Id), + Id: types.StringValue(strconv.Itoa(int(view.Id.Value))), Name: utils.WrapperspbStringToTypeString(view.Name), SearchQuery: searchQuery, TimeSelection: timeSelection, @@ -309,9 +310,19 @@ func extractUpdateView(ctx context.Context, data *ViewModel) (*cxsdk.ReplaceView return nil, diags } + id, err := strconv.Atoi(data.Id.ValueString()) + if err != nil { + return nil, diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid View ID", + fmt.Sprintf("ID '%s' is not a valid integer: %s", data.Id.ValueString(), err.Error()), + ), + } + } + return &cxsdk.ReplaceViewRequest{ View: &cxsdk.View{ - Id: wrapperspb.Int32(data.Id.ValueInt32()), + Id: wrapperspb.Int32(int32(id)), Name: utils.TypeStringToWrapperspbString(data.Name), SearchQuery: searchQuery, TimeSelection: timeSelection, @@ -535,18 +546,27 @@ func (r *ViewResource) Read(ctx context.Context, req resource.ReadRequest, resp return } - id := data.Id.ValueInt32() + idStr := data.Id.ValueString() + id, err := strconv.Atoi(idStr) + if err != nil { + resp.Diagnostics.AddError( + "Invalid View ID", + fmt.Sprintf("ID '%s' is not a valid integer: %s", idStr, err.Error()), + ) + return + } + readReq := &cxsdk.GetViewRequest{ - Id: wrapperspb.Int32(id), + Id: wrapperspb.Int32(int32(id)), } - log.Printf("[INFO] Reading view with ID: %d", id) + log.Printf("[INFO] Reading view with ID: %d", idStr) readResp, err := r.client.Get(ctx, readReq) if err != nil { log.Printf("[ERROR] Received error: %s", err.Error()) if cxsdk.Code(err) == codes.NotFound { resp.Diagnostics.AddWarning( - fmt.Sprintf("View %q is in state, but no longer exists in Coralogix backend", id), - fmt.Sprintf("%d will be recreated when you apply", id), + fmt.Sprintf("View %q is in state, but no longer exists in Coralogix backend", idStr), + fmt.Sprintf("%d will be recreated when you apply", idStr), ) resp.State.RemoveResource(ctx) } else { @@ -612,8 +632,17 @@ func (r *ViewResource) Delete(ctx context.Context, req resource.DeleteRequest, r // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - id := data.Id.ValueInt32() - _, err := r.client.Delete(ctx, &cxsdk.DeleteViewRequest{Id: wrapperspb.Int32(id)}) + idStr := data.Id.ValueString() + id, err := strconv.Atoi(idStr) + if err != nil { + resp.Diagnostics.AddError( + "Invalid View ID", + fmt.Sprintf("ID '%s' is not a valid integer: %s", idStr, err.Error()), + ) + return + } + + _, err = r.client.Delete(ctx, &cxsdk.DeleteViewRequest{Id: wrapperspb.Int32(int32(id))}) if err != nil { log.Printf("[ERROR] Received error: %s", err.Error()) if cxsdk.Code(err) == codes.NotFound { @@ -684,12 +713,12 @@ func ViewResourceSchema(ctx context.Context) schema.Schema { stringvalidator.RegexMatches(regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), ""), }, }, - "id": schema.Int32Attribute{ + "id": schema.StringAttribute{ Computed: true, Description: "id", MarkdownDescription: "id", - PlanModifiers: []planmodifier.Int32{ - int32planmodifier.UseStateForUnknown(), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), }, }, "name": schema.StringAttribute{ @@ -759,7 +788,7 @@ func ViewResourceSchema(ctx context.Context) schema.Schema { type ViewModel struct { Filters types.Object `tfsdk:"filters"` //FiltersModel FolderId types.String `tfsdk:"folder_id"` - Id types.Int32 `tfsdk:"id"` + Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` SearchQuery types.Object `tfsdk:"search_query"` //SearchQueryModel TimeSelection types.Object `tfsdk:"time_selection"` // TimeSelectionModel From 066f13f5138f3b482b683f7817c39cf62caa7554 Mon Sep 17 00:00:00 2001 From: Or Novogroder Date: Tue, 1 Jul 2025 14:36:23 +0300 Subject: [PATCH 09/10] fixing str format --- internal/provider/resource_coralogix_view.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/provider/resource_coralogix_view.go b/internal/provider/resource_coralogix_view.go index a92fb1f2e..3df7a6470 100644 --- a/internal/provider/resource_coralogix_view.go +++ b/internal/provider/resource_coralogix_view.go @@ -559,14 +559,14 @@ func (r *ViewResource) Read(ctx context.Context, req resource.ReadRequest, resp readReq := &cxsdk.GetViewRequest{ Id: wrapperspb.Int32(int32(id)), } - log.Printf("[INFO] Reading view with ID: %d", idStr) + log.Printf("[INFO] Reading view with ID: %s", idStr) readResp, err := r.client.Get(ctx, readReq) if err != nil { log.Printf("[ERROR] Received error: %s", err.Error()) if cxsdk.Code(err) == codes.NotFound { resp.Diagnostics.AddWarning( fmt.Sprintf("View %q is in state, but no longer exists in Coralogix backend", idStr), - fmt.Sprintf("%d will be recreated when you apply", idStr), + fmt.Sprintf("%s will be recreated when you apply", idStr), ) resp.State.RemoveResource(ctx) } else { From da49214d5b9934a34f0738696048e4b4b5b2c92b Mon Sep 17 00:00:00 2001 From: Claus Matzinger Date: Thu, 23 Oct 2025 13:49:01 +0200 Subject: [PATCH 10/10] views --- internal/clientset/clientset.go | 7 +- .../data_source_coralogix_view.go | 44 ++- .../data_source_coralogix_views_folder.go | 9 +- .../resource_coralogix_view.go | 297 ++++++++---------- .../resource_coralogix_views_folder.go | 8 +- .../data_source_coralogix_view_test.go | 2 +- ...data_source_coralogix_views_folder_test.go | 2 +- .../provider/resource_coralogix_view_test.go | 19 +- .../resource_coralogix_views_folder_test.go | 7 +- 9 files changed, 194 insertions(+), 201 deletions(-) rename internal/provider/{ => data_exploration}/data_source_coralogix_view.go (71%) rename internal/provider/{ => data_exploration}/data_source_coralogix_views_folder.go (94%) rename internal/provider/{ => data_exploration}/resource_coralogix_view.go (93%) rename internal/provider/{ => data_exploration}/resource_coralogix_views_folder.go (97%) diff --git a/internal/clientset/clientset.go b/internal/clientset/clientset.go index 35e631cb6..b4781b379 100644 --- a/internal/clientset/clientset.go +++ b/internal/clientset/clientset.go @@ -21,6 +21,7 @@ import ( ipaccess "github.com/coralogix/coralogix-management-sdk/go/openapi/gen/ip_access_service" cxsdkOpenapi "github.com/coralogix/coralogix-management-sdk/go/openapi/cxsdk" + views "github.com/coralogix/coralogix-management-sdk/go/openapi/gen/views_service" cxsdk "github.com/coralogix/coralogix-management-sdk/go" ) @@ -51,7 +52,7 @@ type ClientSet struct { groupGrpc *cxsdk.GroupsClient notifications *cxsdk.NotificationsClient ipaccess *ipaccess.IPAccessServiceAPIService - views *cxsdk.ViewsClient + views *views.ViewsServiceAPIService viewsFolders *cxsdk.ViewFoldersClient grafana *GrafanaClient @@ -166,7 +167,7 @@ func (c *ClientSet) LegacySLOs() *cxsdk.LegacySLOsClient { return c.legacySlos } -func (c *ClientSet) Views() *cxsdk.ViewsClient { +func (c *ClientSet) Views() *views.ViewsServiceAPIService { return c.views } @@ -207,7 +208,7 @@ func NewClientSet(region string, apiKey string, targetUrl string) *ClientSet { groupGrpc: cxsdk.NewGroupsClient(apiKeySdk), notifications: cxsdk.NewNotificationsClient(apiKeySdk), ipaccess: cxsdkOpenapi.NewIPAccessClient(oasTfCPC), - views: cxsdk.NewViewsClient(apiKeySdk), + views: cxsdkOpenapi.NewViewsClient(oasTfCPC), viewsFolders: cxsdk.NewViewFoldersClient(apiKeySdk), grafana: NewGrafanaClient(apikeyCPC), diff --git a/internal/provider/data_source_coralogix_view.go b/internal/provider/data_exploration/data_source_coralogix_view.go similarity index 71% rename from internal/provider/data_source_coralogix_view.go rename to internal/provider/data_exploration/data_source_coralogix_view.go index 225e53b70..15c5545b1 100644 --- a/internal/provider/data_source_coralogix_view.go +++ b/internal/provider/data_exploration/data_source_coralogix_view.go @@ -12,23 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -package coralogix +package data_exploration import ( "context" - "encoding/json" "fmt" "log" "strconv" - "google.golang.org/protobuf/types/known/wrapperspb" - "terraform-provider-coralogix/coralogix/clientset" - "terraform-provider-coralogix/coralogix/utils" + "github.com/coralogix/terraform-provider-coralogix/internal/clientset" + "github.com/coralogix/terraform-provider-coralogix/internal/utils" + + views "github.com/coralogix/coralogix-management-sdk/go/openapi/gen/views_service" - cxsdk "github.com/coralogix/coralogix-management-sdk/go" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/resource" - "google.golang.org/grpc/codes" ) var _ datasource.DataSourceWithConfigure = &ViewDataSource{} @@ -38,7 +36,7 @@ func NewViewDataSource() datasource.DataSource { } type ViewDataSource struct { - client *cxsdk.ViewsClient + client *views.ViewsServiceAPIService } func (d *ViewDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -79,36 +77,30 @@ func (d *ViewDataSource) Read(ctx context.Context, req datasource.ReadRequest, r } //Get refreshed View value from Coralogix idStr := data.Id.ValueString() - id, err := strconv.Atoi(idStr) + id, err := strconv.ParseInt(idStr, 10, 32) + if err != nil { resp.Diagnostics.AddError( "Invalid View ID", - fmt.Sprintf("ID '%s' is not a valid integer: %s", idStr, err.Error()), + fmt.Sprintf("ID '%s' is not a valid 32-bit integer: %s", idStr, err.Error()), ) return } + rq := d.client.ViewsServiceGetView(ctx, int32(id)) + log.Printf("[INFO] Reading new resource: %s", utils.FormatJSON(rq)) + result, _, err := rq.Execute() - log.Printf("[INFO] Reading view: %d", id) - getViewResp, err := d.client.Get(ctx, &cxsdk.GetViewRequest{Id: wrapperspb.Int32(int32(id))}) if err != nil { log.Printf("[ERROR] Received error: %s", err.Error()) - if cxsdk.Code(err) == codes.NotFound { - resp.Diagnostics.AddWarning( - err.Error(), - fmt.Sprintf("View %q is in state, but no longer exists in Coralogix backend", id), - ) - } else { - resp.Diagnostics.AddError( - "Error reading View", - utils.FormatRpcErrors(err, fmt.Sprintf("%s/%d", cxsdk.GetViewRPC, id), ""), - ) - } + resp.Diagnostics.AddError( + "Error reading View", + utils.FormatOpenAPIErrors(err, "Read", nil), + ) return } - respStr, _ := json.Marshal(getViewResp) - log.Printf("[INFO] Received View: %s", string(respStr)) + log.Printf("[INFO] Read resource: %s", utils.FormatJSON(result)) - data, diags = flattenView(ctx, getViewResp.View) + data, diags = flattenView(ctx, result) if diags.HasError() { resp.Diagnostics.Append(diags...) return diff --git a/internal/provider/data_source_coralogix_views_folder.go b/internal/provider/data_exploration/data_source_coralogix_views_folder.go similarity index 94% rename from internal/provider/data_source_coralogix_views_folder.go rename to internal/provider/data_exploration/data_source_coralogix_views_folder.go index e69cdcca3..605c96d74 100644 --- a/internal/provider/data_source_coralogix_views_folder.go +++ b/internal/provider/data_exploration/data_source_coralogix_views_folder.go @@ -1,4 +1,4 @@ -// Copyright 2024 Coralogix Ltd. +// Copyright 2025 Coralogix Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package coralogix +package data_exploration import ( "context" @@ -20,9 +20,10 @@ import ( "fmt" "log" + "github.com/coralogix/terraform-provider-coralogix/coralogix/clientset" + "github.com/coralogix/terraform-provider-coralogix/coralogix/utils" + "google.golang.org/protobuf/types/known/wrapperspb" - "terraform-provider-coralogix/coralogix/clientset" - "terraform-provider-coralogix/coralogix/utils" cxsdk "github.com/coralogix/coralogix-management-sdk/go" "github.com/hashicorp/terraform-plugin-framework/datasource" diff --git a/internal/provider/resource_coralogix_view.go b/internal/provider/data_exploration/resource_coralogix_view.go similarity index 93% rename from internal/provider/resource_coralogix_view.go rename to internal/provider/data_exploration/resource_coralogix_view.go index 3df7a6470..db9c40a1e 100644 --- a/internal/provider/resource_coralogix_view.go +++ b/internal/provider/data_exploration/resource_coralogix_view.go @@ -1,4 +1,4 @@ -// Copyright 2024 Coralogix Ltd. +// Copyright 2025 Coralogix Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,7 +11,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -package coralogix + +package data_exploration import ( "context" @@ -21,6 +22,10 @@ import ( "strconv" "time" + views "github.com/coralogix/coralogix-management-sdk/go/openapi/gen/views_service" + "github.com/coralogix/terraform-provider-coralogix/coralogix/clientset" + "github.com/coralogix/terraform-provider-coralogix/coralogix/utils" + cxsdk "github.com/coralogix/coralogix-management-sdk/go" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" @@ -30,10 +35,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -41,8 +46,6 @@ import ( "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" - "terraform-provider-coralogix/coralogix/clientset" - "terraform-provider-coralogix/coralogix/utils" ) var _ resource.Resource = (*ViewResource)(nil) @@ -52,7 +55,7 @@ func NewViewResource() resource.Resource { } type ViewResource struct { - client *cxsdk.ViewsClient + client *views.ViewsServiceAPIService } func (r *ViewResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -77,7 +80,124 @@ func (r *ViewResource) Configure(_ context.Context, req resource.ConfigureReques } func (r *ViewResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = ViewResourceSchema(ctx) + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.Int32Attribute{ + Computed: true, + Description: "id", + MarkdownDescription: "id", + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.UseStateForUnknown(), + }, + }, + "folder_id": schema.StringAttribute{ + Optional: true, + Description: "Unique identifier for folders", + MarkdownDescription: "Unique identifier for folders", + Validators: []validator.String{ + stringvalidator.LengthBetween(36, 36), + stringvalidator.RegexMatches(regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), ""), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "View name", + MarkdownDescription: "View name", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "search_query": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "query": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + }, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + }, + "filters": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "filters": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Description: "Filter name", + MarkdownDescription: "Filter name", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "selected_values": schema.MapAttribute{ + ElementType: types.BoolType, + Required: true, + Description: "Filter selected values", + MarkdownDescription: "Filter selected values", + }, + }, + }, + Optional: true, + Computed: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + }, + }, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + }, + "time_selection": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "custom_selection": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "from_time": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "to_time": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + }, + Validators: []validator.Object{ + objectvalidator.ExactlyOneOf( + path.MatchRoot("time_selection").AtName("quick_selection"), + ), + }, + Optional: true, + }, + "quick_selection": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "seconds": schema.Int64Attribute{ + Required: true, + Description: "Folder name", + MarkdownDescription: "Folder name", + }, + }, + Optional: true, + }, + }, + Required: true, + }, + }, + } } func (r *ViewResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { @@ -121,7 +241,7 @@ func (r *ViewResource) Create(ctx context.Context, req resource.CreateRequest, r resp.Diagnostics.Append(resp.State.Set(ctx, data)...) } -func flattenView(ctx context.Context, view *cxsdk.View) (*ViewModel, diag.Diagnostics) { +func flattenView(ctx context.Context, view *views.View) (*ViewModel, diag.Diagnostics) { filters, diags := flattenViewFilter(ctx, view.Filters) if diags.HasError() { return nil, diags @@ -132,7 +252,7 @@ func flattenView(ctx context.Context, view *cxsdk.View) (*ViewModel, diag.Diagno return nil, diags } - timeSelection, diags := flattenViewTimeSelection(ctx, view.TimeSelection) + timeSelection, diags := flattenViewTimeSelection(ctx, &view.TimeSelection) if diags.HasError() { return nil, diags } @@ -140,14 +260,14 @@ func flattenView(ctx context.Context, view *cxsdk.View) (*ViewModel, diag.Diagno return &ViewModel{ Filters: filters, FolderId: utils.WrapperspbStringToTypeString(view.FolderId), - Id: types.StringValue(strconv.Itoa(int(view.Id.Value))), + Id: types.Int32Value(view.Id), Name: utils.WrapperspbStringToTypeString(view.Name), SearchQuery: searchQuery, TimeSelection: timeSelection, }, nil } -func flattenViewTimeSelection(ctx context.Context, selection *cxsdk.TimeSelection) (types.Object, diag.Diagnostics) { +func flattenViewTimeSelection(ctx context.Context, selection *views.TimeSelection) (types.Object, diag.Diagnostics) { if selection == nil { return TimeSelectionModel{ CustomSelection: types.ObjectNull(CustomSelectionModel{}.AttributeTypes(ctx)), @@ -211,7 +331,7 @@ func flattenQuickSelection(ctx context.Context, selection *cxsdk.QuickTimeSelect return quickSelectionModel.ToObjectValue(ctx) } -func flattenSearchQuery(ctx context.Context, query *cxsdk.SearchQuery) (types.Object, diag.Diagnostics) { +func flattenSearchQuery(ctx context.Context, query *views.SearchQuery) (types.Object, diag.Diagnostics) { if query == nil { return types.ObjectNull(SearchQueryModel{}.AttributeTypes(ctx)), nil } @@ -221,7 +341,7 @@ func flattenSearchQuery(ctx context.Context, query *cxsdk.SearchQuery) (types.Ob }.ToObjectValue(ctx) } -func flattenViewFilter(ctx context.Context, filters *cxsdk.SelectedFilters) (types.Object, diag.Diagnostics) { +func flattenViewFilter(ctx context.Context, filters *views.SelectedFilters) (types.Object, diag.Diagnostics) { if filters == nil { return types.ObjectNull(FiltersModel{}.AttributeTypes()), nil } @@ -236,7 +356,7 @@ func flattenViewFilter(ctx context.Context, filters *cxsdk.SelectedFilters) (typ }.ToObjectValue(ctx) } -func flattenInnerViewFilters(ctx context.Context, filters []*cxsdk.ViewFilter) (basetypes.ListValue, diag.Diagnostics) { +func flattenInnerViewFilters(ctx context.Context, filters []views.ViewsV1Filter) (basetypes.ListValue, diag.Diagnostics) { if filters == nil { return types.ListNull(types.ObjectType{AttrTypes: InnerFiltersModel{}.AttributeTypes()}), nil } @@ -310,19 +430,9 @@ func extractUpdateView(ctx context.Context, data *ViewModel) (*cxsdk.ReplaceView return nil, diags } - id, err := strconv.Atoi(data.Id.ValueString()) - if err != nil { - return nil, diag.Diagnostics{ - diag.NewErrorDiagnostic( - "Invalid View ID", - fmt.Sprintf("ID '%s' is not a valid integer: %s", data.Id.ValueString(), err.Error()), - ), - } - } - return &cxsdk.ReplaceViewRequest{ View: &cxsdk.View{ - Id: wrapperspb.Int32(int32(id)), + Id: wrapperspb.Int32(int32(data.Id.ValueInt32())), Name: utils.TypeStringToWrapperspbString(data.Name), SearchQuery: searchQuery, TimeSelection: timeSelection, @@ -632,17 +742,9 @@ func (r *ViewResource) Delete(ctx context.Context, req resource.DeleteRequest, r // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - idStr := data.Id.ValueString() - id, err := strconv.Atoi(idStr) - if err != nil { - resp.Diagnostics.AddError( - "Invalid View ID", - fmt.Sprintf("ID '%s' is not a valid integer: %s", idStr, err.Error()), - ) - return - } - - _, err = r.client.Delete(ctx, &cxsdk.DeleteViewRequest{Id: wrapperspb.Int32(int32(id))}) + id := data.Id + rq := r.client.ViewsServiceDeleteView(ctx, id.ValueInt32()) + _, _, err := rq.Execute() if err != nil { log.Printf("[ERROR] Received error: %s", err.Error()) if cxsdk.Code(err) == codes.NotFound { @@ -664,131 +766,10 @@ func (r *ViewResource) Delete(ctx context.Context, req resource.DeleteRequest, r } } -func ViewResourceSchema(ctx context.Context) schema.Schema { - return schema.Schema{ - Attributes: map[string]schema.Attribute{ - "filters": schema.SingleNestedAttribute{ - Attributes: map[string]schema.Attribute{ - "filters": schema.ListNestedAttribute{ - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "name": schema.StringAttribute{ - Required: true, - Description: "Filter name", - MarkdownDescription: "Filter name", - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - }, - "selected_values": schema.MapAttribute{ - ElementType: types.BoolType, - Required: true, - Description: "Filter selected values", - MarkdownDescription: "Filter selected values", - }, - }, - }, - Optional: true, - Computed: true, - Validators: []validator.List{ - listvalidator.SizeAtLeast(1), - }, - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), - }, - }, - }, - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - }, - "folder_id": schema.StringAttribute{ - Optional: true, - Description: "Unique identifier for folders", - MarkdownDescription: "Unique identifier for folders", - Validators: []validator.String{ - stringvalidator.LengthBetween(36, 36), - stringvalidator.RegexMatches(regexp.MustCompile("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), ""), - }, - }, - "id": schema.StringAttribute{ - Computed: true, - Description: "id", - MarkdownDescription: "id", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "name": schema.StringAttribute{ - Required: true, - Description: "View name", - MarkdownDescription: "View name", - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - }, - "search_query": schema.SingleNestedAttribute{ - Attributes: map[string]schema.Attribute{ - "query": schema.StringAttribute{ - Required: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - }, - }, - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.Object{ - objectplanmodifier.UseStateForUnknown(), - }, - }, - "time_selection": schema.SingleNestedAttribute{ - Attributes: map[string]schema.Attribute{ - "custom_selection": schema.SingleNestedAttribute{ - Attributes: map[string]schema.Attribute{ - "from_time": schema.StringAttribute{ - Required: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - }, - "to_time": schema.StringAttribute{ - Required: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - }, - }, - Validators: []validator.Object{ - objectvalidator.ExactlyOneOf( - path.MatchRoot("time_selection").AtName("quick_selection"), - ), - }, - Optional: true, - }, - "quick_selection": schema.SingleNestedAttribute{ - Attributes: map[string]schema.Attribute{ - "seconds": schema.Int64Attribute{ - Required: true, - Description: "Folder name", - MarkdownDescription: "Folder name", - }, - }, - Optional: true, - }, - }, - Required: true, - }, - }, - } -} - type ViewModel struct { Filters types.Object `tfsdk:"filters"` //FiltersModel FolderId types.String `tfsdk:"folder_id"` - Id types.String `tfsdk:"id"` + Id types.Int32 `tfsdk:"id"` Name types.String `tfsdk:"name"` SearchQuery types.Object `tfsdk:"search_query"` //SearchQueryModel TimeSelection types.Object `tfsdk:"time_selection"` // TimeSelectionModel diff --git a/internal/provider/resource_coralogix_views_folder.go b/internal/provider/data_exploration/resource_coralogix_views_folder.go similarity index 97% rename from internal/provider/resource_coralogix_views_folder.go rename to internal/provider/data_exploration/resource_coralogix_views_folder.go index be717b686..c0ec19f66 100644 --- a/internal/provider/resource_coralogix_views_folder.go +++ b/internal/provider/data_exploration/resource_coralogix_views_folder.go @@ -11,13 +11,17 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -package coralogix + +package data_exploration import ( "context" "fmt" "log" + "github.com/coralogix/terraform-provider-coralogix/coralogix/clientset" + "github.com/coralogix/terraform-provider-coralogix/coralogix/utils" + cxsdk "github.com/coralogix/coralogix-management-sdk/go" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" @@ -30,8 +34,6 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/wrapperspb" - "terraform-provider-coralogix/coralogix/clientset" - "terraform-provider-coralogix/coralogix/utils" ) var _ resource.Resource = (*ViewsFolderResource)(nil) diff --git a/internal/provider/data_source_coralogix_view_test.go b/internal/provider/data_source_coralogix_view_test.go index 008a23e8c..509444d49 100644 --- a/internal/provider/data_source_coralogix_view_test.go +++ b/internal/provider/data_source_coralogix_view_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package coralogix +package provider import ( "testing" diff --git a/internal/provider/data_source_coralogix_views_folder_test.go b/internal/provider/data_source_coralogix_views_folder_test.go index 73863ce12..cac2c165c 100644 --- a/internal/provider/data_source_coralogix_views_folder_test.go +++ b/internal/provider/data_source_coralogix_views_folder_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package coralogix +package provider import ( "testing" diff --git a/internal/provider/resource_coralogix_view_test.go b/internal/provider/resource_coralogix_view_test.go index ed972aa52..2b196c686 100644 --- a/internal/provider/resource_coralogix_view_test.go +++ b/internal/provider/resource_coralogix_view_test.go @@ -1,4 +1,18 @@ -package coralogix +// Copyright 2025 Coralogix Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package provider import ( "context" @@ -6,11 +20,12 @@ import ( "strconv" "testing" + "github.com/coralogix/terraform-provider-coralogix/coralogix/clientset" + cxsdk "github.com/coralogix/coralogix-management-sdk/go" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "google.golang.org/protobuf/types/known/wrapperspb" - "terraform-provider-coralogix/coralogix/clientset" ) func TestAccCoralogixResourceView(t *testing.T) { diff --git a/internal/provider/resource_coralogix_views_folder_test.go b/internal/provider/resource_coralogix_views_folder_test.go index f38a8e797..7fbbe820b 100644 --- a/internal/provider/resource_coralogix_views_folder_test.go +++ b/internal/provider/resource_coralogix_views_folder_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Coralogix Ltd. +// Copyright 2025 Coralogix Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,18 +11,19 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -package coralogix +package provider import ( "context" "fmt" "testing" + "github.com/coralogix/terraform-provider-coralogix/coralogix/clientset" + cxsdk "github.com/coralogix/coralogix-management-sdk/go" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "google.golang.org/protobuf/types/known/wrapperspb" - "terraform-provider-coralogix/coralogix/clientset" ) func TestAccCoralogixResourceViewsFolder(t *testing.T) {