sdk

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2022 License: MPL-2.0 Imports: 12 Imported by: 0

README

SDK for Strongly-Typed Resources

This package is a prototype for creating strongly-typed Data Sources and Resources - and in future will likely form the foundation for Terraform Data Sources and Resources in this Provider going forward.

Should I use this package to build resources?

Not at this time - please use Terraform's Plugin SDK instead - reference examples can be found in ./internal/services/notificationhub.

More documentation for this package will ship in the future when this is ready for general use.


What's the long-term intention for this package?

Each Service Package contains the following:

  • Client - giving reference to the SDK Client which should be used to interact with Azure
  • ID Parsers, Formatters and a Validator - giving a canonical ID for each Resource
  • Validation functions specific to this service package, for example for the Name

This package can be used to tie these together in a more strongly typed fashion, for example:

package example

import (
	"context"
	"fmt"
	"time"

	"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2020-06-01/resources"
	"github.com/hashicorp/go-azure-helpers/lang/response"
	"github.com/hashicorp/terraform-provider-azurestack/internal/location"
	"github.com/hashicorp/terraform-provider-azurestack/internal/sdk"
	"github.com/hashicorp/terraform-provider-azurestack/internal/services/resource/parse"
	"github.com/hashicorp/terraform-provider-azurestack/internal/services/resource/validate"

	"github.com/hashicorp/terraform-provider-azurestack/internal/tf/pluginsdk"
	
)

type ResourceGroup struct {
	Name     string            `tfschema:"name"`
	Location string            `tfschema:"location"`
	Tags     map[string]string `tfschema:"tags"`
}

type ResourceGroupResource struct {
}

func (r ResourceGroupResource) Arguments() map[string]*pluginsdk.Schema {
	return map[string]*pluginsdk.Schema{
		"name": {
			Type:     pluginsdk.TypeString,
			Required: true,
		},

		"location": location.Schema(),

		"tags": tags.Schema(),
	}
}

func (r ResourceGroupResource) Attributes() map[string]*pluginsdk.Schema {
	return map[string]*pluginsdk.Schema{}
}

func (r ResourceGroupResource) ResourceType() string {
	return "azurestack_example"
}

func (r ResourceGroupResource) Create() sdk.ResourceFunc {
	return sdk.ResourceFunc{
		Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
			metadata.Logger.Info("Decoding state..")
			var state ResourceGroup
			if err := metadata.Decode(&state); err != nil {
				return err
			}

			metadata.Logger.Infof("creating Resource Group %q..", state.Name)
			client := metadata.Client.Resource.GroupsClient
			subscriptionId := metadata.Client.Account.SubscriptionId

			id := parse.NewResourceGroupID(subscriptionId, state.Name)
			existing, err := client.Get(ctx, state.Name)
			if err != nil && !utils.ResponseWasNotFound(existing.Response) {
				return fmt.Errorf("checking for the presence of an existing Resource Group %q: %+v", state.Name, err)
			}
			if !utils.ResponseWasNotFound(existing.Response) {
				return metadata.ResourceRequiresImport(r.ResourceType(), id)
			}

			input := resources.Group{
				Location: pointer.FromString(state.Location),
				Tags:     tags.FromTypedObject(state.Tags),
			}
			if _, err := client.CreateOrUpdate(ctx, state.Name, input); err != nil {
				return fmt.Errorf("creating Resource Group %q: %+v", state.Name, err)
			}

			metadata.SetID(id)
			return nil
		},
		Timeout: 30 * time.Minute,
	}
}

func (r ResourceGroupResource) Read() sdk.ResourceFunc {
	return sdk.ResourceFunc{
		Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
			client := metadata.Client.Resource.GroupsClient
			id, err := parse.ResourceGroupID(metadata.ResourceData.Id())
			if err != nil {
				return err
			}

			metadata.Logger.Infof("retrieving Resource Group %q..", id.ResourceGroup)
			group, err := client.Get(ctx, id.ResourceGroup)
			if err != nil {
				if utils.ResponseWasNotFound(group.Response) {
					metadata.Logger.Infof("%s was not found - removing from state!", *id)
					return metadata.MarkAsGone(id)
				}

				return fmt.Errorf("retrieving %s: %+v", *id, err)
			}

			return metadata.Encode(&ResourceGroup{
				Name:     id.ResourceGroup,
				Location: location.NormalizeNilable(group.Location),
				Tags:     tags.ToTypedObject(group.Tags),
			})
		},
		Timeout: 5 * time.Minute,
	}
}

