Skip to content

Commit

Permalink
feat: metabase
Browse files Browse the repository at this point in the history
fix: Read for tests

fix: suggested chances
  • Loading branch information
LeCrabe authored and miton18 committed Dec 24, 2024
1 parent d9df803 commit 5d9dc9a
Show file tree
Hide file tree
Showing 8 changed files with 376 additions and 0 deletions.
31 changes: 31 additions & 0 deletions docs/resources/metabase.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "clevercloud_metabase Resource - terraform-provider-clevercloud"
subcategory: ""
description: |-
Manage Metabase https://www.metabase.com/ product.
---

# clevercloud_metabase (Resource)

Manage [Metabase](https://www.metabase.com/) product.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `name` (String) Name of the service
- `plan` (String) Database size and spec

### Optional

- `region` (String) Geographical region where the data will be stored

### Read-Only

- `creation_date` (Number) Date of database creation
- `host` (String) Metabase host, used to connect to
- `id` (String) Generated unique identifier
2 changes: 2 additions & 0 deletions pkg/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"go.clever-cloud.com/terraform-provider/pkg/resources/java"
"go.clever-cloud.com/terraform-provider/pkg/resources/keycloak"
"go.clever-cloud.com/terraform-provider/pkg/resources/materiakv"
"go.clever-cloud.com/terraform-provider/pkg/resources/metabase"
"go.clever-cloud.com/terraform-provider/pkg/resources/mongodb"
"go.clever-cloud.com/terraform-provider/pkg/resources/nodejs"
"go.clever-cloud.com/terraform-provider/pkg/resources/php"
Expand All @@ -27,6 +28,7 @@ var Resources = []func() resource.Resource{
cellar.NewResourceCellar,
java.NewResourceJava("war"),
materiakv.NewResourceMateriaKV,
metabase.NewResourceMetabase,
mongodb.NewResourceMongoDB,
nodejs.NewResourceNodeJS,
php.NewResourcePHP,
Expand Down
185 changes: 185 additions & 0 deletions pkg/resources/metabase/crud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package metabase

import (
"context"
"fmt"
"strings"

"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 *ResourceMetabase) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
tflog.Debug(ctx, "ResourceMetabase.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 *ResourceMetabase) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
mb := Metabase{}

resp.Diagnostics.Append(req.Plan.Get(ctx, &mb)...)
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, "metabase")
plan := pkg.LookupProviderPlan(prov, mb.Plan.ValueString())
if plan == nil || plan.ID == "" {
resp.Diagnostics.AddError("failed to find plan", "expect: "+strings.Join(pkg.ProviderPlansAsList(prov), ", ")+", got: "+mb.Plan.String())
return
}

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

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

mb.ID = pkg.FromStr(res.Payload().ID)
mb.CreationDate = pkg.FromI(res.Payload().CreationDate)
// mb.Plan = pkg.FromStr(res.Payload().Plan.Slug)

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

mbInfoRes := tmp.GetAddonEnv(ctx, r.cc, r.org, mb.ID.ValueString())
if mbInfoRes.HasError() {
resp.Diagnostics.AddError("failed to get Metabase connection infos", mbInfoRes.Error().Error())
return
}

addonMB := *mbInfoRes.Payload()
tflog.Debug(ctx, "API response", map[string]interface{}{
"payload": fmt.Sprintf("%+v", addonMB),
})

hostEnvVar := pkg.First(addonMB, func(v tmp.EnvVar) bool {
return v.Name == "METABASE_URL"
})
if hostEnvVar == nil {
resp.Diagnostics.AddError("cannot get Metabase infos", "missing METABASE_URL env var on created addon")
return
}

mb.Host = pkg.FromStr(hostEnvVar.Value)

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

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

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

addonMBRes := tmp.GetMetabase(ctx, r.cc, mb.ID.ValueString())
if addonMBRes.IsNotFoundError() {
diags = resp.State.SetAttribute(ctx, path.Root("id"), types.StringUnknown())
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// if addonMBRes.IsNotFoundError() {
// resp.State.RemoveResource(ctx)
// return
// }
// if addonMBRes.HasError() {
// resp.Diagnostics.AddError("failed to get Metabase resource", addonMBRes.Error().Error())
// }

addonMB := addonMBRes.Payload()

if addonMB.Status == "TO_DELETE" {
resp.State.RemoveResource(ctx)
return
}
tflog.Debug(ctx, "STATE", map[string]interface{}{"mb": mb})
tflog.Debug(ctx, "API", map[string]interface{}{"mb": addonMB})
// mb.Host = pkg.FromStr(addonMB.Applications[0].Host)
// mb.Port = pkg.FromI(int64(addonMB.Port))
// mb.User = pkg.FromStr(addonMB.User)
// mb.Password = pkg.FromStr(addonMB.Password)

diags = resp.State.Set(ctx, mb)
resp.Diagnostics.Append(diags...)
}

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

// Delete resource
func (r *ResourceMetabase) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var mb Metabase

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

res := tmp.DeleteAddon(ctx, r.cc, r.org, mb.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 *ResourceMetabase) 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)
}
1 change: 1 addition & 0 deletions pkg/resources/metabase/doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Manage [Metabase](https://www.metabase.com/) product.
21 changes: 21 additions & 0 deletions pkg/resources/metabase/metabase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package metabase

import (
"context"

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

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

func NewResourceMetabase() resource.Resource {
return &ResourceMetabase{}
}

func (r *ResourceMetabase) Metadata(ctx context.Context, req resource.MetadataRequest, res *resource.MetadataResponse) {
res.TypeName = req.ProviderTypeName + "_metabase"
}
67 changes: 67 additions & 0 deletions pkg/resources/metabase/metabase_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package metabase_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/helper"
"go.clever-cloud.com/terraform-provider/pkg/provider/impl"
"go.clever-cloud.com/terraform-provider/pkg/tmp"
"go.clever-cloud.dev/client"
)

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

func TestAccMetabase_basic(t *testing.T) {
ctx := context.Background()
rName := fmt.Sprintf("tf-test-mb-%d", time.Now().UnixMilli())
fullName := fmt.Sprintf("clevercloud_metabase.%s", rName)
cc := client.New(client.WithAutoOauthConfig())
org := os.Getenv("ORGANISATION")
providerBlock := helper.NewProvider("clevercloud").SetOrganisation(org).String()
metabaseBlock := helper.NewRessource("clevercloud_metabase", rName, helper.SetKeyValues(map[string]any{"name": rName, "plan": "beta", "region": "par"})).String()

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.GetMetabase(ctx, cc, resource.Primary.ID)
if res.IsNotFoundError() {
continue
}
if res.HasError() {
return fmt.Errorf("unexpectd error: %s", res.Error().Error())
}
if res.Payload().Status == "TO_DELETE" {
continue
}

return fmt.Errorf("expect resource '%s' to be deleted", resource.Primary.ID)
}
return nil
},
Steps: []resource.TestStep{{
ResourceName: rName,
Config: providerBlock + metabaseBlock,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestMatchResourceAttr(fullName, "id", regexp.MustCompile(`^addon_.*`)),
),
}},
})
}
44 changes: 44 additions & 0 deletions pkg/resources/metabase/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package metabase

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"
"go.clever-cloud.com/terraform-provider/pkg/attributes"
)

