Skip to content

Commit

Permalink
feat: MateriaKV
Browse files Browse the repository at this point in the history
  • Loading branch information
miton18 committed Apr 11, 2024
1 parent 0f6a628 commit e3e0888
Show file tree
Hide file tree
Showing 9 changed files with 346 additions and 0 deletions.
2 changes: 2 additions & 0 deletions pkg/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"go.clever-cloud.com/terraform-provider/pkg/resources/cellar"
"go.clever-cloud.com/terraform-provider/pkg/resources/cellar/bucket"
"go.clever-cloud.com/terraform-provider/pkg/resources/java"
"go.clever-cloud.com/terraform-provider/pkg/resources/materiakv"
"go.clever-cloud.com/terraform-provider/pkg/resources/nodejs"
"go.clever-cloud.com/terraform-provider/pkg/resources/php"
"go.clever-cloud.com/terraform-provider/pkg/resources/postgresql"
Expand All @@ -28,4 +29,5 @@ var Resources = []func() resource.Resource{
java.NewResourceJava("war"),
scala.NewResourceScala(),
static.NewResourceStatic(),
materiakv.NewResourceMateriaKV,
}
3 changes: 3 additions & 0 deletions pkg/resources/materiakv/provider_test_block.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
provider "clevercloud" {
organisation = "%s"
}
21 changes: 21 additions & 0 deletions pkg/resources/materiakv/resource_materiakv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package materiakv

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/resource"
"go.clever-cloud.dev/client"
)

type ResourceMateriaKV struct {
cc *client.Client
org string
}

func NewResourceMateriaKV() resource.Resource {
return &ResourceMateriaKV{}
}

func (r *ResourceMateriaKV) Metadata(ctx context.Context, req resource.MetadataRequest, res *resource.MetadataResponse) {
res.TypeName = req.ProviderTypeName + "_materiakv"
}
Empty file.
175 changes: 175 additions & 0 deletions pkg/resources/materiakv/resource_materiakv_crud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package materiakv

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"go.clever-cloud.com/terraform-provider/pkg"
"go.clever-cloud.com/terraform-provider/pkg/provider"
"go.clever-cloud.com/terraform-provider/pkg/tmp"
)

// Weird behaviour, but TF can ask for a Resource without having configured a Provider (maybe for Meta and Schema)
// So we need to handle the case there is no ProviderData
func (r *ResourceMateriaKV) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
tflog.Info(ctx, "ResourceMateriaKV.Configure()")

// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

provider, ok := req.ProviderData.(provider.Provider)
if ok {
r.cc = provider.Client()
r.org = provider.Organization()
}
}

// Create a new resource
func (r *ResourceMateriaKV) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
kv := MateriaKV{}

resp.Diagnostics.Append(req.Plan.Get(ctx, &kv)...)
if resp.Diagnostics.HasError() {
return
}

addonsProvidersRes := tmp.GetAddonsProviders(ctx, r.cc)
if addonsProvidersRes.HasError() {
resp.Diagnostics.AddError("failed to get addon providers", addonsProvidersRes.Error().Error())
return
}

addonsProviders := addonsProvidersRes.Payload()
prov := pkg.LookupAddonProvider(*addonsProviders, "kv")
plan := pkg.LookupProviderPlan(prov, "alpha")

addonReq := tmp.AddonRequest{
Name: kv.Name.ValueString(),
Plan: plan.ID,
ProviderID: "kv",
Region: "par",
}

res := tmp.CreateAddon(ctx, r.cc, r.org, addonReq)
if res.HasError() {
resp.Diagnostics.AddError("failed to create addon", res.Error().Error())
return
}

kv.ID = pkg.FromStr(res.Payload().RealID)
kv.CreationDate = pkg.FromI(res.Payload().CreationDate)

resp.Diagnostics.Append(resp.State.Set(ctx, kv)...)
if resp.Diagnostics.HasError() {
return
}

kvInfoRes := tmp.GetMateriaKV(ctx, r.cc, r.org, kv.ID.ValueString())
if kvInfoRes.HasError() {
resp.Diagnostics.AddError("failed to get materiakv connection infos", kvInfoRes.Error().Error())
return
}