func (r ResourceGroupResource) Update() sdk.ResourceFunc {
	return sdk.ResourceFunc{
		Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
			id, err := parse.ResourceGroupID(metadata.ResourceData.Id())
			if err != nil {
				return err
			}

			metadata.Logger.Info("Decoding state..")
			var state ResourceGroup
			if err := metadata.Decode(&state); err != nil {
				return err
			}

			metadata.Logger.Infof("updating %s..", *id)
			client := metadata.Client.Resource.GroupsClient

			input := resources.GroupPatchable{
				Tags: tags.FromTypedObject(state.Tags),
			}

			if _, err := client.Update(ctx, id.ResourceGroup, input); err != nil {
				return fmt.Errorf("updating %s: %+v", *id, err)
			}

			return nil
		},
		Timeout: 30 * time.Minute,
	}
}

func (r ResourceGroupResource) Delete() sdk.ResourceFunc {
	return sdk.ResourceFunc{
		Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error {
			client := metadata.Client.Resource.GroupsClient
			id, err := parse.ResourceGroupID(metadata.ResourceData.Id())
			if err != nil {
				return err
			}

			metadata.Logger.Infof("deleting %s..", *id)
			future, err := client.Delete(ctx, id.ResourceGroup, "")
			if err != nil {
				return fmt.Errorf("deleting %s: %+v", *id, err)
			}

			metadata.Logger.Infof("waiting for the deletion of %s..", *id)
			if err := future.WaitForCompletionRef(ctx, client.Client); err != nil {
				return fmt.Errorf("waiting for deletion of %s: %+v", *id, err)
			}

			return nil
		},
		Timeout: 30 * time.Minute,
	}
}

func (r ResourceGroupResource) IDValidationFunc() pluginsdk.SchemaValidateFunc {
	return validate.ResourceGroupID
}

func (r ResourceGroupResource) ModelObject() interface{} {
	return ResourceGroup{}
}

The end result being the removal of a lot of common bugs by moving to a convention - for example:

  • The Context object passed into each method always has a deadline/timeout attached to it
  • The Read function is automatically called at the end of a Create and Update function - meaning users don't have to do this
  • Each Resource has to have an ID Formatter and Validation Function
  • The Model Object is validated via unit tests to ensure it contains the relevant struct tags (TODO: also confirming these exist in the state and are of the correct type, so no Set errors occur)

Ultimately this allows bugs to be caught by the Compiler (for example if a Read function is unimplemented) - or Unit Tests (for example should the tfschema struct tags be missing) - rather than during Provider Initialization, which reduces the feedback loop.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ValidateModelObject

func ValidateModelObject(input interface{}) error

ValidateModelObject validates that the object contains the specified `tfschema` tags required to be used with the Encode and Decode functions

Types

type ConsoleLogger

type ConsoleLogger struct{}

ConsoleLogger provides a Logger implementation which writes the log messages to StdOut - in Terraform's perspective that's proxied via the Plugin SDK

func (ConsoleLogger) Info

func (ConsoleLogger) Info(message string)

Info prints out a message prefixed with `[INFO]` verbatim

func (ConsoleLogger) Infof

func (l ConsoleLogger) Infof(format string, args ...interface{})

Infof prints out a message prefixed with `[INFO]` formatted with the specified arguments

func (ConsoleLogger) Warn

func (l ConsoleLogger) Warn(message string)

Warn prints out a message prefixed with `[WARN]` formatted verbatim

func (ConsoleLogger) Warnf

func (l ConsoleLogger) Warnf(format string, args ...interface{})

Warnf prints out a message prefixed with `[WARN]` formatted with the specified arguments

type DataSource

type DataSource interface {

	// Read is a ResourceFunc which looks up and sets field values into the Terraform State
	Read() ResourceFunc
	// contains filtered or unexported methods
}

A Data Source is an object which looks up information about an existing resource and returns this information for use elsewhere

Notably not all Terraform Resources/Azure API's make sense as a Data Source - this information has to be available consistently since these are queried on-demand

type DataSourceWrapper

type DataSourceWrapper struct {
	// contains filtered or unexported fields
}

DataSourceWrapper is a wrapper for converting a DataSource implementation into the object used by the Terraform Plugin SDK

func NewDataSourceWrapper

func NewDataSourceWrapper(dataSource DataSource) DataSourceWrapper

NewDataSourceWrapper returns a DataSourceWrapper for this Data Source implementation

func (*DataSourceWrapper) DataSource

func (dw *DataSourceWrapper) DataSource() (*schema.Resource, error)

DataSource returns the Terraform Plugin SDK type for this DataSource implementation

type DiagnosticsLogger

type DiagnosticsLogger struct {
	// contains filtered or unexported fields
}

func (*DiagnosticsLogger) Info

func (d *DiagnosticsLogger) Info(message string)

func (*DiagnosticsLogger) Infof

func (d *DiagnosticsLogger) Infof(format string, args ...interface{})

func (*DiagnosticsLogger) Warn

func (d *DiagnosticsLogger) Warn(message string)

func (*DiagnosticsLogger) Warnf

func (d *DiagnosticsLogger) Warnf(format string, args ...interface{})

type Logger

type Logger interface {
	// Info prints out a message prefixed with `[INFO]` verbatim
	Info(message string)

	// Infof prints out a message prefixed with `[INFO]` formatted
	// with the specified arguments
	Infof(format string, args ...interface{})

	// Warn prints out a message prefixed with `[WARN]` formatted verbatim
	Warn(message string)

	// Warnf prints out a message prefixed with `[WARN]` formatted
	// with the specified arguments
	Warnf(format string, args ...interface{})
}

Logger is an interface for switching out the Logger implementation

type NullLogger

type NullLogger struct{}

NullLogger disregards the log output - and is intended to be used when the contents of the debug logger aren't interesting to reduce console output

func (NullLogger) Info

func (NullLogger) Info(_ string)

Info prints out a message prefixed with `[INFO]` verbatim

func (NullLogger) Infof

func (NullLogger) Infof(_ string, _ ...interface{})

Infof prints out a message prefixed with `[INFO]` formatted with the specified arguments

func (NullLogger) Warn

func (NullLogger) Warn(_ string)

Warn prints out a message prefixed with `[WARN]` formatted verbatim

func (NullLogger) Warnf

func (NullLogger) Warnf(_ string, _ ...interface{})

Warnf prints out a message prefixed with `[WARN]` formatted with the specified arguments

type Resource

type Resource interface {

	// Create will provision this resource using the information from the Terraform Configuration
	// NOTE: the shim layer will automatically call the Read function once this has been created
	// so it's no longer necessary to call this explicitly
	Create() ResourceFunc

	// Read retrieves the latest values for this object and saves them into Terraform's State
	Read() ResourceFunc

	// Delete will remove an existing resource using the information available in Terraform's State
	Delete() ResourceFunc

	// IDValidationFunc returns the SchemaValidateFunc used to validate the ID is valid during
	// `terraform import` - ensuring users don't inadvertently specify the incorrect Resource ID
	IDValidationFunc() pluginsdk.SchemaValidateFunc
	// contains filtered or unexported methods
}

A Resource is an object which can be provisioned and managed by Terraform that is, Created, Retrieved, Deleted, Imported (and optionally, Updated, by implementing the 'ResourceWithUpdate' interface)

It's worth calling out that not all Azure API's make sense as Terraform Resources - as a general rule if it supports CR(U)D it could, however.

type ResourceFunc

type ResourceFunc struct {
	// Func is the function which should be called for this Resource Func
	// for example, during Read this is the Read function, during Update this is the Update function
	Func ResourceRunFunc

	DiffFunc ResourceRunFunc

	// Timeout is the default timeout, which can be overridden by users
	// for this method - in-turn used for the Azure API
	Timeout time.Duration
}

type ResourceMetaData

type ResourceMetaData struct {
	// Client is a reference to the Azure Providers Client - providing a typed reference to this object
	Client *clients.Client

	// Logger provides a logger for debug purposes
	Logger Logger

	// ResourceData is a reference to the ResourceData object from Terraform's Plugin SDK
	// This is used to be able to call operations directly should Encode/Decode be insufficient
	// for example, to determine if a field has changes
	ResourceData *schema.ResourceData

	// ResourceDiff is a reference to the ResourceDiff object from Terraform's Plugin SDK
	ResourceDiff *schema.ResourceDiff
	// contains filtered or unexported fields
}

func (ResourceMetaData) Decode

func (rmd ResourceMetaData) Decode(input interface{}) error

Decode will decode the Terraform Schema into the specified object NOTE: this object must be passed by value - and must contain `tfschema` struct tags for all fields

Example Usage:

