From 62afc6f6d470b00a88184e1159fc7784213986bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20Tobias=20Skjong-B=C3=B8rsting?= Date: Fri, 22 Jul 2022 13:40:54 +0200 Subject: [PATCH] Add Data Source multipass_instance (#6) * Also some refactoring * Started adding the possibility of provider configuration --- docs/data-sources/instance.md | 29 ++++++ internal/provider/instance_data_source.go | 95 ++++++++++++++++++ internal/provider/provider.go | 99 +++++++++++++++++++ internal/provider/provider_test.go | 22 +++++ .../provider}/resource_instance.go | 63 ++++++------ main.go | 18 +++- multipass/provider.go | 45 --------- terraform-registry-manifest.json | 4 +- 8 files changed, 296 insertions(+), 79 deletions(-) create mode 100644 docs/data-sources/instance.md create mode 100644 internal/provider/instance_data_source.go create mode 100644 internal/provider/provider.go create mode 100644 internal/provider/provider_test.go rename {multipass => internal/provider}/resource_instance.go (73%) delete mode 100644 multipass/provider.go diff --git a/docs/data-sources/instance.md b/docs/data-sources/instance.md new file mode 100644 index 0000000..0b653bf --- /dev/null +++ b/docs/data-sources/instance.md @@ -0,0 +1,29 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "multipass_instance Data Source - terraform-provider-multipass" +subcategory: "" +description: |- + Instance data source +--- + +# multipass_instance (Data Source) + +Instance data source + + + + +## Schema + +### Required + +- `name` (String) Instance name + +### Read-Only + +- `image` (String) The image of the instance +- `image_hash` (String) The image_hash of the instance +- `ipv4` (String) The IPv4 address of the instance +- `state` (String) The state of the instance + + diff --git a/internal/provider/instance_data_source.go b/internal/provider/instance_data_source.go new file mode 100644 index 0000000..e66738b --- /dev/null +++ b/internal/provider/instance_data_source.go @@ -0,0 +1,95 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/larstobi/go-multipass/multipass" +) + +var _ tfsdk.DataSourceType = instanceDataSourceType{} +var _ tfsdk.DataSource = instanceDataSource{} + +type instanceDataSourceType struct{} + +func (t instanceDataSourceType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + MarkdownDescription: "Instance data source", + + Attributes: map[string]tfsdk.Attribute{ + "name": { + MarkdownDescription: "Instance name", + Type: types.StringType, + Required: true, + }, + "ipv4": { + MarkdownDescription: "The IPv4 address of the instance", + Type: types.StringType, + Computed: true, + }, + "state": { + MarkdownDescription: "The state of the instance", + Type: types.StringType, + Computed: true, + }, + "image": { + MarkdownDescription: "The image of the instance", + Type: types.StringType, + Computed: true, + }, + "image_hash": { + MarkdownDescription: "The image_hash of the instance", + Type: types.StringType, + Computed: true, + }, + }, + }, nil +} + +func (t instanceDataSourceType) NewDataSource(ctx context.Context, in tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { + provider, diags := convertProviderType(in) + + return instanceDataSource{ + provider: provider, + }, diags +} + +type instanceDataSourceData struct { + Name types.String `tfsdk:"name"` + IPv4 types.String `tfsdk:"ipv4"` + State types.String `tfsdk:"state"` + Image types.String `tfsdk:"image"` + ImageHash types.String `tfsdk:"image_hash"` +} + +type instanceDataSource struct { + provider provider +} + +func (d instanceDataSource) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) { + var data instanceDataSourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + instance, err := multipass.Info(&multipass.InfoRequest{Name: data.Name.Value}) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read instance, got error: %s", err)) + return + } + + data.IPv4 = types.String{Value: instance.IP} + data.State = types.String{Value: instance.State} + data.Image = types.String{Value: instance.Image} + data.ImageHash = types.String{Value: instance.ImageHash} + + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go new file mode 100644 index 0000000..d28d5b7 --- /dev/null +++ b/internal/provider/provider.go @@ -0,0 +1,99 @@ +package provider + +import ( + "context" + "fmt" + "os" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + // "github.com/hashicorp/terraform-plugin-framework/types" +) + +var stderr = os.Stderr + +type provider struct { + configured bool + version string +} + +// type providerData struct { +// Address types.String `tfsdk:"address"` +// } + +func (p *provider) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{}, + }, nil +} + +type providerData struct{} + +func (p *provider) Configure(ctx context.Context, req tfsdk.ConfigureProviderRequest, resp *tfsdk.ConfigureProviderResponse) { + // Retrieve provider data from configuration + var data providerData + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + p.configured = true +} + +func (p *provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + return map[string]tfsdk.ResourceType{ + "multipass_instance": instanceResourceType{}, + }, nil +} + +func (p *provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) { + return map[string]tfsdk.DataSourceType{ + "multipass_instance": instanceDataSourceType{}, + }, nil + +} + +// func (p *provider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { +// return tfsdk.Schema{ +// Attributes: map[string]tfsdk.Attribute{ +// "address": { +// MarkdownDescription: "Specifies which address to use for the multipassd service. A socket can be specified using unix: or a TCP address can be specified using ", +// Optional: true, +// Type: types.StringType, +// }, +// }, +// }, nil +// } + +func New(version string) func() tfsdk.Provider { + return func() tfsdk.Provider { + return &provider{ + version: version, + } + } +} + +func convertProviderType(in tfsdk.Provider) (provider, diag.Diagnostics) { + var diags diag.Diagnostics + + p, ok := in.(*provider) + + if !ok { + diags.AddError( + "Unexpected Provider Instance Type", + fmt.Sprintf("While creating the data source or resource, an unexpected provider type (%T) was received. This is always a bug in the provider code and should be reported to the provider developers.", p), + ) + return provider{}, diags + } + + if p == nil { + diags.AddError( + "Unexpected Provider Instance Type", + "While creating the data source or resource, an unexpected empty provider instance was received. This is always a bug in the provider code and should be reported to the provider developers.", + ) + return provider{}, diags + } + + return *p, diags +} diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go new file mode 100644 index 0000000..05e143b --- /dev/null +++ b/internal/provider/provider_test.go @@ -0,0 +1,22 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +// testAccProtoV6ProviderFactories are used to instantiate a provider during +// acceptance testing. The factory function will be invoked for every Terraform +// CLI command executed to create a provider server to which the CLI can +// reattach. +var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ + "scaffolding": providerserver.NewProtocol6WithError(New("test")()), +} + +func testAccPreCheck(t *testing.T) { + // You can add code here to run prior to any test case execution, for example assertions + // about the appropriate environment variables being set are common to see in a pre-check + // function. +} diff --git a/multipass/resource_instance.go b/internal/provider/resource_instance.go similarity index 73% rename from multipass/resource_instance.go rename to internal/provider/resource_instance.go index fb76c79..59e14b1 100644 --- a/multipass/resource_instance.go +++ b/internal/provider/resource_instance.go @@ -1,7 +1,8 @@ -package multipass +package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" @@ -10,15 +11,19 @@ import ( "github.com/larstobi/go-multipass/multipass" ) -type resourceInstanceType struct{} +var _ tfsdk.ResourceType = instanceResourceType{} +var _ tfsdk.Resource = instanceResource{} +var _ tfsdk.ResourceWithImportState = instanceResource{} + +type instanceResourceType struct{} -// Instance Resource schema -func (r resourceInstanceType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { +func (r instanceResourceType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { return tfsdk.Schema{ - Description: "Multipass instance resource.", + MarkdownDescription: "Multipass instance resource.", + Version: 0, Attributes: map[string]tfsdk.Attribute{ "name": { - Description: "Name for the instance. If it is 'primary' " + + MarkdownDescription: "Name for the instance. If it is 'primary' " + "(the configured primary instance name), the user's " + "home directory is mounted inside the newly launched " + "instance, in 'Home'.", @@ -29,7 +34,7 @@ func (r resourceInstanceType) GetSchema(_ context.Context) (tfsdk.Schema, diag.D }, }, "image": { - Description: "Optional image to launch. If omitted, then " + + MarkdownDescription: "Optional image to launch. If omitted, then " + "the default Ubuntu LTS will be used. can be " + "either ‘release’ or ‘daily‘. If is " + "omitted, ‘release’ will be used. can be a " + @@ -43,15 +48,15 @@ func (r resourceInstanceType) GetSchema(_ context.Context) (tfsdk.Schema, diag.D }, }, "cpus": { - Description: "Number of CPUs to allocate. Minimum: 1, default: 1.", - Type: types.NumberType, - Optional: true, + MarkdownDescription: "Number of CPUs to allocate. Minimum: 1, default: 1.", + Type: types.NumberType, + Optional: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), }, }, "memory": { - Description: "Amount of memory to allocate. Positive integers, " + + MarkdownDescription: "Amount of memory to allocate. Positive integers, " + "in bytes, or with K, M, G suffix. Minimum: 128M, default: 1G.", Type: types.StringType, Optional: true, @@ -60,7 +65,7 @@ func (r resourceInstanceType) GetSchema(_ context.Context) (tfsdk.Schema, diag.D }, }, "disk": { - Description: "Disk space to allocate. Positive integers, in bytes, " + + MarkdownDescription: "Disk space to allocate. Positive integers, in bytes, " + "or with K, M, G suffix. Minimum: 512M, default: 5G.", Type: types.StringType, Optional: true, @@ -69,9 +74,9 @@ func (r resourceInstanceType) GetSchema(_ context.Context) (tfsdk.Schema, diag.D }, }, "cloudinit_file": { - Description: "Path to a user-data cloud-init configuration.", - Type: types.StringType, - Optional: true, + MarkdownDescription: "Path to a user-data cloud-init configuration.", + Type: types.StringType, + Optional: true, PlanModifiers: []tfsdk.AttributePlanModifier{ tfsdk.RequiresReplace(), }, @@ -80,11 +85,12 @@ func (r resourceInstanceType) GetSchema(_ context.Context) (tfsdk.Schema, diag.D }, nil } -// New resource instance -func (r resourceInstanceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceInstance{ - p: *(p.(*provider)), - }, nil +func (t instanceResourceType) NewResource(ctx context.Context, in tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + provider, diags := convertProviderType(in) + + return instanceResource{ + provider: provider, + }, diags } type Instance struct { @@ -96,12 +102,11 @@ type Instance struct { CloudInitFile types.String `tfsdk:"cloudinit_file"` } -type resourceInstance struct { - p provider +type instanceResource struct { + provider provider } -// Create a new resource -func (r resourceInstance) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { +func (r instanceResource) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { // Retrieve values from plan var plan Instance @@ -111,7 +116,7 @@ func (r resourceInstance) Create(ctx context.Context, req tfsdk.CreateResourceRe return } - tflog.Info(ctx, "Multipass resourceInstance", map[string]interface{}{ + tflog.Info(ctx, "Multipass instanceResource", map[string]interface{}{ "name": plan.Name.String(), }) @@ -139,13 +144,13 @@ func (r resourceInstance) Create(ctx context.Context, req tfsdk.CreateResourceRe } } -func (r resourceInstance) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { +func (r instanceResource) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) { } -func (r resourceInstance) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { +func (r instanceResource) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) { } -func (r resourceInstance) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { +func (r instanceResource) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) { var state Instance diags := req.State.Get(ctx, &state) @@ -170,7 +175,7 @@ func (r resourceInstance) Delete(ctx context.Context, req tfsdk.DeleteResourceRe } // Import resource -func (r resourceInstance) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { +func (r instanceResource) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { // Save the import identifier in the id attribute tfsdk.ResourceImportStatePassthroughID(ctx, tftypes.NewAttributePath().WithAttributeName("id"), req, resp) } diff --git a/main.go b/main.go index e255059..c4b12af 100644 --- a/main.go +++ b/main.go @@ -2,17 +2,29 @@ package main import ( "context" - "github.com/hashicorp/terraform-plugin-framework/providerserver" + "flag" "log" - "terraform-provider-multipass/multipass" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "terraform-provider-multipass/internal/provider" +) + +var ( + version string = "dev" ) func main() { + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + opts := providerserver.ServeOpts{ Address: "registry.terraform.io/larstobi/multipass", + Debug: debug, } - err := providerserver.Serve(context.Background(), multipass.New, opts) + err := providerserver.Serve(context.Background(), provider.New(version), opts) if err != nil { log.Fatal(err.Error()) diff --git a/multipass/provider.go b/multipass/provider.go deleted file mode 100644 index ae9625f..0000000 --- a/multipass/provider.go +++ /dev/null @@ -1,45 +0,0 @@ -package multipass - -import ( - "context" - "os" - - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" -) - -var stderr = os.Stderr - -func New() tfsdk.Provider { - return &provider{} -} - -type provider struct{} - -func (p *provider) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{}, - }, nil -} - -type providerData struct{} - -func (p *provider) Configure(ctx context.Context, req tfsdk.ConfigureProviderRequest, resp *tfsdk.ConfigureProviderResponse) { - // Retrieve provider data from configuration - var config providerData - diags := req.Config.Get(ctx, &config) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } -} - -func (p *provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { - return map[string]tfsdk.ResourceType{ - "multipass_instance": resourceInstanceType{}, - }, nil -} - -func (p *provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) { - return map[string]tfsdk.DataSourceType{}, nil -} diff --git a/terraform-registry-manifest.json b/terraform-registry-manifest.json index 625ab56..295001a 100644 --- a/terraform-registry-manifest.json +++ b/terraform-registry-manifest.json @@ -1,6 +1,6 @@ { "version": 1, "metadata": { - "protocol_versions": ["5.0"] + "protocol_versions": ["6.0"] } -} \ No newline at end of file +}