kvInfo := kvInfoRes.Payload()
tflog.Info(ctx, "API response", map[string]interface{}{
"payload": fmt.Sprintf("%+v", kvInfo),
})
kv.Host = pkg.FromStr(kvInfo.Host)
kv.Port = pkg.FromI(int64(kvInfo.Port))
kv.Token = pkg.FromStr(kvInfo.Token)

resp.Diagnostics.Append(resp.State.Set(ctx, kv)...)
if resp.Diagnostics.HasError() {
return
}
}

// Read resource information
func (r *ResourceMateriaKV) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
tflog.Info(ctx, "MateriaKV READ", map[string]interface{}{"request": req})

var kv MateriaKV
diags := req.State.Get(ctx, &kv)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

addonPGRes := tmp.GetMateriaKV(ctx, r.cc, r.org, kv.ID.ValueString())
if addonPGRes.IsNotFoundError() {
diags = resp.State.SetAttribute(ctx, path.Root("id"), types.StringUnknown())
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
if addonPGRes.IsNotFoundError() {
resp.State.RemoveResource(ctx)
return
}
if addonPGRes.HasError() {
resp.Diagnostics.AddError("failed to get materiakv resource", addonPGRes.Error().Error())
}

addonPG := addonPGRes.Payload()

if addonPG.Status.Status == "TO_DELETE" {
resp.State.RemoveResource(ctx)
return
}

tflog.Info(ctx, "STATE", map[string]interface{}{"kv": kv})
tflog.Info(ctx, "API", map[string]interface{}{"kv": addonPG})
kv.Host = pkg.FromStr(addonPG.Host)
kv.Port = pkg.FromI(int64(addonPG.Port))
kv.Token = pkg.FromStr(addonPG.Token)

diags = resp.State.Set(ctx, kv)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}

// Update resource
func (r *ResourceMateriaKV) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
// TODO
}

// Delete resource
func (r *ResourceMateriaKV) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
kv := MateriaKV{}

diags := req.State.Get(ctx, &kv)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "MateriaKV DELETE", map[string]interface{}{"kv": kv})

res := tmp.DeleteAddon(ctx, r.cc, r.org, kv.ID.ValueString())
if res.IsNotFoundError() {
resp.State.RemoveResource(ctx)
return
}
if res.HasError() {
resp.Diagnostics.AddError("failed to delete addon", res.Error().Error())
return
}

resp.State.RemoveResource(ctx)
}

// Import resource
func (r *ResourceMateriaKV) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
// Save the import identifier in the id attribute
// and call Read() to fill fields
attr := path.Root("id")
resource.ImportStatePassthroughID(ctx, attr, req, resp)
}
44 changes: 44 additions & 0 deletions pkg/resources/materiakv/resource_materiakv_schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package materiakv

import (
"context"
_ "embed"

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

type MateriaKV struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
CreationDate types.Int64 `tfsdk:"creation_date"`
Host types.String `tfsdk:"host"`
Port types.Int64 `tfsdk:"port"`
Token types.String `tfsdk:"token"`
}

//go:embed resource_materiakv.md
var resourceMateriaKVDoc string

func (r ResourceMateriaKV) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Version: 0,
MarkdownDescription: resourceMateriaKVDoc,
Attributes: map[string]schema.Attribute{
// customer provided
"name": schema.StringAttribute{Required: true, MarkdownDescription: "Name of the service"},
// provider
"id": schema.StringAttribute{Computed: true, MarkdownDescription: "Generated unique identifier"},
"creation_date": schema.Int64Attribute{Computed: true, MarkdownDescription: "Date of database creation"},
"host": schema.StringAttribute{Computed: true, MarkdownDescription: "Database host, used to connect to"},
"port": schema.Int64Attribute{Computed: true, MarkdownDescription: "Database port"},
"token": schema.StringAttribute{Computed: true, MarkdownDescription: "Token to authenticate"},
},
}
}