type Metabase struct {
attributes.Addon
Host types.String `tfsdk:"host"`
// HerokuId types.String `tfsdk:"heroku_id"`
// CallbackURL types.String `tfsdk:"callback_url"`
// LogplexToken types.String `tfsdk:"logplex_token"`
// OwnerId types.String `tfsdk:"owner_id"`
}

//go:embed doc.md
var resourceMetabaseDoc string

func (r ResourceMetabase) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Version: 0,
MarkdownDescription: resourceMetabaseDoc,
Attributes: attributes.WithAddonCommons(map[string]schema.Attribute{
// customer provided
// TODO: Markdown description
"host": schema.StringAttribute{Computed: true, MarkdownDescription: "Metabase host, used to connect to"},
// "heroku_id": schema.StringAttribute{Computed: true, MarkdownDescription: "heroku_id"},
// "callback_url": schema.StringAttribute{Computed: true, MarkdownDescription: "callback_url"},
// "logplex_token": schema.StringAttribute{Computed: true, MarkdownDescription: "logplex_token"},
// "owner_id": schema.StringAttribute{Computed: true, MarkdownDescription: "owner_id"},
}),
}
}

// https://developer.hashicorp.com/terraform/plugin/framework/resources/state-upgrade#implementing-state-upgrade-support
func (r ResourceMetabase) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader {
return map[int64]resource.StateUpgrader{}
}
Loading

0 comments on commit 5d9dc9a

Please sign in to comment.