Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSS-10237 generic access imports #571

Merged
merged 2 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 71 additions & 3 deletions internal/provider/resource_access_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type Setter interface {
type resourcer interface {
Info(ctx context.Context, getter Getter, diag *diag.Diagnostics) (genericJAASAccessModel, names.Tag)
Save(ctx context.Context, setter Setter, info genericJAASAccessModel, tag names.Tag) diag.Diagnostics
ImportHint() string
}

// genericJAASAccessResource is a generic resource that can be used for creating access rules with JAAS.
Expand All @@ -74,6 +75,9 @@ type genericJAASAccessModel struct {
ServiceAccounts types.Set `tfsdk:"service_accounts"`
Groups types.Set `tfsdk:"groups"`
Access types.String `tfsdk:"access"`

// ID required for imports
ID types.String `tfsdk:"id"`
}

// ConfigValidators sets validators for the resource.
Expand Down Expand Up @@ -135,6 +139,13 @@ func (r *genericJAASAccessResource) partialAccessSchema() map[string]schema.Attr
setvalidator.ValueStringsAre(stringvalidator.RegexMatches(avoidAtSymbolRe, "service account should not contain an @ symbol")),
},
},
// ID required for imports
"id": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
}
}

Expand Down Expand Up @@ -185,6 +196,7 @@ func (resource *genericJAASAccessResource) Create(ctx context.Context, req resou
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create access relationships for %s, got error: %s", targetTag.String(), err))
return
}
plan.ID = types.StringValue(newJaasAccessID(targetTag, plan.Access.ValueString()))
// Set the plan onto the Terraform state
resp.Diagnostics.Append(resource.targetResource.Save(ctx, &resp.State, plan, targetTag)...)
}
Expand All @@ -196,30 +208,41 @@ func (resource *genericJAASAccessResource) Read(ctx context.Context, req resourc
addClientNotConfiguredError(&resp.Diagnostics, resource.resourceLogName, "read")
return
}

