-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #86
- Loading branch information
Showing
8 changed files
with
324 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
package keycloak | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/path" | ||
"github.com/hashicorp/terraform-plugin-framework/resource" | ||
"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 *ResourceKeycloak) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { | ||
tflog.Debug(ctx, "ResourceKeycloak.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() | ||
} | ||
|
||
tflog.Warn(ctx, "Keycloak product is still in beta, use it with care") | ||
} | ||
|
||
// Create a new resource | ||
func (r *ResourceKeycloak) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { | ||
kc := Keycloak{} | ||
|
||
resp.Diagnostics.Append(req.Plan.Get(ctx, &kc)...) | ||
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() | ||
provider := pkg.LookupAddonProvider(*addonsProviders, "keycloak") | ||
|
||
plan := pkg.LookupProviderPlan(provider, "beta") | ||
if plan == nil { | ||
resp.Diagnostics.AddError("This plan does not exists", "available plans are: "+strings.Join(pkg.ProviderPlansAsList(provider), ", ")) | ||
return | ||
} | ||
|
||
addonReq := tmp.AddonRequest{ | ||
Name: kc.Name.ValueString(), | ||
Plan: plan.ID, | ||
ProviderID: "keycloak", | ||
Region: kc.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 | ||
} | ||
|
||
kc.ID = pkg.FromStr(res.Payload().RealID) | ||
kc.CreationDate = pkg.FromI(res.Payload().CreationDate) | ||
|
||
resp.Diagnostics.Append(resp.State.Set(ctx, kc)...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
kcEnvRes := tmp.GetAddonEnv(ctx, r.cc, r.org, kc.ID.ValueString()) | ||
if kcEnvRes.HasError() { | ||
resp.Diagnostics.AddError("failed to get Keycloak connection infos", kcEnvRes.Error().Error()) | ||
return | ||
} | ||
|
||
kcEnv := *kcEnvRes.Payload() | ||
tflog.Debug(ctx, "API response", map[string]interface{}{ | ||
"payload": fmt.Sprintf("%+v", kcEnv), | ||
}) | ||
|
||
hostEnvVar := pkg.First(kcEnv, func(v tmp.EnvVar) bool { | ||
return v.Name == "CC_KEYCLOAK_URL" | ||
}) | ||
if hostEnvVar == nil { | ||
resp.Diagnostics.AddError("cannot get Keycloak infos", "missing CC_KEYCLOAK_URL env var on created addon") | ||
return | ||
} | ||
|
||
kc.Host = pkg.FromStr(hostEnvVar.Value) | ||
|
||
resp.Diagnostics.Append(resp.State.Set(ctx, kc)...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
} | ||
|
||
// Read resource information | ||
func (r *ResourceKeycloak) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { | ||
tflog.Debug(ctx, "Keycloak READ", map[string]interface{}{"request": req}) | ||
|
||
var kc Keycloak | ||
diags := req.State.Get(ctx, &kc) | ||
resp.Diagnostics.Append(diags...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
// TODO | ||
|
||
diags = resp.State.Set(ctx, kc) | ||
resp.Diagnostics.Append(diags...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
} | ||
|
||
// Update resource | ||
func (r *ResourceKeycloak) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { | ||
// TODO | ||
} | ||
|
||
// Delete resource | ||
func (r *ResourceKeycloak) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { | ||
kc := Keycloak{} | ||
|
||
diags := req.State.Get(ctx, &kc) | ||
resp.Diagnostics.Append(diags...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
tflog.Debug(ctx, "Keycloak DELETE", map[string]interface{}{"keycloak": kc}) | ||
|
||
res := tmp.DeleteAddon(ctx, r.cc, r.org, kc.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 *ResourceKeycloak) 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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Manage Keycloak |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package keycloak | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/resource" | ||
"go.clever-cloud.dev/client" | ||
) | ||
|
||
type ResourceKeycloak struct { | ||
cc *client.Client | ||
org string | ||
} | ||
|
||
func NewResourceKeycloak() resource.Resource { | ||
return &ResourceKeycloak{} | ||
} | ||
|
||
func (r *ResourceKeycloak) Metadata(ctx context.Context, req resource.MetadataRequest, res *resource.MetadataResponse) { | ||
res.TypeName = req.ProviderTypeName + "_keycloak" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package keycloak_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 TestAccKeycloak_basic(t *testing.T) { | ||
ctx := context.Background() | ||
rName := fmt.Sprintf("tf-test-kc-%d", time.Now().UnixMilli()) | ||
fullName := fmt.Sprintf("clevercloud_keycloak.%s", rName) | ||
cc := client.New(client.WithAutoOauthConfig()) | ||
org := os.Getenv("ORGANISATION") | ||
providerBlock := helper.NewProvider("clevercloud").SetOrganisation(org).String() | ||
materiakvBlock := helper.NewRessource("clevercloud_keycloak", rName, helper.SetKeyValues(map[string]any{"name": rName, "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.GetAddon(ctx, cc, org, resource.Primary.ID) | ||
if res.IsNotFoundError() { | ||
continue | ||
} | ||
if res.HasError() { | ||
return fmt.Errorf("unexpectd error: %s", res.Error().Error()) | ||
} | ||
|
||
return fmt.Errorf("expect resource '%s' to be deleted: %+v", resource.Primary.ID, res.Payload()) | ||
} | ||
return nil | ||
}, | ||
Steps: []resource.TestStep{{ | ||
ResourceName: rName, | ||
Config: providerBlock + materiakvBlock, | ||
Check: resource.ComposeAggregateTestCheckFunc( | ||
resource.TestMatchResourceAttr(fullName, "id", regexp.MustCompile(`^keycloak_.*`)), | ||
resource.TestMatchResourceAttr(fullName, "host", regexp.MustCompile(`^.*clever-cloud.com$`)), | ||
), | ||
}}, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package keycloak | ||
|
||
import ( | ||
"context" | ||
_ "embed" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/resource" | ||
"github.com/hashicorp/terraform-plugin-framework/resource/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
) | ||
|
||
type Keycloak struct { | ||
ID types.String `tfsdk:"id"` | ||
Name types.String `tfsdk:"name"` | ||
CreationDate types.Int64 `tfsdk:"creation_date"` | ||
Region types.String `tfsdk:"region"` | ||
Host types.String `tfsdk:"host"` | ||
} | ||
|
||
//go:embed doc.md | ||
var resourceKeycloakDoc string | ||
|
||
func (r ResourceKeycloak) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { | ||
resp.Schema = schema.Schema{ | ||
Version: 0, | ||
MarkdownDescription: resourceKeycloakDoc, | ||
Attributes: map[string]schema.Attribute{ | ||
"name": schema.StringAttribute{Required: true, MarkdownDescription: "Name of the service"}, | ||
"region": schema.StringAttribute{ | ||
Optional: true, | ||
Computed: true, | ||
Default: stringdefault.StaticString("par"), | ||
MarkdownDescription: "Geographical region where the data will be stored", | ||
}, | ||
"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: "URL to access Keycloak"}, | ||
}, | ||
} | ||
} | ||
|
||
// https://developer.hashicorp.com/terraform/plugin/framework/resources/state-upgrade#implementing-state-upgrade-support | ||
func (r ResourceKeycloak) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { | ||
return map[int64]resource.StateUpgrader{} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters