diff --git a/go.mod b/go.mod index 0524edac8..23f04292f 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,10 @@ require ( github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/terraform-plugin-framework v1.6.1 + github.com/hashicorp/terraform-plugin-framework v1.7.0 github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 - github.com/hashicorp/terraform-plugin-go v0.22.0 + github.com/hashicorp/terraform-plugin-go v0.22.1 + github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-mux v0.15.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 github.com/hashicorp/terraform-plugin-testing v1.7.0 @@ -35,7 +36,6 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.20.0 // indirect github.com/hashicorp/terraform-json v0.21.0 // indirect - github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -60,7 +60,7 @@ require ( golang.org/x/tools v0.13.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/grpc v1.62.0 // indirect + google.golang.org/grpc v1.62.1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 7fc59afa3..00788fbbd 100644 --- a/go.sum +++ b/go.sum @@ -74,14 +74,16 @@ github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8J github.com/hashicorp/terraform-exec v0.20.0/go.mod h1:ckKGkJWbsNqFKV1itgMnE0hY9IYf1HoiekpuN0eWoDw= github.com/hashicorp/terraform-json v0.21.0 h1:9NQxbLNqPbEMze+S6+YluEdXgJmhQykRyRNd+zTI05U= github.com/hashicorp/terraform-json v0.21.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk= -github.com/hashicorp/terraform-plugin-framework v1.4.2 h1:P7a7VP1GZbjc4rv921Xy5OckzhoiO3ig6SGxwelD2sI= -github.com/hashicorp/terraform-plugin-framework v1.4.2/go.mod h1:GWl3InPFZi2wVQmdVnINPKys09s9mLmTZr95/ngLnbY= github.com/hashicorp/terraform-plugin-framework v1.6.1 h1:hw2XrmUu8d8jVL52ekxim2IqDc+2Kpekn21xZANARLU= github.com/hashicorp/terraform-plugin-framework v1.6.1/go.mod h1:aJI+n/hBPhz1J+77GdgNfk5svW12y7fmtxe/5L5IuwI= +github.com/hashicorp/terraform-plugin-framework v1.7.0 h1:wOULbVmfONnJo9iq7/q+iBOBJul5vRovaYJIu2cY/Pw= +github.com/hashicorp/terraform-plugin-framework v1.7.0/go.mod h1:jY9Id+3KbZ17OMpulgnWLSfwxNVYSoYBQFTgsx044CI= github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc= github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg= github.com/hashicorp/terraform-plugin-go v0.22.0 h1:1OS1Jk5mO0f5hrziWJGXXIxBrMe2j/B8E+DVGw43Xmc= github.com/hashicorp/terraform-plugin-go v0.22.0/go.mod h1:mPULV91VKss7sik6KFEcEu7HuTogMLLO/EvWCuFkRVE= +github.com/hashicorp/terraform-plugin-go v0.22.1 h1:iTS7WHNVrn7uhe3cojtvWWn83cm2Z6ryIUDTRO0EV7w= +github.com/hashicorp/terraform-plugin-go v0.22.1/go.mod h1:qrjnqRghvQ6KnDbB12XeZ4FluclYwptntoWCr9QaXTI= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-plugin-mux v0.15.0 h1:+/+lDx0WUsIOpkAmdwBIoFU8UP9o2eZASoOnLsWbKME= @@ -225,6 +227,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= diff --git a/ovh/notification_email.go b/ovh/notification_email.go index e170992ce..2efe5b980 100644 --- a/ovh/notification_email.go +++ b/ovh/notification_email.go @@ -7,9 +7,7 @@ import ( "strings" ) -func notificationEmailSortedIds(meta interface{}) ([]int64, error) { - config := meta.(*Config) - +func notificationEmailSortedIds(config *Config) ([]int64, error) { // Create Order log.Printf("[DEBUG] Will read notification emails ids") res := []int64{} @@ -24,10 +22,8 @@ func notificationEmailSortedIds(meta interface{}) ([]int64, error) { return res, nil } -func getNewNotificationEmail(matches []string, oldIds []int64, meta interface{}) (*NotificationEmail, error) { - config := meta.(*Config) - - curIds, err := notificationEmailSortedIds(meta) +func getNewNotificationEmail(matches []string, oldIds []int64, config *Config) (*NotificationEmail, error) { + curIds, err := notificationEmailSortedIds(config) if err != nil { return nil, err } diff --git a/ovh/order.go b/ovh/order.go index 14b89fc9d..ad499b24b 100644 --- a/ovh/order.go +++ b/ovh/order.go @@ -1,6 +1,7 @@ package ovh import ( + "errors" "fmt" "log" "net/url" @@ -8,10 +9,12 @@ import ( "strings" "time" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/ovh/go-ovh/ovh" "github.com/ovh/terraform-provider-ovh/ovh/helpers" + "github.com/ovh/terraform-provider-ovh/ovh/types" ) var ( @@ -218,24 +221,36 @@ func genericOrderSchema(withOptions bool) map[string]*schema.Schema { return orderSchema } -func orderCreate(d *schema.ResourceData, meta interface{}, product string) error { +func orderCreateFromResource(d *schema.ResourceData, meta interface{}, product string) error { config := meta.(*Config) + order := (&OrderModel{}).FromResource(d) + err := orderCreate(order, config, product) + if err != nil { + return err + } + + d.SetId(fmt.Sprint(order.Order.OrderId.ValueInt64())) + + return nil +} + +func orderCreate(d *OrderModel, config *Config, product string) error { // create Cart cartParams := &OrderCartCreateOpts{ - OvhSubsidiary: strings.ToUpper(d.Get("ovh_subsidiary").(string)), + OvhSubsidiary: strings.ToUpper(d.OvhSubsidiary.ValueString()), } - cart, err := orderCartCreate(meta, cartParams, true) + cart, err := orderCartCreate(config, cartParams, true) if err != nil { return fmt.Errorf("calling creating order cart: %q", err) } // Create Product Item item := &OrderCartItem{} - cartPlanParams := (&OrderCartPlanCreateOpts{ - Quantity: 1, - }).FromResourceWithPath(d, "plan.0") + cartPlanParamsList := d.Plan.Elements() + cartPlanParams := cartPlanParamsList[0].(PlanValue) + cartPlanParams.Quantity = types.TfInt64Value{Int64Value: basetypes.NewInt64Value(1)} log.Printf("[DEBUG] Will create order item %s for cart: %s", product, cart.CartId) endpoint := fmt.Sprintf("/order/cart/%s/%s", url.PathEscape(cart.CartId), product) @@ -244,62 +259,53 @@ func orderCreate(d *schema.ResourceData, meta interface{}, product string) error } // apply configurations - nbOfConfigurations := d.Get("plan.0.configuration.#").(int) - for i := 0; i < nbOfConfigurations; i++ { + configs := cartPlanParams.Configuration.Elements() + + for _, cfg := range configs { log.Printf("[DEBUG] Will create order cart item configuration for cart item: %s/%d", item.CartId, item.ItemId, ) itemConfig := &OrderCartItemConfiguration{} - itemConfigParams := (&OrderCartItemConfigurationOpts{}).FromResourceWithPath( - d, - fmt.Sprintf("plan.0.configuration.%d", i), - ) endpoint := fmt.Sprintf("/order/cart/%s/item/%d/configuration", url.PathEscape(item.CartId), item.ItemId, ) - if err := config.OVHClient.Post(endpoint, itemConfigParams, itemConfig); err != nil { - return fmt.Errorf("calling Post %s with params %v:\n\t %q", endpoint, itemConfigParams, err) + if err := config.OVHClient.Post(endpoint, cfg, itemConfig); err != nil { + return fmt.Errorf("calling Post %s with params %v:\n\t %q", endpoint, cfg, err) } } + planOptionValue := d.PlanOption.Elements() + // Create Product Options Items - nbOfOptions := d.Get("plan_option.#").(int) - for i := 0; i < nbOfOptions; i++ { - optionPath := fmt.Sprintf("plan_option.%d", i) + for _, option := range planOptionValue { + opt := option.(PlanOptionValue) + log.Printf("[DEBUG] Will create order item options %s for cart: %s", product, cart.CartId) productOptionsItem := &OrderCartItem{} - cartPlanOptionsParams := (&OrderCartPlanOptionsCreateOpts{ - ItemId: item.ItemId, - Quantity: 1, - }).FromResourceWithPath( - d, - optionPath, - ) + + opt.ItemId = types.TfInt64Value{Int64Value: basetypes.NewInt64Value(item.ItemId)} + opt.Quantity = types.TfInt64Value{Int64Value: basetypes.NewInt64Value(1)} + endpoint := fmt.Sprintf("/order/cart/%s/%s/options", url.PathEscape(cart.CartId), product) - if err := config.OVHClient.Post(endpoint, cartPlanOptionsParams, productOptionsItem); err != nil { + if err := config.OVHClient.Post(endpoint, opt, productOptionsItem); err != nil { return fmt.Errorf("calling Post %s with params %v:\n\t %q", endpoint, cartPlanParams, err) } - // apply configurations - nbOfConfigurations := d.Get(fmt.Sprintf("%s.configuration.#", optionPath)).(int) - for j := 0; j < nbOfConfigurations; j++ { + optionConfigs := opt.Configuration.Elements() + for _, cfg := range optionConfigs { log.Printf("[DEBUG] Will create order cart item configuration for cart item: %s/%d", item.CartId, item.ItemId, ) itemConfig := &OrderCartItemConfiguration{} - itemConfigParams := (&OrderCartItemConfigurationOpts{}).FromResourceWithPath( - d, - fmt.Sprintf("%s.configuration.%d", optionPath, j), - ) endpoint := fmt.Sprintf("/order/cart/%s/item/%d/configuration", url.PathEscape(item.CartId), item.ItemId, ) - if err := config.OVHClient.Post(endpoint, itemConfigParams, itemConfig); err != nil { - return fmt.Errorf("calling Post %s with params %v:\n\t %q", endpoint, itemConfigParams, err) + if err := config.OVHClient.Post(endpoint, cfg, itemConfig); err != nil { + return fmt.Errorf("calling Post %s with params %v:\n\t %q", endpoint, cfg, err) } } } @@ -384,15 +390,33 @@ func orderCreate(d *schema.ResourceData, meta interface{}, product string) error return fmt.Errorf("waiting for order (%d): %s", checkout.OrderID, err) } - d.SetId(fmt.Sprint(checkout.OrderID)) + d.Order.OrderId = types.TfInt64Value{Int64Value: basetypes.NewInt64Value(checkout.OrderID)} return nil } -func orderRead(d *schema.ResourceData, meta interface{}) (*MeOrder, []*MeOrderDetail, error) { +func orderReadInResource(d *schema.ResourceData, meta interface{}) (*MeOrder, []*MeOrderDetail, error) { config := meta.(*Config) orderId := d.Id() + order, details, err := orderRead(orderId, config) + if err != nil { + return nil, nil, err + } + + detailsData := make([]map[string]interface{}, len(details)) + for i, detail := range details { + detailsData[i] = detail.ToMap() + } + + orderData := order.ToMap() + orderData["details"] = detailsData + d.Set("order", []interface{}{orderData}) + + return order, details, nil +} + +func orderRead(orderId string, config *Config) (*MeOrder, []*MeOrderDetail, error) { order := &MeOrder{} log.Printf("[DEBUG] Will read order %s", orderId) endpoint := fmt.Sprintf("/me/order/%s", @@ -408,26 +432,31 @@ func orderRead(d *schema.ResourceData, meta interface{}) (*MeOrder, []*MeOrderDe } if len(details) < 1 { - return nil, nil, fmt.Errorf("There is no order details for id %s. This shouldn't happen. This is a bug with the API.", orderId) + return nil, nil, fmt.Errorf("there is no order details for id %s. This shouldn't happen. This is a bug with the API", orderId) } - detailsData := make([]map[string]interface{}, len(details)) - for i, detail := range details { - detailsData[i] = detail.ToMap() - } - - orderData := order.ToMap() - orderData["details"] = detailsData - d.Set("order", []interface{}{orderData}) - return order, details, nil } type TerminateFunc func() (string, error) type ConfirmTerminationFunc func(token string) error -func orderDelete(d *schema.ResourceData, meta interface{}, terminate TerminateFunc, confirm ConfirmTerminationFunc) error { - oldEmailsIds, err := notificationEmailSortedIds(meta) +func orderDeleteFromResource(d *schema.ResourceData, meta interface{}, terminate TerminateFunc, confirm ConfirmTerminationFunc) error { + config := meta.(*Config) + + if err := orderDelete(config, terminate, confirm); err != nil { + return err + } + + if d != nil { + d.SetId("") + } + + return nil +} + +func orderDelete(config *Config, terminate TerminateFunc, confirm ConfirmTerminationFunc) error { + oldEmailsIds, err := notificationEmailSortedIds(config) if err != nil { return err } @@ -450,7 +479,7 @@ func orderDelete(d *schema.ResourceData, meta interface{}, terminate TerminateFu var email *NotificationEmail // wait for email err = resource.Retry(30*time.Minute, func() *resource.RetryError { - email, err = getNewNotificationEmail(matches, oldEmailsIds, meta) + email, err = getNewNotificationEmail(matches, oldEmailsIds, config) if err != nil { log.Printf("[DEBUG] error while getting email notification. retry: %v", err) return resource.RetryableError(err) @@ -478,10 +507,6 @@ func orderDelete(d *schema.ResourceData, meta interface{}, terminate TerminateFu return err } - if d != nil { - d.SetId("") - } - return nil } @@ -507,6 +532,38 @@ func orderDetails(c *ovh.Client, orderId int64) ([]*MeOrderDetail, error) { return details, nil } +func serviceNameFromOrder(c *ovh.Client, orderId int64, plan string) (string, error) { + detailIds := []int64{} + endpoint := fmt.Sprintf("/me/order/%d/details", orderId) + if err := c.Get(endpoint, &detailIds); err != nil { + return "", fmt.Errorf("calling get %s:\n\t %q", endpoint, err) + } + + for _, detailId := range detailIds { + detailExtension := &MeOrderDetailExtension{} + log.Printf("[DEBUG] Will read order detail extension %d/%d", orderId, detailId) + endpoint := fmt.Sprintf("/me/order/%d/details/%d/extension", orderId, detailId) + if err := c.Get(endpoint, detailExtension); err != nil { + return "", fmt.Errorf("calling get %s:\n\t %q", endpoint, err) + } + + if detailExtension.Order.Plan.Code != plan { + continue + } + + detail := &MeOrderDetail{} + log.Printf("[DEBUG] Will read order detail %d/%d", orderId, detailId) + endpoint = fmt.Sprintf("/me/order/%d/details/%d", orderId, detailId) + if err := c.Get(endpoint, detail); err != nil { + return "", fmt.Errorf("calling get %s:\n\t %q", endpoint, err) + } + + return detail.Domain, nil + } + + return "", errors.New("serviceName not found") +} + func waitForOrder(c *ovh.Client, id int64) resource.StateRefreshFunc { return func() (interface{}, string, error) { var r string diff --git a/ovh/order_resource_gen.go b/ovh/order_resource_gen.go new file mode 100644 index 000000000..269dea096 --- /dev/null +++ b/ovh/order_resource_gen.go @@ -0,0 +1,3894 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package ovh + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "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/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" + legacyschema "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + ovhtypes "github.com/ovh/terraform-provider-ovh/ovh/types" +) + +func OrderResourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "order": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "date": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + }, + "details": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + }, + "detail_type": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + Description: "Product type of item in order", + MarkdownDescription: "Product type of item in order", + }, + "domain": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + }, + "order_detail_id": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Computed: true, + }, + "quantity": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + }, + }, + CustomType: OrderDetailsType{ + ObjectType: types.ObjectType{ + AttrTypes: OrderDetailsValue{}.AttributeTypes(ctx), + }, + }, + }, + CustomType: ovhtypes.NewTfListNestedType[OrderDetailsValue](ctx), + Computed: true, + }, + "expiration_date": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + }, + "order_id": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Computed: true, + }, + }, + CustomType: OrderType{ + ObjectType: types.ObjectType{ + AttrTypes: OrderValue{}.AttributeTypes(ctx), + }, + }, + Computed: true, + Description: "Details about an Order", + MarkdownDescription: "Details about an Order", + }, + "ovh_subsidiary": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "OVH subsidiaries", + MarkdownDescription: "OVH subsidiaries", + }, + "plan": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "configuration": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "label": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Label for your configuration item", + MarkdownDescription: "Label for your configuration item", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "value": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Value or resource URL on API.OVH.COM of your configuration item", + MarkdownDescription: "Value or resource URL on API.OVH.COM of your configuration item", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + CustomType: PlanConfigurationType{ + ObjectType: types.ObjectType{ + AttrTypes: PlanConfigurationValue{}.AttributeTypes(ctx), + }, + }, + }, + CustomType: ovhtypes.NewTfListNestedType[PlanConfigurationValue](ctx), + Optional: true, + Computed: true, + }, + "duration": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Duration selected for the purchase of the product", + MarkdownDescription: "Duration selected for the purchase of the product", + }, + "item_id": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Optional: true, + Description: "Cart item to be linked", + MarkdownDescription: "Cart item to be linked", + }, + "plan_code": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Identifier of the option offer", + MarkdownDescription: "Identifier of the option offer", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "pricing_mode": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Pricing mode selected for the purchase of the product", + MarkdownDescription: "Pricing mode selected for the purchase of the product", + }, + "quantity": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Optional: true, + Description: "Quantity of product desired", + MarkdownDescription: "Quantity of product desired", + }, + }, + CustomType: PlanType{ + ObjectType: types.ObjectType{ + AttrTypes: PlanValue{}.AttributeTypes(ctx), + }, + }, + }, + CustomType: ovhtypes.NewTfListNestedType[PlanValue](ctx), + Optional: true, + Computed: true, + }, + "plan_option": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "configuration": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "label": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Label for your configuration item", + MarkdownDescription: "Label for your configuration item", + }, + "value": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Value or resource URL on API.OVH.COM of your configuration item", + MarkdownDescription: "Value or resource URL on API.OVH.COM of your configuration item", + }, + }, + CustomType: PlanOptionConfigurationType{ + ObjectType: types.ObjectType{ + AttrTypes: PlanOptionConfigurationValue{}.AttributeTypes(ctx), + }, + }, + }, + CustomType: ovhtypes.NewTfListNestedType[PlanOptionConfigurationValue](ctx), + Optional: true, + Computed: true, + }, + "duration": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Duration selected for the purchase of the product", + MarkdownDescription: "Duration selected for the purchase of the product", + }, + "item_id": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Required: true, + Description: "Cart item to be linked", + MarkdownDescription: "Cart item to be linked", + }, + "plan_code": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Identifier of the option offer", + MarkdownDescription: "Identifier of the option offer", + }, + "pricing_mode": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Required: true, + Description: "Pricing mode selected for the purchase of the product", + MarkdownDescription: "Pricing mode selected for the purchase of the product", + }, + "quantity": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Required: true, + Description: "Quantity of product desired", + MarkdownDescription: "Quantity of product desired", + }, + }, + CustomType: PlanOptionType{ + ObjectType: types.ObjectType{ + AttrTypes: PlanOptionValue{}.AttributeTypes(ctx), + }, + }, + }, + CustomType: ovhtypes.NewTfListNestedType[PlanOptionValue](ctx), + Optional: true, + Computed: true, + }, + }, + } +} + +type OrderModel struct { + Order OrderValue `tfsdk:"order" json:"order"` + OvhSubsidiary ovhtypes.TfStringValue `tfsdk:"ovh_subsidiary" json:"ovhSubsidiary"` + Plan ovhtypes.TfListNestedValue[PlanValue] `tfsdk:"plan" json:"plan"` + PlanOption ovhtypes.TfListNestedValue[PlanOptionValue] `tfsdk:"plan_option" json:"planOption"` +} + +func (o *OrderModel) FromResource(d *legacyschema.ResourceData) *OrderModel { + o.OvhSubsidiary = ovhtypes.TfStringValue{StringValue: basetypes.NewStringValue(strings.ToUpper(d.Get("ovh_subsidiary").(string)))} + + var plan []attr.Value + nbPlans := d.Get("plan.#").(int) + for i := 0; i < nbPlans; i++ { + p := (PlanValue{}).FromResourceWithPath(d, fmt.Sprintf("plan.%d", i)) + plan = append(plan, p) + } + o.Plan = ovhtypes.TfListNestedValue[PlanValue]{ + ListValue: basetypes.NewListValueMust(PlanValue{}.Type(context.Background()), plan), + } + + var planOptions []attr.Value + nbPlanOptions := d.Get("plan_option.#").(int) + for i := 0; i < nbPlanOptions; i++ { + planOpt := (PlanOptionValue{}).FromResourceWithPath(d, fmt.Sprintf("plan_option.%d", i)) + planOptions = append(planOptions, planOpt) + } + o.PlanOption = ovhtypes.TfListNestedValue[PlanOptionValue]{ + ListValue: basetypes.NewListValueMust(PlanOptionValue{}.Type(context.Background()), planOptions), + } + + return o +} + +func (v *OrderModel) MergeWith(other *OrderModel) { + if v.Order.IsUnknown() && !other.Order.IsUnknown() { + v.Order = other.Order + } else if !other.Order.IsUnknown() { + v.Order.MergeWith(&other.Order) + } + + if (v.OvhSubsidiary.IsUnknown() || v.OvhSubsidiary.IsNull()) && !other.OvhSubsidiary.IsUnknown() { + v.OvhSubsidiary = other.OvhSubsidiary + } + + if (v.Plan.IsUnknown() || v.Plan.IsNull()) && !other.Plan.IsUnknown() { + v.Plan = other.Plan + } else if !other.Plan.IsUnknown() { + newSlice := make([]attr.Value, 0) + elems := v.Plan.Elements() + newElems := other.Plan.Elements() + + if len(elems) != len(newElems) { + v.Plan = other.Plan + } else { + for idx, e := range elems { + tmp := e.(PlanValue) + tmp2 := newElems[idx].(PlanValue) + tmp.MergeWith(&tmp2) + newSlice = append(newSlice, tmp) + } + + v.Plan = ovhtypes.TfListNestedValue[PlanValue]{ + ListValue: basetypes.NewListValueMust(PlanValue{}.Type(context.Background()), newSlice), + } + } + } + + if (v.PlanOption.IsUnknown() || v.PlanOption.IsNull()) && !other.PlanOption.IsUnknown() { + v.PlanOption = other.PlanOption + } else if !other.PlanOption.IsUnknown() { + newSlice := make([]attr.Value, 0) + elems := v.PlanOption.Elements() + newElems := other.PlanOption.Elements() + + if len(elems) != len(newElems) { + v.PlanOption = other.PlanOption + } else { + for idx, e := range elems { + tmp := e.(PlanOptionValue) + tmp2 := newElems[idx].(PlanOptionValue) + tmp.MergeWith(&tmp2) + newSlice = append(newSlice, tmp) + } + + v.PlanOption = ovhtypes.TfListNestedValue[PlanOptionValue]{ + ListValue: basetypes.NewListValueMust(PlanOptionValue{}.Type(context.Background()), newSlice), + } + } + } +} + +type OrderWritableModel struct { + OvhSubsidiary *ovhtypes.TfStringValue `tfsdk:"ovh_subsidiary" json:"ovhSubsidiary,omitempty"` + Plan *ovhtypes.TfListNestedValue[PlanWritableValue] `tfsdk:"plan" json:"plan,omitempty"` + PlanOption *ovhtypes.TfListNestedValue[PlanOptionWritableValue] `tfsdk:"plan_option" json:"planOption,omitempty"` +} + +func (v OrderModel) ToCreate() *OrderWritableModel { + res := &OrderWritableModel{} + + if !v.OvhSubsidiary.IsUnknown() { + res.OvhSubsidiary = &v.OvhSubsidiary + } + + if !v.Plan.IsUnknown() { + var createPlan []PlanWritableValue + for _, elem := range v.Plan.Elements() { + createPlan = append(createPlan, *elem.(PlanValue).ToCreate()) + } + + newPlan, _ := basetypes.NewListValueFrom(context.Background(), PlanWritableValue{ + PlanValue: &PlanValue{}, + }.Type(context.Background()), createPlan) + res.Plan = &ovhtypes.TfListNestedValue[PlanWritableValue]{ + ListValue: newPlan, + } + } + + if !v.PlanOption.IsUnknown() { + var createPlanOption []PlanOptionWritableValue + for _, elem := range v.PlanOption.Elements() { + createPlanOption = append(createPlanOption, *elem.(PlanOptionValue).ToCreate()) + } + + newPlanOption, _ := basetypes.NewListValueFrom(context.Background(), PlanOptionWritableValue{ + PlanOptionValue: &PlanOptionValue{}, + }.Type(context.Background()), createPlanOption) + res.PlanOption = &ovhtypes.TfListNestedValue[PlanOptionWritableValue]{ + ListValue: newPlanOption, + } + } + + return res +} + +var _ basetypes.ObjectTypable = OrderType{} + +type OrderType struct { + basetypes.ObjectType +} + +func (t OrderType) Equal(o attr.Type) bool { + other, ok := o.(OrderType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t OrderType) String() string { + return "OrderType" +} + +func (t OrderType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + dateAttribute, ok := attributes["date"] + + if !ok { + diags.AddError( + "Attribute Missing", + `date is missing from object`) + + return nil, diags + } + + dateVal, ok := dateAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`date expected to be ovhtypes.TfStringValue, was: %T`, dateAttribute)) + } + + detailsAttribute, ok := attributes["details"] + + if !ok { + diags.AddError( + "Attribute Missing", + `details is missing from object`) + + return nil, diags + } + + detailsVal, ok := detailsAttribute.(ovhtypes.TfListNestedValue[OrderDetailsValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`details expected to be ovhtypes.TfListNestedValue[OrderDetailsValue], was: %T`, detailsAttribute)) + } + + expirationDateAttribute, ok := attributes["expiration_date"] + + if !ok { + diags.AddError( + "Attribute Missing", + `expiration_date is missing from object`) + + return nil, diags + } + + expirationDateVal, ok := expirationDateAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`expiration_date expected to be ovhtypes.TfStringValue, was: %T`, expirationDateAttribute)) + } + + orderIdAttribute, ok := attributes["order_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `order_id is missing from object`) + + return nil, diags + } + + orderIdVal, ok := orderIdAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`order_id expected to be ovhtypes.TfInt64Value, was: %T`, orderIdAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return OrderValue{ + Date: dateVal, + Details: detailsVal, + ExpirationDate: expirationDateVal, + OrderId: orderIdVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewOrderValueNull() OrderValue { + return OrderValue{ + state: attr.ValueStateNull, + } +} + +func NewOrderValueUnknown() OrderValue { + return OrderValue{ + state: attr.ValueStateUnknown, + } +} + +func NewOrderValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (OrderValue, 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 OrderValue Attribute Value", + "While creating a OrderValue value, a missing attribute value was detected. "+ + "A OrderValue 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("OrderValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid OrderValue Attribute Type", + "While creating a OrderValue value, an invalid attribute value was detected. "+ + "A OrderValue 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("OrderValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("OrderValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra OrderValue Attribute Value", + "While creating a OrderValue value, an extra attribute value was detected. "+ + "A OrderValue 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 OrderValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewOrderValueUnknown(), diags + } + + dateAttribute, ok := attributes["date"] + + if !ok { + diags.AddError( + "Attribute Missing", + `date is missing from object`) + + return NewOrderValueUnknown(), diags + } + + dateVal, ok := dateAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`date expected to be ovhtypes.TfStringValue, was: %T`, dateAttribute)) + } + + detailsAttribute, ok := attributes["details"] + + if !ok { + diags.AddError( + "Attribute Missing", + `details is missing from object`) + + return NewOrderValueUnknown(), diags + } + + detailsVal, ok := detailsAttribute.(ovhtypes.TfListNestedValue[OrderDetailsValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`details expected to be ovhtypes.TfListNestedValue[OrderDetailsValue], was: %T`, detailsAttribute)) + } + + expirationDateAttribute, ok := attributes["expiration_date"] + + if !ok { + diags.AddError( + "Attribute Missing", + `expiration_date is missing from object`) + + return NewOrderValueUnknown(), diags + } + + expirationDateVal, ok := expirationDateAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`expiration_date expected to be ovhtypes.TfStringValue, was: %T`, expirationDateAttribute)) + } + + orderIdAttribute, ok := attributes["order_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `order_id is missing from object`) + + return NewOrderValueUnknown(), diags + } + + orderIdVal, ok := orderIdAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`order_id expected to be ovhtypes.TfInt64Value, was: %T`, orderIdAttribute)) + } + + if diags.HasError() { + return NewOrderValueUnknown(), diags + } + + return OrderValue{ + Date: dateVal, + Details: detailsVal, + ExpirationDate: expirationDateVal, + OrderId: orderIdVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewOrderValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) OrderValue { + object, diags := NewOrderValue(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("NewOrderValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t OrderType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewOrderValueNull(), 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 NewOrderValueUnknown(), nil + } + + if in.IsNull() { + return NewOrderValueNull(), 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 NewOrderValueMust(OrderValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t OrderType) ValueType(ctx context.Context) attr.Value { + return OrderValue{} +} + +var _ basetypes.ObjectValuable = OrderValue{} + +type OrderValue struct { + Date ovhtypes.TfStringValue `tfsdk:"date" json:"date"` + Details ovhtypes.TfListNestedValue[OrderDetailsValue] `tfsdk:"details" json:"details"` + ExpirationDate ovhtypes.TfStringValue `tfsdk:"expiration_date" json:"expirationDate"` + OrderId ovhtypes.TfInt64Value `tfsdk:"order_id" json:"orderId"` + state attr.ValueState +} + +type OrderWritableValue struct { + *OrderValue `json:"-"` + Date *ovhtypes.TfStringValue `json:"date,omitempty"` + Details *ovhtypes.TfListNestedValue[OrderDetailsValue] `json:"details,omitempty"` + ExpirationDate *ovhtypes.TfStringValue `json:"expirationDate,omitempty"` + OrderId *ovhtypes.TfInt64Value `json:"orderId,omitempty"` +} + +func (v OrderValue) ToCreate() *OrderWritableValue { + res := &OrderWritableValue{} + + if !v.ExpirationDate.IsNull() { + res.ExpirationDate = &v.ExpirationDate + } + + if !v.OrderId.IsNull() { + res.OrderId = &v.OrderId + } + + if !v.Date.IsNull() { + res.Date = &v.Date + } + + if !v.Details.IsNull() { + res.Details = &v.Details + } + + return res +} + +func (v *OrderValue) UnmarshalJSON(data []byte) error { + type JsonOrderValue OrderValue + + var tmp JsonOrderValue + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + v.Date = tmp.Date + v.Details = tmp.Details + v.ExpirationDate = tmp.ExpirationDate + v.OrderId = tmp.OrderId + + v.state = attr.ValueStateKnown + + return nil +} + +func (v *OrderValue) MergeWith(other *OrderValue) { + + if (v.Date.IsUnknown() || v.Date.IsNull()) && !other.Date.IsUnknown() { + v.Date = other.Date + } + + if (v.Details.IsUnknown() || v.Details.IsNull()) && !other.Details.IsUnknown() { + v.Details = other.Details + } else if !other.Details.IsUnknown() { + newSlice := make([]attr.Value, 0) + elems := v.Details.Elements() + newElems := other.Details.Elements() + + if len(elems) != len(newElems) { + v.Details = other.Details + } else { + for idx, e := range elems { + tmp := e.(OrderDetailsValue) + tmp2 := newElems[idx].(OrderDetailsValue) + tmp.MergeWith(&tmp2) + newSlice = append(newSlice, tmp) + } + + v.Details = ovhtypes.TfListNestedValue[OrderDetailsValue]{ + ListValue: basetypes.NewListValueMust(OrderDetailsValue{}.Type(context.Background()), newSlice), + } + } + } + + if (v.ExpirationDate.IsUnknown() || v.ExpirationDate.IsNull()) && !other.ExpirationDate.IsUnknown() { + v.ExpirationDate = other.ExpirationDate + } + + if (v.OrderId.IsUnknown() || v.OrderId.IsNull()) && !other.OrderId.IsUnknown() { + v.OrderId = other.OrderId + } + + if (v.state == attr.ValueStateUnknown || v.state == attr.ValueStateNull) && other.state != attr.ValueStateUnknown { + v.state = other.state + } +} + +func (v OrderValue) Attributes() map[string]attr.Value { + return map[string]attr.Value{ + "date": v.Date, + "details": v.Details, + "expirationDate": v.ExpirationDate, + "orderId": v.OrderId, + } +} +func (v OrderValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 4) + + var val tftypes.Value + var err error + + attrTypes["date"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["details"] = basetypes.ListType{ + ElemType: OrderDetailsValue{}.Type(ctx), + }.TerraformType(ctx) + attrTypes["expiration_date"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["order_id"] = basetypes.Int64Type{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 4) + + val, err = v.Date.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["date"] = val + + val, err = v.Details.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["details"] = val + + val, err = v.ExpirationDate.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["expiration_date"] = val + + val, err = v.OrderId.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["order_id"] = 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 OrderValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v OrderValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v OrderValue) String() string { + return "OrderValue" +} + +func (v OrderValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "date": ovhtypes.TfStringType{}, + "details": ovhtypes.NewTfListNestedType[OrderDetailsValue](ctx), + "expiration_date": ovhtypes.TfStringType{}, + "order_id": ovhtypes.TfInt64Type{}, + }, + map[string]attr.Value{ + "date": v.Date, + "details": v.Details, + "expiration_date": v.ExpirationDate, + "order_id": v.OrderId, + }) + + return objVal, diags +} + +func (v OrderValue) Equal(o attr.Value) bool { + other, ok := o.(OrderValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Date.Equal(other.Date) { + return false + } + + if !v.Details.Equal(other.Details) { + return false + } + + if !v.ExpirationDate.Equal(other.ExpirationDate) { + return false + } + + if !v.OrderId.Equal(other.OrderId) { + return false + } + + return true +} + +func (v OrderValue) Type(ctx context.Context) attr.Type { + return OrderType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v OrderValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "date": ovhtypes.TfStringType{}, + "details": ovhtypes.NewTfListNestedType[OrderDetailsValue](ctx), + "expiration_date": ovhtypes.TfStringType{}, + "order_id": ovhtypes.TfInt64Type{}, + } +} + +var _ basetypes.ObjectTypable = OrderDetailsType{} + +type OrderDetailsType struct { + basetypes.ObjectType +} + +func (t OrderDetailsType) Equal(o attr.Type) bool { + other, ok := o.(OrderDetailsType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t OrderDetailsType) String() string { + return "OrderDetailsType" +} + +func (t OrderDetailsType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + descriptionAttribute, ok := attributes["description"] + + if !ok { + diags.AddError( + "Attribute Missing", + `description is missing from object`) + + return nil, diags + } + + descriptionVal, ok := descriptionAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`description expected to be ovhtypes.TfStringValue, was: %T`, descriptionAttribute)) + } + + detailTypeAttribute, ok := attributes["detail_type"] + + if !ok { + diags.AddError( + "Attribute Missing", + `detail_type is missing from object`) + + return nil, diags + } + + detailTypeVal, ok := detailTypeAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`detail_type expected to be ovhtypes.TfStringValue, was: %T`, detailTypeAttribute)) + } + + domainAttribute, ok := attributes["domain"] + + if !ok { + diags.AddError( + "Attribute Missing", + `domain is missing from object`) + + return nil, diags + } + + domainVal, ok := domainAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`domain expected to be ovhtypes.TfStringValue, was: %T`, domainAttribute)) + } + + orderDetailIdAttribute, ok := attributes["order_detail_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `order_detail_id is missing from object`) + + return nil, diags + } + + orderDetailIdVal, ok := orderDetailIdAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`order_detail_id expected to be ovhtypes.TfInt64Value, was: %T`, orderDetailIdAttribute)) + } + + quantityAttribute, ok := attributes["quantity"] + + if !ok { + diags.AddError( + "Attribute Missing", + `quantity is missing from object`) + + return nil, diags + } + + quantityVal, ok := quantityAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`quantity expected to be ovhtypes.TfStringValue, was: %T`, quantityAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return OrderDetailsValue{ + Description: descriptionVal, + DetailType: detailTypeVal, + Domain: domainVal, + OrderDetailId: orderDetailIdVal, + Quantity: quantityVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewOrderDetailsValueNull() OrderDetailsValue { + return OrderDetailsValue{ + state: attr.ValueStateNull, + } +} + +func NewOrderDetailsValueUnknown() OrderDetailsValue { + return OrderDetailsValue{ + state: attr.ValueStateUnknown, + } +} + +func NewOrderDetailsValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (OrderDetailsValue, 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 OrderDetailsValue Attribute Value", + "While creating a OrderDetailsValue value, a missing attribute value was detected. "+ + "A OrderDetailsValue 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("OrderDetailsValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid OrderDetailsValue Attribute Type", + "While creating a OrderDetailsValue value, an invalid attribute value was detected. "+ + "A OrderDetailsValue 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("OrderDetailsValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("OrderDetailsValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra OrderDetailsValue Attribute Value", + "While creating a OrderDetailsValue value, an extra attribute value was detected. "+ + "A OrderDetailsValue 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 OrderDetailsValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewOrderDetailsValueUnknown(), diags + } + + descriptionAttribute, ok := attributes["description"] + + if !ok { + diags.AddError( + "Attribute Missing", + `description is missing from object`) + + return NewOrderDetailsValueUnknown(), diags + } + + descriptionVal, ok := descriptionAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`description expected to be ovhtypes.TfStringValue, was: %T`, descriptionAttribute)) + } + + detailTypeAttribute, ok := attributes["detail_type"] + + if !ok { + diags.AddError( + "Attribute Missing", + `detail_type is missing from object`) + + return NewOrderDetailsValueUnknown(), diags + } + + detailTypeVal, ok := detailTypeAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`detail_type expected to be ovhtypes.TfStringValue, was: %T`, detailTypeAttribute)) + } + + domainAttribute, ok := attributes["domain"] + + if !ok { + diags.AddError( + "Attribute Missing", + `domain is missing from object`) + + return NewOrderDetailsValueUnknown(), diags + } + + domainVal, ok := domainAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`domain expected to be ovhtypes.TfStringValue, was: %T`, domainAttribute)) + } + + orderDetailIdAttribute, ok := attributes["order_detail_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `order_detail_id is missing from object`) + + return NewOrderDetailsValueUnknown(), diags + } + + orderDetailIdVal, ok := orderDetailIdAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`order_detail_id expected to be ovhtypes.TfInt64Value, was: %T`, orderDetailIdAttribute)) + } + + quantityAttribute, ok := attributes["quantity"] + + if !ok { + diags.AddError( + "Attribute Missing", + `quantity is missing from object`) + + return NewOrderDetailsValueUnknown(), diags + } + + quantityVal, ok := quantityAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`quantity expected to be ovhtypes.TfStringValue, was: %T`, quantityAttribute)) + } + + if diags.HasError() { + return NewOrderDetailsValueUnknown(), diags + } + + return OrderDetailsValue{ + Description: descriptionVal, + DetailType: detailTypeVal, + Domain: domainVal, + OrderDetailId: orderDetailIdVal, + Quantity: quantityVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewOrderDetailsValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) OrderDetailsValue { + object, diags := NewOrderDetailsValue(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("NewOrderDetailsValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t OrderDetailsType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewOrderDetailsValueNull(), 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 NewOrderDetailsValueUnknown(), nil + } + + if in.IsNull() { + return NewOrderDetailsValueNull(), 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 NewOrderDetailsValueMust(OrderDetailsValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t OrderDetailsType) ValueType(ctx context.Context) attr.Value { + return OrderDetailsValue{} +} + +var _ basetypes.ObjectValuable = OrderDetailsValue{} + +type OrderDetailsValue struct { + Description ovhtypes.TfStringValue `tfsdk:"description" json:"description"` + DetailType ovhtypes.TfStringValue `tfsdk:"detail_type" json:"detailType"` + Domain ovhtypes.TfStringValue `tfsdk:"domain" json:"domain"` + OrderDetailId ovhtypes.TfInt64Value `tfsdk:"order_detail_id" json:"orderDetailId"` + Quantity ovhtypes.TfStringValue `tfsdk:"quantity" json:"quantity"` + state attr.ValueState +} + +type OrderDetailsWritableValue struct { + *OrderDetailsValue `json:"-"` + Description *ovhtypes.TfStringValue `json:"description,omitempty"` + DetailType *ovhtypes.TfStringValue `json:"detailType,omitempty"` + Domain *ovhtypes.TfStringValue `json:"domain,omitempty"` + OrderDetailId *ovhtypes.TfInt64Value `json:"orderDetailId,omitempty"` + Quantity *ovhtypes.TfStringValue `json:"quantity,omitempty"` +} + +func (v OrderDetailsValue) ToCreate() *OrderDetailsWritableValue { + res := &OrderDetailsWritableValue{} + + if !v.OrderDetailId.IsNull() { + res.OrderDetailId = &v.OrderDetailId + } + + if !v.Quantity.IsNull() { + res.Quantity = &v.Quantity + } + + if !v.Description.IsNull() { + res.Description = &v.Description + } + + if !v.DetailType.IsNull() { + res.DetailType = &v.DetailType + } + + if !v.Domain.IsNull() { + res.Domain = &v.Domain + } + + return res +} + +func (v *OrderDetailsValue) UnmarshalJSON(data []byte) error { + type JsonOrderDetailsValue OrderDetailsValue + + var tmp JsonOrderDetailsValue + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + v.Description = tmp.Description + v.DetailType = tmp.DetailType + v.Domain = tmp.Domain + v.OrderDetailId = tmp.OrderDetailId + v.Quantity = tmp.Quantity + + v.state = attr.ValueStateKnown + + return nil +} + +func (v *OrderDetailsValue) MergeWith(other *OrderDetailsValue) { + + if (v.Description.IsUnknown() || v.Description.IsNull()) && !other.Description.IsUnknown() { + v.Description = other.Description + } + + if (v.DetailType.IsUnknown() || v.DetailType.IsNull()) && !other.DetailType.IsUnknown() { + v.DetailType = other.DetailType + } + + if (v.Domain.IsUnknown() || v.Domain.IsNull()) && !other.Domain.IsUnknown() { + v.Domain = other.Domain + } + + if (v.OrderDetailId.IsUnknown() || v.OrderDetailId.IsNull()) && !other.OrderDetailId.IsUnknown() { + v.OrderDetailId = other.OrderDetailId + } + + if (v.Quantity.IsUnknown() || v.Quantity.IsNull()) && !other.Quantity.IsUnknown() { + v.Quantity = other.Quantity + } + + if (v.state == attr.ValueStateUnknown || v.state == attr.ValueStateNull) && other.state != attr.ValueStateUnknown { + v.state = other.state + } +} + +func (v OrderDetailsValue) Attributes() map[string]attr.Value { + return map[string]attr.Value{ + "description": v.Description, + "detailType": v.DetailType, + "domain": v.Domain, + "orderDetailId": v.OrderDetailId, + "quantity": v.Quantity, + } +} +func (v OrderDetailsValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 5) + + var val tftypes.Value + var err error + + attrTypes["description"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["detail_type"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["domain"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["order_detail_id"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["quantity"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 5) + + val, err = v.Description.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["description"] = val + + val, err = v.DetailType.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["detail_type"] = val + + val, err = v.Domain.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["domain"] = val + + val, err = v.OrderDetailId.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["order_detail_id"] = val + + val, err = v.Quantity.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["quantity"] = 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 OrderDetailsValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v OrderDetailsValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v OrderDetailsValue) String() string { + return "OrderDetailsValue" +} + +func (v OrderDetailsValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "description": ovhtypes.TfStringType{}, + "detail_type": ovhtypes.TfStringType{}, + "domain": ovhtypes.TfStringType{}, + "order_detail_id": ovhtypes.TfInt64Type{}, + "quantity": ovhtypes.TfStringType{}, + }, + map[string]attr.Value{ + "description": v.Description, + "detail_type": v.DetailType, + "domain": v.Domain, + "order_detail_id": v.OrderDetailId, + "quantity": v.Quantity, + }) + + return objVal, diags +} + +func (v OrderDetailsValue) Equal(o attr.Value) bool { + other, ok := o.(OrderDetailsValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Description.Equal(other.Description) { + return false + } + + if !v.DetailType.Equal(other.DetailType) { + return false + } + + if !v.Domain.Equal(other.Domain) { + return false + } + + if !v.OrderDetailId.Equal(other.OrderDetailId) { + return false + } + + if !v.Quantity.Equal(other.Quantity) { + return false + } + + return true +} + +func (v OrderDetailsValue) Type(ctx context.Context) attr.Type { + return OrderDetailsType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v OrderDetailsValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "description": ovhtypes.TfStringType{}, + "detail_type": ovhtypes.TfStringType{}, + "domain": ovhtypes.TfStringType{}, + "order_detail_id": ovhtypes.TfInt64Type{}, + "quantity": ovhtypes.TfStringType{}, + } +} + +var _ basetypes.ObjectTypable = PlanType{} + +type PlanType struct { + basetypes.ObjectType +} + +func (t PlanType) Equal(o attr.Type) bool { + other, ok := o.(PlanType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t PlanType) String() string { + return "PlanType" +} + +func (t PlanType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + configurationAttribute, ok := attributes["configuration"] + + if !ok { + diags.AddError( + "Attribute Missing", + `configuration is missing from object`) + + return nil, diags + } + + configurationVal, ok := configurationAttribute.(ovhtypes.TfListNestedValue[PlanConfigurationValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`configuration expected to be ovhtypes.TfListNestedValue[PlanConfigurationValue], was: %T`, configurationAttribute)) + } + + durationAttribute, ok := attributes["duration"] + + if !ok { + diags.AddError( + "Attribute Missing", + `duration is missing from object`) + + return nil, diags + } + + durationVal, ok := durationAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`duration expected to be ovhtypes.TfStringValue, was: %T`, durationAttribute)) + } + + itemIdAttribute, ok := attributes["item_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `item_id is missing from object`) + + return nil, diags + } + + itemIdVal, ok := itemIdAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`item_id expected to be ovhtypes.TfInt64Value, was: %T`, itemIdAttribute)) + } + + planCodeAttribute, ok := attributes["plan_code"] + + if !ok { + diags.AddError( + "Attribute Missing", + `plan_code is missing from object`) + + return nil, diags + } + + planCodeVal, ok := planCodeAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`plan_code expected to be ovhtypes.TfStringValue, was: %T`, planCodeAttribute)) + } + + pricingModeAttribute, ok := attributes["pricing_mode"] + + if !ok { + diags.AddError( + "Attribute Missing", + `pricing_mode is missing from object`) + + return nil, diags + } + + pricingModeVal, ok := pricingModeAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`pricing_mode expected to be ovhtypes.TfStringValue, was: %T`, pricingModeAttribute)) + } + + quantityAttribute, ok := attributes["quantity"] + + if !ok { + diags.AddError( + "Attribute Missing", + `quantity is missing from object`) + + return nil, diags + } + + quantityVal, ok := quantityAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`quantity expected to be ovhtypes.TfInt64Value, was: %T`, quantityAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return PlanValue{ + Configuration: configurationVal, + Duration: durationVal, + ItemId: itemIdVal, + PlanCode: planCodeVal, + PricingMode: pricingModeVal, + Quantity: quantityVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPlanValueNull() PlanValue { + return PlanValue{ + state: attr.ValueStateNull, + } +} + +func NewPlanValueUnknown() PlanValue { + return PlanValue{ + state: attr.ValueStateUnknown, + } +} + +func NewPlanValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (PlanValue, 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 PlanValue Attribute Value", + "While creating a PlanValue value, a missing attribute value was detected. "+ + "A PlanValue 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("PlanValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid PlanValue Attribute Type", + "While creating a PlanValue value, an invalid attribute value was detected. "+ + "A PlanValue 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("PlanValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("PlanValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra PlanValue Attribute Value", + "While creating a PlanValue value, an extra attribute value was detected. "+ + "A PlanValue 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 PlanValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewPlanValueUnknown(), diags + } + + configurationAttribute, ok := attributes["configuration"] + + if !ok { + diags.AddError( + "Attribute Missing", + `configuration is missing from object`) + + return NewPlanValueUnknown(), diags + } + + configurationVal, ok := configurationAttribute.(ovhtypes.TfListNestedValue[PlanConfigurationValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`configuration expected to be ovhtypes.TfListNestedValue[PlanConfigurationValue], was: %T`, configurationAttribute)) + } + + durationAttribute, ok := attributes["duration"] + + if !ok { + diags.AddError( + "Attribute Missing", + `duration is missing from object`) + + return NewPlanValueUnknown(), diags + } + + durationVal, ok := durationAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`duration expected to be ovhtypes.TfStringValue, was: %T`, durationAttribute)) + } + + itemIdAttribute, ok := attributes["item_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `item_id is missing from object`) + + return NewPlanValueUnknown(), diags + } + + itemIdVal, ok := itemIdAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`item_id expected to be ovhtypes.TfInt64Value, was: %T`, itemIdAttribute)) + } + + planCodeAttribute, ok := attributes["plan_code"] + + if !ok { + diags.AddError( + "Attribute Missing", + `plan_code is missing from object`) + + return NewPlanValueUnknown(), diags + } + + planCodeVal, ok := planCodeAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`plan_code expected to be ovhtypes.TfStringValue, was: %T`, planCodeAttribute)) + } + + pricingModeAttribute, ok := attributes["pricing_mode"] + + if !ok { + diags.AddError( + "Attribute Missing", + `pricing_mode is missing from object`) + + return NewPlanValueUnknown(), diags + } + + pricingModeVal, ok := pricingModeAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`pricing_mode expected to be ovhtypes.TfStringValue, was: %T`, pricingModeAttribute)) + } + + quantityAttribute, ok := attributes["quantity"] + + if !ok { + diags.AddError( + "Attribute Missing", + `quantity is missing from object`) + + return NewPlanValueUnknown(), diags + } + + quantityVal, ok := quantityAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`quantity expected to be ovhtypes.TfInt64Value, was: %T`, quantityAttribute)) + } + + if diags.HasError() { + return NewPlanValueUnknown(), diags + } + + return PlanValue{ + Configuration: configurationVal, + Duration: durationVal, + ItemId: itemIdVal, + PlanCode: planCodeVal, + PricingMode: pricingModeVal, + Quantity: quantityVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPlanValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) PlanValue { + object, diags := NewPlanValue(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("NewPlanValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t PlanType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewPlanValueNull(), 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 NewPlanValueUnknown(), nil + } + + if in.IsNull() { + return NewPlanValueNull(), 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 NewPlanValueMust(PlanValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t PlanType) ValueType(ctx context.Context) attr.Value { + return PlanValue{} +} + +var _ basetypes.ObjectValuable = PlanValue{} + +type PlanValue struct { + Configuration ovhtypes.TfListNestedValue[PlanConfigurationValue] `tfsdk:"configuration" json:"configuration"` + Duration ovhtypes.TfStringValue `tfsdk:"duration" json:"duration"` + ItemId ovhtypes.TfInt64Value `tfsdk:"item_id" json:"itemId"` + PlanCode ovhtypes.TfStringValue `tfsdk:"plan_code" json:"planCode"` + PricingMode ovhtypes.TfStringValue `tfsdk:"pricing_mode" json:"pricingMode"` + Quantity ovhtypes.TfInt64Value `tfsdk:"quantity" json:"quantity"` + state attr.ValueState +} + +type PlanWritableValue struct { + *PlanValue `json:"-"` + Configuration *ovhtypes.TfListNestedValue[PlanConfigurationValue] `json:"configuration,omitempty"` + Duration *ovhtypes.TfStringValue `json:"duration,omitempty"` + ItemId *ovhtypes.TfInt64Value `json:"itemId,omitempty"` + PlanCode *ovhtypes.TfStringValue `json:"planCode,omitempty"` + PricingMode *ovhtypes.TfStringValue `json:"pricingMode,omitempty"` + Quantity *ovhtypes.TfInt64Value `json:"quantity,omitempty"` +} + +func (opts PlanValue) FromResourceWithPath(d *legacyschema.ResourceData, path string) PlanValue { + opts.Duration = ovhtypes.TfStringValue{StringValue: basetypes.NewStringValue(d.Get(fmt.Sprintf("%s.duration", path)).(string))} + opts.PlanCode = ovhtypes.TfStringValue{StringValue: basetypes.NewStringValue(d.Get(fmt.Sprintf("%s.plan_code", path)).(string))} + opts.PricingMode = ovhtypes.TfStringValue{StringValue: basetypes.NewStringValue(d.Get(fmt.Sprintf("%s.pricing_mode", path)).(string))} + + nbConfigs := d.Get(fmt.Sprintf("%s.configuration.#", path)).(int) + var configs []attr.Value + for i := 0; i < nbConfigs; i++ { + cfg := PlanConfigurationValue{ + state: attr.ValueStateKnown, + Label: ovhtypes.TfStringValue{StringValue: basetypes.NewStringValue(d.Get(fmt.Sprintf("%s.configuration.%d.label", path, i)).(string))}, + Value: ovhtypes.TfStringValue{StringValue: basetypes.NewStringValue(d.Get(fmt.Sprintf("%s.configuration.%d.value", path, i)).(string))}, + } + configs = append(configs, cfg) + } + + opts.Configuration = ovhtypes.TfListNestedValue[PlanConfigurationValue]{ + ListValue: basetypes.NewListValueMust(PlanConfigurationValue{}.Type(context.Background()), configs), + } + + return opts +} + +func (v PlanValue) ToCreate() *PlanWritableValue { + res := &PlanWritableValue{} + + if !v.PricingMode.IsNull() { + res.PricingMode = &v.PricingMode + } + + if !v.Quantity.IsNull() { + res.Quantity = &v.Quantity + } + + if !v.Configuration.IsNull() { + res.Configuration = &v.Configuration + } + + if !v.Duration.IsNull() { + res.Duration = &v.Duration + } + + if !v.ItemId.IsNull() { + res.ItemId = &v.ItemId + } + + if !v.PlanCode.IsNull() { + res.PlanCode = &v.PlanCode + } + + return res +} + +func (v *PlanValue) UnmarshalJSON(data []byte) error { + type JsonPlanValue PlanValue + + var tmp JsonPlanValue + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + v.Configuration = tmp.Configuration + v.Duration = tmp.Duration + v.ItemId = tmp.ItemId + v.PlanCode = tmp.PlanCode + v.PricingMode = tmp.PricingMode + v.Quantity = tmp.Quantity + + v.state = attr.ValueStateKnown + + return nil +} + +func (v *PlanValue) MergeWith(other *PlanValue) { + + if (v.Configuration.IsUnknown() || v.Configuration.IsNull()) && !other.Configuration.IsUnknown() { + v.Configuration = other.Configuration + } else if !other.Configuration.IsUnknown() { + newSlice := make([]attr.Value, 0) + elems := v.Configuration.Elements() + newElems := other.Configuration.Elements() + + if len(elems) != len(newElems) { + v.Configuration = other.Configuration + } else { + for idx, e := range elems { + tmp := e.(PlanConfigurationValue) + tmp2 := newElems[idx].(PlanConfigurationValue) + tmp.MergeWith(&tmp2) + newSlice = append(newSlice, tmp) + } + + v.Configuration = ovhtypes.TfListNestedValue[PlanConfigurationValue]{ + ListValue: basetypes.NewListValueMust(PlanConfigurationValue{}.Type(context.Background()), newSlice), + } + } + } + + if (v.Duration.IsUnknown() || v.Duration.IsNull()) && !other.Duration.IsUnknown() { + v.Duration = other.Duration + } + + if (v.ItemId.IsUnknown() || v.ItemId.IsNull()) && !other.ItemId.IsUnknown() { + v.ItemId = other.ItemId + } + + if (v.PlanCode.IsUnknown() || v.PlanCode.IsNull()) && !other.PlanCode.IsUnknown() { + v.PlanCode = other.PlanCode + } + + if (v.PricingMode.IsUnknown() || v.PricingMode.IsNull()) && !other.PricingMode.IsUnknown() { + v.PricingMode = other.PricingMode + } + + if (v.Quantity.IsUnknown() || v.Quantity.IsNull()) && !other.Quantity.IsUnknown() { + v.Quantity = other.Quantity + } + + if (v.state == attr.ValueStateUnknown || v.state == attr.ValueStateNull) && other.state != attr.ValueStateUnknown { + v.state = other.state + } +} + +func (v PlanValue) Attributes() map[string]attr.Value { + return map[string]attr.Value{ + "configuration": v.Configuration, + "duration": v.Duration, + "itemId": v.ItemId, + "planCode": v.PlanCode, + "pricingMode": v.PricingMode, + "quantity": v.Quantity, + } +} +func (v PlanValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 6) + + var val tftypes.Value + var err error + + attrTypes["configuration"] = basetypes.ListType{ + ElemType: PlanConfigurationValue{}.Type(ctx), + }.TerraformType(ctx) + attrTypes["duration"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["item_id"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["plan_code"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["pricing_mode"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["quantity"] = basetypes.Int64Type{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 6) + + val, err = v.Configuration.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["configuration"] = val + + val, err = v.Duration.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["duration"] = val + + val, err = v.ItemId.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["item_id"] = val + + val, err = v.PlanCode.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["plan_code"] = val + + val, err = v.PricingMode.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["pricing_mode"] = val + + val, err = v.Quantity.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["quantity"] = 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 PlanValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v PlanValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v PlanValue) String() string { + return "PlanValue" +} + +func (v PlanValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "configuration": ovhtypes.NewTfListNestedType[PlanConfigurationValue](ctx), + "duration": ovhtypes.TfStringType{}, + "item_id": ovhtypes.TfInt64Type{}, + "plan_code": ovhtypes.TfStringType{}, + "pricing_mode": ovhtypes.TfStringType{}, + "quantity": ovhtypes.TfInt64Type{}, + }, + map[string]attr.Value{ + "configuration": v.Configuration, + "duration": v.Duration, + "item_id": v.ItemId, + "plan_code": v.PlanCode, + "pricing_mode": v.PricingMode, + "quantity": v.Quantity, + }) + + return objVal, diags +} + +func (v PlanValue) Equal(o attr.Value) bool { + other, ok := o.(PlanValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Configuration.Equal(other.Configuration) { + return false + } + + if !v.Duration.Equal(other.Duration) { + return false + } + + if !v.ItemId.Equal(other.ItemId) { + return false + } + + if !v.PlanCode.Equal(other.PlanCode) { + return false + } + + if !v.PricingMode.Equal(other.PricingMode) { + return false + } + + if !v.Quantity.Equal(other.Quantity) { + return false + } + + return true +} + +func (v PlanValue) Type(ctx context.Context) attr.Type { + return PlanType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v PlanValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "configuration": ovhtypes.NewTfListNestedType[PlanConfigurationValue](ctx), + "duration": ovhtypes.TfStringType{}, + "item_id": ovhtypes.TfInt64Type{}, + "plan_code": ovhtypes.TfStringType{}, + "pricing_mode": ovhtypes.TfStringType{}, + "quantity": ovhtypes.TfInt64Type{}, + } +} + +var _ basetypes.ObjectTypable = PlanConfigurationType{} + +type PlanConfigurationType struct { + basetypes.ObjectType +} + +func (t PlanConfigurationType) Equal(o attr.Type) bool { + other, ok := o.(PlanConfigurationType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t PlanConfigurationType) String() string { + return "PlanConfigurationType" +} + +func (t PlanConfigurationType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + labelAttribute, ok := attributes["label"] + + if !ok { + diags.AddError( + "Attribute Missing", + `label is missing from object`) + + return nil, diags + } + + labelVal, ok := labelAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`label expected to be ovhtypes.TfStringValue, was: %T`, labelAttribute)) + } + + valueAttribute, ok := attributes["value"] + + if !ok { + diags.AddError( + "Attribute Missing", + `value is missing from object`) + + return nil, diags + } + + valueVal, ok := valueAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`value expected to be ovhtypes.TfStringValue, was: %T`, valueAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return PlanConfigurationValue{ + Label: labelVal, + Value: valueVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPlanConfigurationValueNull() PlanConfigurationValue { + return PlanConfigurationValue{ + state: attr.ValueStateNull, + } +} + +func NewPlanConfigurationValueUnknown() PlanConfigurationValue { + return PlanConfigurationValue{ + state: attr.ValueStateUnknown, + } +} + +func NewPlanConfigurationValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (PlanConfigurationValue, 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 PlanConfigurationValue Attribute Value", + "While creating a PlanConfigurationValue value, a missing attribute value was detected. "+ + "A PlanConfigurationValue 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("PlanConfigurationValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid PlanConfigurationValue Attribute Type", + "While creating a PlanConfigurationValue value, an invalid attribute value was detected. "+ + "A PlanConfigurationValue 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("PlanConfigurationValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("PlanConfigurationValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra PlanConfigurationValue Attribute Value", + "While creating a PlanConfigurationValue value, an extra attribute value was detected. "+ + "A PlanConfigurationValue 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 PlanConfigurationValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewPlanConfigurationValueUnknown(), diags + } + + labelAttribute, ok := attributes["label"] + + if !ok { + diags.AddError( + "Attribute Missing", + `label is missing from object`) + + return NewPlanConfigurationValueUnknown(), diags + } + + labelVal, ok := labelAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`label expected to be ovhtypes.TfStringValue, was: %T`, labelAttribute)) + } + + valueAttribute, ok := attributes["value"] + + if !ok { + diags.AddError( + "Attribute Missing", + `value is missing from object`) + + return NewPlanConfigurationValueUnknown(), diags + } + + valueVal, ok := valueAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`value expected to be ovhtypes.TfStringValue, was: %T`, valueAttribute)) + } + + if diags.HasError() { + return NewPlanConfigurationValueUnknown(), diags + } + + return PlanConfigurationValue{ + Label: labelVal, + Value: valueVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPlanConfigurationValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) PlanConfigurationValue { + object, diags := NewPlanConfigurationValue(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("NewPlanConfigurationValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t PlanConfigurationType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewPlanConfigurationValueNull(), 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 NewPlanConfigurationValueUnknown(), nil + } + + if in.IsNull() { + return NewPlanConfigurationValueNull(), 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 NewPlanConfigurationValueMust(PlanConfigurationValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t PlanConfigurationType) ValueType(ctx context.Context) attr.Value { + return PlanConfigurationValue{} +} + +var _ basetypes.ObjectValuable = PlanConfigurationValue{} + +type PlanConfigurationValue struct { + Label ovhtypes.TfStringValue `tfsdk:"label" json:"label"` + Value ovhtypes.TfStringValue `tfsdk:"value" json:"value"` + state attr.ValueState +} + +type PlanConfigurationWritableValue struct { + *PlanConfigurationValue `json:"-"` + Label *ovhtypes.TfStringValue `json:"label,omitempty"` + Value *ovhtypes.TfStringValue `json:"value,omitempty"` +} + +func (v PlanConfigurationValue) ToCreate() *PlanConfigurationWritableValue { + res := &PlanConfigurationWritableValue{} + + if !v.Label.IsNull() { + res.Label = &v.Label + } + + if !v.Value.IsNull() { + res.Value = &v.Value + } + + return res +} + +func (v *PlanConfigurationValue) UnmarshalJSON(data []byte) error { + type JsonPlanConfigurationValue PlanConfigurationValue + + var tmp JsonPlanConfigurationValue + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + v.Label = tmp.Label + v.Value = tmp.Value + + v.state = attr.ValueStateKnown + + return nil +} + +func (v *PlanConfigurationValue) MergeWith(other *PlanConfigurationValue) { + + if (v.Label.IsUnknown() || v.Label.IsNull()) && !other.Label.IsUnknown() { + v.Label = other.Label + } + + if (v.Value.IsUnknown() || v.Value.IsNull()) && !other.Value.IsUnknown() { + v.Value = other.Value + } + + if (v.state == attr.ValueStateUnknown || v.state == attr.ValueStateNull) && other.state != attr.ValueStateUnknown { + v.state = other.state + } +} + +func (v PlanConfigurationValue) Attributes() map[string]attr.Value { + return map[string]attr.Value{ + "label": v.Label, + "value": v.Value, + } +} +func (v PlanConfigurationValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 2) + + var val tftypes.Value + var err error + + attrTypes["label"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["value"] = 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.Label.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["label"] = val + + val, err = v.Value.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["value"] = 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 PlanConfigurationValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v PlanConfigurationValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v PlanConfigurationValue) String() string { + return "PlanConfigurationValue" +} + +func (v PlanConfigurationValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "label": ovhtypes.TfStringType{}, + "value": ovhtypes.TfStringType{}, + }, + map[string]attr.Value{ + "label": v.Label, + "value": v.Value, + }) + + return objVal, diags +} + +func (v PlanConfigurationValue) Equal(o attr.Value) bool { + other, ok := o.(PlanConfigurationValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Label.Equal(other.Label) { + return false + } + + if !v.Value.Equal(other.Value) { + return false + } + + return true +} + +func (v PlanConfigurationValue) Type(ctx context.Context) attr.Type { + return PlanConfigurationType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v PlanConfigurationValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "label": ovhtypes.TfStringType{}, + "value": ovhtypes.TfStringType{}, + } +} + +var _ basetypes.ObjectTypable = PlanOptionType{} + +type PlanOptionType struct { + basetypes.ObjectType +} + +func (t PlanOptionType) Equal(o attr.Type) bool { + other, ok := o.(PlanOptionType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t PlanOptionType) String() string { + return "PlanOptionType" +} + +func (t PlanOptionType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + configurationAttribute, ok := attributes["configuration"] + + if !ok { + diags.AddError( + "Attribute Missing", + `configuration is missing from object`) + + return nil, diags + } + + configurationVal, ok := configurationAttribute.(ovhtypes.TfListNestedValue[PlanOptionConfigurationValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`configuration expected to be ovhtypes.TfListNestedValue[PlanOptionConfigurationValue], was: %T`, configurationAttribute)) + } + + durationAttribute, ok := attributes["duration"] + + if !ok { + diags.AddError( + "Attribute Missing", + `duration is missing from object`) + + return nil, diags + } + + durationVal, ok := durationAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`duration expected to be ovhtypes.TfStringValue, was: %T`, durationAttribute)) + } + + itemIdAttribute, ok := attributes["item_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `item_id is missing from object`) + + return nil, diags + } + + itemIdVal, ok := itemIdAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`item_id expected to be ovhtypes.TfInt64Value, was: %T`, itemIdAttribute)) + } + + planCodeAttribute, ok := attributes["plan_code"] + + if !ok { + diags.AddError( + "Attribute Missing", + `plan_code is missing from object`) + + return nil, diags + } + + planCodeVal, ok := planCodeAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`plan_code expected to be ovhtypes.TfStringValue, was: %T`, planCodeAttribute)) + } + + pricingModeAttribute, ok := attributes["pricing_mode"] + + if !ok { + diags.AddError( + "Attribute Missing", + `pricing_mode is missing from object`) + + return nil, diags + } + + pricingModeVal, ok := pricingModeAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`pricing_mode expected to be ovhtypes.TfStringValue, was: %T`, pricingModeAttribute)) + } + + quantityAttribute, ok := attributes["quantity"] + + if !ok { + diags.AddError( + "Attribute Missing", + `quantity is missing from object`) + + return nil, diags + } + + quantityVal, ok := quantityAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`quantity expected to be ovhtypes.TfInt64Value, was: %T`, quantityAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return PlanOptionValue{ + Configuration: configurationVal, + Duration: durationVal, + ItemId: itemIdVal, + PlanCode: planCodeVal, + PricingMode: pricingModeVal, + Quantity: quantityVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPlanOptionValueNull() PlanOptionValue { + return PlanOptionValue{ + state: attr.ValueStateNull, + } +} + +func NewPlanOptionValueUnknown() PlanOptionValue { + return PlanOptionValue{ + state: attr.ValueStateUnknown, + } +} + +func NewPlanOptionValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (PlanOptionValue, 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 PlanOptionValue Attribute Value", + "While creating a PlanOptionValue value, a missing attribute value was detected. "+ + "A PlanOptionValue 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("PlanOptionValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid PlanOptionValue Attribute Type", + "While creating a PlanOptionValue value, an invalid attribute value was detected. "+ + "A PlanOptionValue 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("PlanOptionValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("PlanOptionValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra PlanOptionValue Attribute Value", + "While creating a PlanOptionValue value, an extra attribute value was detected. "+ + "A PlanOptionValue 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 PlanOptionValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewPlanOptionValueUnknown(), diags + } + + configurationAttribute, ok := attributes["configuration"] + + if !ok { + diags.AddError( + "Attribute Missing", + `configuration is missing from object`) + + return NewPlanOptionValueUnknown(), diags + } + + configurationVal, ok := configurationAttribute.(ovhtypes.TfListNestedValue[PlanOptionConfigurationValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`configuration expected to be ovhtypes.TfListNestedValue[PlanOptionConfigurationValue], was: %T`, configurationAttribute)) + } + + durationAttribute, ok := attributes["duration"] + + if !ok { + diags.AddError( + "Attribute Missing", + `duration is missing from object`) + + return NewPlanOptionValueUnknown(), diags + } + + durationVal, ok := durationAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`duration expected to be ovhtypes.TfStringValue, was: %T`, durationAttribute)) + } + + itemIdAttribute, ok := attributes["item_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `item_id is missing from object`) + + return NewPlanOptionValueUnknown(), diags + } + + itemIdVal, ok := itemIdAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`item_id expected to be ovhtypes.TfInt64Value, was: %T`, itemIdAttribute)) + } + + planCodeAttribute, ok := attributes["plan_code"] + + if !ok { + diags.AddError( + "Attribute Missing", + `plan_code is missing from object`) + + return NewPlanOptionValueUnknown(), diags + } + + planCodeVal, ok := planCodeAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`plan_code expected to be ovhtypes.TfStringValue, was: %T`, planCodeAttribute)) + } + + pricingModeAttribute, ok := attributes["pricing_mode"] + + if !ok { + diags.AddError( + "Attribute Missing", + `pricing_mode is missing from object`) + + return NewPlanOptionValueUnknown(), diags + } + + pricingModeVal, ok := pricingModeAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`pricing_mode expected to be ovhtypes.TfStringValue, was: %T`, pricingModeAttribute)) + } + + quantityAttribute, ok := attributes["quantity"] + + if !ok { + diags.AddError( + "Attribute Missing", + `quantity is missing from object`) + + return NewPlanOptionValueUnknown(), diags + } + + quantityVal, ok := quantityAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`quantity expected to be ovhtypes.TfInt64Value, was: %T`, quantityAttribute)) + } + + if diags.HasError() { + return NewPlanOptionValueUnknown(), diags + } + + return PlanOptionValue{ + Configuration: configurationVal, + Duration: durationVal, + ItemId: itemIdVal, + PlanCode: planCodeVal, + PricingMode: pricingModeVal, + Quantity: quantityVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPlanOptionValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) PlanOptionValue { + object, diags := NewPlanOptionValue(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("NewPlanOptionValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t PlanOptionType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewPlanOptionValueNull(), 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 NewPlanOptionValueUnknown(), nil + } + + if in.IsNull() { + return NewPlanOptionValueNull(), 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 NewPlanOptionValueMust(PlanOptionValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t PlanOptionType) ValueType(ctx context.Context) attr.Value { + return PlanOptionValue{} +} + +var _ basetypes.ObjectValuable = PlanOptionValue{} + +type PlanOptionValue struct { + Configuration ovhtypes.TfListNestedValue[PlanOptionConfigurationValue] `tfsdk:"configuration" json:"configuration"` + Duration ovhtypes.TfStringValue `tfsdk:"duration" json:"duration"` + ItemId ovhtypes.TfInt64Value `tfsdk:"item_id" json:"itemId"` + PlanCode ovhtypes.TfStringValue `tfsdk:"plan_code" json:"planCode"` + PricingMode ovhtypes.TfStringValue `tfsdk:"pricing_mode" json:"pricingMode"` + Quantity ovhtypes.TfInt64Value `tfsdk:"quantity" json:"quantity"` + state attr.ValueState +} + +type PlanOptionWritableValue struct { + *PlanOptionValue `json:"-"` + Configuration *ovhtypes.TfListNestedValue[PlanOptionConfigurationValue] `json:"configuration,omitempty"` + Duration *ovhtypes.TfStringValue `json:"duration,omitempty"` + ItemId *ovhtypes.TfInt64Value `json:"itemId,omitempty"` + PlanCode *ovhtypes.TfStringValue `json:"planCode,omitempty"` + PricingMode *ovhtypes.TfStringValue `json:"pricingMode,omitempty"` + Quantity *ovhtypes.TfInt64Value `json:"quantity,omitempty"` +} + +func (opts PlanOptionValue) FromResourceWithPath(d *legacyschema.ResourceData, path string) PlanOptionValue { + opts.Duration = ovhtypes.TfStringValue{StringValue: basetypes.NewStringValue(d.Get(fmt.Sprintf("%s.duration", path)).(string))} + opts.PlanCode = ovhtypes.TfStringValue{StringValue: basetypes.NewStringValue(d.Get(fmt.Sprintf("%s.plan_code", path)).(string))} + opts.PricingMode = ovhtypes.TfStringValue{StringValue: basetypes.NewStringValue(d.Get(fmt.Sprintf("%s.pricing_mode", path)).(string))} + + nbConfigs := d.Get(fmt.Sprintf("%s.configuration.#", path)).(int) + var configs []attr.Value + for i := 0; i < nbConfigs; i++ { + cfg := PlanOptionConfigurationValue{ + state: attr.ValueStateKnown, + Label: ovhtypes.TfStringValue{StringValue: basetypes.NewStringValue(d.Get(fmt.Sprintf("%s.configuration.%d.label", path, i)).(string))}, + Value: ovhtypes.TfStringValue{StringValue: basetypes.NewStringValue(d.Get(fmt.Sprintf("%s.configuration.%d.value", path, i)).(string))}, + } + configs = append(configs, cfg) + } + + opts.Configuration = ovhtypes.TfListNestedValue[PlanOptionConfigurationValue]{ + ListValue: basetypes.NewListValueMust(PlanOptionConfigurationValue{}.Type(context.Background()), configs), + } + + return opts +} + +func (v PlanOptionValue) ToCreate() *PlanOptionWritableValue { + res := &PlanOptionWritableValue{} + + if !v.Configuration.IsNull() { + res.Configuration = &v.Configuration + } + + if !v.Duration.IsNull() { + res.Duration = &v.Duration + } + + if !v.ItemId.IsNull() { + res.ItemId = &v.ItemId + } + + if !v.PlanCode.IsNull() { + res.PlanCode = &v.PlanCode + } + + if !v.PricingMode.IsNull() { + res.PricingMode = &v.PricingMode + } + + if !v.Quantity.IsNull() { + res.Quantity = &v.Quantity + } + + return res +} + +func (v *PlanOptionValue) UnmarshalJSON(data []byte) error { + type JsonPlanOptionValue PlanOptionValue + + var tmp JsonPlanOptionValue + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + v.Configuration = tmp.Configuration + v.Duration = tmp.Duration + v.ItemId = tmp.ItemId + v.PlanCode = tmp.PlanCode + v.PricingMode = tmp.PricingMode + v.Quantity = tmp.Quantity + + v.state = attr.ValueStateKnown + + return nil +} + +func (v *PlanOptionValue) MergeWith(other *PlanOptionValue) { + + if (v.Configuration.IsUnknown() || v.Configuration.IsNull()) && !other.Configuration.IsUnknown() { + v.Configuration = other.Configuration + } else if !other.Configuration.IsUnknown() { + newSlice := make([]attr.Value, 0) + elems := v.Configuration.Elements() + newElems := other.Configuration.Elements() + + if len(elems) != len(newElems) { + v.Configuration = other.Configuration + } else { + for idx, e := range elems { + tmp := e.(PlanOptionConfigurationValue) + tmp2 := newElems[idx].(PlanOptionConfigurationValue) + tmp.MergeWith(&tmp2) + newSlice = append(newSlice, tmp) + } + + v.Configuration = ovhtypes.TfListNestedValue[PlanOptionConfigurationValue]{ + ListValue: basetypes.NewListValueMust(PlanOptionConfigurationValue{}.Type(context.Background()), newSlice), + } + } + } + + if (v.Duration.IsUnknown() || v.Duration.IsNull()) && !other.Duration.IsUnknown() { + v.Duration = other.Duration + } + + if (v.ItemId.IsUnknown() || v.ItemId.IsNull()) && !other.ItemId.IsUnknown() { + v.ItemId = other.ItemId + } + + if (v.PlanCode.IsUnknown() || v.PlanCode.IsNull()) && !other.PlanCode.IsUnknown() { + v.PlanCode = other.PlanCode + } + + if (v.PricingMode.IsUnknown() || v.PricingMode.IsNull()) && !other.PricingMode.IsUnknown() { + v.PricingMode = other.PricingMode + } + + if (v.Quantity.IsUnknown() || v.Quantity.IsNull()) && !other.Quantity.IsUnknown() { + v.Quantity = other.Quantity + } + + if (v.state == attr.ValueStateUnknown || v.state == attr.ValueStateNull) && other.state != attr.ValueStateUnknown { + v.state = other.state + } +} + +func (v PlanOptionValue) Attributes() map[string]attr.Value { + return map[string]attr.Value{ + "configuration": v.Configuration, + "duration": v.Duration, + "itemId": v.ItemId, + "planCode": v.PlanCode, + "pricingMode": v.PricingMode, + "quantity": v.Quantity, + } +} +func (v PlanOptionValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 6) + + var val tftypes.Value + var err error + + attrTypes["configuration"] = basetypes.ListType{ + ElemType: PlanOptionConfigurationValue{}.Type(ctx), + }.TerraformType(ctx) + attrTypes["duration"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["item_id"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["plan_code"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["pricing_mode"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["quantity"] = basetypes.Int64Type{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 6) + + val, err = v.Configuration.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["configuration"] = val + + val, err = v.Duration.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["duration"] = val + + val, err = v.ItemId.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["item_id"] = val + + val, err = v.PlanCode.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["plan_code"] = val + + val, err = v.PricingMode.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["pricing_mode"] = val + + val, err = v.Quantity.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["quantity"] = 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 PlanOptionValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v PlanOptionValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v PlanOptionValue) String() string { + return "PlanOptionValue" +} + +func (v PlanOptionValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "configuration": ovhtypes.NewTfListNestedType[PlanOptionConfigurationValue](ctx), + "duration": ovhtypes.TfStringType{}, + "item_id": ovhtypes.TfInt64Type{}, + "plan_code": ovhtypes.TfStringType{}, + "pricing_mode": ovhtypes.TfStringType{}, + "quantity": ovhtypes.TfInt64Type{}, + }, + map[string]attr.Value{ + "configuration": v.Configuration, + "duration": v.Duration, + "item_id": v.ItemId, + "plan_code": v.PlanCode, + "pricing_mode": v.PricingMode, + "quantity": v.Quantity, + }) + + return objVal, diags +} + +func (v PlanOptionValue) Equal(o attr.Value) bool { + other, ok := o.(PlanOptionValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Configuration.Equal(other.Configuration) { + return false + } + + if !v.Duration.Equal(other.Duration) { + return false + } + + if !v.ItemId.Equal(other.ItemId) { + return false + } + + if !v.PlanCode.Equal(other.PlanCode) { + return false + } + + if !v.PricingMode.Equal(other.PricingMode) { + return false + } + + if !v.Quantity.Equal(other.Quantity) { + return false + } + + return true +} + +func (v PlanOptionValue) Type(ctx context.Context) attr.Type { + return PlanOptionType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v PlanOptionValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "configuration": ovhtypes.NewTfListNestedType[PlanOptionConfigurationValue](ctx), + "duration": ovhtypes.TfStringType{}, + "item_id": ovhtypes.TfInt64Type{}, + "plan_code": ovhtypes.TfStringType{}, + "pricing_mode": ovhtypes.TfStringType{}, + "quantity": ovhtypes.TfInt64Type{}, + } +} + +var _ basetypes.ObjectTypable = PlanOptionConfigurationType{} + +type PlanOptionConfigurationType struct { + basetypes.ObjectType +} + +func (t PlanOptionConfigurationType) Equal(o attr.Type) bool { + other, ok := o.(PlanOptionConfigurationType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t PlanOptionConfigurationType) String() string { + return "PlanOptionConfigurationType" +} + +func (t PlanOptionConfigurationType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + labelAttribute, ok := attributes["label"] + + if !ok { + diags.AddError( + "Attribute Missing", + `label is missing from object`) + + return nil, diags + } + + labelVal, ok := labelAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`label expected to be ovhtypes.TfStringValue, was: %T`, labelAttribute)) + } + + valueAttribute, ok := attributes["value"] + + if !ok { + diags.AddError( + "Attribute Missing", + `value is missing from object`) + + return nil, diags + } + + valueVal, ok := valueAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`value expected to be ovhtypes.TfStringValue, was: %T`, valueAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return PlanOptionConfigurationValue{ + Label: labelVal, + Value: valueVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPlanOptionConfigurationValueNull() PlanOptionConfigurationValue { + return PlanOptionConfigurationValue{ + state: attr.ValueStateNull, + } +} + +func NewPlanOptionConfigurationValueUnknown() PlanOptionConfigurationValue { + return PlanOptionConfigurationValue{ + state: attr.ValueStateUnknown, + } +} + +func NewPlanOptionConfigurationValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (PlanOptionConfigurationValue, 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 PlanOptionConfigurationValue Attribute Value", + "While creating a PlanOptionConfigurationValue value, a missing attribute value was detected. "+ + "A PlanOptionConfigurationValue 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("PlanOptionConfigurationValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid PlanOptionConfigurationValue Attribute Type", + "While creating a PlanOptionConfigurationValue value, an invalid attribute value was detected. "+ + "A PlanOptionConfigurationValue 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("PlanOptionConfigurationValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("PlanOptionConfigurationValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra PlanOptionConfigurationValue Attribute Value", + "While creating a PlanOptionConfigurationValue value, an extra attribute value was detected. "+ + "A PlanOptionConfigurationValue 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 PlanOptionConfigurationValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewPlanOptionConfigurationValueUnknown(), diags + } + + labelAttribute, ok := attributes["label"] + + if !ok { + diags.AddError( + "Attribute Missing", + `label is missing from object`) + + return NewPlanOptionConfigurationValueUnknown(), diags + } + + labelVal, ok := labelAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`label expected to be ovhtypes.TfStringValue, was: %T`, labelAttribute)) + } + + valueAttribute, ok := attributes["value"] + + if !ok { + diags.AddError( + "Attribute Missing", + `value is missing from object`) + + return NewPlanOptionConfigurationValueUnknown(), diags + } + + valueVal, ok := valueAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`value expected to be ovhtypes.TfStringValue, was: %T`, valueAttribute)) + } + + if diags.HasError() { + return NewPlanOptionConfigurationValueUnknown(), diags + } + + return PlanOptionConfigurationValue{ + Label: labelVal, + Value: valueVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewPlanOptionConfigurationValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) PlanOptionConfigurationValue { + object, diags := NewPlanOptionConfigurationValue(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("NewPlanOptionConfigurationValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t PlanOptionConfigurationType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewPlanOptionConfigurationValueNull(), 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 NewPlanOptionConfigurationValueUnknown(), nil + } + + if in.IsNull() { + return NewPlanOptionConfigurationValueNull(), 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 NewPlanOptionConfigurationValueMust(PlanOptionConfigurationValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t PlanOptionConfigurationType) ValueType(ctx context.Context) attr.Value { + return PlanOptionConfigurationValue{} +} + +var _ basetypes.ObjectValuable = PlanOptionConfigurationValue{} + +type PlanOptionConfigurationValue struct { + Label ovhtypes.TfStringValue `tfsdk:"label" json:"label"` + Value ovhtypes.TfStringValue `tfsdk:"value" json:"value"` + state attr.ValueState +} + +type PlanOptionConfigurationWritableValue struct { + *PlanOptionConfigurationValue `json:"-"` + Label *ovhtypes.TfStringValue `json:"label,omitempty"` + Value *ovhtypes.TfStringValue `json:"value,omitempty"` +} + +func (v PlanOptionConfigurationValue) ToCreate() *PlanOptionConfigurationWritableValue { + res := &PlanOptionConfigurationWritableValue{} + + if !v.Label.IsNull() { + res.Label = &v.Label + } + + if !v.Value.IsNull() { + res.Value = &v.Value + } + + return res +} + +func (v *PlanOptionConfigurationValue) UnmarshalJSON(data []byte) error { + type JsonPlanOptionConfigurationValue PlanOptionConfigurationValue + + var tmp JsonPlanOptionConfigurationValue + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + v.Label = tmp.Label + v.Value = tmp.Value + + v.state = attr.ValueStateKnown + + return nil +} + +func (v *PlanOptionConfigurationValue) MergeWith(other *PlanOptionConfigurationValue) { + + if (v.Label.IsUnknown() || v.Label.IsNull()) && !other.Label.IsUnknown() { + v.Label = other.Label + } + + if (v.Value.IsUnknown() || v.Value.IsNull()) && !other.Value.IsUnknown() { + v.Value = other.Value + } + + if (v.state == attr.ValueStateUnknown || v.state == attr.ValueStateNull) && other.state != attr.ValueStateUnknown { + v.state = other.state + } +} + +func (v PlanOptionConfigurationValue) Attributes() map[string]attr.Value { + return map[string]attr.Value{ + "label": v.Label, + "value": v.Value, + } +} +func (v PlanOptionConfigurationValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 2) + + var val tftypes.Value + var err error + + attrTypes["label"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["value"] = 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.Label.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["label"] = val + + val, err = v.Value.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["value"] = 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 PlanOptionConfigurationValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v PlanOptionConfigurationValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v PlanOptionConfigurationValue) String() string { + return "PlanOptionConfigurationValue" +} + +func (v PlanOptionConfigurationValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "label": ovhtypes.TfStringType{}, + "value": ovhtypes.TfStringType{}, + }, + map[string]attr.Value{ + "label": v.Label, + "value": v.Value, + }) + + return objVal, diags +} + +func (v PlanOptionConfigurationValue) Equal(o attr.Value) bool { + other, ok := o.(PlanOptionConfigurationValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Label.Equal(other.Label) { + return false + } + + if !v.Value.Equal(other.Value) { + return false + } + + return true +} + +func (v PlanOptionConfigurationValue) Type(ctx context.Context) attr.Type { + return PlanOptionConfigurationType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v PlanOptionConfigurationValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "label": ovhtypes.TfStringType{}, + "value": ovhtypes.TfStringType{}, + } +} diff --git a/ovh/provider_new.go b/ovh/provider_new.go index 79b8c6855..a314a1764 100644 --- a/ovh/provider_new.go +++ b/ovh/provider_new.go @@ -150,6 +150,7 @@ func (p *OvhProvider) Resources(_ context.Context) []func() resource.Resource { NewCloudProjectAlertingResource, NewDomainZoneDnssecResource, NewIploadbalancingUdpFrontendResource, + NewVpsResource, } } diff --git a/ovh/provider_test.go b/ovh/provider_test.go index cc9906b7c..e3dab8a0e 100644 --- a/ovh/provider_test.go +++ b/ovh/provider_test.go @@ -315,6 +315,13 @@ func testAccPreCheckOrderIpLoadbalancing(t *testing.T) { checkEnvOrSkip(t, "OVH_TESTACC_ORDER_IPLOADBALANCING") } +// Checks that the environment variables needed to order /vps for acceptance tests +// are set. +func testAccPreCheckOrderVPS(t *testing.T) { + testAccPreCheckCredentials(t) + checkEnvOrSkip(t, "OVH_TESTACC_ORDER_VPS") +} + // Checks that the environment variables needed to order /vrack for acceptance tests // are set. func testAccPreCheckOrderVrack(t *testing.T) { diff --git a/ovh/resource_cloud_project.go b/ovh/resource_cloud_project.go index 7985a14c9..59af2cb9c 100644 --- a/ovh/resource_cloud_project.go +++ b/ovh/resource_cloud_project.go @@ -71,7 +71,7 @@ func resourceCloudProjectSchema() map[string]*schema.Schema { } func resourceCloudProjectCreate(d *schema.ResourceData, meta interface{}) error { - if err := orderCreate(d, meta, "cloud"); err != nil { + if err := orderCreateFromResource(d, meta, "cloud"); err != nil { return fmt.Errorf("Could not order cloud project: %q", err) } @@ -105,7 +105,7 @@ func resourceCloudProjectGetServiceName(config *Config, order *MeOrder, details } func resourceCloudProjectUpdate(d *schema.ResourceData, meta interface{}) error { - order, details, err := orderRead(d, meta) + order, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read cloud project order: %q", err) } @@ -127,7 +127,7 @@ func resourceCloudProjectUpdate(d *schema.ResourceData, meta interface{}) error } func resourceCloudProjectRead(d *schema.ResourceData, meta interface{}) error { - order, details, err := orderRead(d, meta) + order, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read cloudProject order: %q", err) } @@ -154,7 +154,7 @@ func resourceCloudProjectRead(d *schema.ResourceData, meta interface{}) error { } func resourceCloudProjectDelete(d *schema.ResourceData, meta interface{}) error { - order, details, err := orderRead(d, meta) + order, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read cloudProject order: %q", err) } @@ -194,7 +194,7 @@ func resourceCloudProjectDelete(d *schema.ResourceData, meta interface{}) error return nil } - if err := orderDelete(d, meta, terminate, confirmTerminate); err != nil { + if err := orderDeleteFromResource(d, meta, terminate, confirmTerminate); err != nil { return err } diff --git a/ovh/resource_cloud_project_test.go b/ovh/resource_cloud_project_test.go index 71b620193..ca14f1358 100644 --- a/ovh/resource_cloud_project_test.go +++ b/ovh/resource_cloud_project_test.go @@ -108,7 +108,7 @@ func testSweepCloudProject(region string) error { } err = resource.Retry(5*time.Minute, func() *resource.RetryError { - if err := orderDelete(nil, config, terminate, confirmTerminate); err != nil { + if err := orderDeleteFromResource(nil, config, terminate, confirmTerminate); err != nil { return resource.RetryableError(err) } diff --git a/ovh/resource_domain_zone.go b/ovh/resource_domain_zone.go index a2dfc4301..e8e981c52 100644 --- a/ovh/resource_domain_zone.go +++ b/ovh/resource_domain_zone.go @@ -70,7 +70,7 @@ func resourceDomainZoneSchema() map[string]*schema.Schema { } func resourceDomainZoneCreate(d *schema.ResourceData, meta interface{}) error { - if err := orderCreate(d, meta, "dns"); err != nil { + if err := orderCreateFromResource(d, meta, "dns"); err != nil { return fmt.Errorf("Could not order domain zone: %q", err) } @@ -78,7 +78,7 @@ func resourceDomainZoneCreate(d *schema.ResourceData, meta interface{}) error { } func resourceDomainZoneRead(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read domainZone order: %q", err) } @@ -102,7 +102,7 @@ func resourceDomainZoneRead(d *schema.ResourceData, meta interface{}) error { } func resourceDomainZoneDelete(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read domainZone order: %q", err) } @@ -139,7 +139,7 @@ func resourceDomainZoneDelete(d *schema.ResourceData, meta interface{}) error { return nil } - if err := orderDelete(d, meta, terminate, confirmTerminate); err != nil { + if err := orderDeleteFromResource(d, meta, terminate, confirmTerminate); err != nil { return err } diff --git a/ovh/resource_domain_zone_test.go b/ovh/resource_domain_zone_test.go index 98852b2b2..d8609bb27 100644 --- a/ovh/resource_domain_zone_test.go +++ b/ovh/resource_domain_zone_test.go @@ -105,7 +105,7 @@ func testSweepDomainZone(region string) error { } err = resource.Retry(5*time.Minute, func() *resource.RetryError { - if err := orderDelete(nil, config, terminate, confirmTerminate); err != nil { + if err := orderDeleteFromResource(nil, config, terminate, confirmTerminate); err != nil { return resource.RetryableError(err) } diff --git a/ovh/resource_hosting_privatedatabase.go b/ovh/resource_hosting_privatedatabase.go index 5cf8ea458..bcac06867 100644 --- a/ovh/resource_hosting_privatedatabase.go +++ b/ovh/resource_hosting_privatedatabase.go @@ -141,7 +141,7 @@ func resourceHostingPrivateDatabaseSchema() map[string]*schema.Schema { } func resourceHostingPrivateDatabaseCreate(d *schema.ResourceData, meta interface{}) error { - if err := orderCreate(d, meta, "privateSQL"); err != nil { + if err := orderCreateFromResource(d, meta, "privateSQL"); err != nil { return fmt.Errorf("could not order privateDatabase: %q", err) } @@ -149,7 +149,7 @@ func resourceHostingPrivateDatabaseCreate(d *schema.ResourceData, meta interface } func resourceHostingPrivateDatabaseUpdate(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("could not read privateDatabase order: %q", err) } @@ -168,7 +168,7 @@ func resourceHostingPrivateDatabaseUpdate(d *schema.ResourceData, meta interface } func resourceHostingPrivateDatabaseRead(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("could not read privatedatabase order: %q", err) } @@ -199,7 +199,7 @@ func resourceHostingPrivateDatabaseRead(d *schema.ResourceData, meta interface{} } func resourceHostingPrivateDatabaseDelete(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("could not read privateDatabase order: %q", err) } @@ -235,7 +235,7 @@ func resourceHostingPrivateDatabaseDelete(d *schema.ResourceData, meta interface return nil } - if err := orderDelete(d, meta, terminate, confirmTerminate); err != nil { + if err := orderDeleteFromResource(d, meta, terminate, confirmTerminate); err != nil { return err } diff --git a/ovh/resource_hosting_privatedatabase_test.go b/ovh/resource_hosting_privatedatabase_test.go index 7248d9fea..8316f4c93 100644 --- a/ovh/resource_hosting_privatedatabase_test.go +++ b/ovh/resource_hosting_privatedatabase_test.go @@ -113,7 +113,7 @@ func testSweepHostingPrivateDatabase(region string) error { } err = resource.Retry(5*time.Minute, func() *resource.RetryError { - if err := orderDelete(nil, config, terminate, confirmTerminate); err != nil { + if err := orderDeleteFromResource(nil, config, terminate, confirmTerminate); err != nil { return resource.RetryableError(err) } diff --git a/ovh/resource_ip_service.go b/ovh/resource_ip_service.go index 626e56be8..d8bc5a68d 100644 --- a/ovh/resource_ip_service.go +++ b/ovh/resource_ip_service.go @@ -90,7 +90,7 @@ func resourceIpServiceSchema() map[string]*schema.Schema { } func resourceIpServiceCreate(d *schema.ResourceData, meta interface{}) error { - if err := orderCreate(d, meta, "ip"); err != nil { + if err := orderCreateFromResource(d, meta, "ip"); err != nil { return fmt.Errorf("Could not order ip: %q", err) } @@ -98,7 +98,7 @@ func resourceIpServiceCreate(d *schema.ResourceData, meta interface{}) error { } func resourceIpServiceUpdate(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read ip order: %q", err) } @@ -120,7 +120,7 @@ func resourceIpServiceUpdate(d *schema.ResourceData, meta interface{}) error { } func resourceIpServiceRead(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read ip order: %q", err) } @@ -171,7 +171,7 @@ func resourceIpServiceRead(d *schema.ResourceData, meta interface{}) error { } func resourceIpServiceDelete(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read ip order: %q", err) } @@ -207,7 +207,7 @@ func resourceIpServiceDelete(d *schema.ResourceData, meta interface{}) error { return nil } - if err := orderDelete(d, meta, terminate, confirmTerminate); err != nil { + if err := orderDeleteFromResource(d, meta, terminate, confirmTerminate); err != nil { return err } diff --git a/ovh/resource_ip_service_test.go b/ovh/resource_ip_service_test.go index d82a4db74..b15f6996e 100644 --- a/ovh/resource_ip_service_test.go +++ b/ovh/resource_ip_service_test.go @@ -113,7 +113,7 @@ func testSweepIp(region string) error { } err = resource.Retry(5*time.Minute, func() *resource.RetryError { - if err := orderDelete(nil, config, terminate, confirmTerminate); err != nil { + if err := orderDeleteFromResource(nil, config, terminate, confirmTerminate); err != nil { return resource.RetryableError(err) } diff --git a/ovh/resource_iploadbalancing.go b/ovh/resource_iploadbalancing.go index 7937d802a..9d2861f9a 100644 --- a/ovh/resource_iploadbalancing.go +++ b/ovh/resource_iploadbalancing.go @@ -148,7 +148,7 @@ func resourceIpLoadbalancingSchema() map[string]*schema.Schema { } func resourceIpLoadbalancingCreate(d *schema.ResourceData, meta interface{}) error { - if err := orderCreate(d, meta, "ipLoadbalancing"); err != nil { + if err := orderCreateFromResource(d, meta, "ipLoadbalancing"); err != nil { return fmt.Errorf("Could not order ipLoadbalancing: %q", err) } @@ -156,7 +156,7 @@ func resourceIpLoadbalancingCreate(d *schema.ResourceData, meta interface{}) err } func resourceIpLoadbalancingUpdate(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read ipLoadbalancing order: %q", err) } @@ -181,7 +181,7 @@ func resourceIpLoadbalancingUpdate(d *schema.ResourceData, meta interface{}) err } func resourceIpLoadbalancingRead(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read ipLoadbalancing order: %q", err) } @@ -243,7 +243,7 @@ func resourceIpLoadbalancingDelete(d *schema.ResourceData, meta interface{}) err return nil } - if err := orderDelete(d, meta, terminate, confirmTerminate); err != nil { + if err := orderDeleteFromResource(d, meta, terminate, confirmTerminate); err != nil { return err } diff --git a/ovh/resource_iploadbalancing_test.go b/ovh/resource_iploadbalancing_test.go index 7932bc293..a1525b3c6 100644 --- a/ovh/resource_iploadbalancing_test.go +++ b/ovh/resource_iploadbalancing_test.go @@ -142,7 +142,7 @@ func testSweepIpLoadbalancing(region string) error { } err = resource.Retry(5*time.Minute, func() *resource.RetryError { - if err := orderDelete(nil, config, terminate, confirmTerminate); err != nil { + if err := orderDeleteFromResource(nil, config, terminate, confirmTerminate); err != nil { return resource.RetryableError(err) } diff --git a/ovh/resource_vps.go b/ovh/resource_vps.go new file mode 100644 index 000000000..af2aba8fe --- /dev/null +++ b/ovh/resource_vps.go @@ -0,0 +1,264 @@ +package ovh + +import ( + "context" + "errors" + "fmt" + "log" + "net/url" + "time" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/ovh/go-ovh/ovh" + "github.com/ovh/terraform-provider-ovh/ovh/types" +) + +var ( + _ resource.ResourceWithConfigure = (*vpsResource)(nil) + _ resource.ResourceWithImportState = (*vpsResource)(nil) +) + +func NewVpsResource() resource.Resource { + return &vpsResource{} +} + +type vpsResource struct { + config *Config +} + +func (r *vpsResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_vps" +} + +func (d *vpsResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + config, ok := req.ProviderData.(*Config) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *Config, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.config = config +} + +func (d *vpsResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = VpsResourceSchema(ctx) +} + +func (r *vpsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("service_name"), req.ID)...) +} + +func (r *vpsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data VpsModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Create order and wait for service to be delivered + order := data.ToOrder() + if err := orderCreate(order, r.config, "vps"); err != nil { + resp.Diagnostics.AddError("failed to create order", err.Error()) + } + + // Find service name from order + orderID := order.Order.OrderId.ValueInt64() + plans := []PlanValue{} + resp.Diagnostics.Append(data.Plan.ElementsAs(ctx, &plans, false)...) + if resp.Diagnostics.HasError() { + return + } + + serviceName, err := serviceNameFromOrder(r.config.OVHClient, orderID, plans[0].PlanCode.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed to retrieve service name", err.Error()) + } + data.ServiceName = types.TfStringValue{ + StringValue: basetypes.NewStringValue(serviceName), + } + + // Update resource + endpoint := "/vps/" + url.PathEscape(data.ServiceName.ValueString()) + if err := r.config.OVHClient.Put(endpoint, data.ToUpdate(), nil); err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error calling Put %s", endpoint), + err.Error(), + ) + return + } + + // Read updated resource + responseData, err := r.waitForVPSUpdate(ctx, serviceName, &data) + if err != nil { + resp.Diagnostics.AddError( + "Error fetching updated resource", + err.Error(), + ) + return + } + + data.MergeWith(responseData) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *vpsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data VpsModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Read resource + responseData, err := r.waitForVPSUpdate(ctx, data.ServiceName.ValueString(), &data) + if err != nil { + resp.Diagnostics.AddError( + "Error fetching resource", + err.Error(), + ) + return + } + + data.MergeWith(responseData) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *vpsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data, planData VpsModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &planData)...) + if resp.Diagnostics.HasError() { + return + } + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Update resource + endpoint := "/vps/" + url.PathEscape(data.ServiceName.ValueString()) + if err := r.config.OVHClient.Put(endpoint, planData.ToUpdate(), nil); err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error calling Put %s", endpoint), + err.Error(), + ) + return + } + + // Read updated resource + responseData, err := r.waitForVPSUpdate(ctx, data.ServiceName.ValueString(), &planData) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("Error fetching updated resource %s", endpoint), + err.Error(), + ) + return + } + + responseData.MergeWith(&planData) + responseData.MergeWith(&data) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &responseData)...) +} + +func (r *vpsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data VpsModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + serviceName := data.ServiceName.ValueString() + + terminate := func() (string, error) { + log.Printf("[DEBUG] Will terminate vps %s", serviceName) + endpoint := fmt.Sprintf("/vps/%s/terminate", url.PathEscape(serviceName)) + if err := r.config.OVHClient.Post(endpoint, nil, nil); err != nil { + if errOvh, ok := err.(*ovh.APIError); ok && (errOvh.Code == 404 || errOvh.Code == 460) { + return "", nil + } + return "", fmt.Errorf("calling Post %s:\n\t %q", endpoint, err) + } + return serviceName, nil + } + + confirmTerminate := func(token string) error { + log.Printf("[DEBUG] Will confirm termination of vps %s", serviceName) + endpoint := fmt.Sprintf("/vps/%s/confirmTermination", url.PathEscape(serviceName)) + if err := r.config.OVHClient.Post(endpoint, &ConfirmTerminationOpts{Token: token}, nil); err != nil { + return fmt.Errorf("calling Post %s:\n\t %q", endpoint, err) + } + return nil + } + + if err := orderDelete(r.config, terminate, confirmTerminate); err != nil { + resp.Diagnostics.AddError("failed to delete resource", err.Error()) + return + } +} + +// waitForVPSUpdate fetches the given VPS 20 times in a loop, sleeping 1 min between each fetch. It is done to ensure that +// field `netbootMode` has been updated since it is not done synchronously. +func (r *vpsResource) waitForVPSUpdate(ctx context.Context, serviceName string, planData *VpsModel) (*VpsModel, error) { + var responseData VpsModel + + err := retry.RetryContext(ctx, 20*time.Minute, func() *retry.RetryError { + // Read updated resource + endpoint := "/vps/" + url.PathEscape(serviceName) + if err := r.config.OVHClient.Get(endpoint, &responseData); err != nil { + return retry.NonRetryableError(fmt.Errorf("error calling GET %s", endpoint)) + } + + // Update is succesfull, return + if responseData.NetbootMode == planData.NetbootMode || + planData.NetbootMode.IsNull() || + planData.NetbootMode.IsUnknown() { + return nil + } + + return retry.RetryableError(errors.New("waiting for netbootMode to have its expected value")) + }) + + if err != nil { + return nil, err + } + + service, err := serviceFromServiceName(r.config.OVHClient, "vps", serviceName) + if err != nil { + return nil, fmt.Errorf("failed to retrieve service from service name: %w", err) + } + responseData.Plan = *service.ToPlanValue(ctx, planData.Plan) + + var me MeResponse + if err := r.config.OVHClient.Get("/me", &me); err != nil { + return nil, fmt.Errorf("error retrieving account information: %w", err) + } + responseData.OvhSubsidiary = types.TfStringValue{ + StringValue: basetypes.NewStringValue(me.OvhSubsidiary), + } + + return &responseData, nil +} diff --git a/ovh/resource_vps_gen.go b/ovh/resource_vps_gen.go new file mode 100644 index 000000000..a951123cf --- /dev/null +++ b/ovh/resource_vps_gen.go @@ -0,0 +1,1804 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package ovh + +import ( + "context" + "encoding/json" + "fmt" + "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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" + ovhtypes "github.com/ovh/terraform-provider-ovh/ovh/types" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func VpsResourceSchema(ctx context.Context) schema.Schema { + attrs := map[string]schema.Attribute{ + "cluster": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + }, + "display_name": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + Description: "Set the name displayed in Manager for your VPS (max 50 chars)", + MarkdownDescription: "Set the name displayed in Manager for your VPS (max 50 chars)", + }, + "iam": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "display_name": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + Description: "Resource display name", + MarkdownDescription: "Resource display name", + }, + "id": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + Description: "Unique identifier of the resource", + MarkdownDescription: "Unique identifier of the resource", + }, + "tags": schema.MapAttribute{ + CustomType: ovhtypes.NewTfMapNestedType[ovhtypes.TfStringValue](ctx), + Computed: true, + Description: "Resource tags. Tags that were internally computed are prefixed with ovh:", + MarkdownDescription: "Resource tags. Tags that were internally computed are prefixed with ovh:", + }, + "urn": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + Description: "Unique resource name used in policies", + MarkdownDescription: "Unique resource name used in policies", + }, + }, + CustomType: IamType{ + ObjectType: types.ObjectType{ + AttrTypes: IamValue{}.AttributeTypes(ctx), + }, + }, + Computed: true, + Description: "IAM resource metadata", + MarkdownDescription: "IAM resource metadata", + }, + "keymap": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + Description: "KVM keyboard layout on VPS Cloud", + Validators: []validator.String{ + stringvalidator.OneOf( + "fr", + "us", + ), + }, + }, + "memory_limit": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Optional: true, + Computed: true, + }, + "model": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "available_options": schema.ListAttribute{ + CustomType: ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx), + Optional: true, + Computed: true, + }, + "datacenter": schema.ListAttribute{ + CustomType: ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx), + Optional: true, + Computed: true, + }, + "disk": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Optional: true, + Computed: true, + }, + "maximum_additionnal_ip": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Optional: true, + Computed: true, + }, + "memory": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Optional: true, + Computed: true, + }, + "name": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + }, + "offer": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + }, + "vcore": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Optional: true, + Computed: true, + }, + "version": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + Description: "All versions that VPS can have", + MarkdownDescription: "All versions that VPS can have", + Validators: []validator.String{ + stringvalidator.OneOf( + "2013v1", + "2014v1", + "2015v1", + "2017v1", + "2017v2", + "2017v3", + "2018v1", + "2018v2", + "2019v1", + ), + }, + }, + }, + CustomType: ModelType{ + ObjectType: types.ObjectType{ + AttrTypes: ModelValue{}.AttributeTypes(ctx), + }, + }, + Optional: true, + Computed: true, + Description: "A structure describing characteristics of a VPS model", + MarkdownDescription: "A structure describing characteristics of a VPS model", + }, + "monitoring_ip_blocks": schema.ListAttribute{ + CustomType: ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx), + Optional: true, + Computed: true, + Description: "Ip blocks for OVH monitoring servers", + MarkdownDescription: "Ip blocks for OVH monitoring servers", + }, + "name": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + }, + "netboot_mode": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + Description: "All values a VPS netboot mode can be in", + MarkdownDescription: "All values a VPS netboot mode can be in", + Validators: []validator.String{ + stringvalidator.OneOf( + "local", + "rescue", + ), + }, + }, + "offer_type": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + Description: "All offers a VPS can have", + MarkdownDescription: "All offers a VPS can have", + Validators: []validator.String{ + stringvalidator.OneOf( + "beta-classic", + "classic", + "cloud", + "cloudram", + "game-classic", + "lowlat", + "ssd", + ), + }, + }, + "service_name": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Computed: true, + Description: "The internal name of your VPS offer", + MarkdownDescription: "The internal name of your VPS offer", + }, + "sla_monitoring": schema.BoolAttribute{ + CustomType: ovhtypes.TfBoolType{}, + Optional: true, + Computed: true, + }, + "state": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + Description: "All states a VPS can be in", + MarkdownDescription: "All states a VPS can be in", + Validators: []validator.String{ + stringvalidator.OneOf( + "backuping", + "installing", + "maintenance", + "rebooting", + "rescued", + "running", + "stopped", + "stopping", + "upgrading", + ), + }, + }, + "vcore": schema.Int64Attribute{ + CustomType: ovhtypes.TfInt64Type{}, + Optional: true, + Computed: true, + }, + "zone": schema.StringAttribute{ + CustomType: ovhtypes.TfStringType{}, + Optional: true, + Computed: true, + }, + } + + // Add order attributes + for k, v := range OrderResourceSchema(ctx).Attributes { + attrs[k] = v + } + + return schema.Schema{ + Attributes: attrs, + } +} + +type VpsModel struct { + Cluster ovhtypes.TfStringValue `tfsdk:"cluster" json:"cluster"` + DisplayName ovhtypes.TfStringValue `tfsdk:"display_name" json:"displayName"` + Iam IamValue `tfsdk:"iam" json:"iam"` + Keymap ovhtypes.TfStringValue `tfsdk:"keymap" json:"keymap"` + MemoryLimit ovhtypes.TfInt64Value `tfsdk:"memory_limit" json:"memoryLimit"` + Model ModelValue `tfsdk:"model" json:"model"` + MonitoringIpBlocks ovhtypes.TfListNestedValue[ovhtypes.TfStringValue] `tfsdk:"monitoring_ip_blocks" json:"monitoringIpBlocks"` + Name ovhtypes.TfStringValue `tfsdk:"name" json:"name"` + NetbootMode ovhtypes.TfStringValue `tfsdk:"netboot_mode" json:"netbootMode"` + OfferType ovhtypes.TfStringValue `tfsdk:"offer_type" json:"offerType"` + ServiceName ovhtypes.TfStringValue `tfsdk:"service_name" json:"serviceName"` + SlaMonitoring ovhtypes.TfBoolValue `tfsdk:"sla_monitoring" json:"slaMonitoring"` + State ovhtypes.TfStringValue `tfsdk:"state" json:"state"` + Vcore ovhtypes.TfInt64Value `tfsdk:"vcore" json:"vcore"` + Zone ovhtypes.TfStringValue `tfsdk:"zone" json:"zone"` + // Fields used for order + Order OrderValue `tfsdk:"order" json:"order"` + OvhSubsidiary ovhtypes.TfStringValue `tfsdk:"ovh_subsidiary" json:"ovhSubsidiary"` + Plan ovhtypes.TfListNestedValue[PlanValue] `tfsdk:"plan" json:"plan"` + PlanOption ovhtypes.TfListNestedValue[PlanOptionValue] `tfsdk:"plan_option" json:"planOption"` +} + +func (v *VpsModel) ToOrder() *OrderModel { + return &OrderModel{ + Order: v.Order, + OvhSubsidiary: v.OvhSubsidiary, + Plan: v.Plan, + PlanOption: v.PlanOption, + } +} + +func (v *VpsModel) MergeWith(other *VpsModel) { + if (v.Cluster.IsUnknown() || v.Cluster.IsNull()) && !other.Cluster.IsUnknown() { + v.Cluster = other.Cluster + } + + if (v.DisplayName.IsUnknown() || v.DisplayName.IsNull()) && !other.DisplayName.IsUnknown() { + v.DisplayName = other.DisplayName + } + + if v.Iam.IsUnknown() && !other.Iam.IsUnknown() { + v.Iam = other.Iam + } else if !other.Iam.IsUnknown() { + v.Iam.MergeWith(&other.Iam) + } + + if (v.Keymap.IsUnknown() || v.Keymap.IsNull()) && !other.Keymap.IsUnknown() { + v.Keymap = other.Keymap + } + + if (v.MemoryLimit.IsUnknown() || v.MemoryLimit.IsNull()) && !other.MemoryLimit.IsUnknown() { + v.MemoryLimit = other.MemoryLimit + } + + if v.Model.IsUnknown() && !other.Model.IsUnknown() { + v.Model = other.Model + } else if !other.Model.IsUnknown() { + v.Model.MergeWith(&other.Model) + } + + if (v.MonitoringIpBlocks.IsUnknown() || v.MonitoringIpBlocks.IsNull()) && !other.MonitoringIpBlocks.IsUnknown() { + v.MonitoringIpBlocks = other.MonitoringIpBlocks + } + + if (v.Name.IsUnknown() || v.Name.IsNull()) && !other.Name.IsUnknown() { + v.Name = other.Name + } + + if (v.NetbootMode.IsUnknown() || v.NetbootMode.IsNull()) && !other.NetbootMode.IsUnknown() { + v.NetbootMode = other.NetbootMode + } + + if (v.OfferType.IsUnknown() || v.OfferType.IsNull()) && !other.OfferType.IsUnknown() { + v.OfferType = other.OfferType + } + + if (v.ServiceName.IsUnknown() || v.ServiceName.IsNull()) && !other.ServiceName.IsUnknown() { + v.ServiceName = other.ServiceName + } + + if (v.SlaMonitoring.IsUnknown() || v.SlaMonitoring.IsNull()) && !other.SlaMonitoring.IsUnknown() { + v.SlaMonitoring = other.SlaMonitoring + } + + if (v.State.IsUnknown() || v.State.IsNull()) && !other.State.IsUnknown() { + v.State = other.State + } + + if (v.Vcore.IsUnknown() || v.Vcore.IsNull()) && !other.Vcore.IsUnknown() { + v.Vcore = other.Vcore + } + + if (v.Zone.IsUnknown() || v.Zone.IsNull()) && !other.Zone.IsUnknown() { + v.Zone = other.Zone + } + + if v.Order.IsUnknown() && !other.Order.IsUnknown() { + v.Order = other.Order + } else if !other.Order.IsUnknown() { + v.Order.MergeWith(&other.Order) + } + + if (v.OvhSubsidiary.IsUnknown() || v.OvhSubsidiary.IsNull()) && !other.OvhSubsidiary.IsUnknown() { + v.OvhSubsidiary = other.OvhSubsidiary + } + + if (v.Plan.IsUnknown() || v.Plan.IsNull()) && !other.Plan.IsUnknown() { + v.Plan = other.Plan + } + + if (v.PlanOption.IsUnknown() || v.PlanOption.IsNull()) && !other.PlanOption.IsUnknown() { + v.PlanOption = other.PlanOption + } +} + +type VpsWritableModel struct { + DisplayName *ovhtypes.TfStringValue `tfsdk:"display_name" json:"displayName,omitempty"` + NetbootMode *ovhtypes.TfStringValue `tfsdk:"netboot_mode" json:"netbootMode,omitempty"` + SlaMonitoring *ovhtypes.TfBoolValue `tfsdk:"sla_monitoring" json:"slaMonitoring,omitempty"` +} + +func (v VpsModel) ToCreate() *VpsWritableModel { + res := &VpsWritableModel{} + + if !v.DisplayName.IsUnknown() { + res.DisplayName = &v.DisplayName + } + + if !v.NetbootMode.IsUnknown() { + res.NetbootMode = &v.NetbootMode + } + + if !v.SlaMonitoring.IsUnknown() { + res.SlaMonitoring = &v.SlaMonitoring + } + + return res +} + +func (v VpsModel) ToUpdate() *VpsWritableModel { + res := &VpsWritableModel{} + + if !v.DisplayName.IsUnknown() { + res.DisplayName = &v.DisplayName + } + + if !v.NetbootMode.IsUnknown() { + res.NetbootMode = &v.NetbootMode + } + + return res +} + +var _ basetypes.ObjectTypable = ModelType{} + +type ModelType struct { + basetypes.ObjectType +} + +func (t ModelType) Equal(o attr.Type) bool { + other, ok := o.(ModelType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t ModelType) String() string { + return "ModelType" +} + +func (t ModelType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + availableOptionsAttribute, ok := attributes["available_options"] + + if !ok { + diags.AddError( + "Attribute Missing", + `available_options is missing from object`) + + return nil, diags + } + + availableOptionsVal, ok := availableOptionsAttribute.(ovhtypes.TfListNestedValue[ovhtypes.TfStringValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`available_options expected to be ovhtypes.TfListNestedValue[ovhtypes.TfStringValue], was: %T`, availableOptionsAttribute)) + } + + datacenterAttribute, ok := attributes["datacenter"] + + if !ok { + diags.AddError( + "Attribute Missing", + `datacenter is missing from object`) + + return nil, diags + } + + datacenterVal, ok := datacenterAttribute.(ovhtypes.TfListNestedValue[ovhtypes.TfStringValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`datacenter expected to be ovhtypes.TfListNestedValue[ovhtypes.TfStringValue], was: %T`, datacenterAttribute)) + } + + diskAttribute, ok := attributes["disk"] + + if !ok { + diags.AddError( + "Attribute Missing", + `disk is missing from object`) + + return nil, diags + } + + diskVal, ok := diskAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`disk expected to be ovhtypes.TfInt64Value, was: %T`, diskAttribute)) + } + + maximumAdditionnalIpAttribute, ok := attributes["maximum_additionnal_ip"] + + if !ok { + diags.AddError( + "Attribute Missing", + `maximum_additionnal_ip is missing from object`) + + return nil, diags + } + + maximumAdditionnalIpVal, ok := maximumAdditionnalIpAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`maximum_additionnal_ip expected to be ovhtypes.TfInt64Value, was: %T`, maximumAdditionnalIpAttribute)) + } + + memoryAttribute, ok := attributes["memory"] + + if !ok { + diags.AddError( + "Attribute Missing", + `memory is missing from object`) + + return nil, diags + } + + memoryVal, ok := memoryAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`memory expected to be ovhtypes.TfInt64Value, was: %T`, memoryAttribute)) + } + + nameAttribute, ok := attributes["name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `name is missing from object`) + + return nil, diags + } + + nameVal, ok := nameAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`name expected to be ovhtypes.TfStringValue, was: %T`, nameAttribute)) + } + + offerAttribute, ok := attributes["offer"] + + if !ok { + diags.AddError( + "Attribute Missing", + `offer is missing from object`) + + return nil, diags + } + + offerVal, ok := offerAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`offer expected to be ovhtypes.TfStringValue, was: %T`, offerAttribute)) + } + + vcoreAttribute, ok := attributes["vcore"] + + if !ok { + diags.AddError( + "Attribute Missing", + `vcore is missing from object`) + + return nil, diags + } + + vcoreVal, ok := vcoreAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`vcore expected to be ovhtypes.TfInt64Value, was: %T`, vcoreAttribute)) + } + + versionAttribute, ok := attributes["version"] + + if !ok { + diags.AddError( + "Attribute Missing", + `version is missing from object`) + + return nil, diags + } + + versionVal, ok := versionAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`version expected to be ovhtypes.TfStringValue, was: %T`, versionAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return ModelValue{ + AvailableOptions: availableOptionsVal, + Datacenter: datacenterVal, + Disk: diskVal, + MaximumAdditionnalIp: maximumAdditionnalIpVal, + Memory: memoryVal, + Name: nameVal, + Offer: offerVal, + Vcore: vcoreVal, + Version: versionVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewModelValueNull() ModelValue { + return ModelValue{ + state: attr.ValueStateNull, + } +} + +func NewModelValueUnknown() ModelValue { + return ModelValue{ + state: attr.ValueStateUnknown, + } +} + +func NewModelValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (ModelValue, 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 ModelValue Attribute Value", + "While creating a ModelValue value, a missing attribute value was detected. "+ + "A ModelValue 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("ModelValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid ModelValue Attribute Type", + "While creating a ModelValue value, an invalid attribute value was detected. "+ + "A ModelValue 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("ModelValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("ModelValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra ModelValue Attribute Value", + "While creating a ModelValue value, an extra attribute value was detected. "+ + "A ModelValue 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 ModelValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewModelValueUnknown(), diags + } + + availableOptionsAttribute, ok := attributes["available_options"] + + if !ok { + diags.AddError( + "Attribute Missing", + `available_options is missing from object`) + + return NewModelValueUnknown(), diags + } + + availableOptionsVal, ok := availableOptionsAttribute.(ovhtypes.TfListNestedValue[ovhtypes.TfStringValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`available_options expected to be ovhtypes.TfListNestedValue[ovhtypes.TfStringValue], was: %T`, availableOptionsAttribute)) + } + + datacenterAttribute, ok := attributes["datacenter"] + + if !ok { + diags.AddError( + "Attribute Missing", + `datacenter is missing from object`) + + return NewModelValueUnknown(), diags + } + + datacenterVal, ok := datacenterAttribute.(ovhtypes.TfListNestedValue[ovhtypes.TfStringValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`datacenter expected to be ovhtypes.TfListNestedValue[ovhtypes.TfStringValue], was: %T`, datacenterAttribute)) + } + + diskAttribute, ok := attributes["disk"] + + if !ok { + diags.AddError( + "Attribute Missing", + `disk is missing from object`) + + return NewModelValueUnknown(), diags + } + + diskVal, ok := diskAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`disk expected to be ovhtypes.TfInt64Value, was: %T`, diskAttribute)) + } + + maximumAdditionnalIpAttribute, ok := attributes["maximum_additionnal_ip"] + + if !ok { + diags.AddError( + "Attribute Missing", + `maximum_additionnal_ip is missing from object`) + + return NewModelValueUnknown(), diags + } + + maximumAdditionnalIpVal, ok := maximumAdditionnalIpAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`maximum_additionnal_ip expected to be ovhtypes.TfInt64Value, was: %T`, maximumAdditionnalIpAttribute)) + } + + memoryAttribute, ok := attributes["memory"] + + if !ok { + diags.AddError( + "Attribute Missing", + `memory is missing from object`) + + return NewModelValueUnknown(), diags + } + + memoryVal, ok := memoryAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`memory expected to be ovhtypes.TfInt64Value, was: %T`, memoryAttribute)) + } + + nameAttribute, ok := attributes["name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `name is missing from object`) + + return NewModelValueUnknown(), diags + } + + nameVal, ok := nameAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`name expected to be ovhtypes.TfStringValue, was: %T`, nameAttribute)) + } + + offerAttribute, ok := attributes["offer"] + + if !ok { + diags.AddError( + "Attribute Missing", + `offer is missing from object`) + + return NewModelValueUnknown(), diags + } + + offerVal, ok := offerAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`offer expected to be ovhtypes.TfStringValue, was: %T`, offerAttribute)) + } + + vcoreAttribute, ok := attributes["vcore"] + + if !ok { + diags.AddError( + "Attribute Missing", + `vcore is missing from object`) + + return NewModelValueUnknown(), diags + } + + vcoreVal, ok := vcoreAttribute.(ovhtypes.TfInt64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`vcore expected to be ovhtypes.TfInt64Value, was: %T`, vcoreAttribute)) + } + + versionAttribute, ok := attributes["version"] + + if !ok { + diags.AddError( + "Attribute Missing", + `version is missing from object`) + + return NewModelValueUnknown(), diags + } + + versionVal, ok := versionAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`version expected to be ovhtypes.TfStringValue, was: %T`, versionAttribute)) + } + + if diags.HasError() { + return NewModelValueUnknown(), diags + } + + return ModelValue{ + AvailableOptions: availableOptionsVal, + Datacenter: datacenterVal, + Disk: diskVal, + MaximumAdditionnalIp: maximumAdditionnalIpVal, + Memory: memoryVal, + Name: nameVal, + Offer: offerVal, + Vcore: vcoreVal, + Version: versionVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewModelValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) ModelValue { + object, diags := NewModelValue(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("NewModelValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t ModelType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewModelValueNull(), 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 NewModelValueUnknown(), nil + } + + if in.IsNull() { + return NewModelValueNull(), 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 NewModelValueMust(ModelValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t ModelType) ValueType(ctx context.Context) attr.Value { + return ModelValue{} +} + +var _ basetypes.ObjectValuable = ModelValue{} + +type ModelValue struct { + AvailableOptions ovhtypes.TfListNestedValue[ovhtypes.TfStringValue] `tfsdk:"available_options" json:"availableOptions"` + Datacenter ovhtypes.TfListNestedValue[ovhtypes.TfStringValue] `tfsdk:"datacenter" json:"datacenter"` + Disk ovhtypes.TfInt64Value `tfsdk:"disk" json:"disk"` + MaximumAdditionnalIp ovhtypes.TfInt64Value `tfsdk:"maximum_additionnal_ip" json:"maximumAdditionnalIp"` + Memory ovhtypes.TfInt64Value `tfsdk:"memory" json:"memory"` + Name ovhtypes.TfStringValue `tfsdk:"name" json:"name"` + Offer ovhtypes.TfStringValue `tfsdk:"offer" json:"offer"` + Vcore ovhtypes.TfInt64Value `tfsdk:"vcore" json:"vcore"` + Version ovhtypes.TfStringValue `tfsdk:"version" json:"version"` + state attr.ValueState +} + +func (v *ModelValue) UnmarshalJSON(data []byte) error { + type JsonModelValue ModelValue + + var tmp JsonModelValue + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + v.AvailableOptions = tmp.AvailableOptions + v.Datacenter = tmp.Datacenter + v.Disk = tmp.Disk + v.MaximumAdditionnalIp = tmp.MaximumAdditionnalIp + v.Memory = tmp.Memory + v.Name = tmp.Name + v.Offer = tmp.Offer + v.Vcore = tmp.Vcore + v.Version = tmp.Version + + v.state = attr.ValueStateKnown + + return nil +} + +func (v *ModelValue) MergeWith(other *ModelValue) { + + if (v.AvailableOptions.IsUnknown() || v.AvailableOptions.IsNull()) && !other.AvailableOptions.IsUnknown() { + v.AvailableOptions = other.AvailableOptions + } + + if (v.Datacenter.IsUnknown() || v.Datacenter.IsNull()) && !other.Datacenter.IsUnknown() { + v.Datacenter = other.Datacenter + } + + if (v.Disk.IsUnknown() || v.Disk.IsNull()) && !other.Disk.IsUnknown() { + v.Disk = other.Disk + } + + if (v.MaximumAdditionnalIp.IsUnknown() || v.MaximumAdditionnalIp.IsNull()) && !other.MaximumAdditionnalIp.IsUnknown() { + v.MaximumAdditionnalIp = other.MaximumAdditionnalIp + } + + if (v.Memory.IsUnknown() || v.Memory.IsNull()) && !other.Memory.IsUnknown() { + v.Memory = other.Memory + } + + if (v.Name.IsUnknown() || v.Name.IsNull()) && !other.Name.IsUnknown() { + v.Name = other.Name + } + + if (v.Offer.IsUnknown() || v.Offer.IsNull()) && !other.Offer.IsUnknown() { + v.Offer = other.Offer + } + + if (v.Vcore.IsUnknown() || v.Vcore.IsNull()) && !other.Vcore.IsUnknown() { + v.Vcore = other.Vcore + } + + if (v.Version.IsUnknown() || v.Version.IsNull()) && !other.Version.IsUnknown() { + v.Version = other.Version + } + + if (v.state == attr.ValueStateUnknown || v.state == attr.ValueStateNull) && other.state != attr.ValueStateUnknown { + v.state = other.state + } +} + +func (v ModelValue) Attributes() map[string]attr.Value { + return map[string]attr.Value{ + "availableOptions": v.AvailableOptions, + "datacenter": v.Datacenter, + "disk": v.Disk, + "maximumAdditionnalIp": v.MaximumAdditionnalIp, + "memory": v.Memory, + "name": v.Name, + "offer": v.Offer, + "vcore": v.Vcore, + "version": v.Version, + } +} +func (v ModelValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 9) + + var val tftypes.Value + var err error + + attrTypes["available_options"] = basetypes.ListType{ + ElemType: types.StringType, + }.TerraformType(ctx) + attrTypes["datacenter"] = basetypes.ListType{ + ElemType: types.StringType, + }.TerraformType(ctx) + attrTypes["disk"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["maximum_additionnal_ip"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["memory"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["name"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["offer"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["vcore"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["version"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 9) + + val, err = v.AvailableOptions.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["available_options"] = val + + val, err = v.Datacenter.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["datacenter"] = val + + val, err = v.Disk.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["disk"] = val + + val, err = v.MaximumAdditionnalIp.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["maximum_additionnal_ip"] = val + + val, err = v.Memory.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["memory"] = val + + val, err = v.Name.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["name"] = val + + val, err = v.Offer.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["offer"] = val + + val, err = v.Vcore.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["vcore"] = val + + val, err = v.Version.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["version"] = 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 ModelValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v ModelValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v ModelValue) String() string { + return "ModelValue" +} + +func (v ModelValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "available_options": ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx), + "datacenter": ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx), + "disk": ovhtypes.TfInt64Type{}, + "maximum_additionnal_ip": ovhtypes.TfInt64Type{}, + "memory": ovhtypes.TfInt64Type{}, + "name": ovhtypes.TfStringType{}, + "offer": ovhtypes.TfStringType{}, + "vcore": ovhtypes.TfInt64Type{}, + "version": ovhtypes.TfStringType{}, + }, + map[string]attr.Value{ + "available_options": v.AvailableOptions, + "datacenter": v.Datacenter, + "disk": v.Disk, + "maximum_additionnal_ip": v.MaximumAdditionnalIp, + "memory": v.Memory, + "name": v.Name, + "offer": v.Offer, + "vcore": v.Vcore, + "version": v.Version, + }) + + return objVal, diags +} + +func (v ModelValue) Equal(o attr.Value) bool { + other, ok := o.(ModelValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.AvailableOptions.Equal(other.AvailableOptions) { + return false + } + + if !v.Datacenter.Equal(other.Datacenter) { + return false + } + + if !v.Disk.Equal(other.Disk) { + return false + } + + if !v.MaximumAdditionnalIp.Equal(other.MaximumAdditionnalIp) { + return false + } + + if !v.Memory.Equal(other.Memory) { + return false + } + + if !v.Name.Equal(other.Name) { + return false + } + + if !v.Offer.Equal(other.Offer) { + return false + } + + if !v.Vcore.Equal(other.Vcore) { + return false + } + + if !v.Version.Equal(other.Version) { + return false + } + + return true +} + +func (v ModelValue) Type(ctx context.Context) attr.Type { + return ModelType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v ModelValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "available_options": ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx), + "datacenter": ovhtypes.NewTfListNestedType[ovhtypes.TfStringValue](ctx), + "disk": ovhtypes.TfInt64Type{}, + "maximum_additionnal_ip": ovhtypes.TfInt64Type{}, + "memory": ovhtypes.TfInt64Type{}, + "name": ovhtypes.TfStringType{}, + "offer": ovhtypes.TfStringType{}, + "vcore": ovhtypes.TfInt64Type{}, + "version": ovhtypes.TfStringType{}, + } +} + +var _ basetypes.ObjectTypable = IamType{} + +type IamType struct { + basetypes.ObjectType +} + +func (t IamType) Equal(o attr.Type) bool { + other, ok := o.(IamType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t IamType) String() string { + return "IamType" +} + +func (t IamType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + displayNameAttribute, ok := attributes["display_name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `display_name is missing from object`) + + return nil, diags + } + + displayNameVal, ok := displayNameAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`display_name expected to be ovhtypes.TfStringValue, was: %T`, displayNameAttribute)) + } + + idAttribute, ok := attributes["id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `id is missing from object`) + + return nil, diags + } + + idVal, ok := idAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`id expected to be ovhtypes.TfStringValue, was: %T`, idAttribute)) + } + + tagsAttribute, ok := attributes["tags"] + + if !ok { + diags.AddError( + "Attribute Missing", + `tags is missing from object`) + + return nil, diags + } + + tagsVal, ok := tagsAttribute.(ovhtypes.TfMapNestedValue[ovhtypes.TfStringValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`tags expected to be ovhtypes.TfMapNestedValue[ovhtypes.TfStringValue], was: %T`, tagsAttribute)) + } + + urnAttribute, ok := attributes["urn"] + + if !ok { + diags.AddError( + "Attribute Missing", + `urn is missing from object`) + + return nil, diags + } + + urnVal, ok := urnAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`urn expected to be ovhtypes.TfStringValue, was: %T`, urnAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return IamValue{ + DisplayName: displayNameVal, + Id: idVal, + Tags: tagsVal, + Urn: urnVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewIamValueNull() IamValue { + return IamValue{ + state: attr.ValueStateNull, + } +} + +func NewIamValueUnknown() IamValue { + return IamValue{ + state: attr.ValueStateUnknown, + } +} + +func NewIamValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (IamValue, 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 IamValue Attribute Value", + "While creating a IamValue value, a missing attribute value was detected. "+ + "A IamValue 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("IamValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid IamValue Attribute Type", + "While creating a IamValue value, an invalid attribute value was detected. "+ + "A IamValue 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("IamValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("IamValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra IamValue Attribute Value", + "While creating a IamValue value, an extra attribute value was detected. "+ + "A IamValue 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 IamValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewIamValueUnknown(), diags + } + + displayNameAttribute, ok := attributes["display_name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `display_name is missing from object`) + + return NewIamValueUnknown(), diags + } + + displayNameVal, ok := displayNameAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`display_name expected to be ovhtypes.TfStringValue, was: %T`, displayNameAttribute)) + } + + idAttribute, ok := attributes["id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `id is missing from object`) + + return NewIamValueUnknown(), diags + } + + idVal, ok := idAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`id expected to be ovhtypes.TfStringValue, was: %T`, idAttribute)) + } + + tagsAttribute, ok := attributes["tags"] + + if !ok { + diags.AddError( + "Attribute Missing", + `tags is missing from object`) + + return NewIamValueUnknown(), diags + } + + tagsVal, ok := tagsAttribute.(ovhtypes.TfMapNestedValue[ovhtypes.TfStringValue]) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`tags expected to be ovhtypes.TfMapNestedValue[ovhtypes.TfStringValue], was: %T`, tagsAttribute)) + } + + urnAttribute, ok := attributes["urn"] + + if !ok { + diags.AddError( + "Attribute Missing", + `urn is missing from object`) + + return NewIamValueUnknown(), diags + } + + urnVal, ok := urnAttribute.(ovhtypes.TfStringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`urn expected to be ovhtypes.TfStringValue, was: %T`, urnAttribute)) + } + + if diags.HasError() { + return NewIamValueUnknown(), diags + } + + return IamValue{ + DisplayName: displayNameVal, + Id: idVal, + Tags: tagsVal, + Urn: urnVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewIamValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) IamValue { + object, diags := NewIamValue(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("NewIamValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t IamType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewIamValueNull(), 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 NewIamValueUnknown(), nil + } + + if in.IsNull() { + return NewIamValueNull(), 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 NewIamValueMust(IamValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t IamType) ValueType(ctx context.Context) attr.Value { + return IamValue{} +} + +var _ basetypes.ObjectValuable = IamValue{} + +type IamValue struct { + DisplayName ovhtypes.TfStringValue `tfsdk:"display_name" json:"displayName"` + Id ovhtypes.TfStringValue `tfsdk:"id" json:"id"` + Tags ovhtypes.TfMapNestedValue[ovhtypes.TfStringValue] `tfsdk:"tags" json:"tags"` + Urn ovhtypes.TfStringValue `tfsdk:"urn" json:"urn"` + state attr.ValueState +} + +type IamWritableValue struct { + *IamValue `json:"-"` + DisplayName *ovhtypes.TfStringValue `json:"displayName,omitempty"` + Id *ovhtypes.TfStringValue `json:"id,omitempty"` + Tags *ovhtypes.TfMapNestedValue[ovhtypes.TfStringValue] `json:"tags,omitempty"` + Urn *ovhtypes.TfStringValue `json:"urn,omitempty"` +} + +func (v IamValue) ToCreate() *IamWritableValue { + res := &IamWritableValue{} + + if !v.Id.IsNull() { + res.Id = &v.Id + } + + if !v.Tags.IsNull() { + res.Tags = &v.Tags + } + + if !v.Urn.IsNull() { + res.Urn = &v.Urn + } + + if !v.DisplayName.IsNull() { + res.DisplayName = &v.DisplayName + } + + return res +} + +func (v *IamValue) UnmarshalJSON(data []byte) error { + type JsonIamValue IamValue + + var tmp JsonIamValue + if err := json.Unmarshal(data, &tmp); err != nil { + return err + } + v.DisplayName = tmp.DisplayName + v.Id = tmp.Id + v.Tags = tmp.Tags + v.Urn = tmp.Urn + + v.state = attr.ValueStateKnown + + return nil +} + +func (v *IamValue) MergeWith(other *IamValue) { + + if (v.DisplayName.IsUnknown() || v.DisplayName.IsNull()) && !other.DisplayName.IsUnknown() { + v.DisplayName = other.DisplayName + } + + if (v.Id.IsUnknown() || v.Id.IsNull()) && !other.Id.IsUnknown() { + v.Id = other.Id + } + + if (v.Tags.IsUnknown() || v.Tags.IsNull()) && !other.Tags.IsUnknown() { + v.Tags = other.Tags + } + + if (v.Urn.IsUnknown() || v.Urn.IsNull()) && !other.Urn.IsUnknown() { + v.Urn = other.Urn + } + + if (v.state == attr.ValueStateUnknown || v.state == attr.ValueStateNull) && other.state != attr.ValueStateUnknown { + v.state = other.state + } +} + +func (v IamValue) Attributes() map[string]attr.Value { + return map[string]attr.Value{ + "displayName": v.DisplayName, + "id": v.Id, + "tags": v.Tags, + "urn": v.Urn, + } +} +func (v IamValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 4) + + var val tftypes.Value + var err error + + attrTypes["display_name"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["id"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["tags"] = basetypes.MapType{ + ElemType: types.StringType, + }.TerraformType(ctx) + attrTypes["urn"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 4) + + val, err = v.DisplayName.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["display_name"] = val + + val, err = v.Id.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["id"] = val + + val, err = v.Tags.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["tags"] = val + + val, err = v.Urn.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["urn"] = 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 IamValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v IamValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v IamValue) String() string { + return "IamValue" +} + +func (v IamValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "display_name": ovhtypes.TfStringType{}, + "id": ovhtypes.TfStringType{}, + "tags": ovhtypes.NewTfMapNestedType[ovhtypes.TfStringValue](ctx), + "urn": ovhtypes.TfStringType{}, + }, + map[string]attr.Value{ + "display_name": v.DisplayName, + "id": v.Id, + "tags": v.Tags, + "urn": v.Urn, + }) + + return objVal, diags +} + +func (v IamValue) Equal(o attr.Value) bool { + other, ok := o.(IamValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.DisplayName.Equal(other.DisplayName) { + return false + } + + if !v.Id.Equal(other.Id) { + return false + } + + if !v.Tags.Equal(other.Tags) { + return false + } + + if !v.Urn.Equal(other.Urn) { + return false + } + + return true +} + +func (v IamValue) Type(ctx context.Context) attr.Type { + return IamType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v IamValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "display_name": ovhtypes.TfStringType{}, + "id": ovhtypes.TfStringType{}, + "tags": ovhtypes.NewTfMapNestedType[ovhtypes.TfStringValue](ctx), + "urn": ovhtypes.TfStringType{}, + } +} diff --git a/ovh/resource_vps_test.go b/ovh/resource_vps_test.go new file mode 100644 index 000000000..f81b7c670 --- /dev/null +++ b/ovh/resource_vps_test.go @@ -0,0 +1,73 @@ +package ovh + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +const testAccVpsBasic = ` +data "ovh_me" "myaccount" {} + +data "ovh_order_cart" "mycart" { + ovh_subsidiary = data.ovh_me.myaccount.ovh_subsidiary +} + +data "ovh_order_cart_product_plan" "vps" { + cart_id = data.ovh_order_cart.mycart.id + price_capacity = "renew" + product = "vps" + plan_code = "vps-le-2-2-40" +} + +resource "ovh_vps" "myvps" { + display_name = "%s" + netboot_mode = "rescue" + + ovh_subsidiary = data.ovh_order_cart.mycart.ovh_subsidiary + plan = [ + { + duration = "P1M" + plan_code = data.ovh_order_cart_product_plan.vps.plan_code + pricing_mode = "default" + + configuration = [ + { + label = "vps_datacenter" + value = "WAW" + }, + { + label = "vps_os" + value = "Debian 10" + } + ] + } + ] +} +` + +func TestAccResourceVps_basic(t *testing.T) { + displayName := acctest.RandomWithPrefix(test_prefix) + config := fmt.Sprintf( + testAccVpsBasic, + displayName, + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckOrderVPS(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "ovh_vps.myvps", "netboot_mode", "rescue"), + resource.TestCheckResourceAttr( + "ovh_vps.myvps", "display_name", displayName), + ), + }, + }, + }) +} diff --git a/ovh/resource_vrack.go b/ovh/resource_vrack.go index 55e687edc..accdd3510 100644 --- a/ovh/resource_vrack.go +++ b/ovh/resource_vrack.go @@ -61,7 +61,7 @@ func resourceVrackSchema() map[string]*schema.Schema { } func resourceVrackCreate(d *schema.ResourceData, meta interface{}) error { - if err := orderCreate(d, meta, "vrack"); err != nil { + if err := orderCreateFromResource(d, meta, "vrack"); err != nil { return fmt.Errorf("Could not order vrack: %q", err) } @@ -69,7 +69,7 @@ func resourceVrackCreate(d *schema.ResourceData, meta interface{}) error { } func resourceVrackUpdate(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read vrack order: %q", err) } @@ -88,7 +88,7 @@ func resourceVrackUpdate(d *schema.ResourceData, meta interface{}) error { } func resourceVrackRead(d *schema.ResourceData, meta interface{}) error { - _, details, err := orderRead(d, meta) + _, details, err := orderReadInResource(d, meta) if err != nil { return fmt.Errorf("Could not read vrack order: %q", err) } diff --git a/ovh/services.go b/ovh/services.go new file mode 100644 index 000000000..52708b2d1 --- /dev/null +++ b/ovh/services.go @@ -0,0 +1,27 @@ +package ovh + +import ( + "fmt" + "net/url" + "path" + + "github.com/ovh/go-ovh/ovh" +) + +func serviceFromServiceName(c *ovh.Client, serviceType, serviceName string) (*Service, error) { + var ( + service Service + serviceInfos ServiceInfos + endpoint = path.Join("/", serviceType, url.PathEscape(serviceName), "/serviceInfos") + ) + + if err := c.Get(endpoint, &serviceInfos); err != nil { + return nil, fmt.Errorf("failed to get service infos: %w", err) + } + + if err := c.Get(fmt.Sprintf("/services/%d", serviceInfos.ServiceID), &service); err != nil { + return nil, fmt.Errorf("failed to get service: %w", err) + } + + return &service, nil +} diff --git a/ovh/types.go b/ovh/types.go index 1b8619bfe..b87ac8c06 100644 --- a/ovh/types.go +++ b/ovh/types.go @@ -34,3 +34,7 @@ func (v UnitAndValue) ToMap() map[string]interface{} { return obj } + +type ConfirmTerminationOpts struct { + Token string `json:"token"` +} diff --git a/ovh/types_me_order.go b/ovh/types_me_order.go index 83e90d0f8..5d75c1f89 100644 --- a/ovh/types_me_order.go +++ b/ovh/types_me_order.go @@ -30,6 +30,14 @@ func (v MeOrderDetail) ToMap() map[string]interface{} { return obj } +type MeOrderDetailExtension struct { + Order struct { + Plan struct { + Code string `json:"code"` + } `json:"plan"` + } `json:"order"` +} + type MeOrderDetailOperation struct { Status string `json:"status"` ID int `json:"id"` diff --git a/ovh/types_order_cart.go b/ovh/types_order_cart.go index 096ddcf9a..eef64fdcf 100644 --- a/ovh/types_order_cart.go +++ b/ovh/types_order_cart.go @@ -9,6 +9,30 @@ import ( "github.com/ovh/terraform-provider-ovh/ovh/helpers" ) +type OrderCreateType struct { + OrderID string + *OrderCartCreateOpts + Plans []*OrderCartPlanCreateOpts + PlanOptions []*OrderCartPlanOptionsCreateOpts +} + +func (o *OrderCreateType) FromResource(d *schema.ResourceData) *OrderCreateType { + o.OrderCartCreateOpts = (&OrderCartCreateOpts{}).FromResource(d) + + nbPlans := d.Get("plan.#").(int) + for i := 0; i < nbPlans; i++ { + o.Plans = append(o.Plans, (&OrderCartPlanCreateOpts{}).FromResourceWithPath(d, fmt.Sprintf("plan.%d", i))) + } + + nbPlanOptions := d.Get("plan_option.#").(int) + for i := 0; i < nbPlanOptions; i++ { + o.PlanOptions = append(o.PlanOptions, + (&OrderCartPlanOptionsCreateOpts{}).FromResourceWithPath(d, fmt.Sprintf("plan_option.%d", i))) + } + + return o +} + type OrderCartCreateOpts struct { OvhSubsidiary string `json:"ovhSubsidiary"` Description *string `json:"description,omitempty"` @@ -24,11 +48,12 @@ func (opts *OrderCartCreateOpts) FromResource(d *schema.ResourceData) *OrderCart } type OrderCartPlanCreateOpts struct { - CatalogName *string `json:"catalogName,omitempty"` - Duration string `json:"duration"` - PlanCode string `json:"planCode"` - PricingMode string `json:"pricingMode"` - Quantity int `json:"quantity"` + CatalogName *string `json:"catalogName,omitempty"` + Duration string `json:"duration"` + PlanCode string `json:"planCode"` + PricingMode string `json:"pricingMode"` + Quantity int `json:"quantity"` + Configuration []*OrderCartItemConfigurationOpts `json:"-"` } func (opts *OrderCartPlanCreateOpts) FromResourceWithPath(d *schema.ResourceData, path string) *OrderCartPlanCreateOpts { @@ -36,6 +61,13 @@ func (opts *OrderCartPlanCreateOpts) FromResourceWithPath(d *schema.ResourceData opts.Duration = d.Get(fmt.Sprintf("%s.duration", path)).(string) opts.PlanCode = d.Get(fmt.Sprintf("%s.plan_code", path)).(string) opts.PricingMode = d.Get(fmt.Sprintf("%s.pricing_mode", path)).(string) + + nbOfConfigurations := d.Get(fmt.Sprintf("%s.configuration.#", path)).(int) + for i := 0; i < nbOfConfigurations; i++ { + opts.Configuration = append(opts.Configuration, + (&OrderCartItemConfigurationOpts{}).FromResourceWithPath(d, fmt.Sprintf("%s.configuration.%d", path, i))) + } + return opts } @@ -51,12 +83,13 @@ func (opts *OrderCartPlanCreateOpts) String() string { } type OrderCartPlanOptionsCreateOpts struct { - CatalogName *string `json:"catalogName,omitempty"` - Duration string `json:"duration"` - PlanCode string `json:"planCode"` - PricingMode string `json:"pricingMode"` - Quantity int `json:"quantity"` - ItemId int64 `json:"itemId"` + CatalogName *string `json:"catalogName,omitempty"` + Duration string `json:"duration"` + PlanCode string `json:"planCode"` + PricingMode string `json:"pricingMode"` + Quantity int `json:"quantity"` + ItemId int64 `json:"itemId"` + Configuration []*OrderCartItemConfigurationOpts `json:"-"` } func (opts *OrderCartPlanOptionsCreateOpts) FromResourceWithPath(d *schema.ResourceData, path string) *OrderCartPlanOptionsCreateOpts { @@ -64,6 +97,13 @@ func (opts *OrderCartPlanOptionsCreateOpts) FromResourceWithPath(d *schema.Resou opts.Duration = d.Get(fmt.Sprintf("%s.duration", path)).(string) opts.PlanCode = d.Get(fmt.Sprintf("%s.plan_code", path)).(string) opts.PricingMode = d.Get(fmt.Sprintf("%s.pricing_mode", path)).(string) + + nbConfigs := d.Get(fmt.Sprintf("%s.configuration.#", path)).(int) + for i := 0; i < nbConfigs; i++ { + opts.Configuration = append(opts.Configuration, + (&OrderCartItemConfigurationOpts{}).FromResourceWithPath(d, fmt.Sprintf("%s.configuration.%d", path, i))) + } + return opts } diff --git a/ovh/types_order_cart_item.go b/ovh/types_order_cart_item.go index cbb90633b..b3e4958bc 100644 --- a/ovh/types_order_cart_item.go +++ b/ovh/types_order_cart_item.go @@ -2,6 +2,7 @@ package ovh import ( "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/ovh/types_service.go b/ovh/types_service.go new file mode 100644 index 000000000..509ce51f7 --- /dev/null +++ b/ovh/types_service.go @@ -0,0 +1,60 @@ +package ovh + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/ovh/terraform-provider-ovh/ovh/types" +) + +// ServiceInfos contains the information returned +// by calls to /serviceType/{serviceId}/serviceInfos +type ServiceInfos struct { + ServiceID int `json:"serviceId"` +} + +// Service contains the information returned by +// calls to /services/{serviceId} +type Service struct { + Billing ServiceBilling `json:"billing"` +} + +func (s *Service) ToPlanValue(ctx context.Context, existingPlans types.TfListNestedValue[PlanValue]) *types.TfListNestedValue[PlanValue] { + plan := PlanValue{ + PlanCode: types.TfStringValue{ + StringValue: basetypes.NewStringValue(s.Billing.Plan.Code), + }, + Duration: types.TfStringValue{ + StringValue: basetypes.NewStringValue(s.Billing.Pricing.Duration), + }, + PricingMode: types.TfStringValue{ + StringValue: basetypes.NewStringValue(s.Billing.Pricing.PricingMode), + }, + state: attr.ValueStateKnown, + } + + existingPlansItems := existingPlans.Elements() + if len(existingPlansItems) > 0 { + planToMerge := existingPlansItems[0].(PlanValue) + plan.MergeWith(&planToMerge) + } + + planValue := types.TfListNestedValue[PlanValue]{ListValue: basetypes.NewListValueMust(PlanValue{}.Type(ctx), []attr.Value{plan})} + + return &planValue +} + +type ServiceBilling struct { + Plan ServiceBillingPlan `json:"plan"` + Pricing ServiceBillingPricing `json:"pricing"` +} + +type ServiceBillingPlan struct { + Code string `json:"code"` +} + +type ServiceBillingPricing struct { + PricingMode string `json:"pricingMode"` + Duration string `json:"duration"` +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/all.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/all.go new file mode 100644 index 000000000..f53caba25 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/all.go @@ -0,0 +1,57 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// All returns a validator which ensures that any configured attribute value +// attribute value validates against all the given validators. +// +// Use of All is only necessary when used in conjunction with Any or AnyWithAllWarnings +// as the Validators field automatically applies a logical AND. +func All(validators ...validator.String) validator.String { + return allValidator{ + validators: validators, + } +} + +var _ validator.String = allValidator{} + +// allValidator implements the validator. +type allValidator struct { + validators []validator.String +} + +// Description describes the validation in plain text formatting. +func (v allValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, subValidator := range v.validators { + descriptions = append(descriptions, subValidator.Description(ctx)) + } + + return fmt.Sprintf("Value must satisfy all of the validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v allValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateString performs the validation. +func (v allValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + for _, subValidator := range v.validators { + validateResp := &validator.StringResponse{} + + subValidator.ValidateString(ctx, req, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/also_requires.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/also_requires.go new file mode 100644 index 000000000..97ca59b73 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/also_requires.go @@ -0,0 +1,26 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// AlsoRequires checks that a set of path.Expression has a non-null value, +// if the current attribute or block also has a non-null value. +// +// This implements the validation logic declaratively within the schema. +// Refer to [datasourcevalidator.RequiredTogether], +// [providervalidator.RequiredTogether], or [resourcevalidator.RequiredTogether] +// for declaring this type of validation outside the schema definition. +// +// Relative path.Expression will be resolved using the attribute or block +// being validated. +func AlsoRequires(expressions ...path.Expression) validator.String { + return schemavalidator.AlsoRequiresValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/any.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/any.go new file mode 100644 index 000000000..ba8f218ac --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/any.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// Any returns a validator which ensures that any configured attribute value +// passes at least one of the given validators. +// +// To prevent practitioner confusion should non-passing validators have +// conflicting logic, only warnings from the passing validator are returned. +// Use AnyWithAllWarnings() to return warnings from non-passing validators +// as well. +func Any(validators ...validator.String) validator.String { + return anyValidator{ + validators: validators, + } +} + +var _ validator.String = anyValidator{} + +// anyValidator implements the validator. +type anyValidator struct { + validators []validator.String +} + +// Description describes the validation in plain text formatting. +func (v anyValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, subValidator := range v.validators { + descriptions = append(descriptions, subValidator.Description(ctx)) + } + + return fmt.Sprintf("Value must satisfy at least one of the validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v anyValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateString performs the validation. +func (v anyValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + for _, subValidator := range v.validators { + validateResp := &validator.StringResponse{} + + subValidator.ValidateString(ctx, req, validateResp) + + if !validateResp.Diagnostics.HasError() { + resp.Diagnostics = validateResp.Diagnostics + + return + } + + resp.Diagnostics.Append(validateResp.Diagnostics...) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/any_with_all_warnings.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/any_with_all_warnings.go new file mode 100644 index 000000000..e857424c9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/any_with_all_warnings.go @@ -0,0 +1,67 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// AnyWithAllWarnings returns a validator which ensures that any configured +// attribute value passes at least one of the given validators. This validator +// returns all warnings, including failed validators. +// +// Use Any() to return warnings only from the passing validator. +func AnyWithAllWarnings(validators ...validator.String) validator.String { + return anyWithAllWarningsValidator{ + validators: validators, + } +} + +var _ validator.String = anyWithAllWarningsValidator{} + +// anyWithAllWarningsValidator implements the validator. +type anyWithAllWarningsValidator struct { + validators []validator.String +} + +// Description describes the validation in plain text formatting. +func (v anyWithAllWarningsValidator) Description(ctx context.Context) string { + var descriptions []string + + for _, subValidator := range v.validators { + descriptions = append(descriptions, subValidator.Description(ctx)) + } + + return fmt.Sprintf("Value must satisfy at least one of the validations: %s", strings.Join(descriptions, " + ")) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v anyWithAllWarningsValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// ValidateString performs the validation. +func (v anyWithAllWarningsValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + anyValid := false + + for _, subValidator := range v.validators { + validateResp := &validator.StringResponse{} + + subValidator.ValidateString(ctx, req, validateResp) + + if !validateResp.Diagnostics.HasError() { + anyValid = true + } + + resp.Diagnostics.Append(validateResp.Diagnostics...) + } + + if anyValid { + resp.Diagnostics = resp.Diagnostics.Warnings() + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/at_least_one_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/at_least_one_of.go new file mode 100644 index 000000000..4434fad2b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/at_least_one_of.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// AtLeastOneOf checks that of a set of path.Expression, +// including the attribute this validator is applied to, +// at least one has a non-null value. +// +// This implements the validation logic declaratively within the tfsdk.Schema. +// Refer to [datasourcevalidator.AtLeastOneOf], +// [providervalidator.AtLeastOneOf], or [resourcevalidator.AtLeastOneOf] +// for declaring this type of validation outside the schema definition. +// +// Any relative path.Expression will be resolved using the attribute being +// validated. +func AtLeastOneOf(expressions ...path.Expression) validator.String { + return schemavalidator.AtLeastOneOfValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/conflicts_with.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/conflicts_with.go new file mode 100644 index 000000000..42f514539 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/conflicts_with.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// ConflictsWith checks that a set of path.Expression, +// including the attribute the validator is applied to, +// do not have a value simultaneously. +// +// This implements the validation logic declaratively within the schema. +// Refer to [datasourcevalidator.Conflicting], +// [providervalidator.Conflicting], or [resourcevalidator.Conflicting] +// for declaring this type of validation outside the schema definition. +// +// Relative path.Expression will be resolved using the attribute being +// validated. +func ConflictsWith(expressions ...path.Expression) validator.String { + return schemavalidator.ConflictsWithValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/doc.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/doc.go new file mode 100644 index 000000000..6d95e874d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package stringvalidator provides validators for types.String attributes. +package stringvalidator diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/exactly_one_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/exactly_one_of.go new file mode 100644 index 000000000..e3f557d3c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/exactly_one_of.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +// ExactlyOneOf checks that of a set of path.Expression, +// including the attribute the validator is applied to, +// one and only one attribute has a value. +// It will also cause a validation error if none are specified. +// +// This implements the validation logic declaratively within the schema. +// Refer to [datasourcevalidator.ExactlyOneOf], +// [providervalidator.ExactlyOneOf], or [resourcevalidator.ExactlyOneOf] +// for declaring this type of validation outside the schema definition. +// +// Relative path.Expression will be resolved using the attribute being +// validated. +func ExactlyOneOf(expressions ...path.Expression) validator.String { + return schemavalidator.ExactlyOneOfValidator{ + PathExpressions: expressions, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/length_at_least.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/length_at_least.go new file mode 100644 index 000000000..0ebaffae5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/length_at_least.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.String = lengthAtLeastValidator{} + +// stringLenAtLeastValidator validates that a string Attribute's length is at least a certain value. +type lengthAtLeastValidator struct { + minLength int +} + +// Description describes the validation in plain text formatting. +func (validator lengthAtLeastValidator) Description(_ context.Context) string { + return fmt.Sprintf("string length must be at least %d", validator.minLength) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator lengthAtLeastValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (v lengthAtLeastValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + + if l := len(value); l < v.minLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", l), + )) + + return + } +} + +// LengthAtLeast returns an validator which ensures that any configured +// attribute value is of single-byte character length greater than or equal +// to the given minimum. Null (unconfigured) and unknown (known after apply) +// values are skipped. +// +// Use UTF8LengthAtLeast for checking multiple-byte characters. +func LengthAtLeast(minLength int) validator.String { + if minLength < 0 { + return nil + } + + return lengthAtLeastValidator{ + minLength: minLength, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/length_at_most.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/length_at_most.go new file mode 100644 index 000000000..a793a0b09 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/length_at_most.go @@ -0,0 +1,64 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.String = lengthAtMostValidator{} + +// lengthAtMostValidator validates that a string Attribute's length is at most a certain value. +type lengthAtMostValidator struct { + maxLength int +} + +// Description describes the validation in plain text formatting. +func (validator lengthAtMostValidator) Description(_ context.Context) string { + return fmt.Sprintf("string length must be at most %d", validator.maxLength) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator lengthAtMostValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (v lengthAtMostValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + + if l := len(value); l > v.maxLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", l), + )) + + return + } +} + +// LengthAtMost returns an validator which ensures that any configured +// attribute value is of single-byte character length less than or equal +// to the given maximum. Null (unconfigured) and unknown (known after apply) +// values are skipped. +// +// Use UTF8LengthAtMost for checking multiple-byte characters. +func LengthAtMost(maxLength int) validator.String { + if maxLength < 0 { + return nil + } + + return lengthAtMostValidator{ + maxLength: maxLength, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/length_between.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/length_between.go new file mode 100644 index 000000000..c70f4c05c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/length_between.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.String = lengthBetweenValidator{} + +// stringLenBetweenValidator validates that a string Attribute's length is in a range. +type lengthBetweenValidator struct { + minLength, maxLength int +} + +// Description describes the validation in plain text formatting. +func (validator lengthBetweenValidator) Description(_ context.Context) string { + return fmt.Sprintf("string length must be between %d and %d", validator.minLength, validator.maxLength) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator lengthBetweenValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (v lengthBetweenValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + + if l := len(value); l < v.minLength || l > v.maxLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", l), + )) + + return + } +} + +// LengthBetween returns a validator which ensures that any configured +// attribute value is of single-byte character length greater than or equal +// to the given minimum and less than or equal to the given maximum. Null +// (unconfigured) and unknown (known after apply) values are skipped. +// +// Use UTF8LengthBetween for checking multiple-byte characters. +func LengthBetween(minLength, maxLength int) validator.String { + if minLength < 0 || minLength > maxLength { + return nil + } + + return lengthBetweenValidator{ + minLength: minLength, + maxLength: maxLength, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/none_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/none_of.go new file mode 100644 index 000000000..6bf7dce88 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/none_of.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.String = noneOfValidator{} + +// noneOfValidator validates that the value does not match one of the values. +type noneOfValidator struct { + values []types.String +} + +func (v noneOfValidator) Description(ctx context.Context) string { + return v.MarkdownDescription(ctx) +} + +func (v noneOfValidator) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("value must be none of: %s", v.values) +} + +func (v noneOfValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue + + for _, otherValue := range v.values { + if !value.Equal(otherValue) { + continue + } + + response.Diagnostics.Append(validatordiag.InvalidAttributeValueMatchDiagnostic( + request.Path, + v.Description(ctx), + value.String(), + )) + + break + } +} + +// NoneOf checks that the String held in the attribute +// is none of the given `values`. +func NoneOf(values ...string) validator.String { + frameworkValues := make([]types.String, 0, len(values)) + + for _, value := range values { + frameworkValues = append(frameworkValues, types.StringValue(value)) + } + + return noneOfValidator{ + values: frameworkValues, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/none_of_case_insensitive.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/none_of_case_insensitive.go new file mode 100644 index 000000000..aedb0949a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/none_of_case_insensitive.go @@ -0,0 +1,64 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.String = noneOfCaseInsensitiveValidator{} + +// noneOfCaseInsensitiveValidator validates that the value matches one of expected values. +type noneOfCaseInsensitiveValidator struct { + values []types.String +} + +func (v noneOfCaseInsensitiveValidator) Description(ctx context.Context) string { + return v.MarkdownDescription(ctx) +} + +func (v noneOfCaseInsensitiveValidator) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("value must be none of: %s", v.values) +} + +func (v noneOfCaseInsensitiveValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue + + for _, otherValue := range v.values { + if strings.EqualFold(value.ValueString(), otherValue.ValueString()) { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueMatchDiagnostic( + request.Path, + v.Description(ctx), + value.String(), + )) + + return + } + } +} + +// NoneOfCaseInsensitive checks that the String held in the attribute +// is none of the given `values`. +func NoneOfCaseInsensitive(values ...string) validator.String { + frameworkValues := make([]types.String, 0, len(values)) + + for _, value := range values { + frameworkValues = append(frameworkValues, types.StringValue(value)) + } + + return noneOfCaseInsensitiveValidator{ + values: frameworkValues, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/one_of.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/one_of.go new file mode 100644 index 000000000..c3ae055bd --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/one_of.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.String = oneOfValidator{} + +// oneOfValidator validates that the value matches one of expected values. +type oneOfValidator struct { + values []types.String +} + +func (v oneOfValidator) Description(ctx context.Context) string { + return v.MarkdownDescription(ctx) +} + +func (v oneOfValidator) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("value must be one of: %s", v.values) +} + +func (v oneOfValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue + + for _, otherValue := range v.values { + if value.Equal(otherValue) { + return + } + } + + response.Diagnostics.Append(validatordiag.InvalidAttributeValueMatchDiagnostic( + request.Path, + v.Description(ctx), + value.String(), + )) +} + +// OneOf checks that the String held in the attribute +// is one of the given `values`. +func OneOf(values ...string) validator.String { + frameworkValues := make([]types.String, 0, len(values)) + + for _, value := range values { + frameworkValues = append(frameworkValues, types.StringValue(value)) + } + + return oneOfValidator{ + values: frameworkValues, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/one_of_case_insensitive.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/one_of_case_insensitive.go new file mode 100644 index 000000000..7e5912ab7 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/one_of_case_insensitive.go @@ -0,0 +1,64 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.String = oneOfCaseInsensitiveValidator{} + +// oneOfCaseInsensitiveValidator validates that the value matches one of expected values. +type oneOfCaseInsensitiveValidator struct { + values []types.String +} + +func (v oneOfCaseInsensitiveValidator) Description(ctx context.Context) string { + return v.MarkdownDescription(ctx) +} + +func (v oneOfCaseInsensitiveValidator) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("value must be one of: %s", v.values) +} + +func (v oneOfCaseInsensitiveValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue + + for _, otherValue := range v.values { + if strings.EqualFold(value.ValueString(), otherValue.ValueString()) { + return + } + } + + response.Diagnostics.Append(validatordiag.InvalidAttributeValueMatchDiagnostic( + request.Path, + v.Description(ctx), + value.String(), + )) +} + +// OneOfCaseInsensitive checks that the String held in the attribute +// is one of the given `values`. +func OneOfCaseInsensitive(values ...string) validator.String { + frameworkValues := make([]types.String, 0, len(values)) + + for _, value := range values { + frameworkValues = append(frameworkValues, types.StringValue(value)) + } + + return oneOfCaseInsensitiveValidator{ + values: frameworkValues, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/regex_matches.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/regex_matches.go new file mode 100644 index 000000000..4cab99757 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/regex_matches.go @@ -0,0 +1,67 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.String = regexMatchesValidator{} + +// regexMatchesValidator validates that a string Attribute's value matches the specified regular expression. +type regexMatchesValidator struct { + regexp *regexp.Regexp + message string +} + +// Description describes the validation in plain text formatting. +func (validator regexMatchesValidator) Description(_ context.Context) string { + if validator.message != "" { + return validator.message + } + return fmt.Sprintf("value must match regular expression '%s'", validator.regexp) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator regexMatchesValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (v regexMatchesValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + + if !v.regexp.MatchString(value) { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueMatchDiagnostic( + request.Path, + v.Description(ctx), + value, + )) + } +} + +// RegexMatches returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a string. +// - Matches the given regular expression https://github.com/google/re2/wiki/Syntax. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +// Optionally an error message can be provided to return something friendlier +// than "value must match regular expression 'regexp'". +func RegexMatches(regexp *regexp.Regexp, message string) validator.String { + return regexMatchesValidator{ + regexp: regexp, + message: message, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/utf8_length_at_least.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/utf8_length_at_least.go new file mode 100644 index 000000000..6159eab57 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/utf8_length_at_least.go @@ -0,0 +1,68 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + "unicode/utf8" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.String = utf8LengthAtLeastValidator{} + +// utf8LengthAtLeastValidator implements the validator. +type utf8LengthAtLeastValidator struct { + minLength int +} + +// Description describes the validation in plain text formatting. +func (validator utf8LengthAtLeastValidator) Description(_ context.Context) string { + return fmt.Sprintf("UTF-8 character count must be at least %d", validator.minLength) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator utf8LengthAtLeastValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (v utf8LengthAtLeastValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + + count := utf8.RuneCountInString(value) + + if count < v.minLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", count), + )) + + return + } +} + +// UTF8LengthAtLeast returns an validator which ensures that any configured +// attribute value is of UTF-8 character count greater than or equal to the +// given minimum. Null (unconfigured) and unknown (known after apply) values +// are skipped. +// +// Use LengthAtLeast for checking single-byte character counts. +func UTF8LengthAtLeast(minLength int) validator.String { + if minLength < 0 { + return nil + } + + return utf8LengthAtLeastValidator{ + minLength: minLength, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/utf8_length_at_most.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/utf8_length_at_most.go new file mode 100644 index 000000000..1653d5f88 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/utf8_length_at_most.go @@ -0,0 +1,68 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + "unicode/utf8" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.String = utf8LengthAtMostValidator{} + +// utf8LengthAtMostValidator implements the validator. +type utf8LengthAtMostValidator struct { + maxLength int +} + +// Description describes the validation in plain text formatting. +func (validator utf8LengthAtMostValidator) Description(_ context.Context) string { + return fmt.Sprintf("UTF-8 character count must be at most %d", validator.maxLength) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator utf8LengthAtMostValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (v utf8LengthAtMostValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + + count := utf8.RuneCountInString(value) + + if count > v.maxLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", count), + )) + + return + } +} + +// UTF8LengthAtMost returns an validator which ensures that any configured +// attribute value is of UTF-8 character count less than or equal to the +// given maximum. Null (unconfigured) and unknown (known after apply) values +// are skipped. +// +// Use LengthAtMost for checking single-byte character counts. +func UTF8LengthAtMost(maxLength int) validator.String { + if maxLength < 0 { + return nil + } + + return utf8LengthAtMostValidator{ + maxLength: maxLength, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/utf8_length_between.go b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/utf8_length_between.go new file mode 100644 index 000000000..791b9a569 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator/utf8_length_between.go @@ -0,0 +1,70 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package stringvalidator + +import ( + "context" + "fmt" + "unicode/utf8" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.String = utf8LengthBetweenValidator{} + +// utf8LengthBetweenValidator implements the validator. +type utf8LengthBetweenValidator struct { + maxLength int + minLength int +} + +// Description describes the validation in plain text formatting. +func (v utf8LengthBetweenValidator) Description(_ context.Context) string { + return fmt.Sprintf("UTF-8 character count must be between %d and %d", v.minLength, v.maxLength) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v utf8LengthBetweenValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v utf8LengthBetweenValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + + count := utf8.RuneCountInString(value) + + if count < v.minLength || count > v.maxLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", count), + )) + + return + } +} + +// UTF8LengthBetween returns an validator which ensures that any configured +// attribute value is of UTF-8 character count greater than or equal to the +// given minimum and less than or equal to the given maximum. Null +// (unconfigured) and unknown (known after apply) values are skipped. +// +// Use LengthBetween for checking single-byte character counts. +func UTF8LengthBetween(minLength int, maxLength int) validator.String { + if minLength < 0 || maxLength < 0 || minLength > maxLength { + return nil + } + + return utf8LengthBetweenValidator{ + maxLength: maxLength, + minLength: minLength, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/attr/value.go b/vendor/github.com/hashicorp/terraform-plugin-framework/attr/value.go index 370f0ddc9..b34a3bb73 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/attr/value.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/attr/value.go @@ -17,6 +17,12 @@ const ( // NullValueString should be returned by Value.String() implementations // when Value.IsNull() returns true. NullValueString = "" + + // UnsetValueString should be returned by Value.String() implementations + // when Value does not contain sufficient information to display to users. + // + // This is primarily used for invalid Dynamic Value implementations. + UnsetValueString = "" ) // Value defines an interface for describing data associated with an attribute. diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/dynamic_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/dynamic_attribute.go new file mode 100644 index 000000000..6b1b6c83e --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/dynamic_attribute.go @@ -0,0 +1,188 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "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" +) + +// Ensure the implementation satisifies the desired interfaces. +var ( + _ Attribute = DynamicAttribute{} + _ fwxschema.AttributeWithDynamicValidators = DynamicAttribute{} +) + +// DynamicAttribute represents a schema attribute that is a dynamic, rather +// than a single static type. Static types are always preferable over dynamic +// types in Terraform as practitioners will receive less helpful configuration +// assistance from validation error diagnostics and editor integrations. When +// retrieving the value for this attribute, use types.Dynamic as the value type +// unless the CustomType field is set. +// +// The concrete value type for a dynamic is determined at runtime in this order: +// 1. By Terraform, if defined in the configuration (if Required or Optional). +// 2. By the provider (if Computed). +// +// Once the concrete value type has been determined, it must remain consistent between +// plan and apply or Terraform will return an error. +type DynamicAttribute struct { + // CustomType enables the use of a custom attribute type in place of the + // default basetypes.DynamicType. When retrieving data, the basetypes.DynamicValuable + // associated with this custom type must be used in place of types.Dynamic. + CustomType basetypes.DynamicTypable + + // Required indicates whether the practitioner must enter a value for + // this attribute or not. Required and Optional cannot both be true, + // and Required and Computed cannot both be true. + Required bool + + // Optional indicates whether the practitioner can choose to enter a value + // for this attribute or not. Optional and Required cannot both be true. + Optional bool + + // Computed indicates whether the provider may return its own value for + // this Attribute or not. Required and Computed cannot both be true. If + // Required and Optional are both false, Computed must be true, and the + // attribute will be considered "read only" for the practitioner, with + // only the provider able to set its value. + Computed bool + + // Sensitive indicates whether the value of this attribute should be + // considered sensitive data. Setting it to true will obscure the value + // in CLI output. Sensitive does not impact how values are stored, and + // practitioners are encouraged to store their state as if the entire + // file is sensitive. + Sensitive bool + + // Description is used in various tooling, like the language server, to + // give practitioners more information about what this attribute is, + // what it's for, and how it should be used. It should be written as + // plain text, with no special formatting. + Description string + + // MarkdownDescription is used in various tooling, like the + // documentation generator, to give practitioners more information + // about what this attribute is, what it's for, and how it should be + // used. It should be formatted using Markdown. + MarkdownDescription string + + // DeprecationMessage defines warning diagnostic details to display when + // practitioner configurations use this Attribute. The warning diagnostic + // summary is automatically set to "Attribute Deprecated" along with + // configuration source file and line information. + // + // Set this field to a practitioner actionable message such as: + // + // - "Configure other_attribute instead. This attribute will be removed + // in the next major version of the provider." + // - "Remove this attribute's configuration as it no longer is used and + // the attribute will be removed in the next major version of the + // provider." + // + // In Terraform 1.2.7 and later, this warning diagnostic is displayed any + // time a practitioner attempts to configure a value for this attribute and + // certain scenarios where this attribute is referenced. + // + // In Terraform 1.2.6 and earlier, this warning diagnostic is only + // displayed when the Attribute is Required or Optional, and if the + // practitioner configuration sets the value to a known or unknown value + // (which may eventually be null). It has no effect when the Attribute is + // Computed-only (read-only; not Required or Optional). + // + // Across any Terraform version, there are no warnings raised for + // practitioner configuration values set directly to null, as there is no + // way for the framework to differentiate between an unset and null + // configuration due to how Terraform sends configuration information + // across the protocol. + // + // Additional information about deprecation enhancements for read-only + // attributes can be found in: + // + // - https://github.com/hashicorp/terraform/issues/7569 + // + DeprecationMessage string + + // Validators define value validation functionality for the attribute. All + // elements of the slice of AttributeValidator are run, regardless of any + // previous error diagnostics. + // + // Many common use case validators can be found in the + // github.com/hashicorp/terraform-plugin-framework-validators Go module. + // + // If the Type field points to a custom type that implements the + // xattr.TypeWithValidate interface, the validators defined in this field + // are run in addition to the validation defined by the type. + Validators []validator.Dynamic +} + +// ApplyTerraform5AttributePathStep always returns an error as it is not +// possible to step further into a DynamicAttribute. +func (a DynamicAttribute) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { + return a.GetType().ApplyTerraform5AttributePathStep(step) +} + +// Equal returns true if the given Attribute is a DynamicAttribute +// and all fields are equal. +func (a DynamicAttribute) Equal(o fwschema.Attribute) bool { + if _, ok := o.(DynamicAttribute); !ok { + return false + } + + return fwschema.AttributesEqual(a, o) +} + +// GetDeprecationMessage returns the DeprecationMessage field value. +func (a DynamicAttribute) GetDeprecationMessage() string { + return a.DeprecationMessage +} + +// GetDescription returns the Description field value. +func (a DynamicAttribute) GetDescription() string { + return a.Description +} + +// GetMarkdownDescription returns the MarkdownDescription field value. +func (a DynamicAttribute) GetMarkdownDescription() string { + return a.MarkdownDescription +} + +// GetType returns types.DynamicType or the CustomType field value if defined. +func (a DynamicAttribute) GetType() attr.Type { + if a.CustomType != nil { + return a.CustomType + } + + return types.DynamicType +} + +// IsComputed returns the Computed field value. +func (a DynamicAttribute) IsComputed() bool { + return a.Computed +} + +// IsOptional returns the Optional field value. +func (a DynamicAttribute) IsOptional() bool { + return a.Optional +} + +// IsRequired returns the Required field value. +func (a DynamicAttribute) IsRequired() bool { + return a.Required +} + +// IsSensitive returns the Sensitive field value. +func (a DynamicAttribute) IsSensitive() bool { + return a.Sensitive +} + +// DynamicValidators returns the Validators field value. +func (a DynamicAttribute) DynamicValidators() []validator.Dynamic { + return a.Validators +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_attribute.go index e214cb440..9d502067f 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_attribute.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -44,6 +45,10 @@ var ( type ListAttribute struct { // ElementType is the type for all elements of the list. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. ElementType attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -210,4 +215,8 @@ func (a ListAttribute) ValidateImplementation(ctx context.Context, req fwschema. if a.CustomType == nil && a.ElementType == nil { resp.Diagnostics.Append(fwschema.AttributeMissingElementTypeDiag(req.Path)) } + + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_nested_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_nested_attribute.go index d6d2cef67..b9b70d6fb 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_nested_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_nested_attribute.go @@ -4,11 +4,13 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -17,8 +19,9 @@ import ( // Ensure the implementation satisifies the desired interfaces. var ( - _ NestedAttribute = ListNestedAttribute{} - _ fwxschema.AttributeWithListValidators = ListNestedAttribute{} + _ NestedAttribute = ListNestedAttribute{} + _ fwschema.AttributeWithValidateImplementation = ListNestedAttribute{} + _ fwxschema.AttributeWithListValidators = ListNestedAttribute{} ) // ListNestedAttribute represents an attribute that is a list of objects where @@ -51,6 +54,10 @@ var ( type ListNestedAttribute struct { // NestedObject is the underlying object that contains nested attributes. // This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. NestedObject NestedAttributeObject // CustomType enables the use of a custom attribute type in place of the @@ -227,3 +234,13 @@ func (a ListNestedAttribute) IsSensitive() bool { func (a ListNestedAttribute) ListValidators() []validator.List { return a.Validators } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the attribute to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (a ListNestedAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_nested_block.go b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_nested_block.go index 0722d54d4..4d098bc2d 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_nested_block.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/list_nested_block.go @@ -4,11 +4,13 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -17,8 +19,9 @@ import ( // Ensure the implementation satisifies the desired interfaces. var ( - _ Block = ListNestedBlock{} - _ fwxschema.BlockWithListValidators = ListNestedBlock{} + _ Block = ListNestedBlock{} + _ fwschema.BlockWithValidateImplementation = ListNestedBlock{} + _ fwxschema.BlockWithListValidators = ListNestedBlock{} ) // ListNestedBlock represents a block that is a list of objects where @@ -55,6 +58,10 @@ var ( type ListNestedBlock struct { // NestedObject is the underlying object that contains nested attributes or // blocks. This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this block definition with + // a DynamicAttribute. NestedObject NestedBlockObject // CustomType enables the use of a custom attribute type in place of the @@ -186,3 +193,13 @@ func (b ListNestedBlock) Type() attr.Type { ElemType: b.NestedObject.Type(), } } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the block to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (b ListNestedBlock) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if b.CustomType == nil && fwtype.ContainsCollectionWithDynamic(b.Type()) { + resp.Diagnostics.Append(fwtype.BlockCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/map_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/map_attribute.go index ab4fe6859..d9b701f73 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/map_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/map_attribute.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -47,6 +48,10 @@ var ( type MapAttribute struct { // ElementType is the type for all elements of the map. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. ElementType attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -213,4 +218,8 @@ func (a MapAttribute) ValidateImplementation(ctx context.Context, req fwschema.V if a.CustomType == nil && a.ElementType == nil { resp.Diagnostics.Append(fwschema.AttributeMissingElementTypeDiag(req.Path)) } + + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/map_nested_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/map_nested_attribute.go index 3359fe6fc..2f3a60ec5 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/map_nested_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/map_nested_attribute.go @@ -4,6 +4,7 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -18,8 +20,9 @@ import ( // Ensure the implementation satisifies the desired interfaces. var ( - _ NestedAttribute = MapNestedAttribute{} - _ fwxschema.AttributeWithMapValidators = MapNestedAttribute{} + _ NestedAttribute = MapNestedAttribute{} + _ fwschema.AttributeWithValidateImplementation = MapNestedAttribute{} + _ fwxschema.AttributeWithMapValidators = MapNestedAttribute{} ) // MapNestedAttribute represents an attribute that is a set of objects where @@ -52,6 +55,10 @@ var ( type MapNestedAttribute struct { // NestedObject is the underlying object that contains nested attributes. // This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. NestedObject NestedAttributeObject // CustomType enables the use of a custom attribute type in place of the @@ -228,3 +235,13 @@ func (a MapNestedAttribute) IsSensitive() bool { func (a MapNestedAttribute) MapValidators() []validator.Map { return a.Validators } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the attribute to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (a MapNestedAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/object_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/object_attribute.go index fcb800bdb..eafa40c6e 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/object_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/object_attribute.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -46,6 +47,10 @@ var ( type ObjectAttribute struct { // AttributeTypes is the mapping of underlying attribute names to attribute // types. This field must be set. + // + // Attribute types that contain a collection with a nested dynamic type (i.e. types.List[types.Dynamic]) are not supported. + // If underlying dynamic collection values are required, replace this attribute definition with + // DynamicAttribute instead. AttributeTypes map[string]attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -212,4 +217,8 @@ func (a ObjectAttribute) ValidateImplementation(ctx context.Context, req fwschem if a.AttributeTypes == nil && a.CustomType == nil { resp.Diagnostics.Append(fwschema.AttributeMissingAttributeTypesDiag(req.Path)) } + + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_attribute.go index be7128e95..261b02424 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_attribute.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -42,6 +43,10 @@ var ( type SetAttribute struct { // ElementType is the type for all elements of the set. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. ElementType attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -208,4 +213,8 @@ func (a SetAttribute) ValidateImplementation(ctx context.Context, req fwschema.V if a.CustomType == nil && a.ElementType == nil { resp.Diagnostics.Append(fwschema.AttributeMissingElementTypeDiag(req.Path)) } + + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_nested_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_nested_attribute.go index d9acd9b63..860ab4c96 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_nested_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_nested_attribute.go @@ -4,6 +4,7 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -18,8 +20,9 @@ import ( // Ensure the implementation satisifies the desired interfaces. var ( - _ NestedAttribute = SetNestedAttribute{} - _ fwxschema.AttributeWithSetValidators = SetNestedAttribute{} + _ NestedAttribute = SetNestedAttribute{} + _ fwschema.AttributeWithValidateImplementation = SetNestedAttribute{} + _ fwxschema.AttributeWithSetValidators = SetNestedAttribute{} ) // SetNestedAttribute represents an attribute that is a set of objects where @@ -47,6 +50,10 @@ var ( type SetNestedAttribute struct { // NestedObject is the underlying object that contains nested attributes. // This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. NestedObject NestedAttributeObject // CustomType enables the use of a custom attribute type in place of the @@ -223,3 +230,13 @@ func (a SetNestedAttribute) IsSensitive() bool { func (a SetNestedAttribute) SetValidators() []validator.Set { return a.Validators } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the attribute to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (a SetNestedAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_nested_block.go b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_nested_block.go index 8e89ff944..085163f37 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_nested_block.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/datasource/schema/set_nested_block.go @@ -4,11 +4,13 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -17,8 +19,9 @@ import ( // Ensure the implementation satisifies the desired interfaces. var ( - _ Block = SetNestedBlock{} - _ fwxschema.BlockWithSetValidators = SetNestedBlock{} + _ Block = SetNestedBlock{} + _ fwschema.BlockWithValidateImplementation = SetNestedBlock{} + _ fwxschema.BlockWithSetValidators = SetNestedBlock{} ) // SetNestedBlock represents a block that is a set of objects where @@ -55,6 +58,10 @@ var ( type SetNestedBlock struct { // NestedObject is the underlying object that contains nested attributes or // blocks. This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this block definition with + // a DynamicAttribute. NestedObject NestedBlockObject // CustomType enables the use of a custom attribute type in place of the @@ -186,3 +193,13 @@ func (b SetNestedBlock) Type() attr.Type { ElemType: b.NestedObject.Type(), } } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the block to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (b SetNestedBlock) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if b.CustomType == nil && fwtype.ContainsCollectionWithDynamic(b.Type()) { + resp.Diagnostics.Append(fwtype.BlockCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/definition.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/definition.go index 39c1a408e..87dd45fb2 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/function/definition.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/definition.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" ) // Definition is a function definition. Always set at least the Result field. @@ -89,7 +90,7 @@ func (d Definition) Parameter(ctx context.Context, position int) (Parameter, dia // implementation of the definition to prevent unexpected errors or panics. This // logic runs during the GetProviderSchema RPC, or via provider-defined unit // testing, and should never include false positives. -func (d Definition) ValidateImplementation(ctx context.Context) diag.Diagnostics { +func (d Definition) ValidateImplementation(ctx context.Context, req DefinitionValidateRequest, resp *DefinitionValidateResponse) { var diags diag.Diagnostics if d.Return == nil { @@ -97,33 +98,58 @@ func (d Definition) ValidateImplementation(ctx context.Context) diag.Diagnostics "Invalid Function Definition", "When validating the function definition, an implementation issue was found. "+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - "Definition Return field is undefined", + fmt.Sprintf("Function %q - Definition Return field is undefined", req.FuncName), ) } else if d.Return.GetType() == nil { diags.AddError( "Invalid Function Definition", "When validating the function definition, an implementation issue was found. "+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ - "Definition return data type is undefined", + fmt.Sprintf("Function %q - Definition return data type is undefined", req.FuncName), ) + } else if returnWithValidateImplementation, ok := d.Return.(fwfunction.ReturnWithValidateImplementation); ok { + req := fwfunction.ValidateReturnImplementationRequest{} + resp := &fwfunction.ValidateReturnImplementationResponse{} + + returnWithValidateImplementation.ValidateImplementation(ctx, req, resp) + + diags.Append(resp.Diagnostics...) } paramNames := make(map[string]int, len(d.Parameters)) for pos, param := range d.Parameters { + parameterPosition := int64(pos) name := param.GetName() - // If name is not set, default the param name based on position: "param1", "param2", etc. + // If name is not set, add an error diagnostic, parameter names are mandatory. if name == "" { - name = fmt.Sprintf("%s%d", DefaultParameterNamePrefix, pos+1) + diags.AddError( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Function %q - Parameter at position %d does not have a name", req.FuncName, pos), + ) + } + + if paramWithValidateImplementation, ok := param.(fwfunction.ParameterWithValidateImplementation); ok { + req := fwfunction.ValidateParameterImplementationRequest{ + Name: name, + ParameterPosition: ¶meterPosition, + } + resp := &fwfunction.ValidateParameterImplementationResponse{} + + paramWithValidateImplementation.ValidateImplementation(ctx, req, resp) + + diags.Append(resp.Diagnostics...) } conflictPos, exists := paramNames[name] - if exists { + if exists && name != "" { diags.AddError( "Invalid Function Definition", "When validating the function definition, an implementation issue was found. "+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ "Parameter names must be unique. "+ - fmt.Sprintf("Parameters at position %d and %d have the same name %q", conflictPos, pos, name), + fmt.Sprintf("Function %q - Parameters at position %d and %d have the same name %q", req.FuncName, conflictPos, pos, name), ) continue } @@ -133,24 +159,40 @@ func (d Definition) ValidateImplementation(ctx context.Context) diag.Diagnostics if d.VariadicParameter != nil { name := d.VariadicParameter.GetName() - // If name is not set, default the variadic param name + // If name is not set, add an error diagnostic, parameter names are mandatory. if name == "" { - name = DefaultVariadicParameterName + diags.AddError( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Function %q - The variadic parameter does not have a name", req.FuncName), + ) + } + + if paramWithValidateImplementation, ok := d.VariadicParameter.(fwfunction.ParameterWithValidateImplementation); ok { + req := fwfunction.ValidateParameterImplementationRequest{ + Name: name, + } + resp := &fwfunction.ValidateParameterImplementationResponse{} + + paramWithValidateImplementation.ValidateImplementation(ctx, req, resp) + + diags.Append(resp.Diagnostics...) } conflictPos, exists := paramNames[name] - if exists { + if exists && name != "" { diags.AddError( "Invalid Function Definition", "When validating the function definition, an implementation issue was found. "+ "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ "Parameter names must be unique. "+ - fmt.Sprintf("Parameter at position %d and the variadic parameter have the same name %q", conflictPos, name), + fmt.Sprintf("Function %q - Parameter at position %d and the variadic parameter have the same name %q", req.FuncName, conflictPos, name), ) } } - return diags + resp.Diagnostics.Append(diags...) } // DefinitionRequest represents a request for the Function to return its @@ -170,3 +212,21 @@ type DefinitionResponse struct { // An empty slice indicates success, with no warnings or errors generated. Diagnostics diag.Diagnostics } + +// DefinitionValidateRequest represents a request for the Function to validate its +// definition. An instance of this request struct is supplied as an argument to +// the Definition type ValidateImplementation method. +type DefinitionValidateRequest struct { + // FuncName is the name of the function definition being validated. + FuncName string +} + +// DefinitionValidateResponse represents a response to a DefinitionValidateRequest. +// An instance of this response struct is supplied as an argument to the Definition +// type ValidateImplementation method. +type DefinitionValidateResponse struct { + // Diagnostics report errors or warnings related to validation of a function + // definition. An empty slice indicates success, with no warnings or errors + // generated. + Diagnostics diag.Diagnostics +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/dynamic_parameter.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/dynamic_parameter.go new file mode 100644 index 000000000..1af3c71d0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/dynamic_parameter.go @@ -0,0 +1,102 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package function + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// Ensure the implementation satisifies the desired interfaces. +var _ Parameter = DynamicParameter{} + +// DynamicParameter represents a function parameter that is a dynamic, rather +// than a static type. Static types are always preferable over dynamic +// types in Terraform as practitioners will receive less helpful configuration +// assistance from validation error diagnostics and editor integrations. +// +// When retrieving the argument value for this parameter: +// +// - If CustomType is set, use its associated value type. +// - Otherwise, use the [types.Dynamic] value type. +// +// The concrete value type for a dynamic is determined at runtime by Terraform, +// if defined in the configuration. +type DynamicParameter struct { + // AllowNullValue when enabled denotes that a null argument value can be + // passed to the function. When disabled, Terraform returns an error if the + // argument value is null. + AllowNullValue bool + + // AllowUnknownValues when enabled denotes that an unknown argument value + // can be passed to the function. When disabled, Terraform skips the + // function call entirely and assumes an unknown value result from the + // function. + AllowUnknownValues bool + + // CustomType enables the use of a custom data type in place of the + // default [basetypes.DynamicType]. When retrieving data, the + // [basetypes.DynamicValuable] implementation associated with this custom + // type must be used in place of [types.Dynamic]. + CustomType basetypes.DynamicTypable + + // Description is used in various tooling, like the language server, to + // give practitioners more information about what this parameter is, + // what it is for, and how it should be used. It should be written as + // plain text, with no special formatting. + Description string + + // MarkdownDescription is used in various tooling, like the + // documentation generator, to give practitioners more information + // about what this parameter is, what it is for, and how it should be + // used. It should be formatted using Markdown. + MarkdownDescription string + + // Name is a short usage name for the parameter, such as "data". This name + // is used in documentation, such as generating a function signature, + // however its usage may be extended in the future. + // + // If no name is provided, this will default to "param" with a suffix of the + // position the parameter is in the function definition. ("param1", "param2", etc.) + // If the parameter is variadic, the default name will be "varparam". + // + // This must be a valid Terraform identifier, such as starting with an + // alphabetical character and followed by alphanumeric or underscore + // characters. + Name string +} + +// GetAllowNullValue returns if the parameter accepts a null value. +func (p DynamicParameter) GetAllowNullValue() bool { + return p.AllowNullValue +} + +// GetAllowUnknownValues returns if the parameter accepts an unknown value. +func (p DynamicParameter) GetAllowUnknownValues() bool { + return p.AllowUnknownValues +} + +// GetDescription returns the parameter plaintext description. +func (p DynamicParameter) GetDescription() string { + return p.Description +} + +// GetMarkdownDescription returns the parameter Markdown description. +func (p DynamicParameter) GetMarkdownDescription() string { + return p.MarkdownDescription +} + +// GetName returns the parameter name. +func (p DynamicParameter) GetName() string { + return p.Name +} + +// GetType returns the parameter data type. +func (p DynamicParameter) GetType() attr.Type { + if p.CustomType != nil { + return p.CustomType + } + + return basetypes.DynamicType{} +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/dynamic_return.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/dynamic_return.go new file mode 100644 index 000000000..bab38f857 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/dynamic_return.go @@ -0,0 +1,53 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package function + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// Ensure the implementation satisifies the desired interfaces. +var _ Return = DynamicReturn{} + +// DynamicReturn represents a function return that is a dynamic, rather +// than a static type. Static types are always preferable over dynamic +// types in Terraform as practitioners will receive less helpful configuration +// assistance from validation error diagnostics and editor integrations. +// +// When setting the value for this return: +// +// - If CustomType is set, use its associated value type. +// - Otherwise, use the [types.Dynamic] value type. +type DynamicReturn struct { + // CustomType enables the use of a custom data type in place of the + // default [basetypes.DynamicType]. When setting data, the + // [basetypes.DynamicValuable] implementation associated with this custom + // type must be used in place of [types.Dynamic]. + CustomType basetypes.DynamicTypable +} + +// GetType returns the return data type. +func (r DynamicReturn) GetType() attr.Type { + if r.CustomType != nil { + return r.CustomType + } + + return basetypes.DynamicType{} +} + +// NewResultData returns a new result data based on the type. +func (r DynamicReturn) NewResultData(ctx context.Context) (ResultData, *FuncError) { + value := basetypes.NewDynamicUnknown() + + if r.CustomType == nil { + return NewResultData(value), nil + } + + valuable, diags := r.CustomType.ValueFromDynamic(ctx, value) + + return NewResultData(valuable), FuncErrorFromDiags(ctx, diags) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/func_error.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/func_error.go index 78da0053b..4ce870a2f 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/function/func_error.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/func_error.go @@ -69,6 +69,10 @@ func (fe *FuncError) Equal(other *FuncError) bool { // Error returns the error text. func (fe *FuncError) Error() string { + if fe == nil { + return "" + } + return fe.Text } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/list_parameter.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/list_parameter.go index f2c60d9c3..69d54da94 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/function/list_parameter.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/list_parameter.go @@ -4,12 +4,20 @@ package function import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. -var _ Parameter = ListParameter{} +var ( + _ Parameter = ListParameter{} + _ fwfunction.ParameterWithValidateImplementation = ListParameter{} +) // ListParameter represents a function parameter that is an ordered list of a // single element type. Either the ElementType or CustomType field must be set. @@ -27,6 +35,10 @@ var _ Parameter = ListParameter{} type ListParameter struct { // ElementType is the type for all elements of the list. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this parameter definition with + // DynamicParameter instead. ElementType attr.Type // AllowNullValue when enabled denotes that a null argument value can be @@ -107,3 +119,20 @@ func (p ListParameter) GetType() attr.Type { ElemType: p.ElementType, } } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the parameter to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (p ListParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { + if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { + var diag diag.Diagnostic + if req.ParameterPosition != nil { + diag = fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, req.Name) + } else { + diag = fwtype.VariadicParameterCollectionWithDynamicTypeDiag(req.Name) + } + + resp.Diagnostics.Append(diag) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/list_return.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/list_return.go index 88238aee4..07eac8ad8 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/function/list_return.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/list_return.go @@ -7,11 +7,16 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. -var _ Return = ListReturn{} +var ( + _ Return = ListReturn{} + _ fwfunction.ReturnWithValidateImplementation = ListReturn{} +) // ListReturn represents a function return that is an ordered collection of a // single element type. Either the ElementType or CustomType field must be set. @@ -24,6 +29,10 @@ var _ Return = ListReturn{} type ListReturn struct { // ElementType is the type for all elements of the list. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this return definition with + // DynamicReturn instead. ElementType attr.Type // CustomType enables the use of a custom data type in place of the @@ -56,3 +65,13 @@ func (r ListReturn) NewResultData(ctx context.Context) (ResultData, *FuncError) return NewResultData(valuable), FuncErrorFromDiags(ctx, diags) } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the Return to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (p ListReturn) ValidateImplementation(ctx context.Context, req fwfunction.ValidateReturnImplementationRequest, resp *fwfunction.ValidateReturnImplementationResponse) { + if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/map_parameter.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/map_parameter.go index 2c7a7256f..457720ea2 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/function/map_parameter.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/map_parameter.go @@ -4,12 +4,20 @@ package function import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. -var _ Parameter = MapParameter{} +var ( + _ Parameter = MapParameter{} + _ fwfunction.ParameterWithValidateImplementation = MapParameter{} +) // MapParameter represents a function parameter that is a mapping of a single // element type. Either the ElementType or CustomType field must be set. @@ -27,6 +35,10 @@ var _ Parameter = MapParameter{} type MapParameter struct { // ElementType is the type for all elements of the map. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this parameter definition with + // DynamicParameter instead. ElementType attr.Type // AllowNullValue when enabled denotes that a null argument value can be @@ -107,3 +119,20 @@ func (p MapParameter) GetType() attr.Type { ElemType: p.ElementType, } } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the parameter to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (p MapParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { + if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { + var diag diag.Diagnostic + if req.ParameterPosition != nil { + diag = fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, req.Name) + } else { + diag = fwtype.VariadicParameterCollectionWithDynamicTypeDiag(req.Name) + } + + resp.Diagnostics.Append(diag) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/map_return.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/map_return.go index afc8be0d8..5f83c69c3 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/function/map_return.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/map_return.go @@ -7,11 +7,16 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. -var _ Return = MapReturn{} +var ( + _ Return = MapReturn{} + _ fwfunction.ReturnWithValidateImplementation = MapReturn{} +) // MapReturn represents a function return that is an ordered collect of a // single element type. Either the ElementType or CustomType field must be set. @@ -24,6 +29,10 @@ var _ Return = MapReturn{} type MapReturn struct { // ElementType is the type for all elements of the map. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this return definition with + // DynamicReturn instead. ElementType attr.Type // CustomType enables the use of a custom data type in place of the @@ -56,3 +65,13 @@ func (r MapReturn) NewResultData(ctx context.Context) (ResultData, *FuncError) { return NewResultData(valuable), FuncErrorFromDiags(ctx, diags) } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the Return to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (p MapReturn) ValidateImplementation(ctx context.Context, req fwfunction.ValidateReturnImplementationRequest, resp *fwfunction.ValidateReturnImplementationResponse) { + if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/object_parameter.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/object_parameter.go index 8b8791e46..e558a7549 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/function/object_parameter.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/object_parameter.go @@ -4,12 +4,20 @@ package function import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. -var _ Parameter = ObjectParameter{} +var ( + _ Parameter = ObjectParameter{} + _ fwfunction.ParameterWithValidateImplementation = ObjectParameter{} +) // ObjectParameter represents a function parameter that is a mapping of // defined attribute names to values. Either the AttributeTypes or CustomType @@ -29,6 +37,10 @@ var _ Parameter = ObjectParameter{} type ObjectParameter struct { // AttributeTypes is the mapping of underlying attribute names to attribute // types. This field must be set. + // + // Attribute types that contain a collection with a nested dynamic type (i.e. types.List[types.Dynamic]) are not supported. + // If underlying dynamic collection values are required, replace this parameter definition with + // DynamicParameter instead. AttributeTypes map[string]attr.Type // AllowNullValue when enabled denotes that a null argument value can be @@ -109,3 +121,20 @@ func (p ObjectParameter) GetType() attr.Type { AttrTypes: p.AttributeTypes, } } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the parameter to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (p ObjectParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { + if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { + var diag diag.Diagnostic + if req.ParameterPosition != nil { + diag = fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, req.Name) + } else { + diag = fwtype.VariadicParameterCollectionWithDynamicTypeDiag(req.Name) + } + + resp.Diagnostics.Append(diag) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/object_return.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/object_return.go index 526ab96f1..201960f95 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/function/object_return.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/object_return.go @@ -7,11 +7,16 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. -var _ Return = ObjectReturn{} +var ( + _ Return = ObjectReturn{} + _ fwfunction.ReturnWithValidateImplementation = ObjectReturn{} +) // ObjectReturn represents a function return that is mapping of defined // attribute names to values. When setting the value for this return, use @@ -20,6 +25,10 @@ var _ Return = ObjectReturn{} type ObjectReturn struct { // AttributeTypes is the mapping of underlying attribute names to attribute // types. This field must be set. + // + // Attribute types that contain a collection with a nested dynamic type (i.e. types.List[types.Dynamic]) are not supported. + // If underlying dynamic collection values are required, replace this return definition with + // DynamicReturn instead. AttributeTypes map[string]attr.Type // CustomType enables the use of a custom data type in place of the @@ -52,3 +61,13 @@ func (r ObjectReturn) NewResultData(ctx context.Context) (ResultData, *FuncError return NewResultData(valuable), FuncErrorFromDiags(ctx, diags) } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the Return to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (p ObjectReturn) ValidateImplementation(ctx context.Context, req fwfunction.ValidateReturnImplementationRequest, resp *fwfunction.ValidateReturnImplementationResponse) { + if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/parameter.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/parameter.go index 14a9fbb5d..e5add8828 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/function/parameter.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/parameter.go @@ -7,17 +7,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" ) -const ( - // DefaultParameterNamePrefix is the prefix used to default the name of parameters which do not declare - // a name. Use this to prevent Terraform errors for missing names. This prefix is used with the parameter - // position in a function definition to create a unique name (param1, param2, etc.) - DefaultParameterNamePrefix = "param" - - // DefaultVariadicParameterName is the default name given to a variadic parameter that does not declare - // a name. Use this to prevent Terraform errors for missing names. - DefaultVariadicParameterName = "varparam" -) - // Parameter is the interface for defining function parameters. type Parameter interface { // GetAllowNullValue should return if the parameter accepts a null value. diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/set_parameter.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/set_parameter.go index f920f05ef..0774c559f 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/function/set_parameter.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/set_parameter.go @@ -4,12 +4,20 @@ package function import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. -var _ Parameter = SetParameter{} +var ( + _ Parameter = SetParameter{} + _ fwfunction.ParameterWithValidateImplementation = SetParameter{} +) // SetParameter represents a function parameter that is an unordered set of a // single element type. Either the ElementType or CustomType field must be set. @@ -27,6 +35,10 @@ var _ Parameter = SetParameter{} type SetParameter struct { // ElementType is the type for all elements of the set. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this parameter definition with + // DynamicParameter instead. ElementType attr.Type // AllowNullValue when enabled denotes that a null argument value can be @@ -107,3 +119,20 @@ func (p SetParameter) GetType() attr.Type { ElemType: p.ElementType, } } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the parameter to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (p SetParameter) ValidateImplementation(ctx context.Context, req fwfunction.ValidateParameterImplementationRequest, resp *fwfunction.ValidateParameterImplementationResponse) { + if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { + var diag diag.Diagnostic + if req.ParameterPosition != nil { + diag = fwtype.ParameterCollectionWithDynamicTypeDiag(*req.ParameterPosition, req.Name) + } else { + diag = fwtype.VariadicParameterCollectionWithDynamicTypeDiag(req.Name) + } + + resp.Diagnostics.Append(diag) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/function/set_return.go b/vendor/github.com/hashicorp/terraform-plugin-framework/function/set_return.go index 97ea8e79c..2999a4067 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/function/set_return.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/function/set_return.go @@ -7,11 +7,16 @@ import ( "context" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwfunction" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Ensure the implementation satisifies the desired interfaces. -var _ Return = SetReturn{} +var ( + _ Return = SetReturn{} + _ fwfunction.ReturnWithValidateImplementation = SetReturn{} +) // SetReturn represents a function return that is an unordered collection of a // single element type. Either the ElementType or CustomType field must be set. @@ -24,6 +29,10 @@ var _ Return = SetReturn{} type SetReturn struct { // ElementType is the type for all elements of the set. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this return definition with + // DynamicReturn instead. ElementType attr.Type // CustomType enables the use of a custom data type in place of the @@ -56,3 +65,13 @@ func (r SetReturn) NewResultData(ctx context.Context) (ResultData, *FuncError) { return NewResultData(valuable), FuncErrorFromDiags(ctx, diags) } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the Return to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (p SetReturn) ValidateImplementation(ctx context.Context, req fwfunction.ValidateReturnImplementationRequest, resp *fwfunction.ValidateReturnImplementationResponse) { + if p.CustomType == nil && fwtype.ContainsCollectionWithDynamic(p.GetType()) { + resp.Diagnostics.Append(fwtype.ReturnCollectionWithDynamicTypeDiag()) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwfunction/doc.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwfunction/doc.go new file mode 100644 index 000000000..8acc6b889 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwfunction/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package fwfunction contains shared interfaces and structures for implementing behaviors +// in Terraform Provider function implementations. +package fwfunction diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwfunction/parameter_validate_implementation.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwfunction/parameter_validate_implementation.go new file mode 100644 index 000000000..1e171f016 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwfunction/parameter_validate_implementation.go @@ -0,0 +1,54 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwfunction + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +// MAINTAINER NOTE: This interface doesn't need to be internal but we're initially keeping them +// private until we determine if they would be useful to expose as a public interface. + +// ParameterWithValidateImplementation is an optional interface on +// function.Parameter which enables validation of the provider-defined implementation +// for the function.Parameter. This logic runs during the GetProviderSchema RPC, or via +// provider-defined unit testing, to ensure the provider's definition is valid +// before further usage could cause other unexpected errors or panics. +type ParameterWithValidateImplementation interface { + // ValidateImplementation should contain the logic which validates + // the function.Parameter implementation. Since this logic can prevent the provider + // from being usable, it should be very targeted and defensive against + // false positives. + ValidateImplementation(context.Context, ValidateParameterImplementationRequest, *ValidateParameterImplementationResponse) +} + +// ValidateParameterImplementationRequest contains the information available +// during a ValidateImplementation call to validate the function.Parameter +// definition. ValidateParameterImplementationResponse is the type used for +// responses. +type ValidateParameterImplementationRequest struct { + // ParameterPosition is the position of the parameter in the function definition for reporting diagnostics. + // A parameter without a position (i.e. `nil`) is the variadic parameter. + ParameterPosition *int64 + + // Name is the provider-defined parameter name or the default parameter name for reporting diagnostics. + // + // MAINTAINER NOTE: Since parameter names are not required currently and can be defaulted by internal framework logic, + // we accept the Name in this validate request, rather than using `(function.Parameter).GetName()` for diagnostics, which + // could be empty. + Name string +} + +// ValidateParameterImplementationResponse contains the returned data from a +// ValidateImplementation method call to validate the function.Parameter +// implementation. ValidateParameterImplementationRequest is the type used for +// requests. +type ValidateParameterImplementationResponse struct { + // Diagnostics report errors or warnings related to validating the + // definition of the function.Parameter. An empty slice indicates success, with no + // warnings or errors generated. + Diagnostics diag.Diagnostics +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwfunction/return_validate_implementation.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwfunction/return_validate_implementation.go new file mode 100644 index 000000000..17de03cf5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwfunction/return_validate_implementation.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwfunction + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +// MAINTAINER NOTE: This interface doesn't need to be internal but we're initially keeping them +// private until we determine if they would be useful to expose as a public interface. + +// ReturnWithValidateImplementation is an optional interface on +// function.Return which enables validation of the provider-defined implementation +// for the function.Return. This logic runs during the GetProviderSchema RPC, or via +// provider-defined unit testing, to ensure the provider's definition is valid +// before further usage could cause other unexpected errors or panics. +type ReturnWithValidateImplementation interface { + // ValidateImplementation should contain the logic which validates + // the function.Return implementation. Since this logic can prevent the provider + // from being usable, it should be very targeted and defensive against + // false positives. + ValidateImplementation(context.Context, ValidateReturnImplementationRequest, *ValidateReturnImplementationResponse) +} + +// ValidateReturnImplementationRequest contains the information available +// during a ValidateImplementation call to validate the function.Return +// definition. ValidateReturnImplementationResponse is the type used for +// responses. +type ValidateReturnImplementationRequest struct{} + +// ValidateReturnImplementationResponse contains the returned data from a +// ValidateImplementation method call to validate the function.Return +// implementation. ValidateReturnImplementationRequest is the type used for +// requests. +type ValidateReturnImplementationResponse struct { + // Diagnostics report errors or warnings related to validating the + // definition of the function.Return. An empty slice indicates success, with no + // warnings or errors generated. + Diagnostics diag.Diagnostics +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/attribute_default.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/attribute_default.go index ad30e1026..29e63a7bb 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/attribute_default.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/attribute_default.go @@ -78,3 +78,11 @@ type AttributeWithStringDefaultValue interface { StringDefaultValue() defaults.String } + +// AttributeWithDynamicDefaultValue is an optional interface on Attribute which +// enables Dynamic default value support. +type AttributeWithDynamicDefaultValue interface { + Attribute + + DynamicDefaultValue() defaults.Dynamic +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/errors.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/errors.go index b70a0c805..21e7b065c 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/errors.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/errors.go @@ -6,13 +6,17 @@ package fwschema import "errors" var ( - // ErrPathInsideAtomicAttribute is used with AttributeAtPath is called + // ErrPathInsideAtomicAttribute is used when AttributeAtPath is called // on a path that doesn't have a schema associated with it, because // it's an element, attribute, or block of a complex type, not a nested // attribute. ErrPathInsideAtomicAttribute = errors.New("path leads to element, attribute, or block of a schema.Attribute that has no schema associated with it") - // ErrPathIsBlock is used with AttributeAtPath is called on a path is a + // ErrPathIsBlock is used when AttributeAtPath is called on a path is a // block, not an attribute. Use blockAtPath on the path instead. ErrPathIsBlock = errors.New("path leads to block, not an attribute") + + // ErrPathInsideDynamicAttribute is used when AttributeAtPath is called on a path that doesn't + // have a schema associated with it because it's nested in a dynamic attribute. + ErrPathInsideDynamicAttribute = errors.New("path leads to element or attribute nested in a schema.DynamicAttribute") ) diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema/attribute_plan_modification.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema/attribute_plan_modification.go index 72a626cac..f4cb841de 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema/attribute_plan_modification.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema/attribute_plan_modification.go @@ -88,3 +88,12 @@ type AttributeWithStringPlanModifiers interface { // StringPlanModifiers should return a list of String plan modifiers. StringPlanModifiers() []planmodifier.String } + +// AttributeWithDynamicPlanModifiers is an optional interface on Attribute which +// enables Dynamic plan modifier support. +type AttributeWithDynamicPlanModifiers interface { + fwschema.Attribute + + // DynamicPlanModifiers should return a list of Dynamic plan modifiers. + DynamicPlanModifiers() []planmodifier.Dynamic +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema/attribute_validation.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema/attribute_validation.go index 6e3b6805a..e8274de4d 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema/attribute_validation.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema/attribute_validation.go @@ -88,3 +88,12 @@ type AttributeWithStringValidators interface { // StringValidators should return a list of String validators. StringValidators() []validator.String } + +// AttributeWithDynamicValidators is an optional interface on Attribute which +// enables Dynamic validation support. +type AttributeWithDynamicValidators interface { + fwschema.Attribute + + // DynamicValidators should return a list of Dynamic validators. + DynamicValidators() []validator.Dynamic +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/schema.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/schema.go index 63ddad814..cc51acd8e 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/schema.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschema/schema.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/internal/totftypes" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // Schema is the core interface required for data sources, providers, and @@ -131,7 +132,7 @@ func SchemaAttributeAtTerraformPath(ctx context.Context, s Schema, p *tftypes.At rawType, remaining, err := tftypes.WalkAttributePath(s, p) if err != nil { - return nil, fmt.Errorf("%v still remains in the path: %w", remaining, err) + return nil, checkErrForDynamic(rawType, remaining, err) } switch typ := rawType.(type) { @@ -200,7 +201,7 @@ func SchemaTypeAtTerraformPath(ctx context.Context, s Schema, p *tftypes.Attribu rawType, remaining, err := tftypes.WalkAttributePath(s, p) if err != nil { - return nil, fmt.Errorf("%v still remains in the path: %w", remaining, err) + return nil, checkErrForDynamic(rawType, remaining, err) } switch typ := rawType.(type) { @@ -238,3 +239,32 @@ func SchemaType(s Schema) attr.Type { return types.ObjectType{AttrTypes: attrTypes} } + +// checkErrForDynamic is a helper function that will always return an error. It will return +// an `ErrPathInsideDynamicAttribute` error if rawType: +// - Is a dynamic type +// - Is an attribute that has a dynamic type +func checkErrForDynamic(rawType any, remaining *tftypes.AttributePath, err error) error { + if rawType == nil { + return fmt.Errorf("%v still remains in the path: %w", remaining, err) + } + + // Check to see if we tried walking into a dynamic type (types.DynamicType) + _, isDynamic := rawType.(basetypes.DynamicTypable) + if isDynamic { + // If the type is dynamic there is no schema information underneath it, return an error to allow calling logic to safely skip + return ErrPathInsideDynamicAttribute + } + + // Check to see if we tried walking into an attribute with a dynamic type (schema.DynamicAttribute) + attr, ok := rawType.(Attribute) + if ok { + _, isDynamic := attr.GetType().(basetypes.DynamicTypable) + if isDynamic { + // If the attribute is dynamic there are no nested attributes underneath it, return an error to allow calling logic to safely skip + return ErrPathInsideDynamicAttribute + } + } + + return fmt.Errorf("%v still remains in the path: %w", remaining, err) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_default.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_default.go index 75424bade..d83f5ee05 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_default.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_default.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/logging" "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) // TransformDefaults walks the schema and applies schema defined default values @@ -30,6 +31,35 @@ func (d *Data) TransformDefaults(ctx context.Context, configRaw tftypes.Value) d } d.TerraformValue, err = tftypes.Transform(d.TerraformValue, func(tfTypePath *tftypes.AttributePath, tfTypeValue tftypes.Value) (tftypes.Value, error) { + // Skip the root of the data, only applying defaults to attributes + if len(tfTypePath.Steps()) < 1 { + return tfTypeValue, nil + } + + attrAtPath, err := d.Schema.AttributeAtTerraformPath(ctx, tfTypePath) + + if err != nil { + if errors.Is(err, fwschema.ErrPathInsideAtomicAttribute) { + // ignore attributes/elements inside schema.Attributes, they have no schema of their own + logging.FrameworkTrace(ctx, "attribute is a non-schema attribute, not setting default") + return tfTypeValue, nil + } + + if errors.Is(err, fwschema.ErrPathIsBlock) { + // ignore blocks, they do not have a computed field + logging.FrameworkTrace(ctx, "attribute is a block, not setting default") + return tfTypeValue, nil + } + + if errors.Is(err, fwschema.ErrPathInsideDynamicAttribute) { + // ignore attributes/elements inside schema.DynamicAttribute, they have no schema of their own + logging.FrameworkTrace(ctx, "attribute is inside of a dynamic attribute, not setting default") + return tfTypeValue, nil + } + + return tftypes.Value{}, fmt.Errorf("couldn't find attribute in resource schema: %w", err) + } + fwPath, fwPathDiags := fromtftypes.AttributePath(ctx, tfTypePath, d.Schema) diags.Append(fwPathDiags...) @@ -51,25 +81,22 @@ func (d *Data) TransformDefaults(ctx context.Context, configRaw tftypes.Value) d // Do not transform if rawConfig value is not null. if !configValue.IsNull() { - return tfTypeValue, nil - } - - attrAtPath, err := d.Schema.AttributeAtTerraformPath(ctx, tfTypePath) - - if err != nil { - if errors.Is(err, fwschema.ErrPathInsideAtomicAttribute) { - // ignore attributes/elements inside schema.Attributes, they have no schema of their own - logging.FrameworkTrace(ctx, "attribute is a non-schema attribute, not setting default") + // Dynamic values need to perform more logic to check the config value for null-ness + dynValuable, ok := configValue.(basetypes.DynamicValuable) + if !ok { return tfTypeValue, nil } - if errors.Is(err, fwschema.ErrPathIsBlock) { - // ignore blocks, they do not have a computed field - logging.FrameworkTrace(ctx, "attribute is a block, not setting default") + dynConfigVal, dynDiags := dynValuable.ToDynamicValue(ctx) + if dynDiags.HasError() { return tfTypeValue, nil } - return tftypes.Value{}, fmt.Errorf("couldn't find attribute in resource schema: %w", err) + // For dynamic values, it's possible to be known when only the type is known. + // The underlying value can still be null, so check for that here + if !dynConfigVal.IsUnderlyingValueNull() { + return tfTypeValue, nil + } } switch a := attrAtPath.(type) { @@ -142,7 +169,6 @@ func (d *Data) TransformDefaults(ctx context.Context, configRaw tftypes.Value) d logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue)) return resp.PlanValue.ToTerraformValue(ctx) - case fwschema.AttributeWithListDefaultValue: defaultValue := a.ListDefaultValue() @@ -297,6 +323,29 @@ func (d *Data) TransformDefaults(ctx context.Context, configRaw tftypes.Value) d logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue)) + return resp.PlanValue.ToTerraformValue(ctx) + case fwschema.AttributeWithDynamicDefaultValue: + defaultValue := a.DynamicDefaultValue() + + if defaultValue == nil { + return tfTypeValue, nil + } + + req := defaults.DynamicRequest{ + Path: fwPath, + } + resp := defaults.DynamicResponse{} + + defaultValue.DefaultDynamic(ctx, req, &resp) + + diags.Append(resp.Diagnostics...) + + if resp.Diagnostics.HasError() { + return tfTypeValue, nil + } + + logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue)) + return resp.PlanValue.ToTerraformValue(ctx) } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_nullify_collection_blocks.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_nullify_collection_blocks.go index e1a1032c4..f907d6d16 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_nullify_collection_blocks.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_nullify_collection_blocks.go @@ -5,6 +5,7 @@ package fwschemadata import ( "context" + "errors" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fromtftypes" @@ -22,11 +23,25 @@ func (d *Data) NullifyCollectionBlocks(ctx context.Context) diag.Diagnostics { // Errors are handled as richer diag.Diagnostics instead. d.TerraformValue, _ = tftypes.Transform(d.TerraformValue, func(tfTypePath *tftypes.AttributePath, tfTypeValue tftypes.Value) (tftypes.Value, error) { + // Skip the root of the data + if len(tfTypePath.Steps()) < 1 { + return tfTypeValue, nil + } + // Do not transform if value is already null or is not fully known. if tfTypeValue.IsNull() || !tfTypeValue.IsFullyKnown() { return tfTypeValue, nil } + _, err := d.Schema.AttributeAtTerraformPath(ctx, tfTypePath) + if err != nil { + if errors.Is(err, fwschema.ErrPathInsideDynamicAttribute) { + // ignore attributes/elements inside schema.DynamicAttribute + logging.FrameworkTrace(ctx, "attribute is inside of a dynamic attribute, skipping nullify collection blocks") + return tfTypeValue, nil + } + } + fwPath, fwPathDiags := fromtftypes.AttributePath(ctx, tfTypePath, d.Schema) diags.Append(fwPathDiags...) diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_reify_null_collection_blocks.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_reify_null_collection_blocks.go index 933a6c9de..64d10bd64 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_reify_null_collection_blocks.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_reify_null_collection_blocks.go @@ -5,6 +5,7 @@ package fwschemadata import ( "context" + "errors" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/internal/fromtftypes" @@ -22,11 +23,25 @@ func (d *Data) ReifyNullCollectionBlocks(ctx context.Context) diag.Diagnostics { // Errors are handled as richer diag.Diagnostics instead. d.TerraformValue, _ = tftypes.Transform(d.TerraformValue, func(tfTypePath *tftypes.AttributePath, tfTypeValue tftypes.Value) (tftypes.Value, error) { + // Skip the root of the data + if len(tfTypePath.Steps()) < 1 { + return tfTypeValue, nil + } + // Only transform null values. if !tfTypeValue.IsNull() { return tfTypeValue, nil } + _, err := d.Schema.AttributeAtTerraformPath(ctx, tfTypePath) + if err != nil { + if errors.Is(err, fwschema.ErrPathInsideDynamicAttribute) { + // ignore attributes/elements inside schema.DynamicAttribute + logging.FrameworkTrace(ctx, "attribute is inside of a dynamic attribute, skipping reify null collection blocks") + return tfTypeValue, nil + } + } + fwPath, fwPathDiags := fromtftypes.AttributePath(ctx, tfTypePath, d.Schema) diags.Append(fwPathDiags...) diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_set_at_path.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_set_at_path.go index c7febc8c7..532cb2710 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_set_at_path.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_set_at_path.go @@ -158,10 +158,6 @@ func (d Data) SetAtPathTransformFunc(ctx context.Context, path path.Path, tfVal } if parentValue.IsNull() || !parentValue.IsKnown() { - // TODO: This will break when DynamicPsuedoType is introduced. - // tftypes.Type should implement AttributePathStepper, but it currently does not. - // When it does, we should use: tftypes.WalkAttributePath(p.Raw.Type(), parentPath) - // Reference: https://github.com/hashicorp/terraform-plugin-go/issues/110 parentType := parentAttrType.TerraformType(ctx) var childValue interface{} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/value_semantic_equality.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/value_semantic_equality.go index cc7fb6f85..e93ae8396 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/value_semantic_equality.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/value_semantic_equality.go @@ -79,6 +79,8 @@ func ValueSemanticEquality(ctx context.Context, req ValueSemanticEqualityRequest ValueSemanticEqualitySet(ctx, req, resp) case basetypes.StringValuable: ValueSemanticEqualityString(ctx, req, resp) + case basetypes.DynamicValuable: + ValueSemanticEqualityDynamic(ctx, req, resp) } if resp.NewValue.Equal(req.PriorValue) { diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/value_semantic_equality_dynamic.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/value_semantic_equality_dynamic.go new file mode 100644 index 000000000..6a23cd1ee --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/value_semantic_equality_dynamic.go @@ -0,0 +1,78 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwschemadata + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// ValueSemanticEqualityDynamic performs dynamic type semantic equality. +func ValueSemanticEqualityDynamic(ctx context.Context, req ValueSemanticEqualityRequest, resp *ValueSemanticEqualityResponse) { + priorValuable, ok := req.PriorValue.(basetypes.DynamicValuableWithSemanticEquals) + + // No changes required if the interface is not implemented. + if !ok { + return + } + + proposedNewValuable, ok := req.ProposedNewValue.(basetypes.DynamicValuableWithSemanticEquals) + + // No changes required if the interface is not implemented. + if !ok { + return + } + + logging.FrameworkTrace( + ctx, + "Calling provider defined type-based SemanticEquals", + map[string]interface{}{ + logging.KeyValueType: proposedNewValuable.String(), + }, + ) + + // The prior dynamic value has alredy been checked for null or unknown, however, we also + // need to check the underlying value for null or unknown. + priorValue, diags := priorValuable.ToDynamicValue(ctx) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if priorValue.IsUnderlyingValueNull() || priorValue.IsUnderlyingValueUnknown() { + return + } + + // The proposed new dynamic value has alredy been checked for null or unknown, however, we also + // need to check the underlying value for null or unknown. + proposedValue, diags := proposedNewValuable.ToDynamicValue(ctx) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if proposedValue.IsUnderlyingValueNull() || proposedValue.IsUnderlyingValueUnknown() { + return + } + + usePriorValue, diags := proposedNewValuable.DynamicSemanticEquals(ctx, priorValuable) + + logging.FrameworkTrace( + ctx, + "Called provider defined type-based SemanticEquals", + map[string]interface{}{ + logging.KeyValueType: proposedNewValuable.String(), + }, + ) + + resp.Diagnostics.Append(diags...) + + if !usePriorValue { + return + } + + resp.NewValue = priorValuable +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attr_type.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attr_type.go index 74ab45fdd..85dd3bff0 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attr_type.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attr_type.go @@ -145,3 +145,18 @@ func coerceStringTypable(ctx context.Context, schemaPath path.Path, valuable bas return typable, nil } + +func coerceDynamicTypable(ctx context.Context, schemaPath path.Path, valuable basetypes.DynamicValuable) (basetypes.DynamicTypable, diag.Diagnostics) { + typable, ok := valuable.Type(ctx).(basetypes.DynamicTypable) + + // Type() of a Valuable should always be a Typable to recreate the Valuable, + // but if for some reason it is not, raise an implementation error instead + // of a panic. + if !ok { + return nil, diag.Diagnostics{ + attributePlanModificationTypableError(schemaPath, valuable), + } + } + + return typable, nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attribute_plan_modification.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attribute_plan_modification.go index e73b208fb..36e86dec5 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attribute_plan_modification.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attribute_plan_modification.go @@ -108,6 +108,8 @@ func AttributeModifyPlan(ctx context.Context, a fwschema.Attribute, req ModifyAt AttributePlanModifySet(ctx, attributeWithPlanModifiers, req, resp) case fwxschema.AttributeWithStringPlanModifiers: AttributePlanModifyString(ctx, attributeWithPlanModifiers, req, resp) + case fwxschema.AttributeWithDynamicPlanModifiers: + AttributePlanModifyDynamic(ctx, attributeWithPlanModifiers, req, resp) } if resp.Diagnostics.HasError() { @@ -2119,6 +2121,166 @@ func AttributePlanModifyString(ctx context.Context, attribute fwxschema.Attribut } } +// AttributePlanModifyDynamic performs all types.Dynamic plan modification. +func AttributePlanModifyDynamic(ctx context.Context, attribute fwxschema.AttributeWithDynamicPlanModifiers, req ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) { + // Use basetypes.DynamicValuable until custom types cannot re-implement + // ValueFromTerraform. Until then, custom types are not technically + // required to implement this interface. This opts to enforce the + // requirement before compatibility promises would interfere. + configValuable, ok := req.AttributeConfig.(basetypes.DynamicValuable) + + if !ok { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid Dynamic Attribute Plan Modifier Value Type", + "An unexpected value type was encountered while attempting to perform Dynamic attribute plan modification. "+ + "The value type must implement the basetypes.DynamicValuable interface. "+ + "Please report this to the provider developers.\n\n"+ + fmt.Sprintf("Incoming Value Type: %T", req.AttributeConfig), + ) + + return + } + + configValue, diags := configValuable.ToDynamicValue(ctx) + + resp.Diagnostics.Append(diags...) + + // Only return early on new errors as the resp.Diagnostics may have errors + // from other attributes. + if diags.HasError() { + return + } + + planValuable, ok := req.AttributePlan.(basetypes.DynamicValuable) + + if !ok { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid Dynamic Attribute Plan Modifier Value Type", + "An unexpected value type was encountered while attempting to perform Dynamic attribute plan modification. "+ + "The value type must implement the basetypes.DynamicValuable interface. "+ + "Please report this to the provider developers.\n\n"+ + fmt.Sprintf("Incoming Value Type: %T", req.AttributePlan), + ) + + return + } + + planValue, diags := planValuable.ToDynamicValue(ctx) + + resp.Diagnostics.Append(diags...) + + // Only return early on new errors as the resp.Diagnostics may have errors + // from other attributes. + if diags.HasError() { + return + } + + stateValuable, ok := req.AttributeState.(basetypes.DynamicValuable) + + if !ok { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid Dynamic Attribute Plan Modifier Value Type", + "An unexpected value type was encountered while attempting to perform Dynamic attribute plan modification. "+ + "The value type must implement the basetypes.DynamicValuable interface. "+ + "Please report this to the provider developers.\n\n"+ + fmt.Sprintf("Incoming Value Type: %T", req.AttributeState), + ) + + return + } + + stateValue, diags := stateValuable.ToDynamicValue(ctx) + + resp.Diagnostics.Append(diags...) + + // Only return early on new errors as the resp.Diagnostics may have errors + // from other attributes. + if diags.HasError() { + return + } + + typable, diags := coerceDynamicTypable(ctx, req.AttributePath, planValuable) + + resp.Diagnostics.Append(diags...) + + // Only return early on new errors as the resp.Diagnostics may have errors + // from other attributes. + if diags.HasError() { + return + } + + planModifyReq := planmodifier.DynamicRequest{ + Config: req.Config, + ConfigValue: configValue, + Path: req.AttributePath, + PathExpression: req.AttributePathExpression, + Plan: req.Plan, + PlanValue: planValue, + Private: req.Private, + State: req.State, + StateValue: stateValue, + } + + for _, planModifier := range attribute.DynamicPlanModifiers() { + // Instantiate a new response for each request to prevent plan modifiers + // from modifying or removing diagnostics. + planModifyResp := &planmodifier.DynamicResponse{ + PlanValue: planModifyReq.PlanValue, + Private: resp.Private, + } + + logging.FrameworkTrace( + ctx, + "Calling provider defined planmodifier.Dynamic", + map[string]interface{}{ + logging.KeyDescription: planModifier.Description(ctx), + }, + ) + + planModifier.PlanModifyDynamic(ctx, planModifyReq, planModifyResp) + + logging.FrameworkTrace( + ctx, + "Called provider defined planmodifier.Dynamic", + map[string]interface{}{ + logging.KeyDescription: planModifier.Description(ctx), + }, + ) + + // Prepare next request with base type. + planModifyReq.PlanValue = planModifyResp.PlanValue + + resp.Diagnostics.Append(planModifyResp.Diagnostics...) + resp.Private = planModifyResp.Private + + if planModifyResp.RequiresReplace { + resp.RequiresReplace.Append(req.AttributePath) + } + + // Only on new errors. + if planModifyResp.Diagnostics.HasError() { + return + } + + // A custom value type must be returned in the final response to prevent + // later correctness errors. + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/754 + valuable, valueFromDiags := typable.ValueFromDynamic(ctx, planModifyResp.PlanValue) + + resp.Diagnostics.Append(valueFromDiags...) + + // Only on new errors. + if valueFromDiags.HasError() { + return + } + + resp.AttributePlan = valuable + } +} + func NestedAttributeObjectPlanModify(ctx context.Context, o fwschema.NestedAttributeObject, req planmodifier.ObjectRequest, resp *ModifyAttributePlanResponse) { if objectWithPlanModifiers, ok := o.(fwxschema.NestedAttributeObjectWithPlanModifiers); ok { for _, objectPlanModifier := range objectWithPlanModifiers.ObjectPlanModifiers() { diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attribute_validation.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attribute_validation.go index da5e5835c..1fa4883b0 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attribute_validation.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/attribute_validation.go @@ -127,17 +127,40 @@ func AttributeValidate(ctx context.Context, a fwschema.Attribute, req ValidateAt AttributeValidateSet(ctx, attributeWithValidators, req, resp) case fwxschema.AttributeWithStringValidators: AttributeValidateString(ctx, attributeWithValidators, req, resp) + case fwxschema.AttributeWithDynamicValidators: + AttributeValidateDynamic(ctx, attributeWithValidators, req, resp) } AttributeValidateNestedAttributes(ctx, a, req, resp) // Show deprecation warnings only for known values. if a.GetDeprecationMessage() != "" && !attributeConfig.IsNull() && !attributeConfig.IsUnknown() { - resp.Diagnostics.AddAttributeWarning( - req.AttributePath, - "Attribute Deprecated", - a.GetDeprecationMessage(), - ) + // Dynamic values need to perform more logic to check the config value for null/unknown-ness + dynamicValuable, ok := attributeConfig.(basetypes.DynamicValuable) + if !ok { + resp.Diagnostics.AddAttributeWarning( + req.AttributePath, + "Attribute Deprecated", + a.GetDeprecationMessage(), + ) + return + } + + dynamicConfigVal, diags := dynamicValuable.ToDynamicValue(ctx) + resp.Diagnostics.Append(diags...) + if diags.HasError() { + return + } + + // For dynamic values, it's possible to be known when only the type is known. + // The underlying value can still be null or unknown, so check for that here + if !dynamicConfigVal.IsUnderlyingValueNull() && !dynamicConfigVal.IsUnderlyingValueUnknown() { + resp.Diagnostics.AddAttributeWarning( + req.AttributePath, + "Attribute Deprecated", + a.GetDeprecationMessage(), + ) + } } } @@ -726,6 +749,71 @@ func AttributeValidateString(ctx context.Context, attribute fwxschema.AttributeW } } +// AttributeValidateDynamic performs all types.Dynamic validation. +func AttributeValidateDynamic(ctx context.Context, attribute fwxschema.AttributeWithDynamicValidators, req ValidateAttributeRequest, resp *ValidateAttributeResponse) { + // Use basetypes.DynamicValuable until custom types cannot re-implement + // ValueFromTerraform. Until then, custom types are not technically + // required to implement this interface. This opts to enforce the + // requirement before compatibility promises would interfere. + configValuable, ok := req.AttributeConfig.(basetypes.DynamicValuable) + + if !ok { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid Dynamic Attribute Validator Value Type", + "An unexpected value type was encountered while attempting to perform Dynamic attribute validation. "+ + "The value type must implement the basetypes.DynamicValuable interface. "+ + "Please report this to the provider developers.\n\n"+ + fmt.Sprintf("Incoming Value Type: %T", req.AttributeConfig), + ) + + return + } + + configValue, diags := configValuable.ToDynamicValue(ctx) + + resp.Diagnostics.Append(diags...) + + // Only return early on new errors as the resp.Diagnostics may have errors + // from other attributes. + if diags.HasError() { + return + } + + validateReq := validator.DynamicRequest{ + Config: req.Config, + ConfigValue: configValue, + Path: req.AttributePath, + PathExpression: req.AttributePathExpression, + } + + for _, attributeValidator := range attribute.DynamicValidators() { + // Instantiate a new response for each request to prevent validators + // from modifying or removing diagnostics. + validateResp := &validator.DynamicResponse{} + + logging.FrameworkTrace( + ctx, + "Calling provider defined validator.Dynamic", + map[string]interface{}{ + logging.KeyDescription: attributeValidator.Description(ctx), + }, + ) + + attributeValidator.ValidateDynamic(ctx, validateReq, validateResp) + + logging.FrameworkTrace( + ctx, + "Called provider defined validator.Dynamic", + map[string]interface{}{ + logging.KeyDescription: attributeValidator.Description(ctx), + }, + ) + + resp.Diagnostics.Append(validateResp.Diagnostics...) + } +} + // AttributeValidateNestedAttributes performs all nested Attributes validation. // // TODO: Clean up this abstraction back into an internal Attribute type method. diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_deleteresource.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_deleteresource.go index 01fdc635c..5879b6706 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_deleteresource.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_deleteresource.go @@ -101,7 +101,7 @@ func (s *Server) DeleteResource(ctx context.Context, req *DeleteResourceRequest, logging.FrameworkTrace(ctx, "Called provider defined Resource Delete") if !deleteResp.Diagnostics.HasError() { - logging.FrameworkTrace(ctx, "No provider defined Delete errors detected, ensuring State and Priavate are cleared") + logging.FrameworkTrace(ctx, "No provider defined Delete errors detected, ensuring State and Private are cleared") deleteResp.State.RemoveResource(ctx) // Preserve prior behavior of always returning nil. diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_functions.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_functions.go index c8e6142d2..37c87e7c8 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_functions.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_functions.go @@ -98,11 +98,17 @@ func (s *Server) FunctionDefinitions(ctx context.Context) (map[string]function.D continue } - validateDiags := definitionResp.Definition.ValidateImplementation(ctx) + validateReq := function.DefinitionValidateRequest{ + FuncName: name, + } + + validateResp := function.DefinitionValidateResponse{} + + definitionResp.Definition.ValidateImplementation(ctx, validateReq, &validateResp) - diags.Append(validateDiags...) + diags.Append(validateResp.Diagnostics...) - if validateDiags.HasError() { + if validateResp.Diagnostics.HasError() { continue } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_planresourcechange.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_planresourcechange.go index b183dcf3c..c0df76647 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_planresourcechange.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwserver/server_planresourcechange.go @@ -309,6 +309,32 @@ func MarkComputedNilsAsUnknown(ctx context.Context, config tftypes.Value, resour return val, nil } + attribute, err := resourceSchema.AttributeAtTerraformPath(ctx, path) + + if err != nil { + if errors.Is(err, fwschema.ErrPathInsideAtomicAttribute) { + // ignore attributes/elements inside schema.Attributes, they have no schema of their own + logging.FrameworkTrace(ctx, "attribute is a non-schema attribute, not marking unknown") + return val, nil + } + + if errors.Is(err, fwschema.ErrPathIsBlock) { + // ignore blocks, they do not have a computed field + logging.FrameworkTrace(ctx, "attribute is a block, not marking unknown") + return val, nil + } + + if errors.Is(err, fwschema.ErrPathInsideDynamicAttribute) { + // ignore attributes/elements inside schema.DynamicAttribute, they have no schema of their own + logging.FrameworkTrace(ctx, "attribute is inside of a dynamic attribute, not marking unknown") + return val, nil + } + + logging.FrameworkError(ctx, "couldn't find attribute in resource schema") + + return tftypes.Value{}, fmt.Errorf("couldn't find attribute in resource schema: %w", err) + } + configValIface, _, err := tftypes.WalkAttributePath(config, path) if err != nil && err != tftypes.ErrInvalidStep { @@ -331,26 +357,6 @@ func MarkComputedNilsAsUnknown(ctx context.Context, config tftypes.Value, resour return val, nil } - attribute, err := resourceSchema.AttributeAtTerraformPath(ctx, path) - - if err != nil { - if errors.Is(err, fwschema.ErrPathInsideAtomicAttribute) { - // ignore attributes/elements inside schema.Attributes, they have no schema of their own - logging.FrameworkTrace(ctx, "attribute is a non-schema attribute, not marking unknown") - return val, nil - } - - if errors.Is(err, fwschema.ErrPathIsBlock) { - // ignore blocks, they do not have a computed field - logging.FrameworkTrace(ctx, "attribute is a block, not marking unknown") - return val, nil - } - - logging.FrameworkError(ctx, "couldn't find attribute in resource schema") - - return tftypes.Value{}, fmt.Errorf("couldn't find attribute in resource schema: %w", err) - } - if !attribute.IsComputed() { logging.FrameworkTrace(ctx, "attribute is not computed in schema, not marking unknown") @@ -394,6 +400,10 @@ func MarkComputedNilsAsUnknown(ctx context.Context, config tftypes.Value, resour if a.StringDefaultValue() != nil { return val, nil } + case fwschema.AttributeWithDynamicDefaultValue: + if a.DynamicDefaultValue() != nil { + return val, nil + } } logging.FrameworkDebug(ctx, "marking computed attribute that is null in the config as unknown") diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwtype/doc.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwtype/doc.go new file mode 100644 index 000000000..3cd75b75e --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwtype/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package fwtype implements shared logic for interacting with the framework type system. +package fwtype diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwtype/static_collection_validation.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwtype/static_collection_validation.go new file mode 100644 index 000000000..2a91e2fbb --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/fwtype/static_collection_validation.go @@ -0,0 +1,142 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwtype + +import ( + "fmt" + + "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/types/basetypes" +) + +// ContainsCollectionWithDynamic will return true if an attr.Type is a complex type that either is or contains any +// collection types with dynamic types, which are not supported by the framework type system. Primitives, invalid +// types (missingType), or nil will return false. +// +// Unsupported collection types include: +// - Lists that contain a dynamic type +// - Maps that contain a dynamic type +// - Sets that contain a dynamic type +func ContainsCollectionWithDynamic(typ attr.Type) bool { + switch attrType := typ.(type) { + // We haven't run into a collection type yet, so it's valid for this to be a dynamic type + case basetypes.DynamicTypable: + return false + // Lists, maps, sets + case attr.TypeWithElementType: + // We found a collection, need to ensure there are no dynamics from this point on. + return containsDynamic(attrType.ElementType()) + // Tuples + case attr.TypeWithElementTypes: + for _, elemType := range attrType.ElementTypes() { + hasDynamic := ContainsCollectionWithDynamic(elemType) + if hasDynamic { + return true + } + } + return false + // Objects + case attr.TypeWithAttributeTypes: + for _, objAttrType := range attrType.AttributeTypes() { + hasDynamic := ContainsCollectionWithDynamic(objAttrType) + if hasDynamic { + return true + } + } + return false + // Primitives, missing types, etc. + default: + return false + } +} + +// containsDynamic will return true if `typ` is a dynamic type or has any nested types that contain a dynamic type. +func containsDynamic(typ attr.Type) bool { + switch attrType := typ.(type) { + // Found a dynamic! + case basetypes.DynamicTypable: + return true + // Lists, maps, sets + case attr.TypeWithElementType: + return containsDynamic(attrType.ElementType()) + // Tuples + case attr.TypeWithElementTypes: + for _, elemType := range attrType.ElementTypes() { + hasDynamic := containsDynamic(elemType) + if hasDynamic { + return true + } + } + return false + // Objects + case attr.TypeWithAttributeTypes: + for _, objAttrType := range attrType.AttributeTypes() { + hasDynamic := containsDynamic(objAttrType) + if hasDynamic { + return true + } + } + return false + // Primitives, missing types, etc. + default: + return false + } +} + +func AttributeCollectionWithDynamicTypeDiag(attributePath path.Path) diag.Diagnostic { + return diag.NewErrorDiagnostic( + "Invalid Schema Implementation", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("%q is an attribute that contains a collection type with a nested dynamic type.\n\n", attributePath)+ + "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ + fmt.Sprintf("If underlying dynamic values are required, replace the %q attribute definition with DynamicAttribute instead.", attributePath), + ) +} + +func BlockCollectionWithDynamicTypeDiag(attributePath path.Path) diag.Diagnostic { + return diag.NewErrorDiagnostic( + "Invalid Schema Implementation", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("%q is a block that contains a collection type with a nested dynamic type.\n\n", attributePath)+ + "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ + fmt.Sprintf("If underlying dynamic values are required, replace the %q block definition with a DynamicAttribute.", attributePath), + ) +} + +func ParameterCollectionWithDynamicTypeDiag(argument int64, name string) diag.Diagnostic { + return diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Parameter %q at position %d contains a collection type with a nested dynamic type.\n\n", name, argument)+ + "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ + fmt.Sprintf("If underlying dynamic values are required, replace the %q parameter definition with DynamicParameter instead.", name), + ) +} + +func VariadicParameterCollectionWithDynamicTypeDiag(name string) diag.Diagnostic { + return diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Variadic parameter %q contains a collection type with a nested dynamic type.\n\n", name)+ + "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ + "If underlying dynamic values are required, replace the variadic parameter definition with DynamicParameter instead.", + ) +} + +func ReturnCollectionWithDynamicTypeDiag() diag.Diagnostic { + return diag.NewErrorDiagnostic( + "Invalid Function Definition", + "When validating the function definition, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "Return contains a collection type with a nested dynamic type.\n\n"+ + "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ + "If underlying dynamic values are required, replace the return definition with DynamicReturn instead.", + ) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/reflect/into.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/reflect/into.go index 5cc92d3ca..5615b8b87 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/reflect/into.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/reflect/into.go @@ -148,6 +148,20 @@ func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target re return target, diags } + + // Dynamic reflection is currently only supported using an `attr.Value`, which should have happened in logic above. + if typ.TerraformType(ctx).Is(tftypes.DynamicPseudoType) { + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "Reflection for dynamic types is currently not supported. Use the corresponding `types` package type or a custom type that handles dynamic values.\n\n"+ + fmt.Sprintf("Path: %s\nTarget Type: %s\nSuggested `types` Type: %s", path.String(), target.Type(), reflect.TypeOf(typ.ValueType(ctx))), + ) + + return target, diags + } + // *big.Float and *big.Int are technically pointers, but we want them // handled as numbers if target.Type() == reflect.TypeOf(big.NewFloat(0)) || target.Type() == reflect.TypeOf(big.NewInt(0)) { diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/reflect/struct.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/reflect/struct.go index da646b85d..ab654c834 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/reflect/struct.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/reflect/struct.go @@ -234,8 +234,17 @@ func FromStruct(ctx context.Context, typ attr.TypeWithAttributeTypes, val reflec } } + tfObjTyp := tfObjVal.Type() + + // If the original attribute type is tftypes.DynamicPseudoType, the value could end up being + // a concrete type (like tftypes.String, tftypes.List, etc.). In this scenario, the type used + // to build the final tftypes.Object must stay as tftypes.DynamicPseudoType + if attrTypes[name].TerraformType(ctx).Is(tftypes.DynamicPseudoType) { + tfObjTyp = tftypes.DynamicPseudoType + } + objValues[name] = tfObjVal - objTypes[name] = tfObjVal.Type() + objTypes[name] = tfObjTyp } tfVal := tftypes.NewValue(tftypes.Object{ diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/toproto5/function.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/toproto5/function.go index 7b4cfc071..5ee2e16e2 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/toproto5/function.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/toproto5/function.go @@ -5,7 +5,6 @@ package toproto5 import ( "context" - "fmt" "github.com/hashicorp/terraform-plugin-go/tfprotov5" @@ -30,25 +29,13 @@ func Function(ctx context.Context, fw function.Definition) *tfprotov5.Function { proto.DescriptionKind = tfprotov5.StringKindPlain } - for i, fwParameter := range fw.Parameters { + for _, fwParameter := range fw.Parameters { protoParam := FunctionParameter(ctx, fwParameter) - - // If name is not set, default the param name based on position: "param1", "param2", etc. - if protoParam.Name == "" { - protoParam.Name = fmt.Sprintf("%s%d", function.DefaultParameterNamePrefix, i+1) - } - proto.Parameters = append(proto.Parameters, protoParam) } if fw.VariadicParameter != nil { protoParam := FunctionParameter(ctx, fw.VariadicParameter) - - // If name is not set, default the variadic param name - if protoParam.Name == "" { - protoParam.Name = function.DefaultVariadicParameterName - } - proto.VariadicParameter = protoParam } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/toproto6/function.go b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/toproto6/function.go index 90925be10..70edabf2e 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/internal/toproto6/function.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/internal/toproto6/function.go @@ -5,7 +5,6 @@ package toproto6 import ( "context" - "fmt" "github.com/hashicorp/terraform-plugin-go/tfprotov6" @@ -30,25 +29,13 @@ func Function(ctx context.Context, fw function.Definition) *tfprotov6.Function { proto.DescriptionKind = tfprotov6.StringKindPlain } - for i, fwParameter := range fw.Parameters { + for _, fwParameter := range fw.Parameters { protoParam := FunctionParameter(ctx, fwParameter) - - // If name is not set, default the param name based on position: "param1", "param2", etc. - if protoParam.Name == "" { - protoParam.Name = fmt.Sprintf("%s%d", function.DefaultParameterNamePrefix, i+1) - } - proto.Parameters = append(proto.Parameters, protoParam) } if fw.VariadicParameter != nil { protoParam := FunctionParameter(ctx, fw.VariadicParameter) - - // If name is not set, default the variadic param name - if protoParam.Name == "" { - protoParam.Name = function.DefaultVariadicParameterName - } - proto.VariadicParameter = protoParam } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/dynamic_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/dynamic_attribute.go new file mode 100644 index 000000000..c738d348d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/dynamic_attribute.go @@ -0,0 +1,177 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "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" +) + +// Ensure the implementation satisifies the desired interfaces. +var ( + _ Attribute = DynamicAttribute{} + _ fwxschema.AttributeWithDynamicValidators = DynamicAttribute{} +) + +// DynamicAttribute represents a schema attribute that is a dynamic, rather +// than a single static type. Static types are always preferable over dynamic +// types in Terraform as practitioners will receive less helpful configuration +// assistance from validation error diagnostics and editor integrations. When +// retrieving the value for this attribute, use types.Dynamic as the value type +// unless the CustomType field is set. +// +// The concrete value type for a dynamic is determined at runtime by Terraform, +// if defined in the configuration. +type DynamicAttribute struct { + // CustomType enables the use of a custom attribute type in place of the + // default basetypes.DynamicType. When retrieving data, the basetypes.DynamicValuable + // associated with this custom type must be used in place of types.Dynamic. + CustomType basetypes.DynamicTypable + + // Required indicates whether the practitioner must enter a value for + // this attribute or not. Required and Optional cannot both be true, + // and Required and Computed cannot both be true. + Required bool + + // Optional indicates whether the practitioner can choose to enter a value + // for this attribute or not. Optional and Required cannot both be true. + Optional bool + + // Sensitive indicates whether the value of this attribute should be + // considered sensitive data. Setting it to true will obscure the value + // in CLI output. Sensitive does not impact how values are stored, and + // practitioners are encouraged to store their state as if the entire + // file is sensitive. + Sensitive bool + + // Description is used in various tooling, like the language server, to + // give practitioners more information about what this attribute is, + // what it's for, and how it should be used. It should be written as + // plain text, with no special formatting. + Description string + + // MarkdownDescription is used in various tooling, like the + // documentation generator, to give practitioners more information + // about what this attribute is, what it's for, and how it should be + // used. It should be formatted using Markdown. + MarkdownDescription string + + // DeprecationMessage defines warning diagnostic details to display when + // practitioner configurations use this Attribute. The warning diagnostic + // summary is automatically set to "Attribute Deprecated" along with + // configuration source file and line information. + // + // Set this field to a practitioner actionable message such as: + // + // - "Configure other_attribute instead. This attribute will be removed + // in the next major version of the provider." + // - "Remove this attribute's configuration as it no longer is used and + // the attribute will be removed in the next major version of the + // provider." + // + // In Terraform 1.2.7 and later, this warning diagnostic is displayed any + // time a practitioner attempts to configure a value for this attribute and + // certain scenarios where this attribute is referenced. + // + // In Terraform 1.2.6 and earlier, this warning diagnostic is only + // displayed when the Attribute is Required or Optional, and if the + // practitioner configuration sets the value to a known or unknown value + // (which may eventually be null). It has no effect when the Attribute is + // Computed-only (read-only; not Required or Optional). + // + // Across any Terraform version, there are no warnings raised for + // practitioner configuration values set directly to null, as there is no + // way for the framework to differentiate between an unset and null + // configuration due to how Terraform sends configuration information + // across the protocol. + // + // Additional information about deprecation enhancements for read-only + // attributes can be found in: + // + // - https://github.com/hashicorp/terraform/issues/7569 + // + DeprecationMessage string + + // Validators define value validation functionality for the attribute. All + // elements of the slice of AttributeValidator are run, regardless of any + // previous error diagnostics. + // + // Many common use case validators can be found in the + // github.com/hashicorp/terraform-plugin-framework-validators Go module. + // + // If the Type field points to a custom type that implements the + // xattr.TypeWithValidate interface, the validators defined in this field + // are run in addition to the validation defined by the type. + Validators []validator.Dynamic +} + +// ApplyTerraform5AttributePathStep always returns an error as it is not +// possible to step further into a DynamicAttribute. +func (a DynamicAttribute) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { + return a.GetType().ApplyTerraform5AttributePathStep(step) +} + +// Equal returns true if the given Attribute is a DynamicAttribute +// and all fields are equal. +func (a DynamicAttribute) Equal(o fwschema.Attribute) bool { + if _, ok := o.(DynamicAttribute); !ok { + return false + } + + return fwschema.AttributesEqual(a, o) +} + +// GetDeprecationMessage returns the DeprecationMessage field value. +func (a DynamicAttribute) GetDeprecationMessage() string { + return a.DeprecationMessage +} + +// GetDescription returns the Description field value. +func (a DynamicAttribute) GetDescription() string { + return a.Description +} + +// GetMarkdownDescription returns the MarkdownDescription field value. +func (a DynamicAttribute) GetMarkdownDescription() string { + return a.MarkdownDescription +} + +// GetType returns types.DynamicType or the CustomType field value if defined. +func (a DynamicAttribute) GetType() attr.Type { + if a.CustomType != nil { + return a.CustomType + } + + return types.DynamicType +} + +// IsComputed always returns false as provider schemas cannot be Computed. +func (a DynamicAttribute) IsComputed() bool { + return false +} + +// IsOptional returns the Optional field value. +func (a DynamicAttribute) IsOptional() bool { + return a.Optional +} + +// IsRequired returns the Required field value. +func (a DynamicAttribute) IsRequired() bool { + return a.Required +} + +// IsSensitive returns the Sensitive field value. +func (a DynamicAttribute) IsSensitive() bool { + return a.Sensitive +} + +// DynamicValidators returns the Validators field value. +func (a DynamicAttribute) DynamicValidators() []validator.Dynamic { + return a.Validators +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_attribute.go index fe4cf3c18..e733b297f 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_attribute.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -44,6 +45,10 @@ var ( type ListAttribute struct { // ElementType is the type for all elements of the list. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. ElementType attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -203,4 +208,8 @@ func (a ListAttribute) ValidateImplementation(ctx context.Context, req fwschema. if a.CustomType == nil && a.ElementType == nil { resp.Diagnostics.Append(fwschema.AttributeMissingElementTypeDiag(req.Path)) } + + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_nested_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_nested_attribute.go index 965f991d9..0c82da4ad 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_nested_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_nested_attribute.go @@ -4,11 +4,13 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -17,8 +19,9 @@ import ( // Ensure the implementation satisifies the desired interfaces. var ( - _ NestedAttribute = ListNestedAttribute{} - _ fwxschema.AttributeWithListValidators = ListNestedAttribute{} + _ NestedAttribute = ListNestedAttribute{} + _ fwschema.AttributeWithValidateImplementation = ListNestedAttribute{} + _ fwxschema.AttributeWithListValidators = ListNestedAttribute{} ) // ListNestedAttribute represents an attribute that is a list of objects where @@ -51,6 +54,10 @@ var ( type ListNestedAttribute struct { // NestedObject is the underlying object that contains nested attributes. // This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. NestedObject NestedAttributeObject // CustomType enables the use of a custom attribute type in place of the @@ -220,3 +227,13 @@ func (a ListNestedAttribute) IsSensitive() bool { func (a ListNestedAttribute) ListValidators() []validator.List { return a.Validators } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the attribute to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (a ListNestedAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_nested_block.go b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_nested_block.go index 0722d54d4..4d098bc2d 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_nested_block.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/list_nested_block.go @@ -4,11 +4,13 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -17,8 +19,9 @@ import ( // Ensure the implementation satisifies the desired interfaces. var ( - _ Block = ListNestedBlock{} - _ fwxschema.BlockWithListValidators = ListNestedBlock{} + _ Block = ListNestedBlock{} + _ fwschema.BlockWithValidateImplementation = ListNestedBlock{} + _ fwxschema.BlockWithListValidators = ListNestedBlock{} ) // ListNestedBlock represents a block that is a list of objects where @@ -55,6 +58,10 @@ var ( type ListNestedBlock struct { // NestedObject is the underlying object that contains nested attributes or // blocks. This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this block definition with + // a DynamicAttribute. NestedObject NestedBlockObject // CustomType enables the use of a custom attribute type in place of the @@ -186,3 +193,13 @@ func (b ListNestedBlock) Type() attr.Type { ElemType: b.NestedObject.Type(), } } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the block to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (b ListNestedBlock) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if b.CustomType == nil && fwtype.ContainsCollectionWithDynamic(b.Type()) { + resp.Diagnostics.Append(fwtype.BlockCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/map_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/map_attribute.go index 4a3d800a6..079535e77 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/map_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/map_attribute.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -47,6 +48,10 @@ var ( type MapAttribute struct { // ElementType is the type for all elements of the map. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. ElementType attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -206,4 +211,8 @@ func (a MapAttribute) ValidateImplementation(ctx context.Context, req fwschema.V if a.CustomType == nil && a.ElementType == nil { resp.Diagnostics.Append(fwschema.AttributeMissingElementTypeDiag(req.Path)) } + + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/map_nested_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/map_nested_attribute.go index 44da39c63..0cdc9b5e0 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/map_nested_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/map_nested_attribute.go @@ -4,6 +4,7 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -52,6 +54,10 @@ var ( type MapNestedAttribute struct { // NestedObject is the underlying object that contains nested attributes. // This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. NestedObject NestedAttributeObject // CustomType enables the use of a custom attribute type in place of the @@ -221,3 +227,13 @@ func (a MapNestedAttribute) IsSensitive() bool { func (a MapNestedAttribute) MapValidators() []validator.Map { return a.Validators } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the attribute to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (a MapNestedAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/object_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/object_attribute.go index c69445656..3041f5a79 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/object_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/object_attribute.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -46,6 +47,10 @@ var ( type ObjectAttribute struct { // AttributeTypes is the mapping of underlying attribute names to attribute // types. This field must be set. + // + // Attribute types that contain a collection with a nested dynamic type (i.e. types.List[types.Dynamic]) are not supported. + // If underlying dynamic collection values are required, replace this attribute definition with + // DynamicAttribute instead. AttributeTypes map[string]attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -205,4 +210,8 @@ func (a ObjectAttribute) ValidateImplementation(ctx context.Context, req fwschem if a.AttributeTypes == nil && a.CustomType == nil { resp.Diagnostics.Append(fwschema.AttributeMissingAttributeTypesDiag(req.Path)) } + + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_attribute.go index 9d8cfe9d4..3297452b7 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_attribute.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -42,6 +43,10 @@ var ( type SetAttribute struct { // ElementType is the type for all elements of the set. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. ElementType attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -201,4 +206,8 @@ func (a SetAttribute) ValidateImplementation(ctx context.Context, req fwschema.V if a.CustomType == nil && a.ElementType == nil { resp.Diagnostics.Append(fwschema.AttributeMissingElementTypeDiag(req.Path)) } + + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_nested_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_nested_attribute.go index 1d02f3ce3..64fed1ea2 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_nested_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_nested_attribute.go @@ -4,6 +4,7 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-go/tftypes" @@ -11,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -18,8 +20,9 @@ import ( // Ensure the implementation satisifies the desired interfaces. var ( - _ NestedAttribute = SetNestedAttribute{} - _ fwxschema.AttributeWithSetValidators = SetNestedAttribute{} + _ NestedAttribute = SetNestedAttribute{} + _ fwschema.AttributeWithValidateImplementation = SetNestedAttribute{} + _ fwxschema.AttributeWithSetValidators = SetNestedAttribute{} ) // SetNestedAttribute represents an attribute that is a set of objects where @@ -47,6 +50,10 @@ var ( type SetNestedAttribute struct { // NestedObject is the underlying object that contains nested attributes. // This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. NestedObject NestedAttributeObject // CustomType enables the use of a custom attribute type in place of the @@ -216,3 +223,13 @@ func (a SetNestedAttribute) IsSensitive() bool { func (a SetNestedAttribute) SetValidators() []validator.Set { return a.Validators } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the attribute to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (a SetNestedAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_nested_block.go b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_nested_block.go index 8e89ff944..085163f37 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_nested_block.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/provider/schema/set_nested_block.go @@ -4,11 +4,13 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -17,8 +19,9 @@ import ( // Ensure the implementation satisifies the desired interfaces. var ( - _ Block = SetNestedBlock{} - _ fwxschema.BlockWithSetValidators = SetNestedBlock{} + _ Block = SetNestedBlock{} + _ fwschema.BlockWithValidateImplementation = SetNestedBlock{} + _ fwxschema.BlockWithSetValidators = SetNestedBlock{} ) // SetNestedBlock represents a block that is a set of objects where @@ -55,6 +58,10 @@ var ( type SetNestedBlock struct { // NestedObject is the underlying object that contains nested attributes or // blocks. This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this block definition with + // a DynamicAttribute. NestedObject NestedBlockObject // CustomType enables the use of a custom attribute type in place of the @@ -186,3 +193,13 @@ func (b SetNestedBlock) Type() attr.Type { ElemType: b.NestedObject.Type(), } } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the block to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (b SetNestedBlock) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if b.CustomType == nil && fwtype.ContainsCollectionWithDynamic(b.Type()) { + resp.Diagnostics.Append(fwtype.BlockCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/resource.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/resource.go index 1d395fc10..7113d4bf5 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/resource.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/resource.go @@ -65,8 +65,8 @@ type ResourceWithConfigure interface { Resource // Configure enables provider-level data or clients to be set in the - // provider-defined DataSource type. It is separately executed for each - // ReadDataSource RPC. + // provider-defined Resource type. It is separately executed for each + // ReadResource RPC. Configure(context.Context, ConfigureRequest, *ConfigureResponse) } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults/dynamic.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults/dynamic.go new file mode 100644 index 000000000..313a3d699 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults/dynamic.go @@ -0,0 +1,36 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package defaults + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Dynamic is a schema default value for types.Dynamic attributes. +type Dynamic interface { + Describer + + // DefaultDynamic should set the default value. + DefaultDynamic(context.Context, DynamicRequest, *DynamicResponse) +} + +type DynamicRequest struct { + // Path contains the path of the attribute for setting the + // default value. Use this path for any response diagnostics. + Path path.Path +} + +type DynamicResponse struct { + // Diagnostics report errors or warnings related to setting the + // default value resource configuration. An empty slice + // indicates success, with no warnings or errors generated. + Diagnostics diag.Diagnostics + + // PlanValue is the planned new state for the attribute. + PlanValue types.Dynamic +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults/string.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults/string.go index acef6a0a3..90aa822a1 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults/string.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults/string.go @@ -15,18 +15,18 @@ import ( type String interface { Describer - // DefaultString should String the default value. + // DefaultString should set the default value. DefaultString(context.Context, StringRequest, *StringResponse) } type StringRequest struct { - // Path contains the path of the attribute for Stringting the + // Path contains the path of the attribute for setting the // default value. Use this path for any response diagnostics. Path path.Path } type StringResponse struct { - // Diagnostics report errors or warnings related to Stringting the + // Diagnostics report errors or warnings related to setting the // default value resource configuration. An empty slice // indicates success, with no warnings or errors generated. Diagnostics diag.Diagnostics diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamic_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamic_attribute.go new file mode 100644 index 000000000..7b97625d9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamic_attribute.go @@ -0,0 +1,241 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" + "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" +) + +// Ensure the implementation satisfies the desired interfaces. +var ( + _ Attribute = DynamicAttribute{} + _ fwschema.AttributeWithValidateImplementation = DynamicAttribute{} + _ fwschema.AttributeWithDynamicDefaultValue = DynamicAttribute{} + _ fwxschema.AttributeWithDynamicPlanModifiers = DynamicAttribute{} + _ fwxschema.AttributeWithDynamicValidators = DynamicAttribute{} +) + +// DynamicAttribute represents a schema attribute that is a dynamic, rather +// than a single static type. Static types are always preferable over dynamic +// types in Terraform as practitioners will receive less helpful configuration +// assistance from validation error diagnostics and editor integrations. When +// retrieving the value for this attribute, use types.Dynamic as the value type +// unless the CustomType field is set. +// +// The concrete value type for a dynamic is determined at runtime in this order: +// 1. By Terraform, if defined in the configuration (if Required or Optional). +// 2. By the provider (if Computed). +// +// Once the concrete value type has been determined, it must remain consistent between +// plan and apply or Terraform will return an error. +type DynamicAttribute struct { + // CustomType enables the use of a custom attribute type in place of the + // default basetypes.DynamicType. When retrieving data, the basetypes.DynamicValuable + // associated with this custom type must be used in place of types.Dynamic. + CustomType basetypes.DynamicTypable + + // Required indicates whether the practitioner must enter a value for + // this attribute or not. Required and Optional cannot both be true, + // and Required and Computed cannot both be true. + Required bool + + // Optional indicates whether the practitioner can choose to enter a value + // for this attribute or not. Optional and Required cannot both be true. + Optional bool + + // Computed indicates whether the provider may return its own value for + // this Attribute or not. Required and Computed cannot both be true. If + // Required and Optional are both false, Computed must be true, and the + // attribute will be considered "read only" for the practitioner, with + // only the provider able to set its value. + Computed bool + + // Sensitive indicates whether the value of this attribute should be + // considered sensitive data. Setting it to true will obscure the value + // in CLI output. Sensitive does not impact how values are stored, and + // practitioners are encouraged to store their state as if the entire + // file is sensitive. + Sensitive bool + + // Description is used in various tooling, like the language server, to + // give practitioners more information about what this attribute is, + // what it's for, and how it should be used. It should be written as + // plain text, with no special formatting. + Description string + + // MarkdownDescription is used in various tooling, like the + // documentation generator, to give practitioners more information + // about what this attribute is, what it's for, and how it should be + // used. It should be formatted using Markdown. + MarkdownDescription string + + // DeprecationMessage defines warning diagnostic details to display when + // practitioner configurations use this Attribute. The warning diagnostic + // summary is automatically set to "Attribute Deprecated" along with + // configuration source file and line information. + // + // Set this field to a practitioner actionable message such as: + // + // - "Configure other_attribute instead. This attribute will be removed + // in the next major version of the provider." + // - "Remove this attribute's configuration as it no longer is used and + // the attribute will be removed in the next major version of the + // provider." + // + // In Terraform 1.2.7 and later, this warning diagnostic is displayed any + // time a practitioner attempts to configure a value for this attribute and + // certain scenarios where this attribute is referenced. + // + // In Terraform 1.2.6 and earlier, this warning diagnostic is only + // displayed when the Attribute is Required or Optional, and if the + // practitioner configuration sets the value to a known or unknown value + // (which may eventually be null). It has no effect when the Attribute is + // Computed-only (read-only; not Required or Optional). + // + // Across any Terraform version, there are no warnings raised for + // practitioner configuration values set directly to null, as there is no + // way for the framework to differentiate between an unset and null + // configuration due to how Terraform sends configuration information + // across the protocol. + // + // Additional information about deprecation enhancements for read-only + // attributes can be found in: + // + // - https://github.com/hashicorp/terraform/issues/7569 + // + DeprecationMessage string + + // Validators define value validation functionality for the attribute. All + // elements of the slice of AttributeValidator are run, regardless of any + // previous error diagnostics. + // + // Many common use case validators can be found in the + // github.com/hashicorp/terraform-plugin-framework-validators Go module. + // + // If the Type field points to a custom type that implements the + // xattr.TypeWithValidate interface, the validators defined in this field + // are run in addition to the validation defined by the type. + Validators []validator.Dynamic + + // PlanModifiers defines a sequence of modifiers for this attribute at + // plan time. Schema-based plan modifications occur before any + // resource-level plan modifications. + // + // Schema-based plan modifications can adjust Terraform's plan by: + // + // - Requiring resource recreation. Typically used for configuration + // updates which cannot be done in-place. + // - Setting the planned value. Typically used for enhancing the plan + // to replace unknown values. Computed must be true or Terraform will + // return an error. If the plan value is known due to a known + // configuration value, the plan value cannot be changed or Terraform + // will return an error. + // + // Any errors will prevent further execution of this sequence or modifiers. + PlanModifiers []planmodifier.Dynamic + + // Default defines a proposed new state (plan) value for the attribute + // if the configuration value is null. Default prevents the framework + // from automatically marking the value as unknown during planning when + // other proposed new state changes are detected. If the attribute is + // computed and the value could be altered by other changes then a default + // should be avoided and a plan modifier should be used instead. + Default defaults.Dynamic +} + +// ApplyTerraform5AttributePathStep always returns an error as it is not +// possible to step further into a DynamicAttribute. +func (a DynamicAttribute) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { + return a.GetType().ApplyTerraform5AttributePathStep(step) +} + +// Equal returns true if the given Attribute is a DynamicAttribute +// and all fields are equal. +func (a DynamicAttribute) Equal(o fwschema.Attribute) bool { + if _, ok := o.(DynamicAttribute); !ok { + return false + } + + return fwschema.AttributesEqual(a, o) +} + +// GetDeprecationMessage returns the DeprecationMessage field value. +func (a DynamicAttribute) GetDeprecationMessage() string { + return a.DeprecationMessage +} + +// GetDescription returns the Description field value. +func (a DynamicAttribute) GetDescription() string { + return a.Description +} + +// GetMarkdownDescription returns the MarkdownDescription field value. +func (a DynamicAttribute) GetMarkdownDescription() string { + return a.MarkdownDescription +} + +// GetType returns types.DynamicType or the CustomType field value if defined. +func (a DynamicAttribute) GetType() attr.Type { + if a.CustomType != nil { + return a.CustomType + } + + return types.DynamicType +} + +// IsComputed returns the Computed field value. +func (a DynamicAttribute) IsComputed() bool { + return a.Computed +} + +// IsOptional returns the Optional field value. +func (a DynamicAttribute) IsOptional() bool { + return a.Optional +} + +// IsRequired returns the Required field value. +func (a DynamicAttribute) IsRequired() bool { + return a.Required +} + +// IsSensitive returns the Sensitive field value. +func (a DynamicAttribute) IsSensitive() bool { + return a.Sensitive +} + +// DynamicDefaultValue returns the Default field value. +func (a DynamicAttribute) DynamicDefaultValue() defaults.Dynamic { + return a.Default +} + +// DynamicPlanModifiers returns the PlanModifiers field value. +func (a DynamicAttribute) DynamicPlanModifiers() []planmodifier.Dynamic { + return a.PlanModifiers +} + +// DynamicValidators returns the Validators field value. +func (a DynamicAttribute) DynamicValidators() []validator.Dynamic { + return a.Validators +} + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the attribute to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (a DynamicAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if !a.IsComputed() && a.DynamicDefaultValue() != nil { + resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_attribute.go index d033e8175..1dc0e0e8c 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_attribute.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -49,6 +50,10 @@ var ( type ListAttribute struct { // ElementType is the type for all elements of the list. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. ElementType attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -251,6 +256,10 @@ func (a ListAttribute) ValidateImplementation(ctx context.Context, req fwschema. resp.Diagnostics.Append(fwschema.AttributeMissingElementTypeDiag(req.Path)) } + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } + if a.ListDefaultValue() != nil { if !a.IsComputed() { resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path)) diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_nested_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_nested_attribute.go index fb18c62f3..95fd2ba01 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_nested_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_nested_attribute.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -58,6 +59,10 @@ var ( type ListNestedAttribute struct { // NestedObject is the underlying object that contains nested attributes. // This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. NestedObject NestedAttributeObject // CustomType enables the use of a custom attribute type in place of the @@ -275,6 +280,10 @@ func (a ListNestedAttribute) ListValidators() []validator.List { // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (a ListNestedAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } + if a.ListDefaultValue() != nil { if !a.IsComputed() { resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path)) diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_nested_block.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_nested_block.go index e105d1164..b82cfea65 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_nested_block.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/list_nested_block.go @@ -4,11 +4,13 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" @@ -18,9 +20,10 @@ import ( // Ensure the implementation satisifies the desired interfaces. var ( - _ Block = ListNestedBlock{} - _ fwxschema.BlockWithListPlanModifiers = ListNestedBlock{} - _ fwxschema.BlockWithListValidators = ListNestedBlock{} + _ Block = ListNestedBlock{} + _ fwschema.BlockWithValidateImplementation = ListNestedBlock{} + _ fwxschema.BlockWithListPlanModifiers = ListNestedBlock{} + _ fwxschema.BlockWithListValidators = ListNestedBlock{} ) // ListNestedBlock represents a block that is a list of objects where @@ -57,6 +60,10 @@ var ( type ListNestedBlock struct { // NestedObject is the underlying object that contains nested attributes or // blocks. This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this block definition with + // a DynamicAttribute. NestedObject NestedBlockObject // CustomType enables the use of a custom attribute type in place of the @@ -210,3 +217,13 @@ func (b ListNestedBlock) Type() attr.Type { ElemType: b.NestedObject.Type(), } } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the block to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (b ListNestedBlock) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if b.CustomType == nil && fwtype.ContainsCollectionWithDynamic(b.Type()) { + resp.Diagnostics.Append(fwtype.BlockCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/doc.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/doc.go new file mode 100644 index 000000000..22fa0a61e --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package listplanmodifier provides plan modifiers for types.List attributes. +package listplanmodifier diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace.go new file mode 100644 index 000000000..eecf57bb4 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package listplanmodifier + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +// RequiresReplace returns a plan modifier that conditionally requires +// resource replacement if: +// +// - The resource is planned for update. +// - The plan and state values are not equal. +// +// Use RequiresReplaceIfConfigured if the resource replacement should +// only occur if there is a configuration value (ignore unconfigured drift +// detection changes). Use RequiresReplaceIf if the resource replacement +// should check provider-defined conditional logic. +func RequiresReplace() planmodifier.List { + return RequiresReplaceIf( + func(_ context.Context, _ planmodifier.ListRequest, resp *RequiresReplaceIfFuncResponse) { + resp.RequiresReplace = true + }, + "If the value of this attribute changes, Terraform will destroy and recreate the resource.", + "If the value of this attribute changes, Terraform will destroy and recreate the resource.", + ) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace_if.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace_if.go new file mode 100644 index 000000000..840c5223b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace_if.go @@ -0,0 +1,73 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package listplanmodifier + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +// RequiresReplaceIf returns a plan modifier that conditionally requires +// resource replacement if: +// +// - The resource is planned for update. +// - The plan and state values are not equal. +// - The given function returns true. Returning false will not unset any +// prior resource replacement. +// +// Use RequiresReplace if the resource replacement should always occur on value +// changes. Use RequiresReplaceIfConfigured if the resource replacement should +// occur on value changes, but only if there is a configuration value (ignore +// unconfigured drift detection changes). +func RequiresReplaceIf(f RequiresReplaceIfFunc, description, markdownDescription string) planmodifier.List { + return requiresReplaceIfModifier{ + ifFunc: f, + description: description, + markdownDescription: markdownDescription, + } +} + +// requiresReplaceIfModifier is an plan modifier that sets RequiresReplace +// on the attribute if a given function is true. +type requiresReplaceIfModifier struct { + ifFunc RequiresReplaceIfFunc + description string + markdownDescription string +} + +// Description returns a human-readable description of the plan modifier. +func (m requiresReplaceIfModifier) Description(_ context.Context) string { + return m.description +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m requiresReplaceIfModifier) MarkdownDescription(_ context.Context) string { + return m.markdownDescription +} + +// PlanModifyList implements the plan modification logic. +func (m requiresReplaceIfModifier) PlanModifyList(ctx context.Context, req planmodifier.ListRequest, resp *planmodifier.ListResponse) { + // Do not replace on resource creation. + if req.State.Raw.IsNull() { + return + } + + // Do not replace on resource destroy. + if req.Plan.Raw.IsNull() { + return + } + + // Do not replace if the plan and state values are equal. + if req.PlanValue.Equal(req.StateValue) { + return + } + + ifFuncResp := &RequiresReplaceIfFuncResponse{} + + m.ifFunc(ctx, req, ifFuncResp) + + resp.Diagnostics.Append(ifFuncResp.Diagnostics...) + resp.RequiresReplace = ifFuncResp.RequiresReplace +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace_if_configured.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace_if_configured.go new file mode 100644 index 000000000..81ffdb3d1 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace_if_configured.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package listplanmodifier + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +// RequiresReplaceIfConfigured returns a plan modifier that conditionally requires +// resource replacement if: +// +// - The resource is planned for update. +// - The plan and state values are not equal. +// - The configuration value is not null. +// +// Use RequiresReplace if the resource replacement should occur regardless of +// the presence of a configuration value. Use RequiresReplaceIf if the resource +// replacement should check provider-defined conditional logic. +func RequiresReplaceIfConfigured() planmodifier.List { + return RequiresReplaceIf( + func(_ context.Context, req planmodifier.ListRequest, resp *RequiresReplaceIfFuncResponse) { + if req.ConfigValue.IsNull() { + return + } + + resp.RequiresReplace = true + }, + "If the value of this attribute is configured and changes, Terraform will destroy and recreate the resource.", + "If the value of this attribute is configured and changes, Terraform will destroy and recreate the resource.", + ) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace_if_func.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace_if_func.go new file mode 100644 index 000000000..e6dabd6c2 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/requires_replace_if_func.go @@ -0,0 +1,25 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package listplanmodifier + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +// RequiresReplaceIfFunc is a conditional function used in the RequiresReplaceIf +// plan modifier to determine whether the attribute requires replacement. +type RequiresReplaceIfFunc func(context.Context, planmodifier.ListRequest, *RequiresReplaceIfFuncResponse) + +// RequiresReplaceIfFuncResponse is the response type for a RequiresReplaceIfFunc. +type RequiresReplaceIfFuncResponse struct { + // Diagnostics report errors or warnings related to this logic. An empty + // or unset slice indicates success, with no warnings or errors generated. + Diagnostics diag.Diagnostics + + // RequiresReplace should be enabled if the resource should be replaced. + RequiresReplace bool +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/use_state_for_unknown.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/use_state_for_unknown.go new file mode 100644 index 000000000..c8b2f3bf5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier/use_state_for_unknown.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package listplanmodifier + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +// UseStateForUnknown returns a plan modifier that copies a known prior state +// value into the planned value. Use this when it is known that an unconfigured +// value will remain the same after a resource update. +// +// To prevent Terraform errors, the framework automatically sets unconfigured +// and Computed attributes to an unknown value "(known after apply)" on update. +// Using this plan modifier will instead display the prior state value in the +// plan, unless a prior plan modifier adjusts the value. +func UseStateForUnknown() planmodifier.List { + return useStateForUnknownModifier{} +} + +// useStateForUnknownModifier implements the plan modifier. +type useStateForUnknownModifier struct{} + +// Description returns a human-readable description of the plan modifier. +func (m useStateForUnknownModifier) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m useStateForUnknownModifier) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifyList implements the plan modification logic. +func (m useStateForUnknownModifier) PlanModifyList(_ context.Context, req planmodifier.ListRequest, resp *planmodifier.ListResponse) { + // Do nothing if there is no state value. + if req.StateValue.IsNull() { + return + } + + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up. + if req.ConfigValue.IsUnknown() { + return + } + + resp.PlanValue = req.StateValue +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/map_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/map_attribute.go index 8268b57c9..ea08fa7b2 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/map_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/map_attribute.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -52,6 +53,10 @@ var ( type MapAttribute struct { // ElementType is the type for all elements of the map. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. ElementType attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -254,6 +259,10 @@ func (a MapAttribute) ValidateImplementation(ctx context.Context, req fwschema.V resp.Diagnostics.Append(fwschema.AttributeMissingElementTypeDiag(req.Path)) } + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } + if a.MapDefaultValue() != nil { if !a.IsComputed() { resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path)) diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/map_nested_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/map_nested_attribute.go index daebc4359..faa1f3276 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/map_nested_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/map_nested_attribute.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -58,6 +59,10 @@ var ( type MapNestedAttribute struct { // NestedObject is the underlying object that contains nested attributes. // This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. NestedObject NestedAttributeObject // CustomType enables the use of a custom attribute type in place of the @@ -275,6 +280,10 @@ func (a MapNestedAttribute) MapValidators() []validator.Map { // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (a MapNestedAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } + if a.MapDefaultValue() != nil { if !a.IsComputed() { resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path)) diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/object_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/object_attribute.go index 93cddfe9a..7b9fe6a56 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/object_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/object_attribute.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -51,6 +52,10 @@ var ( type ObjectAttribute struct { // AttributeTypes is the mapping of underlying attribute names to attribute // types. This field must be set. + // + // Attribute types that contain a collection with a nested dynamic type (i.e. types.List[types.Dynamic]) are not supported. + // If underlying dynamic collection values are required, replace this attribute definition with + // DynamicAttribute instead. AttributeTypes map[string]attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -253,6 +258,10 @@ func (a ObjectAttribute) ValidateImplementation(ctx context.Context, req fwschem resp.Diagnostics.Append(fwschema.AttributeMissingAttributeTypesDiag(req.Path)) } + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } + if a.ObjectDefaultValue() != nil { if !a.IsComputed() { resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path)) diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/bool.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/bool.go index e97053ace..9b60038b9 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/bool.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/bool.go @@ -82,7 +82,7 @@ type BoolResponse struct { Private *privatestate.ProviderData // Diagnostics report errors or warnings related to modifying the resource - // configuration. An empty slice indicates success, with no warnings or + // plan. An empty slice indicates success, with no warnings or // errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/dynamic.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/dynamic.go new file mode 100644 index 000000000..cbf9a7758 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/dynamic.go @@ -0,0 +1,88 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package planmodifier + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/privatestate" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Dynamic is a schema plan modifier for types.Dynamic attributes. +type Dynamic interface { + Describer + + // PlanModifyDynamic should perform the modification. + PlanModifyDynamic(context.Context, DynamicRequest, *DynamicResponse) +} + +// DynamicRequest is a request for types.Dynamic schema plan modification. +type DynamicRequest struct { + // Path contains the path of the attribute for modification. Use this path + // for any response diagnostics. + Path path.Path + + // PathExpression contains the expression matching the exact path + // of the attribute for modification. + PathExpression path.Expression + + // Config contains the entire configuration of the resource. + Config tfsdk.Config + + // ConfigValue contains the value of the attribute for modification from the configuration. + ConfigValue types.Dynamic + + // Plan contains the entire proposed new state of the resource. + Plan tfsdk.Plan + + // PlanValue contains the value of the attribute for modification from the proposed new state. + PlanValue types.Dynamic + + // State contains the entire prior state of the resource. + State tfsdk.State + + // StateValue contains the value of the attribute for modification from the prior state. + StateValue types.Dynamic + + // Private is provider-defined resource private state data which was previously + // stored with the resource state. This data is opaque to Terraform and does + // not affect plan output. Any existing data is copied to + // DynamicResponse.Private to prevent accidental private state data loss. + // + // The private state data is always the original data when the schema-based plan + // modification began or, is updated as the logic traverses deeper into underlying + // attributes. + // + // Use the GetKey method to read data. Use the SetKey method on + // DynamicResponse.Private to update or remove a value. + Private *privatestate.ProviderData +} + +// DynamicResponse is a response to a DynamicRequest. +type DynamicResponse struct { + // PlanValue is the planned new state for the attribute. + PlanValue types.Dynamic + + // RequiresReplace indicates whether a change in the attribute + // requires replacement of the whole resource. + RequiresReplace bool + + // Private is the private state resource data following the PlanModifyDynamic operation. + // This field is pre-populated from DynamicRequest.Private and + // can be modified during the resource's PlanModifyDynamic operation. + // + // The private state data is always the original data when the schema-based plan + // modification began or, is updated as the logic traverses deeper into underlying + // attributes. + Private *privatestate.ProviderData + + // Diagnostics report errors or warnings related to modifying the resource + // plan. An empty slice indicates success, with no warnings or + // errors generated. + Diagnostics diag.Diagnostics +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/float64.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/float64.go index 534fd4f43..971586b36 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/float64.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/float64.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// Float64 is a schema validator for types.Float64 attributes. +// Float64 is a schema plan modifier for types.Float64 attributes. type Float64 interface { Describer @@ -81,8 +81,8 @@ type Float64Response struct { // attributes. Private *privatestate.ProviderData - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings - // or errors generated. + // Diagnostics report errors or warnings related to modifying the resource + // plan. An empty slice indicates success, with no warnings or + // errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/int64.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/int64.go index 7df16c594..f65d63b5a 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/int64.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/int64.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// Int64 is a schema validator for types.Int64 attributes. +// Int64 is a schema plan modifier for types.Int64 attributes. type Int64 interface { Describer @@ -81,8 +81,8 @@ type Int64Response struct { // attributes. Private *privatestate.ProviderData - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings - // or errors generated. + // Diagnostics report errors or warnings related to modifying the resource + // plan. An empty slice indicates success, with no warnings or + // errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/list.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/list.go index 53e77ff12..dfbff6a81 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/list.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/list.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// List is a schema validator for types.List attributes. +// List is a schema plan modifier for types.List attributes. type List interface { Describer @@ -81,8 +81,8 @@ type ListResponse struct { // attributes. Private *privatestate.ProviderData - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings - // or errors generated. + // Diagnostics report errors or warnings related to modifying the resource + // plan. An empty slice indicates success, with no warnings or + // errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/map.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/map.go index bf7d7501a..5969d5ded 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/map.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/map.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// Map is a schema validator for types.Map attributes. +// Map is a schema plan modifier for types.Map attributes. type Map interface { Describer @@ -81,8 +81,8 @@ type MapResponse struct { // attributes. Private *privatestate.ProviderData - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings - // or errors generated. + // Diagnostics report errors or warnings related to modifying the resource + // plan. An empty slice indicates success, with no warnings or + // errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/number.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/number.go index d50a6c766..44978912a 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/number.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/number.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// Number is a schema validator for types.Number attributes. +// Number is a schema plan modifier for types.Number attributes. type Number interface { Describer @@ -81,8 +81,8 @@ type NumberResponse struct { // attributes. Private *privatestate.ProviderData - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings - // or errors generated. + // Diagnostics report errors or warnings related to modifying the resource + // plan. An empty slice indicates success, with no warnings or + // errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/object.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/object.go index bae7b2224..1f426d735 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/object.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/object.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// Object is a schema validator for types.Object attributes. +// Object is a schema plan modifier for types.Object attributes. type Object interface { Describer @@ -81,8 +81,8 @@ type ObjectResponse struct { // attributes. Private *privatestate.ProviderData - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings - // or errors generated. + // Diagnostics report errors or warnings related to modifying the resource + // plan. An empty slice indicates success, with no warnings or + // errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/set.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/set.go index 6aeb2977d..21e157a78 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/set.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/set.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// Set is a schema validator for types.Set attributes. +// Set is a schema plan modifier for types.Set attributes. type Set interface { Describer @@ -81,8 +81,8 @@ type SetResponse struct { // attributes. Private *privatestate.ProviderData - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings - // or errors generated. + // Diagnostics report errors or warnings related to modifying the resource + // plan. An empty slice indicates success, with no warnings or + // errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/string.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/string.go index d72774f2d..b8c938c81 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/string.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier/string.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -// String is a schema validator for types.String attributes. +// String is a schema plan modifier for types.String attributes. type String interface { Describer @@ -81,8 +81,8 @@ type StringResponse struct { // attributes. Private *privatestate.ProviderData - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings - // or errors generated. + // Diagnostics report errors or warnings related to modifying the resource + // plan. An empty slice indicates success, with no warnings or + // errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_attribute.go index 80d551131..7a54221bb 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_attribute.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -47,6 +48,10 @@ var ( type SetAttribute struct { // ElementType is the type for all elements of the set. This field must be // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. ElementType attr.Type // CustomType enables the use of a custom attribute type in place of the @@ -249,6 +254,10 @@ func (a SetAttribute) ValidateImplementation(ctx context.Context, req fwschema.V resp.Diagnostics.Append(fwschema.AttributeMissingElementTypeDiag(req.Path)) } + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } + if a.SetDefaultValue() != nil { if !a.IsComputed() { resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path)) diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_nested_attribute.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_nested_attribute.go index 191991604..3f2b3d1bb 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_nested_attribute.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_nested_attribute.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -53,6 +54,10 @@ var ( type SetNestedAttribute struct { // NestedObject is the underlying object that contains nested attributes. // This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. NestedObject NestedAttributeObject // CustomType enables the use of a custom attribute type in place of the @@ -270,6 +275,10 @@ func (a SetNestedAttribute) SetValidators() []validator.Set { // errors or panics. This logic runs during the GetProviderSchema RPC and // should never include false positives. func (a SetNestedAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } + if a.SetDefaultValue() != nil { if !a.IsComputed() { resp.Diagnostics.Append(nonComputedAttributeWithDefaultDiag(req.Path)) diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_nested_block.go b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_nested_block.go index 025cb15a8..78de8afb1 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_nested_block.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/resource/schema/set_nested_block.go @@ -4,11 +4,13 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" @@ -18,9 +20,10 @@ import ( // Ensure the implementation satisifies the desired interfaces. var ( - _ Block = SetNestedBlock{} - _ fwxschema.BlockWithSetPlanModifiers = SetNestedBlock{} - _ fwxschema.BlockWithSetValidators = SetNestedBlock{} + _ Block = SetNestedBlock{} + _ fwschema.BlockWithValidateImplementation = SetNestedBlock{} + _ fwxschema.BlockWithSetPlanModifiers = SetNestedBlock{} + _ fwxschema.BlockWithSetValidators = SetNestedBlock{} ) // SetNestedBlock represents a block that is a set of objects where @@ -57,6 +60,10 @@ var ( type SetNestedBlock struct { // NestedObject is the underlying object that contains nested attributes or // blocks. This field must be set. + // + // Nested attributes that contain a dynamic type (i.e. DynamicAttribute) are not supported. + // If underlying dynamic values are required, replace this block definition with + // a DynamicAttribute. NestedObject NestedBlockObject // CustomType enables the use of a custom attribute type in place of the @@ -210,3 +217,13 @@ func (b SetNestedBlock) Type() attr.Type { ElemType: b.NestedObject.Type(), } } + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the block to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (b SetNestedBlock) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if b.CustomType == nil && fwtype.ContainsCollectionWithDynamic(b.Type()) { + resp.Diagnostics.Append(fwtype.BlockCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/bool.go b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/bool.go index 32d38ec8e..64115e713 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/bool.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/bool.go @@ -39,8 +39,8 @@ type BoolRequest struct { // BoolResponse is a response to a BoolRequest. type BoolResponse struct { - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings + // Diagnostics report errors or warnings related to validating the data source, provider, or resource + // configuration. An empty slice indicates success, with no warnings // or errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/dynamic.go b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/dynamic.go new file mode 100644 index 000000000..b035175a1 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/dynamic.go @@ -0,0 +1,46 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package validator + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Dynamic is a schema validator for types.Dynamic attributes. +type Dynamic interface { + Describer + + // ValidateDynamic should perform the validation. + ValidateDynamic(context.Context, DynamicRequest, *DynamicResponse) +} + +// DynamicRequest is a request for types.Dynamic schema validation. +type DynamicRequest struct { + // Path contains the path of the attribute for validation. Use this path + // for any response diagnostics. + Path path.Path + + // PathExpression contains the expression matching the exact path + // of the attribute for validation. + PathExpression path.Expression + + // Config contains the entire configuration of the data source, provider, or resource. + Config tfsdk.Config + + // ConfigValue contains the value of the attribute for validation from the configuration. + ConfigValue types.Dynamic +} + +// DynamicResponse is a response to a DynamicRequest. +type DynamicResponse struct { + // Diagnostics report errors or warnings related to validating the data source, provider, or resource + // configuration. An empty slice indicates success, with no warnings + // or errors generated. + Diagnostics diag.Diagnostics +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/float64.go b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/float64.go index ed28150a7..f09111ac7 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/float64.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/float64.go @@ -39,8 +39,8 @@ type Float64Request struct { // Float64Response is a response to a Float64Request. type Float64Response struct { - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings + // Diagnostics report errors or warnings related to validating the data source, provider, or resource + // configuration. An empty slice indicates success, with no warnings // or errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/int64.go b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/int64.go index 6e5228622..8e8accdcb 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/int64.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/int64.go @@ -39,8 +39,8 @@ type Int64Request struct { // Int64Response is a response to a Int64Request. type Int64Response struct { - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings + // Diagnostics report errors or warnings related to validating the data source, provider, or resource + // configuration. An empty slice indicates success, with no warnings // or errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/list.go b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/list.go index ed05df1b1..e5b6083d8 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/list.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/list.go @@ -39,8 +39,8 @@ type ListRequest struct { // ListResponse is a response to a ListRequest. type ListResponse struct { - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings + // Diagnostics report errors or warnings related to validating the data source, provider, or resource + // configuration. An empty slice indicates success, with no warnings // or errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/map.go b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/map.go index 644a13b44..2a41cc7ac 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/map.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/map.go @@ -39,8 +39,8 @@ type MapRequest struct { // MapResponse is a response to a MapRequest. type MapResponse struct { - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings + // Diagnostics report errors or warnings related to validating the data source, provider, or resource + // configuration. An empty slice indicates success, with no warnings // or errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/number.go b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/number.go index bce7c5224..ef7692c20 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/number.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/number.go @@ -39,8 +39,8 @@ type NumberRequest struct { // NumberResponse is a response to a NumberRequest. type NumberResponse struct { - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings + // Diagnostics report errors or warnings related to validating the data source, provider, or resource + // configuration. An empty slice indicates success, with no warnings // or errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/object.go b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/object.go index 62e004e9e..88029e0ad 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/object.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/object.go @@ -39,8 +39,8 @@ type ObjectRequest struct { // ObjectResponse is a response to a ObjectRequest. type ObjectResponse struct { - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings + // Diagnostics report errors or warnings related to validating the data source, provider, or resource + // configuration. An empty slice indicates success, with no warnings // or errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/set.go b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/set.go index a8e3653c2..ce7cdea34 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/set.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/set.go @@ -39,8 +39,8 @@ type SetRequest struct { // SetResponse is a response to a SetRequest. type SetResponse struct { - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings + // Diagnostics report errors or warnings related to validating the data source, provider, or resource + // configuration. An empty slice indicates success, with no warnings // or errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/string.go b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/string.go index e9d31a314..b453a7bfc 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/string.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/schema/validator/string.go @@ -39,8 +39,8 @@ type StringRequest struct { // StringResponse is a response to a StringRequest. type StringResponse struct { - // Diagnostics report errors or warnings related to validating the data - // source configuration. An empty slice indicates success, with no warnings + // Diagnostics report errors or warnings related to validating the data source, provider, or resource + // configuration. An empty slice indicates success, with no warnings // or errors generated. Diagnostics diag.Diagnostics } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/dynamic_type.go b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/dynamic_type.go new file mode 100644 index 000000000..87138984a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/dynamic_type.go @@ -0,0 +1,198 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package basetypes + +import ( + "context" + "errors" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// DynamicTypable extends attr.Type for dynamic types. Implement this interface to create a custom DynamicType type. +type DynamicTypable interface { + attr.Type + + // ValueFromDynamic should convert the DynamicValue to a DynamicValuable type. + ValueFromDynamic(context.Context, DynamicValue) (DynamicValuable, diag.Diagnostics) +} + +var _ DynamicTypable = DynamicType{} + +// DynamicType is the base framework type for a dynamic. Static types are always +// preferable over dynamic types in Terraform as practitioners will receive less +// helpful configuration assistance from validation error diagnostics and editor +// integrations. +// +// DynamicValue is the associated value type and, when known, contains a concrete +// value of another framework type. (StringValue, ListValue, ObjectValue, MapValue, etc.) +type DynamicType struct{} + +// ApplyTerraform5AttributePathStep applies the given AttributePathStep to the type. +func (t DynamicType) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { + // MAINTAINER NOTE: Based on dynamic type alone, there is no alternative type information to return related to a path step. + // When working with dynamics, we should always use DynamicValue to determine underlying type information. + return nil, fmt.Errorf("cannot apply AttributePathStep %T to %s", step, t.String()) +} + +// Equal returns true if the given type is equivalent. +// +// Dynamic types do not contain a reference to the underlying `attr.Value` type, so this equality check +// only asserts that both types are DynamicType. +func (t DynamicType) Equal(o attr.Type) bool { + _, ok := o.(DynamicType) + + return ok +} + +// String returns a human-friendly description of the DynamicType. +func (t DynamicType) String() string { + return "basetypes.DynamicType" +} + +// TerraformType returns the tftypes.Type that should be used to represent this type. +func (t DynamicType) TerraformType(ctx context.Context) tftypes.Type { + return tftypes.DynamicPseudoType +} + +// ValueFromDynamic returns a DynamicValuable type given a DynamicValue. +func (t DynamicType) ValueFromDynamic(ctx context.Context, v DynamicValue) (DynamicValuable, diag.Diagnostics) { + return v, nil +} + +// ValueFromTerraform returns an attr.Value given a tftypes.Value. This is meant to convert +// the tftypes.Value into a more convenient Go type for the provider to consume the data with. +func (t DynamicType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewDynamicNull(), nil + } + + // For dynamic values, it's possible the incoming value is unknown but the concrete type itself is known. In this + // situation, we can't return a dynamic unknown as we will lose that concrete type information. If the type here + // is not dynamic, then we use the concrete `(attr.Type).ValueFromTerraform` below to produce the unknown value. + if !in.IsKnown() && in.Type().Is(tftypes.DynamicPseudoType) { + return NewDynamicUnknown(), nil + } + + // For dynamic values, it's possible the incoming value is null but the concrete type itself is known. In this + // situation, we can't return a dynamic null as we will lose that concrete type information. If the type here + // is not dynamic, then we use the concrete `(attr.Type).ValueFromTerraform` below to produce the null value. + if in.IsNull() && in.Type().Is(tftypes.DynamicPseudoType) { + return NewDynamicNull(), nil + } + + // MAINTAINER NOTE: It should not be possible for Terraform core to send a known value of `tftypes.DynamicPseudoType`. + // This check prevents an infinite recursion that would result if this scenario occurs when attempting to create a dynamic value. + if in.Type().Is(tftypes.DynamicPseudoType) { + return nil, errors.New("ambiguous known value for `tftypes.DynamicPseudoType` detected") + } + + attrType, err := tftypeToFrameworkType(in.Type()) + if err != nil { + return nil, err + } + + val, err := attrType.ValueFromTerraform(ctx, in) + if err != nil { + return nil, err + } + + return NewDynamicValue(val), nil +} + +// ValueType returns the Value type. +func (t DynamicType) ValueType(_ context.Context) attr.Value { + return DynamicValue{} +} + +// tftypeToFrameworkType is a helper function that returns the framework type equivalent for a given Terraform type. +// +// Custom dynamic type implementations shouldn't need to override this method, but if needed, they can implement similar logic +// in their `ValueFromTerraform` implementation. +func tftypeToFrameworkType(in tftypes.Type) (attr.Type, error) { + // Primitive types + if in.Is(tftypes.Bool) { + return BoolType{}, nil + } + if in.Is(tftypes.Number) { + return NumberType{}, nil + } + if in.Is(tftypes.String) { + return StringType{}, nil + } + + if in.Is(tftypes.DynamicPseudoType) { + // Null and Unknown values that do not have a type determined will have a type of DynamicPseudoType + return DynamicType{}, nil + } + + // Collection types + if in.Is(tftypes.List{}) { + //nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function + l := in.(tftypes.List) + + elemType, err := tftypeToFrameworkType(l.ElementType) + if err != nil { + return nil, err + } + return ListType{ElemType: elemType}, nil + } + if in.Is(tftypes.Map{}) { + //nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function + m := in.(tftypes.Map) + + elemType, err := tftypeToFrameworkType(m.ElementType) + if err != nil { + return nil, err + } + + return MapType{ElemType: elemType}, nil + } + if in.Is(tftypes.Set{}) { + //nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function + s := in.(tftypes.Set) + + elemType, err := tftypeToFrameworkType(s.ElementType) + if err != nil { + return nil, err + } + + return SetType{ElemType: elemType}, nil + } + + // Structural types + if in.Is(tftypes.Object{}) { + //nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function + o := in.(tftypes.Object) + + attrTypes := make(map[string]attr.Type, len(o.AttributeTypes)) + for name, tfType := range o.AttributeTypes { + t, err := tftypeToFrameworkType(tfType) + if err != nil { + return nil, err + } + attrTypes[name] = t + } + return ObjectType{AttrTypes: attrTypes}, nil + } + if in.Is(tftypes.Tuple{}) { + //nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function + tup := in.(tftypes.Tuple) + + elemTypes := make([]attr.Type, len(tup.ElementTypes)) + for i, tfType := range tup.ElementTypes { + t, err := tftypeToFrameworkType(tfType) + if err != nil { + return nil, err + } + elemTypes[i] = t + } + return TupleType{ElemTypes: elemTypes}, nil + } + + return nil, fmt.Errorf("unsupported tftypes.Type detected: %T", in) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/dynamic_value.go b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/dynamic_value.go new file mode 100644 index 000000000..07946e18c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/dynamic_value.go @@ -0,0 +1,197 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package basetypes + +import ( + "context" + "errors" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +var ( + _ DynamicValuable = DynamicValue{} +) + +// DynamicValuable extends attr.Value for dynamic value types. Implement this interface +// to create a custom Dynamic value type. +type DynamicValuable interface { + attr.Value + + // ToDynamicValue should convert the value type to a DynamicValue. + ToDynamicValue(context.Context) (DynamicValue, diag.Diagnostics) +} + +// DynamicValuableWithSemanticEquals extends DynamicValuable with semantic equality logic. +type DynamicValuableWithSemanticEquals interface { + DynamicValuable + + // DynamicSemanticEquals should return true if the given value is + // semantically equal to the current value. This logic is used to prevent + // Terraform data consistency errors and resource drift where a value change + // may have inconsequential differences. + // + // Only known values are compared with this method as changing a value's + // state implicitly represents a different value. + DynamicSemanticEquals(context.Context, DynamicValuable) (bool, diag.Diagnostics) +} + +// NewDynamicValue creates a Dynamic with a known value. Access the value via the Dynamic +// type UnderlyingValue method. The concrete value type returned to Terraform from this value +// will be determined by the provided `(attr.Value).ToTerraformValue` function. +func NewDynamicValue(value attr.Value) DynamicValue { + if value == nil { + return NewDynamicNull() + } + + return DynamicValue{ + value: value, + state: attr.ValueStateKnown, + } +} + +// NewDynamicNull creates a Dynamic with a null value. The concrete value type returned to Terraform +// from this value will be tftypes.DynamicPseudoType. +func NewDynamicNull() DynamicValue { + return DynamicValue{ + state: attr.ValueStateNull, + } +} + +// NewDynamicUnknown creates a Dynamic with an unknown value. The concrete value type returned to Terraform +// from this value will be tftypes.DynamicPseudoType. +func NewDynamicUnknown() DynamicValue { + return DynamicValue{ + state: attr.ValueStateUnknown, + } +} + +// DynamicValue represents a dynamic value. Static types are always +// preferable over dynamic types in Terraform as practitioners will receive less +// helpful configuration assistance from validation error diagnostics and editor +// integrations. +type DynamicValue struct { + // value contains the known value, if not null or unknown. + value attr.Value + + // state represents whether the value is null, unknown, or known. The + // zero-value is null. + state attr.ValueState +} + +// Type returns DynamicType. +func (v DynamicValue) Type(ctx context.Context) attr.Type { + return DynamicType{} +} + +// ToTerraformValue returns the equivalent tftypes.Value for the DynamicValue. +func (v DynamicValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + switch v.state { + case attr.ValueStateKnown: + if v.value == nil { + return tftypes.NewValue(tftypes.DynamicPseudoType, tftypes.UnknownValue), + errors.New("invalid Dynamic state in ToTerraformValue: DynamicValue is known but the underlying value is unset") + } + + return v.value.ToTerraformValue(ctx) + case attr.ValueStateNull: + return tftypes.NewValue(tftypes.DynamicPseudoType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(tftypes.DynamicPseudoType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Dynamic state in ToTerraformValue: %s", v.state)) + } +} + +// Equal returns true if the given attr.Value is also a DynamicValue and contains an equal underlying value as defined by its Equal method. +func (v DynamicValue) Equal(o attr.Value) bool { + other, ok := o.(DynamicValue) + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + // Prevent panic and force inequality if either underlying value is nil + if v.value == nil || other.value == nil { + return false + } + + return v.value.Equal(other.value) +} + +// IsNull returns true if the DynamicValue represents a null value. +func (v DynamicValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +// IsUnknown returns true if the DynamicValue represents an unknown value. +func (v DynamicValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +// String returns a human-readable representation of the DynamicValue. The string returned here is not protected by any compatibility guarantees, +// and is intended for logging and error reporting. +func (v DynamicValue) String() string { + if v.IsUnknown() { + return attr.UnknownValueString + } + + if v.IsNull() { + return attr.NullValueString + } + + if v.value == nil { + return attr.UnsetValueString + } + + return v.value.String() +} + +// ToDynamicValue returns DynamicValue. +func (v DynamicValue) ToDynamicValue(ctx context.Context) (DynamicValue, diag.Diagnostics) { + return v, nil +} + +// UnderlyingValue returns the concrete underlying value in the DynamicValue. This will return `nil` +// if DynamicValue is null or unknown. +// +// A known DynamicValue can have an underlying value that is in null or unknown state in the +// scenario that the underlying value type has been refined by Terraform. +func (v DynamicValue) UnderlyingValue() attr.Value { + return v.value +} + +// IsUnderlyingValueNull is a helper method that return true only in the case where the underlying value has a +// known type but the value is null. This method will return false if the underlying type is not known. +// +// IsNull should be used to determine if the dynamic value does not have a known type and the value is null. +// +// An example of a known type with a null underlying value would be: +// +// types.DynamicValue(types.StringNull()) +func (v DynamicValue) IsUnderlyingValueNull() bool { + return v.value != nil && v.value.IsNull() +} + +// IsUnderlyingValueUnknown is a helper method that return true only in the case where the underlying value has a +// known type but the value is unknown. This method will return false if the underlying type is not known. +// +// IsUnknown should be used to determine if the dynamic value does not have a known type and the value is unknown. +// +// An example of a known type with an unknown underlying value would be: +// +// types.DynamicValue(types.StringUnknown()) +func (v DynamicValue) IsUnderlyingValueUnknown() bool { + return v.value != nil && v.value.IsUnknown() +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/list_type.go b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/list_type.go index 146f3a4fb..b97378d40 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/list_type.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/list_type.go @@ -65,6 +65,20 @@ func (l ListType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (att if in.Type() == nil { return NewListNull(l.ElementType()), nil } + + // MAINTAINER NOTE: + // ListType does not support DynamicType as an element type. It is not explicitly prevented from being created with the + // Framework type system, but the Framework-supported ListAttribute, ListNestedAttribute, and ListNestedBlock all prevent DynamicType + // from being used as an element type. An attempt to use DynamicType as the element type will eventually lead you to an error on this line :) + // + // In the future, if we ever need to support a list of dynamic element types, this type equality check will need to be modified to allow + // dynamic types to not return an error, as the tftypes.Value coming in (if known) will be a concrete value, for example: + // + // - l.TerraformType(ctx): tftypes.List[tftypes.DynamicPseudoType] + // - in.Type(): tftypes.List[tftypes.String] + // + // The `ValueFromTerraform` function for a dynamic type will be able create the correct concrete dynamic value with this modification in place. + // if !in.Type().Equal(l.TerraformType(ctx)) { return nil, fmt.Errorf("can't use %s as value of List with ElementType %T, can only use %s values", in.String(), l.ElementType(), l.ElementType().TerraformType(ctx).String()) } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/list_value.go b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/list_value.go index b37145c66..f89cbb73f 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/list_value.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/list_value.go @@ -209,6 +209,19 @@ func (l ListValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) switch l.state { case attr.ValueStateKnown: + // MAINTAINER NOTE: + // ListValue does not support DynamicType as an element type. It is not explicitly prevented from being created with the + // Framework type system, but the Framework-supported ListAttribute, ListNestedAttribute, and ListNestedBlock all prevent DynamicType + // from being used as an element type. + // + // In the future, if we ever need to support a list of dynamic element types, this tftypes.List creation logic will need to be modified to ensure + // that known values contain the exact same concrete element type, specifically with unknown and null values. Dynamic values will return the correct concrete + // element type for known values from `elem.ToTerraformValue`, but unknown and null values will be tftypes.DynamicPseudoType, causing an error due to multiple element + // types in a tftypes.List. + // + // Unknown and null element types of tftypes.DynamicPseudoType must be recreated as the concrete element type unknown/null value. This can be done by checking `l.elements` + // for a single concrete type (i.e. not tftypes.DynamicPseudoType), and using that concrete type to create unknown and null dynamic values later. + // vals := make([]tftypes.Value, 0, len(l.elements)) for _, elem := range l.elements { diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/map_type.go b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/map_type.go index 0c356f67f..03d2f7ce2 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/map_type.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/map_type.go @@ -68,6 +68,20 @@ func (m MapType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr if !in.Type().Is(tftypes.Map{}) { return nil, fmt.Errorf("can't use %s as value of MapValue, can only use tftypes.Map values", in.String()) } + + // MAINTAINER NOTE: + // MapType does not support DynamicType as an element type. It is not explicitly prevented from being created with the + // Framework type system, but the Framework-supported MapAttribute and MapNestedAttribute prevent DynamicType + // from being used as an element type. An attempt to use DynamicType as the element type will eventually lead you to an error on this line :) + // + // In the future, if we ever need to support a map of dynamic element types, this type equality check will need to be modified to allow + // dynamic types to not return an error, as the tftypes.Value coming in (if known) will be a concrete value, for example: + // + // - m.TerraformType(ctx): tftypes.Map[tftypes.DynamicPseudoType] + // - in.Type(): tftypes.Map[tftypes.String] + // + // The `ValueFromTerraform` function for a dynamic type will be able create the correct concrete dynamic value with this modification in place. + // if !in.Type().Equal(tftypes.Map{ElementType: m.ElementType().TerraformType(ctx)}) { return nil, fmt.Errorf("can't use %s as value of Map with ElementType %T, can only use %s values", in.String(), m.ElementType(), m.ElementType().TerraformType(ctx).String()) } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/map_value.go b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/map_value.go index 9fcbfbdd3..56a64361c 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/map_value.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/map_value.go @@ -216,6 +216,19 @@ func (m MapValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { switch m.state { case attr.ValueStateKnown: + // MAINTAINER NOTE: + // MapValue does not support DynamicType as an element type. It is not explicitly prevented from being created with the + // Framework type system, but the Framework-supported MapAttribute and MapNestedAttribute prevent DynamicType + // from being used as an element type. + // + // In the future, if we ever need to support a map of dynamic element types, this tftypes.Map creation logic will need to be modified to ensure + // that known values contain the exact same concrete element type, specifically with unknown and null values. Dynamic values will return the correct concrete + // element type for known values from `elem.ToTerraformValue`, but unknown and null values will be tftypes.DynamicPseudoType, causing an error due to multiple element + // types in a tftypes.Map. + // + // Unknown and null element types of tftypes.DynamicPseudoType must be recreated as the concrete element type unknown/null value. This can be done by checking `m.elements` + // for a single concrete type (i.e. not tftypes.DynamicPseudoType), and using that concrete type to create unknown and null dynamic values later. + // vals := make(map[string]tftypes.Value, len(m.elements)) for key, elem := range m.elements { diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/set_type.go b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/set_type.go index 1f89957f4..9542b94ac 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/set_type.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/set_type.go @@ -68,6 +68,20 @@ func (st SetType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (att if in.Type() == nil { return NewSetNull(st.ElementType()), nil } + + // MAINTAINER NOTE: + // SetType does not support DynamicType as an element type. It is not explicitly prevented from being created with the + // Framework type system, but the Framework-supported SetAttribute, SetNestedAttribute, and SetNestedBlock all prevent DynamicType + // from being used as an element type. An attempt to use DynamicType as the element type will eventually lead you to an error on this line :) + // + // In the future, if we ever need to support a set of dynamic element types, this type equality check will need to be modified to allow + // dynamic types to not return an error, as the tftypes.Value coming in (if known) will be a concrete value, for example: + // + // - st.TerraformType(ctx): tftypes.Set[tftypes.DynamicPseudoType] + // - in.Type(): tftypes.Set[tftypes.String] + // + // The `ValueFromTerraform` function for a dynamic type will be able create the correct concrete dynamic value with this modification in place. + // if !in.Type().Equal(st.TerraformType(ctx)) { return nil, fmt.Errorf("can't use %s as value of Set with ElementType %T, can only use %s values", in.String(), st.ElementType(), st.ElementType().TerraformType(ctx).String()) } diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/set_value.go b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/set_value.go index d29a7022c..9beb2da3d 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/set_value.go +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/types/basetypes/set_value.go @@ -209,6 +209,19 @@ func (s SetValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { switch s.state { case attr.ValueStateKnown: + // MAINTAINER NOTE: + // SetValue does not support DynamicType as an element type. It is not explicitly prevented from being created with the + // Framework type system, but the Framework-supported SetAttribute, SetNestedAttribute, and SetNestedBlock all prevent DynamicType + // from being used as an element type. + // + // In the future, if we ever need to support a set of dynamic element types, this tftypes.Set creation logic will need to be modified to ensure + // that known values contain the exact same concrete element type, specifically with unknown and null values. Dynamic values will return the correct concrete + // element type for known values from `elem.ToTerraformValue`, but unknown and null values will be tftypes.DynamicPseudoType, causing an error due to multiple element + // types in a tftypes.Set. + // + // Unknown and null element types of tftypes.DynamicPseudoType must be recreated as the concrete element type unknown/null value. This can be done by checking `s.elements` + // for a single concrete type (i.e. not tftypes.DynamicPseudoType), and using that concrete type to create unknown and null dynamic values later. + // vals := make([]tftypes.Value, 0, len(s.elements)) for _, elem := range s.elements { diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/types/dynamic_type.go b/vendor/github.com/hashicorp/terraform-plugin-framework/types/dynamic_type.go new file mode 100644 index 000000000..f63d30cbc --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/types/dynamic_type.go @@ -0,0 +1,8 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package types + +import "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + +var DynamicType = basetypes.DynamicType{} diff --git a/vendor/github.com/hashicorp/terraform-plugin-framework/types/dynamic_value.go b/vendor/github.com/hashicorp/terraform-plugin-framework/types/dynamic_value.go new file mode 100644 index 000000000..845cd377a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-framework/types/dynamic_value.go @@ -0,0 +1,29 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package types + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +type Dynamic = basetypes.DynamicValue + +// DynamicNull creates a Dynamic with a null value. Determine whether the value is +// null via the Dynamic type IsNull method. +func DynamicNull() basetypes.DynamicValue { + return basetypes.NewDynamicNull() +} + +// DynamicUnknown creates a Dynamic with an unknown value. Determine whether the +// value is unknown via the Dynamic type IsUnknown method. +func DynamicUnknown() basetypes.DynamicValue { + return basetypes.NewDynamicUnknown() +} + +// DynamicValue creates a Dynamic with a known value. Access the value via the Dynamic +// type UnderlyingValue method. +func DynamicValue(value attr.Value) basetypes.DynamicValue { + return basetypes.NewDynamicValue(value) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5/tfplugin5.pb.go b/vendor/github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5/tfplugin5.pb.go index 4f3f970d4..e10c839bf 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5/tfplugin5.pb.go +++ b/vendor/github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5/tfplugin5.pb.go @@ -22,7 +22,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc v4.25.1 // source: tfplugin5.proto diff --git a/vendor/github.com/hashicorp/terraform-plugin-go/tfprotov6/internal/tfplugin6/tfplugin6.pb.go b/vendor/github.com/hashicorp/terraform-plugin-go/tfprotov6/internal/tfplugin6/tfplugin6.pb.go index 5f7747288..9016dfe4c 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-go/tfprotov6/internal/tfplugin6/tfplugin6.pb.go +++ b/vendor/github.com/hashicorp/terraform-plugin-go/tfprotov6/internal/tfplugin6/tfplugin6.pb.go @@ -22,7 +22,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 +// protoc-gen-go v1.33.0 // protoc v4.25.1 // source: tfplugin6.proto diff --git a/vendor/github.com/hashicorp/terraform-plugin-go/tftypes/value.go b/vendor/github.com/hashicorp/terraform-plugin-go/tftypes/value.go index b84507839..63570211f 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-go/tftypes/value.go +++ b/vendor/github.com/hashicorp/terraform-plugin-go/tftypes/value.go @@ -223,7 +223,7 @@ func (val Value) Equal(o Value) bool { } deepEqual, err := val.deepEqual(o) if err != nil { - panic(err) + return false } return deepEqual } diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index f6e815e6b..f0b7f3200 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -1772,6 +1772,8 @@ func parseTarget(target string) (resolver.Target, error) { return resolver.Target{URL: *u}, nil } +// encodeAuthority escapes the authority string based on valid chars defined in +// https://datatracker.ietf.org/doc/html/rfc3986#section-3.2. func encodeAuthority(authority string) string { const upperhex = "0123456789ABCDEF" diff --git a/vendor/google.golang.org/grpc/internal/transport/transport.go b/vendor/google.golang.org/grpc/internal/transport/transport.go index b7b8fec18..d3796c256 100644 --- a/vendor/google.golang.org/grpc/internal/transport/transport.go +++ b/vendor/google.golang.org/grpc/internal/transport/transport.go @@ -28,6 +28,7 @@ import ( "fmt" "io" "net" + "strings" "sync" "sync/atomic" "time" @@ -362,8 +363,12 @@ func (s *Stream) SendCompress() string { // ClientAdvertisedCompressors returns the compressor names advertised by the // client via grpc-accept-encoding header. -func (s *Stream) ClientAdvertisedCompressors() string { - return s.clientAdvertisedCompressors +func (s *Stream) ClientAdvertisedCompressors() []string { + values := strings.Split(s.clientAdvertisedCompressors, ",") + for i, v := range values { + values[i] = strings.TrimSpace(v) + } + return values } // Done returns a channel which is closed when it receives the final status diff --git a/vendor/google.golang.org/grpc/resolver/resolver.go b/vendor/google.golang.org/grpc/resolver/resolver.go index adf89dd9c..d72f21c1b 100644 --- a/vendor/google.golang.org/grpc/resolver/resolver.go +++ b/vendor/google.golang.org/grpc/resolver/resolver.go @@ -168,6 +168,9 @@ type BuildOptions struct { // field. In most cases though, it is not appropriate, and this field may // be ignored. Dialer func(context.Context, string) (net.Conn, error) + // Authority is the effective authority of the clientconn for which the + // resolver is built. + Authority string } // An Endpoint is one network endpoint, or server, which may have multiple diff --git a/vendor/google.golang.org/grpc/resolver_wrapper.go b/vendor/google.golang.org/grpc/resolver_wrapper.go index c79bab121..f845ac958 100644 --- a/vendor/google.golang.org/grpc/resolver_wrapper.go +++ b/vendor/google.golang.org/grpc/resolver_wrapper.go @@ -75,6 +75,7 @@ func (ccr *ccResolverWrapper) start() error { DialCreds: ccr.cc.dopts.copts.TransportCredentials, CredsBundle: ccr.cc.dopts.copts.CredsBundle, Dialer: ccr.cc.dopts.copts.Dialer, + Authority: ccr.cc.authority, } var err error ccr.resolver, err = ccr.cc.resolverBuilder.Build(ccr.cc.parsedTarget, ccr, opts) diff --git a/vendor/google.golang.org/grpc/rpc_util.go b/vendor/google.golang.org/grpc/rpc_util.go index d17ede0fa..82493d237 100644 --- a/vendor/google.golang.org/grpc/rpc_util.go +++ b/vendor/google.golang.org/grpc/rpc_util.go @@ -744,17 +744,19 @@ type payloadInfo struct { uncompressedBytes []byte } -func recvAndDecompress(p *parser, s *transport.Stream, dc Decompressor, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor) ([]byte, error) { - pf, buf, err := p.recvMsg(maxReceiveMessageSize) +// recvAndDecompress reads a message from the stream, decompressing it if necessary. +// +// Cancelling the returned cancel function releases the buffer back to the pool. So the caller should cancel as soon as +// the buffer is no longer needed. +func recvAndDecompress(p *parser, s *transport.Stream, dc Decompressor, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor, +) (uncompressedBuf []byte, cancel func(), err error) { + pf, compressedBuf, err := p.recvMsg(maxReceiveMessageSize) if err != nil { - return nil, err - } - if payInfo != nil { - payInfo.compressedLength = len(buf) + return nil, nil, err } if st := checkRecvPayload(pf, s.RecvCompress(), compressor != nil || dc != nil); st != nil { - return nil, st.Err() + return nil, nil, st.Err() } var size int @@ -762,21 +764,35 @@ func recvAndDecompress(p *parser, s *transport.Stream, dc Decompressor, maxRecei // To match legacy behavior, if the decompressor is set by WithDecompressor or RPCDecompressor, // use this decompressor as the default. if dc != nil { - buf, err = dc.Do(bytes.NewReader(buf)) - size = len(buf) + uncompressedBuf, err = dc.Do(bytes.NewReader(compressedBuf)) + size = len(uncompressedBuf) } else { - buf, size, err = decompress(compressor, buf, maxReceiveMessageSize) + uncompressedBuf, size, err = decompress(compressor, compressedBuf, maxReceiveMessageSize) } if err != nil { - return nil, status.Errorf(codes.Internal, "grpc: failed to decompress the received message: %v", err) + return nil, nil, status.Errorf(codes.Internal, "grpc: failed to decompress the received message: %v", err) } if size > maxReceiveMessageSize { // TODO: Revisit the error code. Currently keep it consistent with java // implementation. - return nil, status.Errorf(codes.ResourceExhausted, "grpc: received message after decompression larger than max (%d vs. %d)", size, maxReceiveMessageSize) + return nil, nil, status.Errorf(codes.ResourceExhausted, "grpc: received message after decompression larger than max (%d vs. %d)", size, maxReceiveMessageSize) } + } else { + uncompressedBuf = compressedBuf } - return buf, nil + + if payInfo != nil { + payInfo.compressedLength = len(compressedBuf) + payInfo.uncompressedBytes = uncompressedBuf + + cancel = func() {} + } else { + cancel = func() { + p.recvBufferPool.Put(&compressedBuf) + } + } + + return uncompressedBuf, cancel, nil } // Using compressor, decompress d, returning data and size. @@ -796,6 +812,9 @@ func decompress(compressor encoding.Compressor, d []byte, maxReceiveMessageSize // size is used as an estimate to size the buffer, but we // will read more data if available. // +MinRead so ReadFrom will not reallocate if size is correct. + // + // TODO: If we ensure that the buffer size is the same as the DecompressedSize, + // we can also utilize the recv buffer pool here. buf := bytes.NewBuffer(make([]byte, 0, size+bytes.MinRead)) bytesRead, err := buf.ReadFrom(io.LimitReader(dcReader, int64(maxReceiveMessageSize)+1)) return buf.Bytes(), int(bytesRead), err @@ -811,18 +830,15 @@ func decompress(compressor encoding.Compressor, d []byte, maxReceiveMessageSize // dc takes precedence over compressor. // TODO(dfawley): wrap the old compressor/decompressor using the new API? func recv(p *parser, c baseCodec, s *transport.Stream, dc Decompressor, m any, maxReceiveMessageSize int, payInfo *payloadInfo, compressor encoding.Compressor) error { - buf, err := recvAndDecompress(p, s, dc, maxReceiveMessageSize, payInfo, compressor) + buf, cancel, err := recvAndDecompress(p, s, dc, maxReceiveMessageSize, payInfo, compressor) if err != nil { return err } + defer cancel() + if err := c.Unmarshal(buf, m); err != nil { return status.Errorf(codes.Internal, "grpc: failed to unmarshal the received message: %v", err) } - if payInfo != nil { - payInfo.uncompressedBytes = buf - } else { - p.recvBufferPool.Put(&buf) - } return nil } diff --git a/vendor/google.golang.org/grpc/server.go b/vendor/google.golang.org/grpc/server.go index 0bf5c78b0..a6a11704b 100644 --- a/vendor/google.golang.org/grpc/server.go +++ b/vendor/google.golang.org/grpc/server.go @@ -1342,7 +1342,8 @@ func (s *Server) processUnaryRPC(ctx context.Context, t transport.ServerTranspor if len(shs) != 0 || len(binlogs) != 0 { payInfo = &payloadInfo{} } - d, err := recvAndDecompress(&parser{r: stream, recvBufferPool: s.opts.recvBufferPool}, stream, dc, s.opts.maxReceiveMessageSize, payInfo, decomp) + + d, cancel, err := recvAndDecompress(&parser{r: stream, recvBufferPool: s.opts.recvBufferPool}, stream, dc, s.opts.maxReceiveMessageSize, payInfo, decomp) if err != nil { if e := t.WriteStatus(stream, status.Convert(err)); e != nil { channelz.Warningf(logger, s.channelzID, "grpc: Server.processUnaryRPC failed to write status: %v", e) @@ -1353,6 +1354,8 @@ func (s *Server) processUnaryRPC(ctx context.Context, t transport.ServerTranspor t.IncrMsgRecv() } df := func(v any) error { + defer cancel() + if err := s.getCodec(stream.ContentSubtype()).Unmarshal(d, v); err != nil { return status.Errorf(codes.Internal, "grpc: error unmarshalling request: %v", err) } @@ -2117,7 +2120,7 @@ func ClientSupportedCompressors(ctx context.Context) ([]string, error) { return nil, fmt.Errorf("failed to fetch the stream from the given context %v", ctx) } - return strings.Split(stream.ClientAdvertisedCompressors(), ","), nil + return stream.ClientAdvertisedCompressors(), nil } // SetTrailer sets the trailer metadata that will be sent when an RPC returns. @@ -2157,7 +2160,7 @@ func (c *channelzServer) ChannelzMetric() *channelz.ServerInternalMetric { // validateSendCompressor returns an error when given compressor name cannot be // handled by the server or the client based on the advertised compressors. -func validateSendCompressor(name, clientCompressors string) error { +func validateSendCompressor(name string, clientCompressors []string) error { if name == encoding.Identity { return nil } @@ -2166,7 +2169,7 @@ func validateSendCompressor(name, clientCompressors string) error { return fmt.Errorf("compressor not registered %q", name) } - for _, c := range strings.Split(clientCompressors, ",") { + for _, c := range clientCompressors { if c == name { return nil // found match } diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go index df85a021a..46ad8113f 100644 --- a/vendor/google.golang.org/grpc/version.go +++ b/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.62.0" +const Version = "1.62.1" diff --git a/vendor/modules.txt b/vendor/modules.txt index b266787d8..3b44ca192 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -124,7 +124,7 @@ github.com/hashicorp/terraform-exec/tfexec # github.com/hashicorp/terraform-json v0.21.0 ## explicit; go 1.18 github.com/hashicorp/terraform-json -# github.com/hashicorp/terraform-plugin-framework v1.6.1 +# github.com/hashicorp/terraform-plugin-framework v1.7.0 ## explicit; go 1.21 github.com/hashicorp/terraform-plugin-framework/attr github.com/hashicorp/terraform-plugin-framework/attr/xattr @@ -135,10 +135,12 @@ github.com/hashicorp/terraform-plugin-framework/function github.com/hashicorp/terraform-plugin-framework/internal/fromproto5 github.com/hashicorp/terraform-plugin-framework/internal/fromproto6 github.com/hashicorp/terraform-plugin-framework/internal/fromtftypes +github.com/hashicorp/terraform-plugin-framework/internal/fwfunction github.com/hashicorp/terraform-plugin-framework/internal/fwschema github.com/hashicorp/terraform-plugin-framework/internal/fwschema/fwxschema github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata github.com/hashicorp/terraform-plugin-framework/internal/fwserver +github.com/hashicorp/terraform-plugin-framework/internal/fwtype github.com/hashicorp/terraform-plugin-framework/internal/logging github.com/hashicorp/terraform-plugin-framework/internal/privatestate github.com/hashicorp/terraform-plugin-framework/internal/proto5server @@ -155,6 +157,7 @@ github.com/hashicorp/terraform-plugin-framework/providerserver github.com/hashicorp/terraform-plugin-framework/resource github.com/hashicorp/terraform-plugin-framework/resource/schema github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults +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 @@ -166,7 +169,8 @@ github.com/hashicorp/terraform-plugin-framework/types/basetypes github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag github.com/hashicorp/terraform-plugin-framework-validators/int64validator github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator -# github.com/hashicorp/terraform-plugin-go v0.22.0 +github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator +# github.com/hashicorp/terraform-plugin-go v0.22.1 ## explicit; go 1.21 github.com/hashicorp/terraform-plugin-go/internal/logging github.com/hashicorp/terraform-plugin-go/tfprotov5 @@ -380,7 +384,7 @@ google.golang.org/appengine/internal/remote_api # google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 ## explicit; go 1.19 google.golang.org/genproto/googleapis/rpc/status -# google.golang.org/grpc v1.62.0 +# google.golang.org/grpc v1.62.1 ## explicit; go 1.19 google.golang.org/grpc google.golang.org/grpc/attributes diff --git a/website/docs/r/vps.html.markdown b/website/docs/r/vps.html.markdown new file mode 100644 index 000000000..fb04eaff5 --- /dev/null +++ b/website/docs/r/vps.html.markdown @@ -0,0 +1,132 @@ +--- +subcategory : "VPS" +--- + +# ovh_vps + +Creates an OVHcloud Virtual Private Server (VPS). + +## Important + +-> __NOTE__ To order a product through Terraform, your account needs to have a default payment method defined. This can be done in the [OVHcloud Control Panel](https://www.ovh.com/manager/#/dedicated/billing/payment/method) or via API with the [/me/payment/method](https://api.ovh.com/console/#/me/payment/method~GET) endpoint. + +~> __WARNING__ `BANK_ACCOUNT` is not supported anymore, please update your default payment method to `SEPA_DIRECT_DEBIT` + +## Example Usage + +```hcl +data "ovh_me" "myaccount" {} + +data "ovh_order_cart" "mycart" { + ovh_subsidiary = data.ovh_me.myaccount.ovh_subsidiary +} + +data "ovh_order_cart_product_plan" "vps" { + cart_id = data.ovh_order_cart.mycart.id + price_capacity = "renew" + product = "vps" + plan_code = "vps-le-2-2-40" +} + +resource "ovh_vps" "myvps" { + display_name = dev_vps" + + ovh_subsidiary = data.ovh_order_cart.mycart.ovh_subsidiary + plan = [ + { + duration = "P1M" + plan_code = data.ovh_order_cart_product_plan.vps.plan_code + pricing_mode = "default" + + configuration = [ + { + label = "vps_datacenter" + value = "WAW" + }, + { + label = "vps_os" + value = "Debian 10" + } + ] + } + ] +} + +output "vps_display_name" { + value = ovh_vps.myvps.display_name +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - Custom display name +* `netboot_mode` - VPS netboot mode (local┃rescue) +* `ovh_subsidiary` - (Required) OVHcloud Subsidiary. Country of OVHcloud legal entity you'll be billed by. List of supported subsidiaries available on API at [/1.0/me.json](https://eu.api.ovh.com/console-preview/?section=%2Fme&branch=v1#get-/me) +* `plan` - (Required) Product Plan to order + * `duration` - (Required) duration + * `plan_code` - (Required) Plan code + * `pricing_mode` - (Required) Pricing model identifier + * `configuration` - (Optional) Representation of a configuration item for personalizing product + * `label` - (Required) Identifier of the resource + * `value` - (Required) Path to the resource in api.ovh.com +* `plan_option` - (Optional) Product Plan to order + * `duration` - (Required) duration + * `plan_code` - (Required) Plan code + * `pricing_mode` - (Required) Pricing model identifier + * `configuration` - (Optional) Representation of a configuration item for personalizing product + * `label` - (Required) Identifier of the resource + * `value` - (Required) Path to the resource in api.ovh.com + +## Attributes Reference + +The following attributes are exported: + +* `iam` - IAM resource information + * `urn` - URN of the private database, used when writing IAM policies + * `display_name` - Resource display name + * `id` - Unique identifier of the resource in the IAM + * `tags` - Resource tags. Tags that were internally computed are prefixed with `ovh:` +* `cluster` - VPS cluster +* `display_name` - Custom display name +* `keymap` - KVM keyboard layout on VPS Cloud +* `memory_limit` - RAM of this VPS +* `model` - Structure describing characteristics of a VPS model + * `available_options` - All options the VPS can have (additionalDisk┃automatedBackup┃cpanel┃ftpbackup┃plesk┃snapshot┃veeam┃windows) + * `datacenter` - Datacenters where this model is available + * `disk` - Disk capacity of this VPS + * `maximum_additionnal_ip` - Maximum number of additional IPs + * `memory` - RAM of the VPS + * `name` - Plan code of the VPS + * `offer` - Description of this VPS offer + * `vcore` - Number of vcores + * `version` - All versions that VPS can have (2013v1┃2014v1┃2015v1┃2017v1┃2017v2┃2017v3┃2018v1┃2018v2┃2019v1) +* `monitoring_ip_blocks` - IP blocks for OVH monitoring servers +* `name` - Name of the VPS +* `netboot_mode` - VPS netboot mode (local┃rescue) +* `offer_type` - All offers a VPS can have (beta-classic┃classic┃cloud┃cloudram┃game-classic┃lowlat┃ssd) +* `sla_monitoring` +* `state` - State of the VPS (backuping┃installing┃maintenance┃rebooting┃rescued┃running┃stopped┃stopping┃upgrading) +* `vcore` - Number of vcores +* `zone` - OpenStask region where the VPS is located + +## Import + +OVHcloud VPS database can be imported using the `service_name`, E.g., + +```hcl +import { + to = ovh_vps.myvps + id = "" +} +``` + +You can then run: + +```sh +terraform plan -generate-config-out=./vps.tf +``` + +The file `vps.tf` will then contain the imported resource's configuration, that can be copied next to the `import` block above. +See https://developer.hashicorp.com/terraform/language/import/generating-configuration for more details. \ No newline at end of file