// Read Terraform configuration from the request into the resource model
state, targetTag := resource.info(ctx, req.State, &resp.Diagnostics)
// Ignore the target tag as it will come from the ID
state, _ := resource.info(ctx, req.State, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

// Create a tuple that defines what relations we are interested in
// Retrieve information necessary for reads from the ID to handle imports
targetTag, access := retrieveJaasAccessFromID(state.ID, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

// Perform read request for relations
readTuple := juju.JaasTuple{
Target: targetTag.String(),
Relation: state.Access.ValueString(),
Relation: access,
}
tuples, err := resource.client.Jaas.ReadRelations(ctx, &readTuple)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read access rules for %s, got error: %s", targetTag.String(), err))
return
}

// Transform the tuples into an access model
newModel := tuplesToModel(ctx, tuples, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

state.Users = newModel.Users
state.Groups = newModel.Groups
state.ServiceAccounts = newModel.ServiceAccounts
state.Access = basetypes.NewStringValue(access)
resp.Diagnostics.Append(resource.targetResource.Save(ctx, &resp.State, state, targetTag)...)
}

Expand All @@ -233,6 +256,8 @@ func (resource *genericJAASAccessResource) Update(ctx context.Context, req resou

// Note: We only need to read the targetID from either the plan or the state.
// If it changed, the resource should be replaced rather than updated.
// The same also applies to the access level.
// For this reason we don't need to update the ID as a new ID implies a different resource.

// Read Terraform configuration from the state
state, targetTag := resource.info(ctx, req.State, &resp.Diagnostics)
Expand Down Expand Up @@ -443,3 +468,46 @@ func (a *genericJAASAccessResource) info(ctx context.Context, getter Getter, dia
func (a *genericJAASAccessResource) save(ctx context.Context, setter Setter, info genericJAASAccessModel, tag names.Tag) diag.Diagnostics {
return a.targetResource.Save(ctx, setter, info, tag)
}

func newJaasAccessID(targetTag names.Tag, accessStr string) string {
return fmt.Sprintf("%s:%s", targetTag.String(), accessStr)
}

func retrieveJaasAccessFromID(ID types.String, diag *diag.Diagnostics) (resourceTag names.Tag, access string) {
resID := strings.Split(ID.ValueString(), ":")
if len(resID) != 2 {
diag.AddError("Malformed ID", fmt.Sprintf("Access ID %q is malformed", resID))
return nil, ""
}
tag, err := jimmnames.ParseTag(resID[0])
if err != nil {
diag.AddError("ID Error", fmt.Sprintf("Tag %s from ID is not valid: %s", tag, err))
return nil, ""
}
return tag, resID[1]
}

// Importstate validates the user provided ID and attempts to create a resource by
// reading and importing the object referred to by the provided ID.
func (a *genericJAASAccessResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
kian99 marked this conversation as resolved.
Show resolved Hide resolved
IDstr := req.ID
resID := strings.Split(IDstr, ":")
if len(resID) != 2 {
resp.Diagnostics.AddError(
"ImportState Failure",
fmt.Sprintf("Malformed Import ID %q, "+
"please use format %q", IDstr, a.targetResource.ImportHint()),
)
return
}
_, err := jimmnames.ParseTag(resID[0])
if err != nil {
resp.Diagnostics.AddError(
"ImportState Failure",
fmt.Sprintf("Malformed Import ID %q, "+
"%s is not a valid tag", IDstr, resID[0]),
)
return
}
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
11 changes: 11 additions & 0 deletions internal/provider/resource_access_jaas_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &jaasAccessModelResource{}
var _ resource.ResourceWithConfigure = &jaasAccessModelResource{}
var _ resource.ResourceWithImportState = &jaasAccessModelResource{}
var _ resource.ResourceWithConfigValidators = &jaasAccessModelResource{}

// NewJAASAccessModelResource returns a new resource for JAAS model access.
Expand All @@ -37,6 +38,7 @@ func (j modelInfo) Info(ctx context.Context, getter Getter, diag *diag.Diagnosti
modelAccess := jaasAccessModelResourceModel{}
diag.Append(getter.Get(ctx, &modelAccess)...)
accessModel := genericJAASAccessModel{
ID: modelAccess.ID,
Users: modelAccess.Users,
Groups: modelAccess.Groups,
ServiceAccounts: modelAccess.ServiceAccounts,
Expand All @@ -49,6 +51,7 @@ func (j modelInfo) Info(ctx context.Context, getter Getter, diag *diag.Diagnosti
func (j modelInfo) Save(ctx context.Context, setter Setter, info genericJAASAccessModel, tag names.Tag) diag.Diagnostics {
modelAccess := jaasAccessModelResourceModel{
ModelUUID: basetypes.NewStringValue(tag.Id()),
ID: info.ID,
Users: info.Users,
Groups: info.Groups,
ServiceAccounts: info.ServiceAccounts,
Expand All @@ -57,6 +60,11 @@ func (j modelInfo) Save(ctx context.Context, setter Setter, info genericJAASAcce
return setter.Set(ctx, modelAccess)
}

// ImportHint implements [resourceInfo] and provides a hint to users on the import string format.
func (j modelInfo) ImportHint() string {
return "model-<UUID>:<access-level>"
}

type jaasAccessModelResource struct {
genericJAASAccessResource
}
Expand All @@ -67,6 +75,9 @@ type jaasAccessModelResourceModel struct {
ServiceAccounts types.Set `tfsdk:"service_accounts"`
Groups types.Set `tfsdk:"groups"`
Access types.String `tfsdk:"access"`

// ID required for imports
ID types.String `tfsdk:"id"`
}

// Metadata returns metadata about the JAAS model access resource.
Expand Down
9 changes: 8 additions & 1 deletion internal/provider/resource_access_jaas_model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ func TestAcc_ResourceJaasAccessModel(t *testing.T) {

// Test 0: Test an invalid access string.
// Test 1: Test adding a valid set of users.
// Test 2: Test updating the users to remove 1 user.
// Test 2: Test importing works
// Test 3: Test updating the users to remove 1 user.
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: frameworkProviderFactories,
Expand All @@ -55,6 +56,12 @@ func TestAcc_ResourceJaasAccessModel(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "users.#", "2"),
),
},
{
Destroy: false,
ImportStateVerify: true,
ImportState: true,
ResourceName: resourceName,
},
{
Config: testAccResourceJaasAccessModelOneUser(modelName, accessSuccess, userOne),
Check: resource.ComposeTestCheckFunc(
Expand Down
Loading