Skip to content

Commit

Permalink
Add test
Browse files Browse the repository at this point in the history
Add test

WIP tests

Use shortened role names

Basic working version

Rework schema

WIP
  • Loading branch information
gigerdo committed Sep 5, 2024
1 parent 4b2e295 commit 8322411
Show file tree
Hide file tree
Showing 12 changed files with 1,871 additions and 0 deletions.
60 changes: 60 additions & 0 deletions ec/ecresource/organizationresource/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package organizationresource

import (
"context"
"github.com/elastic/cloud-sdk-go/pkg/api/organizationapi"
"github.com/elastic/cloud-sdk-go/pkg/models"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
)

func (r *Resource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
// It is not possible to create an organization, it already exists
// Instead, just import the already existing organization
response.Diagnostics.AddError("organization already exists", "please import the organization using terraform import")
}

func (r *Resource) createInvitation(ctx context.Context, email string, plan OrganizationMember, organizationID string, diagnostics *diag.Diagnostics) *OrganizationMember {
apiModel := modelToApi(ctx, plan, organizationID, diagnostics)
if diagnostics.HasError() {
return nil
}

invitations, err := organizationapi.CreateInvitation(organizationapi.CreateInvitationParams{
API: r.client,
OrganizationID: organizationID,
Emails: []string{email},
ExpiresIn: "7d",
RoleAssignments: apiModel.RoleAssignments,
})
if err != nil {
diagnostics.Append(diag.NewErrorDiagnostic("Failed to create invitation", err.Error()))
return nil
}

invitation := invitations.Invitations[0]
organizationMember := apiToModel(ctx, models.OrganizationMembership{
Email: *invitation.Email,
OrganizationID: invitation.Organization.ID,
RoleAssignments: invitation.RoleAssignments,
}, true, diagnostics)

return organizationMember
}
70 changes: 70 additions & 0 deletions ec/ecresource/organizationresource/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package organizationresource

import (
"context"
"github.com/elastic/cloud-sdk-go/pkg/api/organizationapi"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
)

func (r *Resource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
// It is not possible to delete an organization
}

func (r *Resource) deleteMember(member OrganizationMember, organizationID string, diags *diag.Diagnostics) {
if member.InvitationPending.ValueBool() {
r.deleteInvitation(member, organizationID, diags)
} else {
_, err := organizationapi.DeleteMember(organizationapi.DeleteMemberParams{
API: r.client,
OrganizationID: organizationID,
UserIDs: []string{member.UserID.ValueString()},
})
if err != nil {
diags.Append(diag.NewErrorDiagnostic("Removing organization member failed.", err.Error()))
return
}
}
}

func (r *Resource) deleteInvitation(member OrganizationMember, organizationID string, diags *diag.Diagnostics) {
invitations, err := organizationapi.ListInvitations(organizationapi.ListInvitationsParams{
API: r.client,
OrganizationID: organizationID,
})
if err != nil {
diags.Append(diag.NewErrorDiagnostic("Listing organization members failed", err.Error()))
return
}
for _, invitation := range invitations.Invitations {
if *invitation.Email == member.Email.ValueString() {
_, err := organizationapi.DeleteInvitation(organizationapi.DeleteInvitationParams{
API: r.client,
OrganizationID: organizationID,
InvitationTokens: []string{*invitation.Token},
})
if err != nil {
diags.Append(diag.NewErrorDiagnostic("Removing member invitation failed", err.Error()))
return
}
return
}
}
}
34 changes: 34 additions & 0 deletions ec/ecresource/organizationresource/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package organizationresource

import (
"context"
"github.com/hashicorp/terraform-plugin-framework/resource"
)

func (r *Resource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
organizationID := request.ID

result := r.readFromApi(ctx, organizationID, &response.Diagnostics)
if response.Diagnostics.HasError() {
return
}

response.State.Set(ctx, &result)
}
40 changes: 40 additions & 0 deletions ec/ecresource/organizationresource/mapper_roles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package organizationresource

import "strings"

type RoleType string

const (
Deployment = "deployment"
ProjectElasticsearch = "elasticsearch"
ProjectObservability = "observability"
ProjectSecurity = "security"
)

// Adds the prefix to a role (e.g. admin -> elasticsearch-admin)
func roleModelToApi(modelRole string, roleType RoleType) *string {
apiRole := string(roleType) + "-" + modelRole
return &apiRole
}

// Removes the prefix from a role (e.g. elasticsearch-admin -> admin)
func roleApiToModel(apiRole string, roleType RoleType) string {
return strings.TrimPrefix(apiRole, string(roleType)+"-")
}
143 changes: 143 additions & 0 deletions ec/ecresource/organizationresource/mapper_to_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package organizationresource

import (
"context"
"github.com/elastic/cloud-sdk-go/pkg/models"
"github.com/elastic/cloud-sdk-go/pkg/util/ec"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"sort"
)

func modelToApi(ctx context.Context, m OrganizationMember, organizationID string, diagnostics *diag.Diagnostics) *models.OrganizationMembership {
// org
var apiOrgRoleAssignments []*models.OrganizationRoleAssignment
if !m.OrganizationRole.IsNull() && !m.OrganizationRole.IsUnknown() {
apiOrgRoleAssignments = append(apiOrgRoleAssignments, &models.OrganizationRoleAssignment{
OrganizationID: ec.String(organizationID),
RoleID: m.OrganizationRole.ValueStringPointer(),
})
}

// deployment
var modelDeploymentRoles []DeploymentRoleAssignment
diags := m.DeploymentRoles.ElementsAs(ctx, &modelDeploymentRoles, false)
if diags.HasError() {
diagnostics.Append(diags...)
return nil
}
var apiDeploymentRoleAssignments []*models.DeploymentRoleAssignment
for _, roleAssignment := range modelDeploymentRoles {

var deploymentIds []string
diags = roleAssignment.DeploymentIDs.ElementsAs(ctx, &deploymentIds, false)
if diags.HasError() {
diagnostics.Append(diags...)
return nil
}
sort.Strings(deploymentIds)

var applicationRoles []string
diags = roleAssignment.ApplicationRoles.ElementsAs(ctx, &applicationRoles, false)
if diags.HasError() {
diagnostics.Append(diags...)
return nil
}
sort.Strings(applicationRoles)

apiDeploymentRoleAssignments = append(apiDeploymentRoleAssignments, &models.DeploymentRoleAssignment{
OrganizationID: ec.String(organizationID),
RoleID: roleModelToApi(roleAssignment.Role.ValueString(), Deployment),
All: roleAssignment.ForAllDeployments.ValueBoolPointer(),
DeploymentIds: deploymentIds,
ApplicationRoles: applicationRoles,
})
}

// elasticsearch
apiElasticsearchRoles := projectRolesModelToApi(ctx, m.ProjectElasticsearchRoles, ProjectElasticsearch, organizationID, diagnostics)
if diagnostics.HasError() {
return nil
}

// observability
apiObservabilityRoles := projectRolesModelToApi(ctx, m.ProjectObservabilityRoles, ProjectObservability, organizationID, diagnostics)
if diagnostics.HasError() {
return nil
}

// security
apiSecurityRoles := projectRolesModelToApi(ctx, m.ProjectSecurityRoles, ProjectSecurity, organizationID, diagnostics)
if diagnostics.HasError() {
return nil
}

apiRoleAssignments := models.RoleAssignments{
Organization: apiOrgRoleAssignments,
Deployment: apiDeploymentRoleAssignments,
Project: &models.ProjectRoleAssignments{
Elasticsearch: apiElasticsearchRoles,
Observability: apiObservabilityRoles,
Security: apiSecurityRoles,
},
}
return &models.OrganizationMembership{
Email: m.Email.ValueString(),
UserID: m.UserID.ValueStringPointer(),
RoleAssignments: &apiRoleAssignments,
}
}

func projectRolesModelToApi(ctx context.Context, roles types.Set, roleType RoleType, organizationID string, diagnostics *diag.Diagnostics) []*models.ProjectRoleAssignment {
var modelRoles []ProjectRoleAssignment
diags := roles.ElementsAs(ctx, &modelRoles, false)
if diags.HasError() {
diagnostics.Append(diags...)
return nil
}
var apiRoles []*models.ProjectRoleAssignment
for _, roleAssignment := range modelRoles {

var projectIds []string
diags = roleAssignment.ProjectIDs.ElementsAs(ctx, &projectIds, false)
if diags.HasError() {
diagnostics.Append(diags...)
return nil
}
sort.Strings(projectIds)

var applicationRoles []string
diags = roleAssignment.ApplicationRoles.ElementsAs(ctx, &applicationRoles, false)
if diags.HasError() {
diagnostics.Append(diags...)
return nil
}
sort.Strings(applicationRoles)

apiRoles = append(apiRoles, &models.ProjectRoleAssignment{
OrganizationID: ec.String(organizationID),
RoleID: roleModelToApi(roleAssignment.Role.ValueString(), roleType),
All: roleAssignment.ForAllProjects.ValueBoolPointer(),
ProjectIds: projectIds,
ApplicationRoles: applicationRoles,
})
}
return apiRoles
}
Loading

0 comments on commit 8322411

Please sign in to comment.