// https://developer.hashicorp.com/terraform/plugin/framework/resources/state-upgrade#implementing-state-upgrade-support
func (r ResourceMateriaKV) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader {
return map[int64]resource.StateUpgrader{}
}
73 changes: 73 additions & 0 deletions pkg/resources/materiakv/resource_materiakv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package materiakv_test

import (
"context"
_ "embed"
"fmt"
"os"
"regexp"
"testing"
"time"

"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"go.clever-cloud.com/terraform-provider/pkg/provider/impl"
"go.clever-cloud.com/terraform-provider/pkg/tmp"
"go.clever-cloud.dev/client"
)

//go:embed resource_materiakv_test_block.tf
var materiakvBlock string

//go:embed provider_test_block.tf
var providerBlock string

var protoV6Provider = map[string]func() (tfprotov6.ProviderServer, error){
"clevercloud": providerserver.NewProtocol6WithError(impl.New("test")()),
}

func TestAccMateriaKV_basic(t *testing.T) {
ctx := context.Background()
rName := fmt.Sprintf("tf-test-kv-%d", time.Now().UnixMilli())
fullName := fmt.Sprintf("clevercloud_materiakv.%s", rName)
cc := client.New(client.WithAutoOauthConfig())
org := os.Getenv("ORGANISATION")

resource.Test(t, resource.TestCase{
PreCheck: func() {
if org == "" {
t.Fatalf("missing ORGANISATION env var")
}
},
ProtoV6ProviderFactories: protoV6Provider,
CheckDestroy: func(state *terraform.State) error {
for _, resource := range state.RootModule().Resources {
res := tmp.GetMateriaKV(ctx, cc, org, resource.Primary.ID)
if res.IsNotFoundError() {
continue
}
if res.HasError() {
return fmt.Errorf("unexpectd error: %s", res.Error().Error())
}
if res.Payload().Status.Status == "TO_DELETE" {
continue
}

return fmt.Errorf("expect resource '%s' to be deleted", resource.Primary.ID)
}
return nil
},
Steps: []resource.TestStep{{
ResourceName: rName,
Config: fmt.Sprintf(providerBlock, org) + fmt.Sprintf(materiakvBlock, rName, rName),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestMatchResourceAttr(fullName, "id", regexp.MustCompile(`^kv_.*`)),
resource.TestMatchResourceAttr(fullName, "host", regexp.MustCompile(`^.*clever-cloud.com$`)),
resource.TestCheckResourceAttrSet(fullName, "port"),
resource.TestCheckResourceAttrSet(fullName, "token"),
),
}},
})
}
3 changes: 3 additions & 0 deletions pkg/resources/materiakv/resource_materiakv_test_block.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resource "clevercloud_materiakv" "%s" {
name = "%s"
}
25 changes: 25 additions & 0 deletions pkg/tmp/addon.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,31 @@ func GetPostgreSQL(ctx context.Context, cc *client.Client, postgresqlID string)
return client.Get[PostgreSQL](ctx, cc, path)
}

type MateriaKV struct {
ID string `json:"id"`
ClusterID string `json:"clusterId"`
OrganisationID string `json:"ownerId"`
Kind string `json:"kind"`
Plan string `json:"plan"`
Host string `json:"host"`
Port int64 `json:"port"`
Token string `json:"token"`
TokenID string `json:"tokenId"`
Status MateriaKVStatus `json:"status"`
// ccapiUrl "https://api.clever-cloud.com/v2/vendor/apps/addon_dbf12716-9353-41ef-aabf-e4b7fce1ba5e"
}

type MateriaKVStatus struct {
ID string `json:"id"`
ServiceID string `json:"serviceId"`
Status string `json:"status"`
}

func GetMateriaKV(ctx context.Context, cc *client.Client, organisationID, postgresqlID string) client.Response[MateriaKV] {
path := fmt.Sprintf("/v4/materia/organisations/%s/materia/databases/%s", organisationID, postgresqlID)
return client.Get[MateriaKV](ctx, cc, path)
}

type DeleteAddonResponse struct {
ID int64 `json:"id"`
Message string `json:"message"`
Expand Down

0 comments on commit e3e0888

Please sign in to comment.