-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(organization): add support for group projects (#1496)
- Loading branch information
1 parent
e649c48
commit 51b3340
Showing
7 changed files
with
351 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "aiven_organization_group_project Resource - terraform-provider-aiven" | ||
subcategory: "" | ||
description: |- | ||
Creates and manages an organization group project relations in Aiven. | ||
--- | ||
|
||
# aiven_organization_group_project (Resource) | ||
|
||
Creates and manages an organization group project relations in Aiven. | ||
|
||
|
||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `group_id` (String) Organization group identifier of the organization group project relation. | ||
- `project` (String) Tenant identifier of the organization. | ||
- `role` (String) Role of the organization group project relation. | ||
|
||
### Optional | ||
|
||
- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) | ||
|
||
<a id="nestedblock--timeouts"></a> | ||
### Nested Schema for `timeouts` | ||
|
||
Optional: | ||
|
||
- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). | ||
- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs. | ||
- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled. | ||
- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
246 changes: 246 additions & 0 deletions
246
internal/plugin/service/organization/organization_group_project.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
package organization | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/aiven/aiven-go-client/v2" | ||
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" | ||
"github.com/hashicorp/terraform-plugin-framework/resource" | ||
"github.com/hashicorp/terraform-plugin-framework/resource/schema" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
|
||
"github.com/aiven/terraform-provider-aiven/internal/plugin/util" | ||
) | ||
|
||
var ( | ||
_ resource.Resource = &organizationGroupProjectResource{} | ||
_ resource.ResourceWithConfigure = &organizationGroupProjectResource{} | ||
_ resource.ResourceWithImportState = &organizationGroupProjectResource{} | ||
|
||
_ util.TypeNameable = &organizationGroupProjectResource{} | ||
) | ||
|
||
// NewOrganizationGroupProjectResource is a constructor for the organization resource. | ||
func NewOrganizationGroupProjectResource() resource.Resource { | ||
return &organizationGroupProjectResource{} | ||
} | ||
|
||
// organizationGroupUserResource is the organization resource implementation. | ||
type organizationGroupProjectResource struct { | ||
// client is the instance of the Aiven client to use. | ||
client *aiven.Client | ||
|
||
// typeName is the name of the resource type. | ||
typeName string | ||
} | ||
|
||
// organizationGroupProjectResourceModel is the model for the organization resource. | ||
type organizationGroupProjectResourceModel struct { | ||
// Name is the name of the organization. | ||
Project types.String `tfsdk:"project"` | ||
// OrganizationID is the identifier of the organization group. | ||
OrganizationGroupID types.String `tfsdk:"group_id"` | ||
// Role is the role of the organization group project relation. | ||
Role types.String `tfsdk:"role"` | ||
// Timeouts is the configuration for resource-specific timeouts. | ||
Timeouts timeouts.Value `tfsdk:"timeouts"` | ||
} | ||
|
||
// Metadata returns the metadata for the organization resource. | ||
func (r *organizationGroupProjectResource) Metadata( | ||
_ context.Context, | ||
req resource.MetadataRequest, | ||
resp *resource.MetadataResponse, | ||
) { | ||
resp.TypeName = req.ProviderTypeName + "_organization_group_project" | ||
|
||
r.typeName = resp.TypeName | ||
} | ||
|
||
// TypeName returns the resource type name. | ||
func (r *organizationGroupProjectResource) TypeName() string { | ||
return r.typeName | ||
} | ||
|
||
// Schema returns the schema for the resource. | ||
func (r *organizationGroupProjectResource) Schema( | ||
ctx context.Context, | ||
_ resource.SchemaRequest, | ||
resp *resource.SchemaResponse) { | ||
resp.Schema = util.GeneralizeSchema(ctx, schema.Schema{ | ||
Description: "Creates and manages an organization group project relations in Aiven.", | ||
Attributes: map[string]schema.Attribute{ | ||
"group_id": schema.StringAttribute{ | ||
Description: "Organization group identifier of the organization group project relation.", | ||
Required: true, | ||
}, | ||
"project": schema.StringAttribute{ | ||
Description: "Tenant identifier of the organization.", | ||
Required: true, | ||
}, | ||
"role": schema.StringAttribute{ | ||
Description: "Role of the organization group project relation.", | ||
Required: true, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
// Configure is called to configure the resource. | ||
func (r *organizationGroupProjectResource) Configure( | ||
_ context.Context, | ||
req resource.ConfigureRequest, | ||
resp *resource.ConfigureResponse, | ||
) { | ||
if req.ProviderData == nil { | ||
return | ||
} | ||
|
||
client, ok := req.ProviderData.(*aiven.Client) | ||
if !ok { | ||
resp.Diagnostics = util.DiagErrorUnexpectedProviderDataType(resp.Diagnostics, req.ProviderData) | ||
|
||
return | ||
} | ||
|
||
r.client = client | ||
} | ||
|
||
// CustomizeDiff helps to customize the diff for the resource. | ||
func (r *organizationGroupProjectResource) fillModel( | ||
ctx context.Context, | ||
m *organizationGroupProjectResourceModel, | ||
) error { | ||
list, err := r.client.ProjectOrganization.List( | ||
ctx, | ||
m.Project.ValueString()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var isFound bool | ||
for _, project := range list { | ||
if project.OrganizationGroupID == m.OrganizationGroupID.ValueString() { | ||
isFound = true | ||
m.OrganizationGroupID = types.StringValue(project.OrganizationGroupID) | ||
m.Role = types.StringValue(project.Role) | ||
} | ||
} | ||
|
||
if !isFound { | ||
return fmt.Errorf("organization group project relation not found, organization group id: %s, project: %s", | ||
m.OrganizationGroupID.ValueString(), m.Project.ValueString()) | ||
} | ||
|
||
// There is not API endpoint to get the permission of the organization group project relation. | ||
|
||
return nil | ||
} | ||
|
||
// Diff helps to differentiate desired from the existing state of the resource. | ||
func (r *organizationGroupProjectResource) Create( | ||
ctx context.Context, | ||
req resource.CreateRequest, | ||
resp *resource.CreateResponse, | ||
) { | ||
var plan organizationGroupProjectResourceModel | ||
if !util.PlanStateToModel(ctx, &req.Plan, &plan, &resp.Diagnostics) { | ||
return | ||
} | ||
|
||
err := r.client.ProjectOrganization.Add( | ||
ctx, | ||
plan.Project.ValueString(), | ||
plan.OrganizationGroupID.ValueString(), | ||
plan.Role.ValueString()) | ||
if err != nil { | ||
resp.Diagnostics = util.DiagErrorCreatingResource(resp.Diagnostics, r, err) | ||
|
||
return | ||
} | ||
|
||
err = r.fillModel(ctx, &plan) | ||
if err != nil { | ||
resp.Diagnostics = util.DiagErrorCreatingResource(resp.Diagnostics, r, err) | ||
|
||
return | ||
} | ||
|
||
if !util.ModelToPlanState(ctx, plan, &resp.State, &resp.Diagnostics) { | ||
return | ||
} | ||
} | ||
|
||
// Delete deletes an organization resource. | ||
func (r *organizationGroupProjectResource) Delete( | ||
ctx context.Context, | ||
req resource.DeleteRequest, | ||
resp *resource.DeleteResponse, | ||
) { | ||
var plan organizationGroupProjectResourceModel | ||
|
||
if !util.PlanStateToModel(ctx, &req.State, &plan, &resp.Diagnostics) { | ||
return | ||
} | ||
|
||
err := r.client.ProjectOrganization.Delete( | ||
ctx, | ||
plan.Project.ValueString(), | ||
plan.OrganizationGroupID.ValueString()) | ||
if err != nil { | ||
resp.Diagnostics = util.DiagErrorDeletingResource(resp.Diagnostics, r, err) | ||
|
||
return | ||
} | ||
} | ||
|
||
// Read reads the existing state of the resource. | ||
func (r *organizationGroupProjectResource) Read( | ||
ctx context.Context, | ||
req resource.ReadRequest, | ||
resp *resource.ReadResponse, | ||
) { | ||
var state organizationGroupProjectResourceModel | ||
|
||
if !util.PlanStateToModel(ctx, &req.State, &state, &resp.Diagnostics) { | ||
return | ||
} | ||
|
||
err := r.fillModel(ctx, &state) | ||
if err != nil { | ||
resp.Diagnostics = util.DiagErrorReadingResource(resp.Diagnostics, r, err) | ||
|
||
return | ||
} | ||
|
||
if !util.ModelToPlanState(ctx, state, &resp.State, &resp.Diagnostics) { | ||
return | ||
} | ||
} | ||
|
||
// ImportState imports an existing resource into Terraform. | ||
func (r *organizationGroupProjectResource) ImportState( | ||
_ context.Context, | ||
_ resource.ImportStateRequest, | ||
resp *resource.ImportStateResponse, | ||
) { | ||
util.DiagErrorUpdatingResource( | ||
resp.Diagnostics, | ||
r, | ||
fmt.Errorf("cannot import %s resource", r.TypeName()), | ||
) | ||
} | ||
|
||
// Update updates an organization group project resource. | ||
func (r *organizationGroupProjectResource) Update( | ||
_ context.Context, | ||
_ resource.UpdateRequest, | ||
resp *resource.UpdateResponse, | ||
) { | ||
util.DiagErrorUpdatingResource( | ||
resp.Diagnostics, | ||
r, | ||
fmt.Errorf("cannot update %s resource", r.TypeName()), | ||
) | ||
} |
63 changes: 63 additions & 0 deletions
63
internal/plugin/service/organization/organization_group_project_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package organization_test | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-testing/helper/acctest" | ||
"github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
|
||
acc "github.com/aiven/terraform-provider-aiven/internal/acctest" | ||
) | ||
|
||
func TestAccOrganizationGroupProject(t *testing.T) { | ||
orgID, found := os.LookupEnv("AIVEN_ORG_ID") | ||
if !found { | ||
t.Skip("Skipping test due to missing AIVEN_ORG_ID environment variable") | ||
} | ||
|
||
if _, ok := os.LookupEnv("PROVIDER_AIVEN_ENABLE_BETA"); !ok { | ||
t.Skip("Skipping test due to missing PROVIDER_AIVEN_ENABLE_BETA environment variable") | ||
} | ||
|
||
suffix := acctest.RandStringFromCharSet(acc.DefaultRandomSuffixLength, acctest.CharSetAlphaNum) | ||
|
||
resourceName := "aiven_organization_group_project.foo" | ||
resource.ParallelTest(t, resource.TestCase{ | ||
ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, | ||
PreCheck: func() { acc.TestAccPreCheck(t) }, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccOrganizationUserGroupProjectResource(suffix, orgID), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr(resourceName, "project", "pr-"+suffix), | ||
resource.TestCheckResourceAttrSet(resourceName, "group_id"), | ||
resource.TestCheckResourceAttr(resourceName, "role", "admin"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccOrganizationUserGroupProjectResource(rand, orgID string) string { | ||
return fmt.Sprintf(` | ||
resource "aiven_organization_user_group" "foo" { | ||
organization_id = "%[1]s" | ||
name = "test-group" | ||
description = "test-group-description" | ||
} | ||
resource "aiven_project" "foo" { | ||
project = "pr-%[2]s" | ||
parent_id = "%[1]s" | ||
} | ||
resource "aiven_organization_group_project" "foo" { | ||
project = aiven_project.foo.project | ||
group_id = aiven_organization_user_group.foo.group_id | ||
role = "admin" | ||
depends_on = [aiven_organization_user_group.foo, aiven_project.foo] | ||
} | ||
`, orgID, rand) | ||
} |