Skip to content

Commit

Permalink
Implement ACL mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
majori committed Mar 19, 2024
1 parent df19c81 commit e2a8d38
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 9 deletions.
5 changes: 5 additions & 0 deletions examples/provider-install-verification/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ resource "dependencytrack_project" "sub2" {
active = true
}

resource "dependencytrack_acl_mapping" "main" {
team_id = dependencytrack_team.main.id
project_id = dependencytrack_project.main.id
}

output "team" {
value = resource.dependencytrack_team.main
}
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@ module github.com/futurice/terraform-provider-dependencytrack

go 1.21

replace "github.com/futurice/dependency-track-client-go" v0.0.0 => "../dependency-track-client-go"
replace github.com/futurice/dependency-track-client-go => ../dependency-track-client-go

require (
github.com/futurice/dependency-track-client-go v0.0.0-20240315082339-09ffc71f01c0
github.com/google/uuid v1.6.0
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/terraform-plugin-docs v0.18.0
github.com/hashicorp/terraform-plugin-framework v1.6.1
github.com/hashicorp/terraform-plugin-go v0.22.1
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-testing v1.7.0
)

Expand All @@ -38,11 +36,13 @@ require (
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/hc-install v0.6.3 // indirect
github.com/hashicorp/hcl/v2 v2.20.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.20.0 // indirect
github.com/hashicorp/terraform-json v0.21.0 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/futurice/dependency-track-client-go v0.0.0-20240315082339-09ffc71f01c0 h1:foclSzgCnVpchmyFJlt1jwae+FZHuCty8W4ZiJqor9s=
github.com/futurice/dependency-track-client-go v0.0.0-20240315082339-09ffc71f01c0/go.mod h1:nSSUhNjXItvlllTmfE3BdooP1GtAuu6dDXFAHSem0Jk=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
Expand Down
190 changes: 190 additions & 0 deletions internal/provider/acl_mapping_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package provider

import (
"context"
"fmt"

dtrack "github.com/futurice/dependency-track-client-go"
"github.com/google/uuid"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &ACLMappingResource{}
var _ resource.ResourceWithImportState = &ACLMappingResource{}

func NewACLMappingResource() resource.Resource {
return &ACLMappingResource{}
}

// ACLMappingResource defines the resource implementation.
type ACLMappingResource struct {
client *dtrack.Client
}

// ACLResourceModel describes the resource data model.
type ACLResourceModel struct {
TeamID types.String `tfsdk:"team_id"`
ProjectID types.String `tfsdk:"project_id"`
}

func (r *ACLMappingResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_acl_mapping"
}

func (r *ACLMappingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "ACL mapping",

Attributes: map[string]schema.Attribute{
"team_id": schema.StringAttribute{
MarkdownDescription: "Team UUID",
Required: true,
},
"project_id": schema.StringAttribute{
MarkdownDescription: "Project UUID",
Required: true,
},
},
}
}

func (r *ACLMappingResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*dtrack.Client)

if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *dtrack.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

r.client = client
}

func (r *ACLMappingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan ACLResourceModel

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)

mapping := dtrack.ACLMapping{
Team: uuid.MustParse(plan.TeamID.ValueString()),
Project: uuid.MustParse(plan.ProjectID.ValueString()),
}

err := r.client.ACLMapping.Create(ctx, mapping)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create ACL mapping, got error: %s", err))
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

func (r *ACLMappingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state ACLResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

projectMappings, err := r.client.ACLMapping.Get(ctx, uuid.MustParse(state.TeamID.ValueString()))
if err != nil {
if apiErr, ok := err.(*dtrack.APIError); ok && apiErr.StatusCode == 404 {
resp.State.RemoveResource(ctx)
return
}

resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read ACL mapping, got error: %s", err))
return
}

projectID := uuid.MustParse(state.ProjectID.ValueString())
found := false
for _, project := range projectMappings {
if project.UUID == projectID {
found = true
break
}
}

if !found {
resp.State.RemoveResource(ctx)
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *ACLMappingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan, state ACLResourceModel

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

if resp.Diagnostics.HasError() {
return
}

newMapping := dtrack.ACLMapping{
Team: uuid.MustParse(plan.TeamID.ValueString()),
Project: uuid.MustParse(plan.ProjectID.ValueString()),
}

oldMapping := dtrack.ACLMapping{
Team: uuid.MustParse(state.TeamID.ValueString()),
Project: uuid.MustParse(state.ProjectID.ValueString()),
}

err := r.client.ACLMapping.Create(ctx, newMapping)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update ACL mapping, got error: %s", err))
return
}

err = r.client.ACLMapping.Delete(ctx, oldMapping)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update ACL mapping, got error: %s", err))
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *ACLMappingResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state ACLResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

if resp.Diagnostics.HasError() {
return
}

mapping := dtrack.ACLMapping{
Team: uuid.MustParse(state.TeamID.ValueString()),
Project: uuid.MustParse(state.ProjectID.ValueString()),
}

err := r.client.ACLMapping.Delete(ctx, mapping)
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete ACL mapping, got error: %s", err))
return
}

resp.State.RemoveResource(ctx)
}

func (r *ACLMappingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func (p *DependencyTrackProvider) Resources(ctx context.Context) []func() resour
NewTeamResource,
NewTeamPermissionResource,
NewProjectResource,
NewACLMappingResource,
}
}

Expand Down
9 changes: 5 additions & 4 deletions internal/provider/team_permission_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package provider
import (
"context"
"fmt"
"strings"

dtrack "github.com/futurice/dependency-track-client-go"
"github.com/google/uuid"
Expand Down Expand Up @@ -87,9 +86,11 @@ func (r *TeamPermissionResource) Create(ctx context.Context, req resource.Create

respTeam, err := r.client.Permission.AddPermissionToTeam(ctx, permission, uuid.MustParse(plan.TeamID.ValueString()))
if err != nil {
if strings.Contains(err.Error(), "status: 304") {
resp.Diagnostics.AddError("Client Error", "The permission already existed on the team")
if strings.Contains(err.Error(), "status: 404") {
if apiErr, ok := err.(*dtrack.APIError); ok {
switch apiErr.StatusCode {
case 304:
resp.Diagnostics.AddError("Client Error", "The permission already existed on the team")
case 404:
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("The permission '%s' not found", permission.Name))
}
} else {
Expand Down

0 comments on commit e2a8d38

Please sign in to comment.