diff --git a/list/config_validator.go b/list/config_validator.go new file mode 100644 index 000000000..378b638c5 --- /dev/null +++ b/list/config_validator.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package list + +import "context" + +// ConfigValidator describes reusable ListResource configuration validation +// functionality. +type ConfigValidator interface { + // Description describes the validation in plain text formatting. + // + // This information may be automatically added to list resource plain text + // descriptions by external tooling. + Description(context.Context) string + + // MarkdownDescription describes the validation in Markdown formatting. + // + // This information may be automatically added to list resource Markdown + // descriptions by external tooling. + MarkdownDescription(context.Context) string + + // ValidateResource performs the validation. + // + // This method name is separate from ConfigValidators in resource and other packages in + // order to allow generic validators. + ValidateListResourceConfig(context.Context, ValidateConfigRequest, *ValidateConfigResponse) +} diff --git a/list/list_resource.go b/list/list_resource.go new file mode 100644 index 000000000..def1abfd3 --- /dev/null +++ b/list/list_resource.go @@ -0,0 +1,163 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package list + +import ( + "context" + "iter" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +// ListResource represents an implementation of listing instances of a managed resource +// This is the core interface for all list resource implementations. +// +// ListResource implementations can optionally implement these additional concepts: +// +// - Configure: Include provider-level data or clients. +// - Validation: Schema-based or entire configuration via +// ListResourceWithConfigValidators or ListResourceWithValidateConfig. +type ListResource interface { + // Metadata should return the full name of the list resource such as + // examplecloud_thing. This name should match the full name of the managed + // resource to be listed; otherwise, the GetMetadata RPC will return an + // error diagnostic. + // + // The method signature is intended to be compatible with the Metadata + // method signature in the Resource interface. One implementation of + // Metadata can satisfy both interfaces. + Metadata(context.Context, resource.MetadataRequest, *resource.MetadataResponse) + + // ListResourceConfigSchema should return the schema for list blocks. + ListResourceConfigSchema(context.Context, ListResourceSchemaRequest, *ListResourceSchemaResponse) + + // List is called when the provider must list instances of a managed + // resource type that satisfy a user-provided request. + List(context.Context, ListRequest, *ListResultsStream) +} + +// ListResourceWithConfigure is an interface type that extends ListResource to include a method +// which the framework will automatically call so provider developers have the +// opportunity to setup any necessary provider-level data or clients. +type ListResourceWithConfigure interface { + ListResource + + // Configure enables provider-level data or clients to be set. The method + // signature is intended to be compatible with the Configure method + // signature in the Resource interface. One implementation of Configure can + // satisfy both interfaces. + Configure(context.Context, resource.ConfigureRequest, *resource.ConfigureResponse) +} + +// ListResourceWithConfigValidators is an interface type that extends +// ListResource to include declarative validations. +// +// Declaring validation using this methodology simplifies implementation of +// reusable functionality. These also include descriptions, which can be used +// for automating documentation. +// +// Validation will include ListResourceConfigValidators and +// ValidateListResourceConfig, if both are implemented, in addition to any +// Attribute or Type validation. +type ListResourceWithConfigValidators interface { + ListResource + + // ListResourceConfigValidators returns a list of functions which will all be performed during validation. + ListResourceConfigValidators(context.Context) []ConfigValidator +} + +// ListResourceWithValidateConfig is an interface type that extends ListResource to include +// imperative validation. +// +// Declaring validation using this methodology simplifies one-off +// functionality that typically applies to a single resource. Any documentation +// of this functionality must be manually added into schema descriptions. +// +// Validation will include ListResourceConfigValidators and ValidateListResourceConfig, if both +// are implemented, in addition to any Attribute or Type validation. +type ListResourceWithValidateConfig interface { + ListResource + + // ValidateListResourceConfig performs the validation. + ValidateListResourceConfig(context.Context, ValidateConfigRequest, *ValidateConfigResponse) +} + +// ListRequest represents a request for the provider to list instances +// of a managed resource type that satisfy a user-defined request. An instance +// of this reqeuest struct is passed as an argument to the provider's +// ListResource function implementation. +type ListRequest struct { + // Config is the configuration the user supplied for listing resource + // instances. + Config tfsdk.Config + + // IncludeResource indicates whether the provider should populate the + // Resource field in the ListResult struct. + IncludeResource bool +} + +// ListResultsStream represents a streaming response to a ListRequest. +// An instance of this struct is supplied as an argument to the provider's +// ListResource function implementation function. The provider should set a Results +// iterator function that yields zero or more results of type ListResult. +// +// For convenience, a provider implementation may choose to convert a slice of +// results into an iterator using [slices.Values]. +// +// [slices.Values]: https://pkg.go.dev/slices#Values +type ListResultsStream struct { + // Results is a function that emits ListResult values via its yield + // function argument. + Results iter.Seq[ListResult] +} + +// ListResult represents a listed managed resource instance. +type ListResult struct { + // Identity is the identity of the managed resource instance. + // + // A nil value will raise will raise a diagnostic. + Identity *tfsdk.ResourceIdentity + + // Resource is the provider's representation of the attributes of the + // listed managed resource instance. + // + // If ListRequest.IncludeResource is true, a nil value will raise + // a warning diagnostic. + Resource *tfsdk.Resource + + // DisplayName is a provider-defined human-readable description of the + // listed managed resource instance, intended for CLI and browser UIs. + DisplayName string + + // Diagnostics report errors or warnings related to the listed managed + // resource instance. An empty slice indicates a successful operation with + // no warnings or errors generated. + Diagnostics diag.Diagnostics +} + +// ValidateConfigRequest represents a request to validate the configuration of +// a list resource. An instance of this request struct is supplied as an +// argument to the ValidateListResourceConfig receiver method or automatically +// passed through to each ListResourceConfigValidator. +type ValidateConfigRequest struct { + // Config is the configuration the user supplied for the resource. + // + // This configuration may contain unknown values if a user uses + // interpolation or other functionality that would prevent Terraform + // from knowing the value at request time. + Config tfsdk.Config +} + +// ValidateConfigResponse represents a response to a ValidateConfigRequest. An +// instance of this response struct is supplied as an argument to the +// list.ValidateListResourceConfig receiver method or automatically passed +// through to each ConfigValidator. +type ValidateConfigResponse struct { + // Diagnostics report errors or warnings related to validating the list + // configuration. An empty slice indicates success, with no warnings + // or errors generated. + Diagnostics diag.Diagnostics +} diff --git a/list/list_resource_test.go b/list/list_resource_test.go new file mode 100644 index 000000000..c2c723c5f --- /dev/null +++ b/list/list_resource_test.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package list_test + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/list" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +type ComputeInstanceResource struct { + NoOpListResource + NoOpResource +} + +type ComputeInstanceWithValidateListResourceConfig struct { + ComputeInstanceResource +} + +type ComputeInstanceWithListResourceConfigValidators struct { + ComputeInstanceResource +} + +func (c *ComputeInstanceResource) Configure(_ context.Context, _ resource.ConfigureRequest, _ *resource.ConfigureResponse) { +} + +func (c *ComputeInstanceResource) Metadata(_ context.Context, _ resource.MetadataRequest, _ *resource.MetadataResponse) { +} + +func (c *ComputeInstanceWithValidateListResourceConfig) ValidateListResourceConfig(_ context.Context, _ list.ValidateConfigRequest, _ *list.ValidateConfigResponse) { +} + +func (c *ComputeInstanceWithListResourceConfigValidators) ListResourceConfigValidators(_ context.Context) []list.ConfigValidator { + return nil +} + +// ExampleResource_listable demonstrates a resource.Resource that implements +// list.ListResource interfaces. +func ExampleResource_listable() { + var _ list.ListResource = &ComputeInstanceResource{} + var _ list.ListResourceWithConfigure = &ComputeInstanceResource{} + var _ list.ListResourceWithValidateConfig = &ComputeInstanceWithValidateListResourceConfig{} + var _ list.ListResourceWithConfigValidators = &ComputeInstanceWithListResourceConfigValidators{} + + var _ resource.Resource = &ComputeInstanceResource{} + var _ resource.ResourceWithConfigure = &ComputeInstanceResource{} + + // Output: +} diff --git a/list/no_op_list_resource_test.go b/list/no_op_list_resource_test.go new file mode 100644 index 000000000..9b84e7ea4 --- /dev/null +++ b/list/no_op_list_resource_test.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package list_test + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/list" +) + +type NoOpListResource struct{} + +func (*NoOpListResource) ListResourceConfigSchema(_ context.Context, _ list.ListResourceSchemaRequest, _ *list.ListResourceSchemaResponse) { +} + +func (*NoOpListResource) List(_ context.Context, _ list.ListRequest, _ *list.ListResultsStream) { +} diff --git a/list/no_op_resource_test.go b/list/no_op_resource_test.go new file mode 100644 index 000000000..18c0d18f1 --- /dev/null +++ b/list/no_op_resource_test.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package list_test + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +type NoOpResource struct{} + +func (*NoOpResource) Schema(_ context.Context, _ resource.SchemaRequest, _ *resource.SchemaResponse) { +} + +func (*NoOpResource) Create(_ context.Context, _ resource.CreateRequest, _ *resource.CreateResponse) { +} + +func (*NoOpResource) Read(_ context.Context, _ resource.ReadRequest, _ *resource.ReadResponse) { +} + +func (*NoOpResource) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) { +} + +func (*NoOpResource) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) { +} diff --git a/list/schema.go b/list/schema.go new file mode 100644 index 000000000..c4d9e6092 --- /dev/null +++ b/list/schema.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package list + +import ( + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/list/schema" +) + +// ListResourceSchemaRequest represents a request for the ListResource to +// return its schema. An instance of this request struct is supplied as an +// argument to the ListResource type ListResourceSchema method. +type ListResourceSchemaRequest struct{} + +// ListResourceSchemaResponse represents a response to a +// ListResourceSchemaRequest. An instance of this response struct is supplied +// as an argument to the ListResource type ListResourceResourceSchema method. +type ListResourceSchemaResponse struct { + // Schema is the schema of the list resource. + Schema schema.Schema + + // Diagnostics report errors or warnings related to retrieving the list + // resource schema. An empty slice indicates success, with no warnings + // or errors generated. + Diagnostics diag.Diagnostics +} diff --git a/list/schema/schema.go b/list/schema/schema.go new file mode 100644 index 000000000..f1c142dd6 --- /dev/null +++ b/list/schema/schema.go @@ -0,0 +1,7 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +type Schema struct { +} diff --git a/tfsdk/resource.go b/tfsdk/resource.go new file mode 100644 index 000000000..49bc09b8e --- /dev/null +++ b/tfsdk/resource.go @@ -0,0 +1,53 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfsdk + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Resource represents a Terraform resource. +type Resource struct { + Raw tftypes.Value + Schema fwschema.Schema +} + +// Get populates the struct passed as `target` with the resource. +func (c Resource) Get(ctx context.Context, target interface{}) diag.Diagnostics { + return c.data().Get(ctx, target) +} + +// GetAttribute retrieves the attribute or block found at `path` and populates +// the `target` with the value. This method is intended for top level schema +// attributes or blocks. Use `types` package methods or custom types to step +// into collections. +// +// Attributes or elements under null or unknown collections return null +// values, however this behavior is not protected by compatibility promises. +func (c Resource) GetAttribute(ctx context.Context, path path.Path, target interface{}) diag.Diagnostics { + return c.data().GetAtPath(ctx, path, target) +} + +// PathMatches returns all matching path.Paths from the given path.Expression. +// +// If a parent path is null or unknown, which would prevent a full expression +// from matching, the parent path is returned rather than no match to prevent +// false positives. +func (c Resource) PathMatches(ctx context.Context, pathExpr path.Expression) (path.Paths, diag.Diagnostics) { + return c.data().PathMatches(ctx, pathExpr) +} + +func (c Resource) data() fwschemadata.Data { + return fwschemadata.Data{ + Description: fwschemadata.DataDescriptionConfiguration, + Schema: c.Schema, + TerraformValue: c.Raw, + } +}