Skip to content

Commit

Permalink
add example provider
Browse files Browse the repository at this point in the history
  • Loading branch information
kmoe committed May 25, 2021
1 parent 5f345b8 commit 857d3d7
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 0 deletions.
43 changes: 43 additions & 0 deletions example_provider/placeholders.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package provider

import (
"context"

tfsdk "github.com/hashicorp/terraform-plugin-framework"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

type Provider interface{}

type Resource interface{}

type CreateResourceRequest struct {
Plan Plan
}

type Plan struct{}

func (p Plan) Get(ctx context.Context, target interface{}) error {
return nil
}

func (p Plan) GetAttribute(ctx context.Context, attr *tftypes.AttributePath, target interface{}) error {
return nil
}

type CreateResourceResponse struct {
State State
}

func (c CreateResourceResponse) WithError(title string, err error) {
}

type State struct{}

func (s State) Set(ctx context.Context, val tfsdk.AttributeValue) error {
return nil
}

func (s State) SetAttribute(ctx context.Context, attr *tftypes.AttributePath, value tfsdk.AttributeValue) error {
return nil
}
160 changes: 160 additions & 0 deletions example_provider/resource_compute_instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package provider

import (
"bytes"
"context"
"encoding/json"
"io/ioutil"
"net/http"

"github.com/hashicorp/terraform-plugin-framework/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

type resourceComputeInstanceType struct{}

func (r resourceComputeInstanceType) GetSchema() schema.Schema {
return schema.Schema{
Version: 1,
Attributes: map[string]schema.Attribute{
"name": {
Type: types.StringType,
Description: "The name to associate with the compute instance.",
Required: true,
},
"disks": {
Attributes: schema.ListNestedAttributes(map[string]schema.Attribute{
"id": {
Type: types.StringType,
Description: "The ID of the pre-existing disk to associate with this compute instance.",
Required: true,
},
"delete_with_instance": {
Type: types.BoolType,
Description: "Set to true to delete the disk and all its contents when the instance is deleted.",
Optional: true,
},
}, schema.ListNestedAttributesOptions{}),
Optional: true,
Computed: true,
},
},
}
}

// This would actually be `NewResource(p schemaProvider) schemaResource` but we
// don't have those types yet, so... placeholders. Gross.
func (r resourceComputeInstanceType) NewResource(p Provider) Resource {
return resourceComputeInstance{
client: p.(*http.Client),
}
}

type resourceComputeInstance struct {
client *http.Client
}

type resourceComputeInstanceValues struct {
Name types.String `tfsdk:"name"`
Disks types.List `tfsdk:"disks"`
}

type resourceComputeInstanceDisksValues struct {
ID types.String `tfsdk:"id"`
DeleteWithInstance types.Bool `tfsdk:"delete_with_instance"`
}

func (r resourceComputeInstance) Create(ctx context.Context, req CreateResourceRequest, resp CreateResourceResponse) {
var values resourceComputeInstanceValues
err := req.Plan.Get(ctx, &values)
if err != nil {
// If this is a tftypes.AttributePathError, we could have it
// set the attribute path for us automatically :D
resp.WithError("Error parsing plan", err)
return
}
apiReq := map[string]interface{}{
// name is required, it will never be null or unknown
"name": values.Name.Value,
}
// disks are optional and computed; they'll never be null, but they may
// be unknown, in which case we'll fill them in ourselves.
if !values.Disks.Unknown {
// we don't want to get these as a types.Object, we want them
// as a resourceComputeInstanceDisksValues. So let's get them
// as that.
path := tftypes.NewAttributePath().WithAttributeName("disks")
apiReq["disks"] = make([]map[string]interface{}, 0, len(values.Disks.Elems))
for pos := range values.Disks.Elems {
var disk resourceComputeInstanceDisksValues
err := req.Plan.GetAttribute(ctx, path.WithElementKeyInt(int64(pos)), &disk)
if err != nil {
resp.WithError("Error parsing disk from plan", err)
return
}
apiReq["disks"] = append(apiReq["disks"].([]map[string]interface{}), map[string]interface{}{
"id": disk.ID,
"delete_with_instance": disk.DeleteWithInstance,
})
}
}
apiResp, err := createComputeInstance(ctx, apiReq)
if err != nil {
resp.WithError("Error creating instance", err)
return
}
err = resp.State.SetAttribute(ctx, tftypes.NewAttributePath().WithAttributeName("name"), values.Name)
if err != nil {
resp.WithError("Error setting name in response", err)
return
}
if values.Disks.Unknown {
// if our disks were unknown, we should set them from the response
for diskNo := range values.Disks.Elems {
path := tftypes.NewAttributePath().WithAttributeName("disks").WithElementKeyInt(int64(diskNo))
// this next line is gross, but wouldn't be if this were real, because API clients are a thing
disk := apiResp["disks"].([]map[string]interface{})[diskNo]
err = resp.State.SetAttribute(ctx, path.WithAttributeName("id"), types.String{Value: disk["id"].(string)})
if err != nil {
resp.WithError("Error setting disk ID in response", err)
return
}
err = resp.State.SetAttribute(ctx, path.WithAttributeName("delete_with_instance"), types.Bool{Value: disk["delete_with_instance"].(bool)})
if err != nil {
resp.WithError("Error setting disk DeleteWithInstance in response", err)
return
}
}
} else {
// otherwise, we should set them from what was in the config
err = resp.State.SetAttribute(ctx, tftypes.NewAttributePath().WithAttributeName("disks"), values.Disks)
if err != nil {
resp.WithError("Error setting disks in response", err)
return
}
}
}

// a lazy placeholder client
func createComputeInstance(ctx context.Context, vals map[string]interface{}) (map[string]interface{}, error) {
b, err := json.Marshal(vals)
if err != nil {
return nil, err
}
apiResp, err := http.Post("https://my.api/resource", "application/json", bytes.NewReader(b))
if err != nil {
return nil, err
}
defer apiResp.Body.Close()
apiRespBody, err := ioutil.ReadAll(apiResp.Body)
if err != nil {
return nil, err
}
apiRespParsed := map[string]interface{}{}
err = json.Unmarshal(apiRespBody, &apiRespParsed)
if err != nil {
return nil, err
}
return apiRespParsed, nil
}
18 changes: 18 additions & 0 deletions example_provider/resource_compute_instance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package provider

import (
"context"
"testing"
)

func TestResourceComputeInstance(t *testing.T) {
var r resourceComputeInstance

req := CreateResourceRequest{
Plan: Plan{},
}
resp := CreateResourceResponse{
State: State{},
}
r.Create(context.Background(), req, resp)
}

0 comments on commit 857d3d7

Please sign in to comment.