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

fix: display project_vpc_id computed on plan #1948

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ nav_order: 1
- Add `alloydbomni` BETA resource and datasource
- Add `aiven_alloydbomni_user` BETA resource and datasource
- Add `aiven_alloydbomni_database` BETA resource and datasource
- Fix `terraform plan`: new resources don't display zero values for user configuration options
- Fix `terraform plan` doesn't display automatically assigned `project_vpc_id`
- Fix `terraform plan` doesn't display fields with zero values in service user config
- Add `aiven_service_integration` resource and datasource field `destination_service_project`: Destination service project name
- Add `aiven_service_integration` resource and datasource field `source_service_project`: Source service project name
- Change `aiven_account_team_project` resource and datasource field `team_type` (enum): remove
Expand Down
58 changes: 58 additions & 0 deletions internal/schemautil/custom_diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
func CustomizeDiffGenericService(serviceType string) schema.CustomizeDiffFunc {
return customdiff.Sequence(
SetServiceTypeIfEmpty(serviceType),
CustomizeDiffProjectVPCID,
CustomizeDiffDisallowMultipleManyToOneKeys,
customdiff.IfValueChange("tag",
ShouldNotBeEmpty,
Expand Down Expand Up @@ -320,3 +321,60 @@ func CustomizeDiffAdditionalDiskSpace(ctx context.Context, diff *schema.Resource
// which otherwise will be suppressed by TF
return diff.SetNew(k, "0B")
}

// CustomizeDiffProjectVPCID
// 1. Validates project_vpc_id when set
// 2. Sets project_vpc_id when there is only one VPC in the cloud (that's how it works on the backend)
func CustomizeDiffProjectVPCID(ctx context.Context, d *schema.ResourceDiff, _ any) error {
client, err := common.GenClient()
if err != nil {
return err
}

if v, ok := d.GetOk("project_vpc_id"); ok {
return validateProjectVPCID(ctx, client, v.(string))
}

project := d.Get("project").(string)
list, err := client.VpcList(ctx, project)
if err != nil {
return err
}

vpcIDs := make([]string, 0)
cloudName := d.Get("cloud_name").(string)
for _, v := range list {
if v.CloudName == cloudName {
vpcIDs = append(vpcIDs, v.ProjectVpcId)
}
}

switch len(vpcIDs) {
case 0:
// It would be nice to set the project_vpc_id to an empty string here,
// but it just won't work because TF takes "" and nil as nothing set.
// That would still result in project_vpc_id marked "known after apply" in the plan,
// even though we know it's going to be empty.
return nil
case 1:
return d.SetNew("project_vpc_id", BuildResourceID(project, vpcIDs[0]))
}

return fmt.Errorf(
"project %q has multiple active VPCs in cloud %q. Please specify the project_vpc_id: %s",
project, cloudName, strings.Join(vpcIDs, ", "),
)
}

func validateProjectVPCID(ctx context.Context, client avngen.Client, projectVPCID string) error {
project, vpcID, err := SplitResourceID2(projectVPCID)
if err != nil {
return err
}

_, err = client.VpcGet(ctx, project, vpcID)
if avngen.IsNotFound(err) {
return fmt.Errorf("VPC ID %q not found in project %q", vpcID, project)
}
return err
}
25 changes: 25 additions & 0 deletions internal/schemautil/schemautil.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/aiven/aiven-go-client/v2"
"github.com/aiven/go-client-codegen/handler/service"
"github.com/docker/go-units"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

Expand Down Expand Up @@ -195,6 +197,29 @@ func ValidateEmailAddress(v any, k string) (ws []string, errors []error) {
return
}

// ValidateIDN validates that the given string is a valid resource ID with n parts
func ValidateIDN(n int, expected ...string) schema.SchemaValidateDiagFunc {
if n != len(expected) {
panic(fmt.Sprintf("expected %d parts, got %d", n, len(expected)))
}

return func(i any, _ cty.Path) diag.Diagnostics {
_, err := SplitResourceID(i.(string), n)
if err == nil {
return nil
}

return diag.Errorf(
"invalid resource id, should have the following format %q",
strings.Join(expected, "/"),
)
}
}

func ValidateIDWithProject(expected string) schema.SchemaValidateDiagFunc {
return ValidateIDN(2, "project_name", expected)
}

func BuildResourceID(parts ...string) string {
finalParts := make([]string, len(parts))
for idx, part := range parts {
Expand Down
9 changes: 5 additions & 4 deletions internal/schemautil/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,11 @@ func ServiceCommonSchema() map[string]*schema.Schema {
Description: "Aiven internal service type code",
},
"project_vpc_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Specifies the VPC the service should run in. If the value is not set the service is not run inside a VPC. When set, the value should be given as a reference to set up dependencies correctly and the VPC must be in the same cloud and region as the service itself. Project can be freely moved to and from VPC after creation but doing so triggers migration to new servers so the operation can take significant amount of time to complete if the service has a lot of data.",
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Specifies the VPC the service should run in. If the value is not set the service is not run inside a VPC. When set, the value should be given as a reference to set up dependencies correctly and the VPC must be in the same cloud and region as the service itself. Project can be freely moved to and from VPC after creation but doing so triggers migration to new servers so the operation can take significant amount of time to complete if the service has a lot of data.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

The ID of the the aiven_project_vpc to deploy the service in. If not set, the service is automatically deployed in a VPC when that project has only one VPC. To avoid this, set the value to null. You can move the service to a different VPC after creation. This migration can take some time to complete if the service has a lot of data.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like that ^ is not accurate based on internal discussions? So maybe it should be:

The ID of the the aiven_project_vpc to deploy the service in. If not set, the service is automatically deployed in a VPC when that project has only one VPC. You can move the service to a different VPC after creation. This migration can take some time to complete if the service has a lot of data.

Let me know if other changes are needed. 🙏🏻

ValidateDiagFunc: ValidateIDWithProject("project_vpc_id"),
},
"maintenance_window_dow": {
Type: schema.TypeString,
Expand Down
Loading