diff --git a/Makefile b/Makefile
index ab61c7b..399c132 100644
--- a/Makefile
+++ b/Makefile
@@ -13,3 +13,4 @@ lint:
docs:
go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs
tfplugindocs generate
+ @echo "Use this site to preview markdown rendering: https://registry.terraform.io/tools/doc-preview"
diff --git a/docs/data-sources/database.md b/docs/data-sources/database.md
index fc5fe16..3063686 100644
--- a/docs/data-sources/database.md
+++ b/docs/data-sources/database.md
@@ -25,4 +25,13 @@ Retrieves a database. Use this data source to retrieve information for a specifi
- `cluster_id` (String) The ID of the cluster that you want to manage.
- `max_columns_per_table` (Number) The maximum number of columns per table for the cluster database.
- `max_tables` (Number) The maximum number of tables for the cluster database.
+- `partition_template` (Attributes List) The template partitioning of the cluster database. (see [below for nested schema](#nestedatt--partition_template))
- `retention_period` (Number) The retention period of the cluster database in nanoseconds.
+
+
+### Nested Schema for `partition_template`
+
+Read-Only:
+
+- `type` (String) The type of template part.
+- `value` (String) The value of template part.
diff --git a/docs/data-sources/databases.md b/docs/data-sources/databases.md
index 73f2c5f..2ec6e4c 100644
--- a/docs/data-sources/databases.md
+++ b/docs/data-sources/databases.md
@@ -29,4 +29,13 @@ Read-Only:
- `max_columns_per_table` (Number) The maximum number of columns per table for the cluster database.
- `max_tables` (Number) The maximum number of tables for the cluster database.
- `name` (String) The name of the cluster database.
+- `partition_template` (Attributes List) The template partitioning of the cluster database. (see [below for nested schema](#nestedatt--databases--partition_template))
- `retention_period` (Number) The retention period of the cluster database in nanoseconds.
+
+
+### Nested Schema for `databases.partition_template`
+
+Read-Only:
+
+- `type` (String) The type of template part.
+- `value` (String) The value of template part.
diff --git a/docs/resources/database.md b/docs/resources/database.md
index ac1f410..e147e54 100644
--- a/docs/resources/database.md
+++ b/docs/resources/database.md
@@ -1,5 +1,3 @@
----
-# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "influxdb3_database Resource - terraform-provider-influxdb3"
subcategory: ""
description: |-
@@ -10,7 +8,36 @@ description: |-
Creates and manages a database.
+## Example Usage
+
+```terraform
+resource "influxdb3_database" "signals" {
+ name = "signals"
+ retention_period = 604800
+ partition_template = [
+ {
+ type = "tag"
+ value = "line"
+ },
+ {
+ type = "tag"
+ value = "station"
+ },
+ {
+ type = "time"
+ value = "%Y-%m-%d"
+ },
+ {
+ type = "bucket"
+ value = jsonencode({
+ "tagName" : "temperature",
+ "numberOfBuckets" : 10
+ })
+ },
+ ]
+}
+```
## Schema
@@ -23,9 +50,18 @@ Creates and manages a database.
- `max_columns_per_table` (Number) The maximum number of columns per table for the cluster database. The default is `200`
- `max_tables` (Number) The maximum number of tables for the cluster database. The default is `500`
+- `partition_template` (Attributes List) A template for [partitioning](https://docs.influxdata.com/influxdb/cloud-dedicated/admin/custom-partitions/partition-templates/) a cluster database. **Note:** A partition template can include up to 7 total tag and tag bucket parts and only 1 time part. (see [below for nested schema](#nestedatt--partition_template))
- `retention_period` (Number) The retention period of the cluster database in nanoseconds. The default is `0`. If the retention period is not set or is set to `0`, the database will have infinite retention.
### Read-Only
- `account_id` (String) The ID of the account that the cluster belongs to.
- `cluster_id` (String) The ID of the cluster that you want to manage.
+
+
+### Nested Schema for `partition_template`
+
+Required:
+
+- `type` (String) The type of template part. Valid values are `bucket`, `tag` or `time`.
+- `value` (String) The value of template part. **Note:** For `bucket` partition template type use `jsonencode()` function to encode the value to a string.
diff --git a/examples/resources/database/main.tf b/examples/resources/database/main.tf
new file mode 100755
index 0000000..30dba7e
--- /dev/null
+++ b/examples/resources/database/main.tf
@@ -0,0 +1,26 @@
+resource "influxdb3_database" "signals" {
+ name = "signals"
+ retention_period = 604800
+
+ partition_template = [
+ {
+ type = "tag"
+ value = "line"
+ },
+ {
+ type = "tag"
+ value = "station"
+ },
+ {
+ type = "time"
+ value = "%Y-%m-%d"
+ },
+ {
+ type = "bucket"
+ value = jsonencode({
+ "tagName" : "temperature",
+ "numberOfBuckets" : 10
+ })
+ },
+ ]
+}
diff --git a/examples/resources/database/outputs.tf b/examples/resources/database/outputs.tf
new file mode 100644
index 0000000..17357da
--- /dev/null
+++ b/examples/resources/database/outputs.tf
@@ -0,0 +1,3 @@
+output "signals_database" {
+ value = influxdb3_database.signals
+}
diff --git a/examples/resources/database/provider.tf b/examples/resources/database/provider.tf
new file mode 100644
index 0000000..738d843
--- /dev/null
+++ b/examples/resources/database/provider.tf
@@ -0,0 +1,9 @@
+terraform {
+ required_providers {
+ influxdb3 = {
+ source = "komminarlabs/influxdb3"
+ }
+ }
+}
+
+provider "influxdb3" {}
diff --git a/examples/resources/database/resource.tf b/examples/resources/database/resource.tf
deleted file mode 100644
index ff749e1..0000000
--- a/examples/resources/database/resource.tf
+++ /dev/null
@@ -1,18 +0,0 @@
-terraform {
- required_providers {
- influxdb3 = {
- source = "komminarlabs/influxdb3"
- }
- }
-}
-
-provider "influxdb3" {}
-
-resource "influxdb3_database" "signals" {
- name = "signals"
- retention_period = 604800
-}
-
-output "signals_database" {
- value = influxdb3_database.signals
-}
diff --git a/internal/provider/database_data_source.go b/internal/provider/database_data_source.go
index 4462c8a..8a9612c 100644
--- a/internal/provider/database_data_source.go
+++ b/internal/provider/database_data_source.go
@@ -63,6 +63,22 @@ func (d *DatabaseDataSource) Schema(ctx context.Context, req datasource.SchemaRe
Computed: true,
Description: "The retention period of the cluster database in nanoseconds.",
},
+ "partition_template": schema.ListNestedAttribute{
+ Computed: true,
+ Description: "The template partitioning of the cluster database.",
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Computed: true,
+ Description: "The type of template part.",
+ },
+ "value": schema.StringAttribute{
+ Computed: true,
+ Description: "The value of template part.",
+ },
+ },
+ },
+ },
},
}
}
@@ -124,7 +140,14 @@ func (d *DatabaseDataSource) Read(ctx context.Context, req datasource.ReadReques
}
// Check if the database exists
- readDatabase := getDatabaseByName(*readDatabasesResponse, databaseName.ValueString())
+ readDatabase, err := getDatabaseByName(*readDatabasesResponse, databaseName.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error getting database",
+ "Unexpected error: "+err.Error(),
+ )
+ return
+ }
if readDatabase == nil {
resp.Diagnostics.AddError(
"Database not found",
diff --git a/internal/provider/database_model.go b/internal/provider/database_model.go
index 87ccc08..8b183e9 100644
--- a/internal/provider/database_model.go
+++ b/internal/provider/database_model.go
@@ -1,33 +1,91 @@
package provider
import (
+ "encoding/json"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/komminarlabs/influxdb3"
)
// DatabaseModel maps InfluxDB database schema data.
type DatabaseModel struct {
- AccountId types.String `tfsdk:"account_id"`
- ClusterId types.String `tfsdk:"cluster_id"`
- Name types.String `tfsdk:"name"`
- MaxTables types.Int64 `tfsdk:"max_tables"`
- MaxColumnsPerTable types.Int64 `tfsdk:"max_columns_per_table"`
- RetentionPeriod types.Int64 `tfsdk:"retention_period"`
+ AccountId types.String `tfsdk:"account_id"`
+ ClusterId types.String `tfsdk:"cluster_id"`
+ Name types.String `tfsdk:"name"`
+ MaxTables types.Int64 `tfsdk:"max_tables"`
+ MaxColumnsPerTable types.Int64 `tfsdk:"max_columns_per_table"`
+ RetentionPeriod types.Int64 `tfsdk:"retention_period"`
+ PartitionTemplate []DatabasePartitionTemplateModel `tfsdk:"partition_template"`
+}
+
+// DatabasePartitionTemplateModel maps InfluxDB database partition template schema data.
+type DatabasePartitionTemplateModel struct {
+ Type types.String `json:"type" tfsdk:"type"`
+ Value types.String `json:"value" tfsdk:"value"`
+}
+
+// GetAttrType returns the attribute type for the DatabasePartitionTemplateModel.
+func (d DatabasePartitionTemplateModel) GetAttrType() attr.Type {
+ return types.ObjectType{AttrTypes: map[string]attr.Type{
+ "type": types.StringType,
+ "value": types.StringType,
+ }}
}
-func getDatabaseByName(databases influxdb3.GetClusterDatabasesResponse, name string) *DatabaseModel {
+func getDatabaseByName(databases influxdb3.GetClusterDatabasesResponse, name string) (*DatabaseModel, error) {
for _, database := range *databases.JSON200 {
if database.Name == name {
+ partitionTemplate, err := getPartitionTemplate(database.PartitionTemplate)
+ if err != nil {
+ return nil, err
+ }
+
db := DatabaseModel{
AccountId: types.StringValue(database.AccountId.String()),
ClusterId: types.StringValue(database.ClusterId.String()),
Name: types.StringValue(database.Name),
MaxTables: types.Int64Value(int64(database.MaxTables)),
MaxColumnsPerTable: types.Int64Value(int64(database.MaxColumnsPerTable)),
+ PartitionTemplate: partitionTemplate,
RetentionPeriod: types.Int64Value(database.RetentionPeriod),
}
- return &db
+ return &db, nil
+ }
+ }
+ return nil, nil
+}
+
+func getPartitionTemplate(partitionTemplates *influxdb3.ClusterDatabasePartitionTemplate) ([]DatabasePartitionTemplateModel, error) {
+ if partitionTemplates == nil {
+ return nil, nil
+ }
+
+ partitionTemplateModels := make([]DatabasePartitionTemplateModel, 0)
+ for _, v := range *partitionTemplates {
+ partitionTemplate := make(map[string]any)
+ b, err := v.MarshalJSON()
+ if err != nil {
+ return nil, err
+ }
+
+ json.Unmarshal(b, &partitionTemplate)
+ if partitionTemplate["type"] == "time" || partitionTemplate["type"] == "tag" {
+ partitionTemplateModels = append(partitionTemplateModels, DatabasePartitionTemplateModel{
+ Type: types.StringValue(partitionTemplate["type"].(string)),
+ Value: types.StringValue(partitionTemplate["value"].(string)),
+ })
+ } else if partitionTemplate["type"] == "bucket" {
+ jsonEncoded, err := json.Marshal(partitionTemplate["value"])
+ if err != nil {
+ return nil, err
+ }
+
+ partitionTemplateModels = append(partitionTemplateModels, DatabasePartitionTemplateModel{
+ Type: types.StringValue(partitionTemplate["type"].(string)),
+ Value: types.StringValue(string(jsonEncoded)),
+ })
}
}
- return nil
+ return partitionTemplateModels, nil
}
diff --git a/internal/provider/database_resource.go b/internal/provider/database_resource.go
index 17ee67c..c2bbca7 100644
--- a/internal/provider/database_resource.go
+++ b/internal/provider/database_resource.go
@@ -2,13 +2,17 @@ package provider
import (
"context"
+ "encoding/json"
"fmt"
+ "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"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/int64default"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"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"
@@ -83,6 +87,35 @@ func (r *DatabaseResource) Schema(ctx context.Context, req resource.SchemaReques
Default: int64default.StaticInt64(0),
Description: "The retention period of the cluster database in nanoseconds. The default is `0`. If the retention period is not set or is set to `0`, the database will have infinite retention.",
},
+ "partition_template": schema.ListNestedAttribute{
+ Computed: true,
+ Optional: true,
+ Default: listdefault.StaticValue(types.ListNull(DatabasePartitionTemplateModel{}.GetAttrType())),
+ Description: "A template for [partitioning](https://docs.influxdata.com/influxdb/cloud-dedicated/admin/custom-partitions/partition-templates/) a cluster database. **Note:** A partition template can include up to 7 total tag and tag bucket parts and only 1 time part.",
+ Validators: []validator.List{
+ listvalidator.UniqueValues(),
+ listvalidator.SizeBetween(1, 8),
+ },
+ PlanModifiers: []planmodifier.List{
+ listplanmodifier.UseStateForUnknown(),
+ listplanmodifier.RequiresReplace(),
+ },
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Required: true,
+ Description: "The type of template part. Valid values are `bucket`, `tag` or `time`.",
+ Validators: []validator.String{
+ stringvalidator.OneOf([]string{"bucket", "tag", "time"}...),
+ },
+ },
+ "value": schema.StringAttribute{
+ Required: true,
+ Description: "The value of template part. **Note:** For `bucket` partition template type use `jsonencode()` function to encode the value to a string.",
+ },
+ },
+ },
+ },
},
}
}
@@ -98,16 +131,54 @@ func (r *DatabaseResource) Create(ctx context.Context, req resource.CreateReques
}
// Generate API request body from plan
+ partitionTemplates := []influxdb3.ClusterDatabasePartitionTemplatePart{}
+ for _, pt := range plan.PartitionTemplate {
+ t := influxdb3.ClusterDatabasePartitionTemplatePart{}
+ if pt.Type.ValueString() == "time" {
+ timeTemplate := influxdb3.ClusterDatabasePartitionTemplatePartTimeFormat{
+ Type: (*influxdb3.ClusterDatabasePartitionTemplatePartTimeFormatType)(pt.Type.ValueStringPointer()),
+ Value: pt.Value.ValueStringPointer(),
+ }
+ t.MergeClusterDatabasePartitionTemplatePartTimeFormat(timeTemplate)
+ } else if pt.Type.ValueString() == "tag" {
+ tagTemplate := influxdb3.ClusterDatabasePartitionTemplatePartTagValue{
+ Type: (*influxdb3.ClusterDatabasePartitionTemplatePartTagValueType)(pt.Type.ValueStringPointer()),
+ Value: pt.Value.ValueStringPointer(),
+ }
+ t.MergeClusterDatabasePartitionTemplatePartTagValue(tagTemplate)
+ } else if pt.Type.ValueString() == "bucket" {
+ var encodedJSONData struct {
+ NumberOfBuckets *int32 `json:"numberOfBuckets,omitempty"`
+ TagName *string `json:"tagName,omitempty"`
+ }
+ err := json.Unmarshal([]byte(pt.Value.ValueString()), &encodedJSONData)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error creating database partition template",
+ "Failed to unmarshal JSON data: "+err.Error(),
+ )
+ return
+ }
+ bucketTemplate := influxdb3.ClusterDatabasePartitionTemplatePartBucket{
+ Type: (*influxdb3.ClusterDatabasePartitionTemplatePartBucketType)(pt.Type.ValueStringPointer()),
+ Value: &encodedJSONData,
+ }
+ t.MergeClusterDatabasePartitionTemplatePartBucket(bucketTemplate)
+ }
+ partitionTemplates = append(partitionTemplates, t)
+ }
+
maxTables := int32(plan.MaxTables.ValueInt64())
maxColumnsPerTable := int32(plan.MaxColumnsPerTable.ValueInt64())
createDatabaseRequest := influxdb3.CreateClusterDatabaseJSONRequestBody{
- Name: plan.Name.ValueString(),
MaxTables: &maxTables,
MaxColumnsPerTable: &maxColumnsPerTable,
+ Name: plan.Name.ValueString(),
+ PartitionTemplate: &partitionTemplates,
RetentionPeriod: plan.RetentionPeriod.ValueInt64Pointer(),
}
- createDatabasesResponse, err := r.client.CreateClusterDatabaseWithResponse(ctx, r.accountID, r.clusterID, createDatabaseRequest)
+ createDatabaseResponse, err := r.client.CreateClusterDatabaseWithResponse(ctx, r.accountID, r.clusterID, createDatabaseRequest)
if err != nil {
resp.Diagnostics.AddError(
"Error creating database",
@@ -116,22 +187,32 @@ func (r *DatabaseResource) Create(ctx context.Context, req resource.CreateReques
return
}
- if createDatabasesResponse.StatusCode() != 200 {
+ if createDatabaseResponse.StatusCode() != 200 {
resp.Diagnostics.AddError(
"Error creating database",
- fmt.Sprintf("Status: %s", createDatabasesResponse.Status()),
+ fmt.Sprintf("Status: %s", createDatabaseResponse.Status()),
)
return
}
- createDatabases := createDatabasesResponse.JSON200
+ createDatabase := createDatabaseResponse.JSON200
// Map response body to schema and populate Computed attribute values
- plan.AccountId = types.StringValue(createDatabases.AccountId.String())
- plan.ClusterId = types.StringValue(createDatabases.ClusterId.String())
- plan.Name = types.StringValue(createDatabases.Name)
- plan.MaxTables = types.Int64Value(int64(createDatabases.MaxTables))
- plan.MaxColumnsPerTable = types.Int64Value(int64(createDatabases.MaxColumnsPerTable))
- plan.RetentionPeriod = types.Int64Value(createDatabases.RetentionPeriod)
+ plan.AccountId = types.StringValue(createDatabase.AccountId.String())
+ plan.ClusterId = types.StringValue(createDatabase.ClusterId.String())
+ plan.MaxTables = types.Int64Value(int64(createDatabase.MaxTables))
+ plan.MaxColumnsPerTable = types.Int64Value(int64(createDatabase.MaxColumnsPerTable))
+ plan.Name = types.StringValue(createDatabase.Name)
+ plan.RetentionPeriod = types.Int64Value(createDatabase.RetentionPeriod)
+
+ partitionTemplate, err := getPartitionTemplate(createDatabase.PartitionTemplate)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error getting database partition template",
+ "Could not create database, unexpected error: "+err.Error(),
+ )
+ return
+ }
+ plan.PartitionTemplate = partitionTemplate
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
@@ -170,7 +251,14 @@ func (r *DatabaseResource) Read(ctx context.Context, req resource.ReadRequest, r
}
// Check if the database exists
- readDatabase := getDatabaseByName(*readDatabasesResponse, state.Name.ValueString())
+ readDatabase, err := getDatabaseByName(*readDatabasesResponse, state.Name.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error getting database",
+ err.Error(),
+ )
+ return
+ }
if readDatabase == nil {
resp.Diagnostics.AddError(
"Database not found",
@@ -230,9 +318,9 @@ func (r *DatabaseResource) Update(ctx context.Context, req resource.UpdateReques
// Map response body to schema and populate Computed attribute values
plan.AccountId = types.StringValue(updateDatabase.AccountId.String())
plan.ClusterId = types.StringValue(updateDatabase.ClusterId.String())
- plan.Name = types.StringValue(updateDatabase.Name)
plan.MaxTables = types.Int64Value(int64(updateDatabase.MaxTables))
plan.MaxColumnsPerTable = types.Int64Value(int64(updateDatabase.MaxColumnsPerTable))
+ plan.Name = types.StringValue(updateDatabase.Name)
plan.RetentionPeriod = types.Int64Value(updateDatabase.RetentionPeriod)
// Save updated data into Terraform state
diff --git a/internal/provider/databases_data_source.go b/internal/provider/databases_data_source.go
index 68bc575..faae0fd 100644
--- a/internal/provider/databases_data_source.go
+++ b/internal/provider/databases_data_source.go
@@ -73,6 +73,22 @@ func (d *DatabasesDataSource) Schema(ctx context.Context, req datasource.SchemaR
Computed: true,
Description: "The retention period of the cluster database in nanoseconds.",
},
+ "partition_template": schema.ListNestedAttribute{
+ Computed: true,
+ Description: "The template partitioning of the cluster database.",
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "type": schema.StringAttribute{
+ Computed: true,
+ Description: "The type of template part.",
+ },
+ "value": schema.StringAttribute{
+ Computed: true,
+ Description: "The value of template part.",
+ },
+ },
+ },
+ },
},
},
},
@@ -124,12 +140,22 @@ func (d *DatabasesDataSource) Read(ctx context.Context, req datasource.ReadReque
// Map response body to model
for _, database := range *readDatabasesResponse.JSON200 {
+ partitionTemplate, err := getPartitionTemplate(database.PartitionTemplate)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error getting Databases",
+ err.Error(),
+ )
+ return
+ }
+
databaseState := DatabaseModel{
AccountId: types.StringValue(database.AccountId.String()),
ClusterId: types.StringValue(database.ClusterId.String()),
- Name: types.StringValue(database.Name),
MaxTables: types.Int64Value(int64(database.MaxTables)),
MaxColumnsPerTable: types.Int64Value(int64(database.MaxColumnsPerTable)),
+ Name: types.StringValue(database.Name),
+ PartitionTemplate: partitionTemplate,
RetentionPeriod: types.Int64Value(database.RetentionPeriod),
}
state.Databases = append(state.Databases, databaseState)
diff --git a/templates/resources/database.md.tmpl b/templates/resources/database.md.tmpl
new file mode 100644
index 0000000..fb7062a
--- /dev/null
+++ b/templates/resources/database.md.tmpl
@@ -0,0 +1,23 @@
+page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}"
+subcategory: ""
+description: |-
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
+---
+
+# {{.Name}} ({{.Type}})
+
+{{ .Description | trimspace }}
+
+## Example Usage
+
+{{tffile "examples/resources/database/main.tf" }}
+
+{{ .SchemaMarkdown | trimspace }}
+{{- if .HasImport }}
+
+## Import
+
+Import is supported using the following syntax:
+
+{{codefile "shell" .ImportFile }}
+{{- end }}