type Person struct {
	 Name string `tfschema:"name"
}

var person Person if err := metadata.Decode(&person); err != nil { .. }

func (ResourceMetaData) DecodeDiff

func (rmd ResourceMetaData) DecodeDiff(input interface{}) error

DecodeDiff decodes the Terraform Schema into the specified object in the same manner as Decode, but using the ResourceDiff as a source. Intended for use in CustomizeDiff functions.

func (ResourceMetaData) Encode

func (rmd ResourceMetaData) Encode(input interface{}) error

Encode will encode the specified object into the Terraform State NOTE: this requires that the object passed in is a pointer and all fields contain `tfschema` struct tags

func (ResourceMetaData) MarkAsGone

func (rmd ResourceMetaData) MarkAsGone(idFormatter resourceid.Formatter) error

MarkAsGone marks this resource as removed in the Remote API, so this is no longer available

func (ResourceMetaData) ResourceRequiresImport

func (rmd ResourceMetaData) ResourceRequiresImport(resourceName string, idFormatter resourceid.Formatter) error

ResourceRequiresImport returns an error saying that this resource must be imported with instructions on how to do this (namely, using `terraform import`

func (ResourceMetaData) SetID

func (rmd ResourceMetaData) SetID(formatter resourceid.Formatter)

SetID uses the specified ID Formatter to set the Resource ID

type ResourceRunFunc

type ResourceRunFunc func(ctx context.Context, metadata ResourceMetaData) error

ResourceRunFunc is the function which can be run ctx provides a Context instance with the user-provided timeout metadata is a reference to an object containing the Client, ResourceData and a Logger

type ResourceWithCustomImporter

type ResourceWithCustomImporter interface {
	Resource

	// CustomImporter returns a ResourceRunFunc which allows overriding the import
	CustomImporter() ResourceRunFunc
}

type ResourceWithCustomizeDiff

type ResourceWithCustomizeDiff interface {
	Resource

	// CustomizeDiff returns a ResourceFunc that runs the Custom Diff logic
	CustomizeDiff() ResourceFunc
}

ResourceWithCustomizeDiff is an optional interface

type ResourceWithDeprecationAndNoReplacement

type ResourceWithDeprecationAndNoReplacement interface {
	Resource

	// DeprecationMessage returns the Deprecation message for this resource
	// NOTE: this must return a non-empty string
	DeprecationMessage() string
}

ResourceWithDeprecationAndNoReplacement is an optional interface

Resources implementing this interface will be marked as Deprecated and output the DeprecationMessage during Terraform operations.

type ResourceWithDeprecationReplacedBy

type ResourceWithDeprecationReplacedBy interface {
	Resource

	// DeprecatedInFavourOfResource returns the name of the resource that this has been deprecated in favour of
	// NOTE: this must return a non-empty string
	// nolint:gocritic
	DeprecatedInFavourOfResource() string
}

ResourceWithDeprecationReplacedBy is an optional interface

Resources implementing this interface will be marked as Deprecated and output the DeprecationMessage during Terraform operations.

type ResourceWithStateMigration

type ResourceWithStateMigration interface {
	Resource
	StateUpgraders() StateUpgradeData
}

type ResourceWithUpdate

type ResourceWithUpdate interface {
	Resource

	// Update will make changes to this resource using the information from the Terraform Configuration/Plan
	// NOTE: the shim layer will automatically call the Read function once this has been created
	// so it's no longer necessary to call this explicitly
	Update() ResourceFunc
}

ResourceWithUpdate is an optional interface

Notably the Arguments for Resources implementing this interface cannot be entirely ForceNew - else this interface implementation is superfluous.

type ResourceWrapper

type ResourceWrapper struct {
	// contains filtered or unexported fields
}

ResourceWrapper is a wrapper for converting a Resource implementation into the object used by the Terraform Plugin SDK

func NewResourceWrapper

func NewResourceWrapper(resource Resource) ResourceWrapper

NewResourceWrapper returns a ResourceWrapper for this Resource implementation

func (*ResourceWrapper) Resource

func (rw *ResourceWrapper) Resource() (*schema.Resource, error)

Resource returns the Terraform Plugin SDK type for this Resource implementation

type StateUpgradeData

type StateUpgradeData struct {
	SchemaVersion int
	Upgraders     map[int]pluginsdk.StateUpgrade
}

type TypedServiceRegistration

type TypedServiceRegistration interface {
	// Name is the name of this Service
	Name() string

	// DataSources returns a list of Data Sources supported by this Service
	DataSources() []DataSource

	// Resources returns a list of Resources supported by this Service
	Resources() []Resource

	// WebsiteCategories returns a list of categories which can be used for the sidebar
	WebsiteCategories() []string
}

TypedServiceRegistration is a Service Registration using Types meaning that we can abstract on top of the Plugin SDK and use Native Types where possible

type UntypedServiceRegistration

type UntypedServiceRegistration interface {
	// Name is the name of this Service
	Name() string

	// WebsiteCategories returns a list of categories which can be used for the sidebar
	WebsiteCategories() []string

	// SupportedDataSources returns the supported Data Sources supported by this Service
	SupportedDataSources() map[string]*pluginsdk.Resource

	// SupportedResources returns the supported Resources supported by this Service
	SupportedResources() map[string]*pluginsdk.Resource
}

UntypedServiceRegistration is the interface used for untyped/raw Plugin SDK resources in the future this'll be superseded by the TypedServiceRegistration which allows for stronger Typed resources to be used.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL