diff --git a/internal/provider/teamapikey/team_api_key_resource.go b/internal/provider/teamapikey/team_api_key_resource.go index 2fff0b0..6c080ca 100644 --- a/internal/provider/teamapikey/team_api_key_resource.go +++ b/internal/provider/teamapikey/team_api_key_resource.go @@ -6,11 +6,10 @@ package teamapikey import ( "context" "fmt" + "github.com/futurice/terraform-provider-dependencytrack/internal/utils" "strings" 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" @@ -86,12 +85,17 @@ func (r *TeamAPIKeyResource) Create(ctx context.Context, req resource.CreateRequ var plan TeamAPIKeyResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + teamUUID, teamUUIDDiags := utils.ParseUUID(plan.TeamID.ValueString()) + resp.Diagnostics.Append(teamUUIDDiags...) if resp.Diagnostics.HasError() { return } - apiKey, err := r.client.Team.GenerateAPIKey(ctx, uuid.MustParse(plan.TeamID.String())) + apiKey, err := r.client.Team.GenerateAPIKey(ctx, teamUUID) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create API key, got error: %s", err)) return @@ -106,7 +110,6 @@ func (r *TeamAPIKeyResource) Read(ctx context.Context, req resource.ReadRequest, var state TeamAPIKeyResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { return } @@ -148,7 +151,6 @@ func (r *TeamAPIKeyResource) Delete(ctx context.Context, req resource.DeleteRequ var state TeamAPIKeyResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { return } diff --git a/internal/provider/teamapikey/team_api_key_resource_test.go b/internal/provider/teamapikey/team_api_key_resource_test.go new file mode 100644 index 0000000..d3db678 --- /dev/null +++ b/internal/provider/teamapikey/team_api_key_resource_test.go @@ -0,0 +1,145 @@ +package teamapikey_test + +import ( + "fmt" + "github.com/futurice/terraform-provider-dependencytrack/internal/testutils" + "github.com/futurice/terraform-provider-dependencytrack/internal/testutils/teamtestutils" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "os" + "testing" +) + +var testDependencyTrack *testutils.TestDependencyTrack + +func TestMain(m *testing.M) { + if os.Getenv(resource.EnvTfAcc) != "" { + var cleanup func() + testDependencyTrack, cleanup = testutils.InitTestDependencyTrack() + defer cleanup() + } + + m.Run() +} + +func TestAccTeamResource_basic(t *testing.T) { + ctx := testutils.CreateTestContext(t) + + teamName := acctest.RandomWithPrefix("test-team") + + teamResourceName := teamtestutils.CreateTeamResourceName("test") + otherTeamResourceName := teamtestutils.CreateTeamResourceName("test-other") + apiKeyResourceName := teamtestutils.CreateTeamAPIKeyResourceName("test") + + var teamID, otherTeamID, teamAPIKey, otherTeamAPIKey string + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testutils.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccTeamAPIKeyConfigBasic(testDependencyTrack, teamName), + Check: resource.ComposeAggregateTestCheckFunc( + teamtestutils.TestAccCheckGetTeamSingleAPIKey(ctx, testDependencyTrack, teamResourceName, &teamAPIKey), + testutils.TestAccCheckGetResourceID(teamResourceName, &teamID), + resource.TestCheckResourceAttrPtr(apiKeyResourceName, "team_id", &teamID), + resource.TestCheckResourceAttrPtr(apiKeyResourceName, "value", &teamAPIKey), + ), + }, + { + ResourceName: apiKeyResourceName, + ImportStateIdFunc: func(state *terraform.State) (string, error) { + return fmt.Sprintf("%s/%s", teamID, teamAPIKey), nil + }, + ImportState: true, + // Unable to verify since the resource has no ID and no non-sensitive ID can be synthesised; we are just smoke-testing the import + ImportStateVerify: false, + }, + { + Config: testAccTeamAPIKeyConfigOtherTeam(testDependencyTrack, teamName), + Check: resource.ComposeAggregateTestCheckFunc( + teamtestutils.TestAccCheckTeamHasNoAPIKeys(ctx, testDependencyTrack, teamResourceName), + teamtestutils.TestAccCheckGetTeamSingleAPIKey(ctx, testDependencyTrack, otherTeamResourceName, &otherTeamAPIKey), + testutils.TestAccCheckGetResourceID(otherTeamResourceName, &otherTeamID), + resource.TestCheckResourceAttrPtr(apiKeyResourceName, "team_id", &otherTeamID), + resource.TestCheckResourceAttrPtr(apiKeyResourceName, "value", &otherTeamAPIKey), + ), + }, + { + Config: testAccTeamPermissionConfigNoAPIKey(testDependencyTrack, teamName), + Check: resource.ComposeAggregateTestCheckFunc( + teamtestutils.TestAccCheckTeamHasNoAPIKeys(ctx, testDependencyTrack, teamResourceName), + teamtestutils.TestAccCheckTeamHasNoAPIKeys(ctx, testDependencyTrack, otherTeamResourceName), + ), + }, + }, + // CheckDestroy is not practical here since the team is destroyed as well, and we can no longer query its permissions + }) +} + +func testAccTeamAPIKeyConfigBasic(testDependencyTrack *testutils.TestDependencyTrack, teamName string) string { + return testDependencyTrack.AddProviderConfiguration( + testutils.ComposeConfigs( + fmt.Sprintf(` +resource "dependencytrack_team" "test" { + name = %[1]q +} +`, + teamName, + ), + ` +resource "dependencytrack_team_api_key" "test" { + team_id = dependencytrack_team.test.id +} +`, + ), + ) +} + +func testAccTeamAPIKeyConfigOtherTeam(testDependencyTrack *testutils.TestDependencyTrack, teamName string) string { + return testDependencyTrack.AddProviderConfiguration( + testutils.ComposeConfigs( + fmt.Sprintf(` +resource "dependencytrack_team" "test" { + name = %[1]q +} +`, + teamName, + ), + fmt.Sprintf(` +resource "dependencytrack_team" "test-other" { + name = "%[1]s-other" +} +`, + teamName, + ), + ` +resource "dependencytrack_team_api_key" "test" { + team_id = dependencytrack_team.test-other.id +} +`, + ), + ) +} + +func testAccTeamPermissionConfigNoAPIKey(testDependencyTrack *testutils.TestDependencyTrack, teamName string) string { + return testDependencyTrack.AddProviderConfiguration( + testutils.ComposeConfigs( + fmt.Sprintf(` +resource "dependencytrack_team" "test" { + name = %[1]q +} +`, + teamName, + ), + fmt.Sprintf(` +resource "dependencytrack_team" "test-other" { + name = "%[1]s-other" +} +`, + teamName, + ), + ), + ) +} diff --git a/internal/testutils/teamtestutils/teamtestutils.go b/internal/testutils/teamtestutils/teamtestutils.go index 80fa620..4355f67 100644 --- a/internal/testutils/teamtestutils/teamtestutils.go +++ b/internal/testutils/teamtestutils/teamtestutils.go @@ -2,7 +2,6 @@ package teamtestutils import ( "context" - "errors" "fmt" dtrack "github.com/futurice/dependency-track-client-go" "github.com/futurice/terraform-provider-dependencytrack/internal/testutils" @@ -82,6 +81,44 @@ func TestAccCheckTeamHasExpectedPermissions(ctx context.Context, testDependencyT } } +func TestAccCheckTeamHasNoAPIKeys(ctx context.Context, testDependencyTrack *testutils.TestDependencyTrack, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + team, err := FindTeamByResourceName(ctx, testDependencyTrack, state, resourceName) + if err != nil { + return err + } + if team == nil { + return fmt.Errorf("team for resource %s does not exist in Dependency-Track", resourceName) + } + + if len(team.APIKeys) != 0 { + return fmt.Errorf("team for resource %s has %d API keys instead of the expected 0", resourceName, len(team.APIKeys)) + } + + return nil + } +} + +func TestAccCheckGetTeamSingleAPIKey(ctx context.Context, testDependencyTrack *testutils.TestDependencyTrack, resourceName string, apiKeyTarget *string) resource.TestCheckFunc { + return func(state *terraform.State) error { + team, err := FindTeamByResourceName(ctx, testDependencyTrack, state, resourceName) + if err != nil { + return err + } + if team == nil { + return fmt.Errorf("team for resource %s does not exist in Dependency-Track", resourceName) + } + + if len(team.APIKeys) != 1 { + return fmt.Errorf("team for resource %s has %d API keys instead of the expected 1", resourceName, len(team.APIKeys)) + } + + *apiKeyTarget = team.APIKeys[0].Key + + return nil + } +} + func FindTeamByResourceName(ctx context.Context, testDependencyTrack *testutils.TestDependencyTrack, state *terraform.State, resourceName string) (*dtrack.Team, error) { teamID, err := testutils.GetResourceID(state, resourceName) if err != nil { @@ -97,23 +134,31 @@ func FindTeamByResourceName(ctx context.Context, testDependencyTrack *testutils. } func FindTeam(ctx context.Context, testDependencyTrack *testutils.TestDependencyTrack, teamID uuid.UUID) (*dtrack.Team, error) { - team, err := testDependencyTrack.Client.Team.Get(ctx, teamID) + // Currently the endpoint for getting one team does not return most of the data + // see https://github.com/DependencyTrack/dependency-track/issues/4000 + teams, err := testDependencyTrack.Client.Team.GetAll(ctx, dtrack.PageOptions{}) if err != nil { - var apiErr *dtrack.APIError - ok := errors.As(err, &apiErr) - if !ok || apiErr.StatusCode != 404 { - return nil, fmt.Errorf("failed to get team from Dependency-Track: %w", err) - } - - return nil, nil + return nil, fmt.Errorf("failed to get teams from Dependency-Track: %w", err) } - // normalize the returned object not to contain an empty array reference - if len(team.Permissions) == 0 { - team.Permissions = nil + for _, team := range teams.Items { + if team.UUID == teamID { + // normalize the returned object not to contain empty array references + if len(team.Permissions) == 0 { + team.Permissions = nil + } + if len(team.APIKeys) == 0 { + team.APIKeys = nil + } + if len(team.MappedOIDCGroups) == 0 { + team.MappedOIDCGroups = nil + } + + return &team, nil + } } - return &team, nil + return nil, nil } func CreateTeamResourceName(localName string) string { @@ -123,3 +168,7 @@ func CreateTeamResourceName(localName string) string { func CreateTeamPermissionResourceName(localName string) string { return fmt.Sprintf("dependencytrack_team_permission.%s", localName) } + +func CreateTeamAPIKeyResourceName(localName string) string { + return fmt.Sprintf("dependencytrack_team_api_key.%s", localName) +}