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

[AV-65601] DB Creds changes on UI not picked up in terraform db #112

Merged
merged 4 commits into from
Dec 7, 2023
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
12 changes: 8 additions & 4 deletions internal/api/database_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,14 @@ type CreateDatabaseCredentialResponse struct {

// GetDatabaseCredentialResponse represents the schema for the GET Capella V4 API request that fetches the database credential details.
type GetDatabaseCredentialResponse struct {
Name string `json:"name"`
Audit CouchbaseAuditData `json:"audit"`
Access []Access `json:"access"`
Id uuid.UUID `json:"id"`
Name string `json:"name"`
Password string `json:"password"`
OrganizationId string `json:"organizationId"`
ProjectId string `json:"projectId"`
ClusterId string `json:"clusterId"`
Audit CouchbaseAuditData `json:"audit"`
Access []Access `json:"access"`
Id uuid.UUID `json:"id"`
}

// PutDatabaseCredentialRequest represents the schema for the PUT Capella V4 API request that updates an existing database credential.
Expand Down
5 changes: 1 addition & 4 deletions internal/resources/cluster_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,13 @@ func ClusterSchema() schema.Schema {
objectplanmodifier.RequiresReplace(),
},
},
"configuration_type": stringAttribute(optional, computed, requiresReplace),
"configuration_type": stringAttribute(optional, computed),
"couchbase_server": schema.SingleNestedAttribute{
Optional: true,
Computed: true,
Attributes: map[string]schema.Attribute{
"version": stringAttribute(optional, computed),
},
PlanModifiers: []planmodifier.Object{
objectplanmodifier.RequiresReplace(),
},
},
"service_groups": schema.ListNestedAttribute{
Required: true,
Expand Down
55 changes: 19 additions & 36 deletions internal/resources/database_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,19 +145,13 @@ func (r *DatabaseCredential) Create(ctx context.Context, req resource.CreateRequ
}

refreshedState.Password = types.StringValue(dbResponse.Password)
// store the password that was either auto-generated or supplied during credential creation request.
// todo: there is a bug in the V4 public APIs where the API returns the password in the response only if it is auto-generated.
// This will be fixed in AV-62867.
// For now, we are working around this issue.
if dbResponse.Password == "" {
// this means the customer had provided a password in the terraform file during creation, store that.
refreshedState.Password = plan.Password
}

// todo: there is a bug in cp-open-api where the access field is empty in the GET API response,
// we are going to work around this for private preview.
// The fix will be done in SURF-7366
// For now, we are appending same permissions that the customer passed in the terraform files and not relying on the GET API response.

// Update: GET API response gives the access field however the formats passed in terraform files and the GET response are different.
refreshedState.Access = mapAccess(plan)

// Set state to fully populated data
Expand Down Expand Up @@ -217,7 +211,7 @@ func (r *DatabaseCredential) Read(ctx context.Context, req resource.ReadRequest,
// we are going to work around this for private preview.
// The fix will be done in SURF-7366
// For now, we are appending same permissions that the customer passed in the terraform files and not relying on the GET API response.
refreshedState.Access = mapAccess(state)
refreshedState.Access = mapAccess(*refreshedState)

// Set refreshed state
diags = resp.State.Set(ctx, &refreshedState)
Expand Down Expand Up @@ -369,7 +363,7 @@ func (r *DatabaseCredential) ImportState(ctx context.Context, req resource.Impor

// retrieveDatabaseCredential fetches the database credential by making a GET API call to the Capella V4 Public API.
// This usually helps retrieve the state of a newly created database credential that was created from Terraform.
func (r *DatabaseCredential) retrieveDatabaseCredential(_ context.Context, organizationId, projectId, clusterId, dbId string) (*providerschema.OneDatabaseCredential, error) {
func (r *DatabaseCredential) retrieveDatabaseCredential(ctx context.Context, organizationId, projectId, clusterId, dbId string) (*providerschema.DatabaseCredential, error) {
url := fmt.Sprintf("%s/v4/organizations/%s/projects/%s/clusters/%s/users/%s", r.HostURL, organizationId, projectId, clusterId, dbId)
cfg := api.EndpointCfg{Url: url, Method: http.MethodGet, SuccessStatus: http.StatusOK}
response, err := r.Client.Execute(
Expand All @@ -388,33 +382,22 @@ func (r *DatabaseCredential) retrieveDatabaseCredential(_ context.Context, organ
return nil, fmt.Errorf("%s: %w", errors.ErrUnmarshallingResponse, err)
}

refreshedState := providerschema.OneDatabaseCredential{
Id: types.StringValue(dbResp.Id.String()),
Name: types.StringValue(dbResp.Name),
OrganizationId: types.StringValue(organizationId),
ProjectId: types.StringValue(projectId),
ClusterId: types.StringValue(clusterId),
Audit: providerschema.CouchbaseAuditData{
CreatedAt: types.StringValue(dbResp.Audit.CreatedAt.String()),
CreatedBy: types.StringValue(dbResp.Audit.CreatedBy),
ModifiedAt: types.StringValue(dbResp.Audit.ModifiedAt.String()),
ModifiedBy: types.StringValue(dbResp.Audit.ModifiedBy),
Version: types.Int64Value(int64(dbResp.Audit.Version)),
},
audit := providerschema.NewCouchbaseAuditData(dbResp.Audit)
auditObj, diags := types.ObjectValueFrom(ctx, audit.AttributeTypes(), audit)
if diags.HasError() {
return nil, fmt.Errorf("%s: %w", errors.ErrUnableToConvertAuditData, err)
}
// todo: there is a bug in cp-open-api where the access field is empty in the GET API response,
// we are going to work around this for private preview.
// The fix will be done in SURF-7366
// For now, we are appending same permissions that the customer passed in the terraform files and not relying on the GET API response.
// the below code will be uncommented once the bug is fixed.
/* for i, access := range dbResp.Access {
refreshedState.Access[i] = providerschema.Access{}
for _, permission := range access.Privileges {
refreshedState.Access[i].Privileges = append(refreshedState.Access[i].Privileges, types.StringValue(permission))
}
}
*/
return &refreshedState, nil

refreshedState := providerschema.NewDatabaseCredential(
types.StringValue(dbResp.Id.String()),
types.StringValue(dbResp.Name),
types.StringValue(organizationId),
types.StringValue(projectId),
types.StringValue(clusterId),
auditObj,
)

return refreshedState, nil
}

// todo: add a unit test for this, tracking under: https://couchbasecloud.atlassian.net/browse/AV-63401
Expand Down
44 changes: 15 additions & 29 deletions internal/schema/database_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,35 +80,21 @@ type Scope struct {
Collections []types.String `tfsdk:"collections"`
}

// OneDatabaseCredential is used to retrieve the new state of a database credential after it is created by Terraform.
// This struct is separate from the DatabaseCredential struct because of the change in data type of its attributes after retrieval.
type OneDatabaseCredential struct {
Id types.String `tfsdk:"id"`

// Name is the name of the database credential, the name of the database credential should follow this naming criteria:
// A database credential name should have at least 2 characters and up to 256 characters and should not contain spaces.
Name types.String `tfsdk:"name"`

// Password is the password that you may want to use to create this database credential.
// This password can later be used to authenticate connections to the underlying couchbase server.
// The password should contain 8+ characters, at least 1 lower, 1 upper, 1 numerical and 1 special character.
Password types.String `tfsdk:"password"`

// OrganizationId is the ID of the organization to which the Capella cluster belongs.
// The database credential will be created for the cluster.
OrganizationId types.String `tfsdk:"organization_id"`

// ProjectId is the ID of the project to which the Capella cluster belongs.
// The database credential will be created for the cluster.
ProjectId types.String `tfsdk:"project_id"`

// ClusterId is the ID of the cluster for which the database credential needs to be created.
ClusterId types.String `tfsdk:"cluster_id"`

// Access is a list of access which can be narrowed to the scope level of every bucket in the Capella cluster.
// Access can be "read", "write" or both.
Access []Access `tfsdk:"access"`
Audit CouchbaseAuditData `tfsdk:"audit"`
func NewDatabaseCredential(
Id types.String,
name types.String,
organizationId, projectId, clusterId types.String,
auditObject basetypes.ObjectValue,
) *DatabaseCredential {
newDatabaseCredential := DatabaseCredential{
Id: Id,
Name: name,
OrganizationId: organizationId,
ProjectId: projectId,
ClusterId: clusterId,
Audit: auditObject,
}
return &newDatabaseCredential
}

// Validate will split the IDs by a delimiter i.e. comma , in case a terraform import CLI is invoked.
Expand Down