Skip to content

Commit

Permalink
OCM-12837 | feat: support shared vpc attributes in cluster resource
Browse files Browse the repository at this point in the history
  • Loading branch information
gdbranco committed Nov 28, 2024
1 parent 66f9087 commit 9515aba
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 28 deletions.
28 changes: 23 additions & 5 deletions internal/ocm/resource/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,11 @@ func (c *Cluster) CreateAWSBuilder(clusterTopology rosaTypes.ClusterTopology,
isPrivateLink bool, awsAccountID *string, awsBillingAccountId *string,
stsBuilder *cmv1.STSBuilder, awsSubnetIDs []string,
privateHostedZoneID *string, privateHostedZoneRoleARN *string,
hcpInternalCommunicationPrivateHostedZoneId *string, vpceRoleArn *string,
additionalComputeSecurityGroupIds []string,
additionalInfraSecurityGroupIds []string,
additionalControlPlaneSecurityGroupIds []string) error {
additionalControlPlaneSecurityGroupIds []string,
additionalAllowedPrincipals []string) error {

if clusterTopology == rosaTypes.Hcp && awsSubnetIDs == nil {
return errors.New("Hosted Control Plane clusters must have a pre-configure VPC. Make sure to specify the subnet ids.")
Expand Down Expand Up @@ -209,14 +211,30 @@ func (c *Cluster) CreateAWSBuilder(clusterTopology rosaTypes.ClusterTopology,
}

if privateHostedZoneID != nil && privateHostedZoneRoleARN != nil {
if !privateHostedZoneRoleArnRE.MatchString(*privateHostedZoneRoleARN) {
return errors.New(fmt.Sprintf("Expected a valid value for PrivateHostedZoneRoleARN matching %s. Got %s", privateHostedZoneRoleArnRE, *privateHostedZoneRoleARN))
}
if awsSubnetIDs == nil || stsBuilder == nil {
return errors.New("PrivateHostedZone parameters require STS and SubnetIDs configurations.")
return errors.New("Shared VPC parameters require STS and SubnetIDs configurations.")
}
privateRoleArnField := "PrivateHostedZoneRoleARN"
if clusterTopology == rosaTypes.Hcp {
privateRoleArnField = "Route53RoleArn"
}
if !privateHostedZoneRoleArnRE.MatchString(*privateHostedZoneRoleARN) {
return errors.New(fmt.Sprintf("Expected a valid value for %s matching %s. Got %s",
privateRoleArnField, privateHostedZoneRoleArnRE, *privateHostedZoneRoleARN))
}
awsBuilder.PrivateHostedZoneID(*privateHostedZoneID)
awsBuilder.PrivateHostedZoneRoleARN(*privateHostedZoneRoleARN)
if clusterTopology == rosaTypes.Hcp && hcpInternalCommunicationPrivateHostedZoneId != nil && vpceRoleArn != nil {
if !privateHostedZoneRoleArnRE.MatchString(*vpceRoleArn) {
return errors.New(fmt.Sprintf("Expected a valid value for VpcEndpointRoleArn matching %s. Got %s", privateHostedZoneRoleArnRE, *vpceRoleArn))
}
awsBuilder.HcpInternalCommunicationHostedZoneId(*hcpInternalCommunicationPrivateHostedZoneId)
awsBuilder.VpcEndpointRoleArn(*vpceRoleArn)
}
}

if additionalAllowedPrincipals != nil {
awsBuilder.AdditionalAllowedPrincipals(additionalAllowedPrincipals...)
}

c.clusterBuilder.AWS(awsBuilder)
Expand Down
18 changes: 9 additions & 9 deletions internal/ocm/resource/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,17 +203,17 @@ var _ = Describe("Cluster", func() {
})
Context("CreateAWSBuilder validation", func() {
It("PrivateLink true subnets IDs empty - failure", func() {
err := cluster.CreateAWSBuilder(rosaTypes.Classic, nil, nil, nil, nil, true, nil, nil, nil, nil, nil, nil, nil, nil, nil)
err := cluster.CreateAWSBuilder(rosaTypes.Classic, nil, nil, nil, nil, true, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("Clusters with PrivateLink must have a pre-configured VPC. Make sure to specify the subnet ids."))
})
It("PrivateLink false invalid kmsKeyARN - failure", func() {
err := cluster.CreateAWSBuilder(rosaTypes.Classic, nil, nil, pointer("test"), nil, false, nil, nil, nil, nil, nil, nil, nil, nil, nil)
err := cluster.CreateAWSBuilder(rosaTypes.Classic, nil, nil, pointer("test"), nil, false, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal(fmt.Sprintf("expected the kms-key-arn: %s to match %s", "test", kmsArnRegexpValidator.KmsArnRE)))
})
It("PrivateLink false empty kmsKeyARN - success", func() {
err := cluster.CreateAWSBuilder(rosaTypes.Classic, nil, nil, nil, nil, false, nil, nil, nil, nil, nil, nil, nil, nil, nil)
err := cluster.CreateAWSBuilder(rosaTypes.Classic, nil, nil, nil, nil, false, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
Expect(err).NotTo(HaveOccurred())
ocmCluster, err := cluster.Build()
Expect(err).NotTo(HaveOccurred())
Expand All @@ -228,7 +228,7 @@ var _ = Describe("Cluster", func() {
})
It("PrivateLink false invalid Ec2MetadataHttpTokens - success", func() {
// TODO Need to add validation for Ec2MetadataHttpTokens
err := cluster.CreateAWSBuilder(rosaTypes.Classic, nil, pointer("test"), nil, nil, false, nil, nil, nil, nil, nil, nil, nil, nil, nil)
err := cluster.CreateAWSBuilder(rosaTypes.Classic, nil, pointer("test"), nil, nil, false, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
Expect(err).NotTo(HaveOccurred())
ocmCluster, err := cluster.Build()
Expect(err).NotTo(HaveOccurred())
Expand Down Expand Up @@ -257,7 +257,7 @@ var _ = Describe("Cluster", func() {
err := cluster.CreateAWSBuilder(rosaTypes.Classic, map[string]string{"key1": "val1"},
pointer(string(cmv1.Ec2MetadataHttpTokensRequired)),
pointer(validKmsKey), nil, true, pointer(accountID), nil,
sts, subnets, nil, nil, nil, nil, nil)
sts, subnets, nil, nil, nil, nil, nil, nil, nil, nil)
Expect(err).NotTo(HaveOccurred())
ocmCluster, err := cluster.Build()
Expect(err).NotTo(HaveOccurred())
Expand Down Expand Up @@ -299,7 +299,7 @@ var _ = Describe("Cluster", func() {
err := cluster.CreateAWSBuilder(rosaTypes.Classic, map[string]string{"key1": "val1"},
pointer(string(cmv1.Ec2MetadataHttpTokensRequired)),
pointer(validKmsKey), nil, true, pointer(accountID), nil,
sts, subnets, &privateHZId, &privateHZRoleArn, nil, nil, nil)
sts, subnets, &privateHZId, &privateHZRoleArn, nil, nil, nil, nil, nil, nil)
Expect(err).NotTo(HaveOccurred())
ocmCluster, err := cluster.Build()
Expect(err).NotTo(HaveOccurred())
Expand All @@ -324,7 +324,7 @@ var _ = Describe("Cluster", func() {
err := cluster.CreateAWSBuilder(rosaTypes.Classic, map[string]string{"key1": "val1"},
pointer(string(cmv1.Ec2MetadataHttpTokensRequired)),
pointer(validKmsKey), nil, true, pointer(accountID), nil,
sts, subnets, &privateHZId, &privateHZRoleArn, nil, nil, nil)
sts, subnets, &privateHZId, &privateHZRoleArn, nil, nil, nil, nil, nil, nil)
Expect(err).To(HaveOccurred())
})
It("PrivateHostedZone set missing STS - fail", func() {
Expand All @@ -336,7 +336,7 @@ var _ = Describe("Cluster", func() {
err := cluster.CreateAWSBuilder(rosaTypes.Classic, map[string]string{"key1": "val1"},
pointer(string(cmv1.Ec2MetadataHttpTokensRequired)),
pointer(validKmsKey), nil, true, pointer(accountID), nil,
nil, subnets, &privateHZId, &privateHZRoleArn, nil, nil, nil)
nil, subnets, &privateHZId, &privateHZRoleArn, nil, nil, nil, nil, nil, nil)
Expect(err).To(HaveOccurred())
})
It("PrivateHostedZone set missing subnet ids - fail", func() {
Expand All @@ -355,7 +355,7 @@ var _ = Describe("Cluster", func() {
err := cluster.CreateAWSBuilder(rosaTypes.Classic, map[string]string{"key1": "val1"},
pointer(string(cmv1.Ec2MetadataHttpTokensRequired)),
pointer(validKmsKey), nil, true, pointer(accountID), nil,
sts, nil, &privateHZId, &privateHZRoleArn, nil, nil, nil)
sts, nil, &privateHZId, &privateHZRoleArn, nil, nil, nil, nil, nil, nil)
Expect(err).To(HaveOccurred())
})
})
Expand Down
5 changes: 3 additions & 2 deletions provider/clusterrosa/classic/cluster_rosa_classic_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -653,9 +653,10 @@ func createClassicClusterObject(ctx context.Context,
}
if err := ocmClusterResource.CreateAWSBuilder(rosaTypes.Classic, awsTags, ec2MetadataHttpTokens,
kmsKeyARN, nil,
isPrivateLink, awsAccountID, nil, stsBuilder, awsSubnetIDs, privateHostedZoneID, privateHostedZoneRoleARN,
isPrivateLink, awsAccountID, nil, stsBuilder, awsSubnetIDs,
privateHostedZoneID, privateHostedZoneRoleARN, nil, nil,
awsAdditionalComputeSecurityGroupIds, awsAdditionalInfraSecurityGroupIds,
awsAdditionalControlPlaneSecurityGroupIds); err != nil {
awsAdditionalControlPlaneSecurityGroupIds, nil); err != nil {
return nil, err
}

Expand Down
2 changes: 1 addition & 1 deletion provider/clusterrosa/common/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ var AvailabilityZoneValidator = attrvalidators.NewStringValidator("AZ should be
}
})

var PrivateHZValidator = attrvalidators.NewObjectValidator("proxy map should not include an hard coded OCM proxy",
var PrivateHZValidator = attrvalidators.NewObjectValidator("Private Hosted Zone attribute must include all attributes",
func(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
Expand Down
13 changes: 12 additions & 1 deletion provider/clusterrosa/hcp/datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
rosa "github.com/terraform-redhat/terraform-provider-rhcs/provider/clusterrosa/common"
rosaTypes "github.com/terraform-redhat/terraform-provider-rhcs/provider/clusterrosa/common/types"
sharedvpc "github.com/terraform-redhat/terraform-provider-rhcs/provider/clusterrosa/hcp/shared_vpc"
"github.com/terraform-redhat/terraform-provider-rhcs/provider/clusterrosa/sts"
"github.com/terraform-redhat/terraform-provider-rhcs/provider/proxy"
"github.com/terraform-redhat/terraform-provider-rhcs/provider/registry_config"
Expand Down Expand Up @@ -119,6 +120,11 @@ func (r *ClusterRosaHcpDatasource) Schema(ctx context.Context, req datasource.Sc
Description: "DNS domain of cluster.",
Computed: true,
},
"base_dns_domain": schema.StringAttribute{
//nolint:lll
Description: "Base DNS domain name previously reserved, e.g. '1vo8.p3.openshiftapps.com'. " + common.ValueCannotBeChangedStringDescription,
Computed: true,
},
"replicas": schema.Int64Attribute{
Description: deprecatedMessage,
Computed: true,
Expand Down Expand Up @@ -265,6 +271,11 @@ func (r *ClusterRosaHcpDatasource) Schema(ctx context.Context, req datasource.Sc
ElementType: types.StringType,
Computed: true,
},
"shared_vpc": schema.SingleNestedAttribute{
Description: "Shared VPC configuration." + common.ValueCannotBeChangedStringDescription,
Attributes: sharedvpc.HcpStsDatasource(),
Computed: true,
},
},
}
}
Expand Down Expand Up @@ -322,7 +333,7 @@ func (r *ClusterRosaHcpDatasource) Read(ctx context.Context, request datasource.
object := get.Body()

// Save the state:
err = populateRosaHcpClusterState(ctx, object, state, common.DefaultHttpClient{})
err = populateRosaHcpClusterState(ctx, object, state)
if err != nil {
response.Diagnostics.AddError(
"Can't populate cluster state",
Expand Down
94 changes: 87 additions & 7 deletions provider/clusterrosa/hcp/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/terraform-redhat/terraform-provider-rhcs/provider/registry_config"

"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
Expand Down Expand Up @@ -64,6 +65,7 @@ import (
ocmr "github.com/terraform-redhat/terraform-provider-rhcs/internal/ocm/resource"
rosa "github.com/terraform-redhat/terraform-provider-rhcs/provider/clusterrosa/common"
rosaTypes "github.com/terraform-redhat/terraform-provider-rhcs/provider/clusterrosa/common/types"
sharedvpc "github.com/terraform-redhat/terraform-provider-rhcs/provider/clusterrosa/hcp/shared_vpc"
"github.com/terraform-redhat/terraform-provider-rhcs/provider/clusterrosa/hcp/upgrade"
"github.com/terraform-redhat/terraform-provider-rhcs/provider/clusterrosa/sts"
)
Expand Down Expand Up @@ -177,6 +179,15 @@ func (r *ClusterRosaHcpResource) Schema(ctx context.Context, req resource.Schema
Description: "DNS domain of cluster.",
Computed: true,
},
"base_dns_domain": schema.StringAttribute{
//nolint:lll
Description: "Base DNS domain name previously reserved, e.g. '1vo8.p3.openshiftapps.com'. " + common.ValueCannotBeChangedStringDescription,
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"replicas": schema.Int64Attribute{
Description: "Number of worker/compute nodes to provision. " +
"Requires that the number supplied be a multiple of the number of private subnets. " +
Expand Down Expand Up @@ -379,6 +390,20 @@ func (r *ClusterRosaHcpResource) Schema(ctx context.Context, req resource.Schema
ElementType: types.StringType,
Optional: true,
},
"shared_vpc": schema.SingleNestedAttribute{
Description: "Shared VPC configuration." + common.ValueCannotBeChangedStringDescription,
Attributes: sharedvpc.SharedVpcResource(),
Optional: true,
Validators: []validator.Object{
objectvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("base_dns_domain")),
sharedvpc.HcpSharedVpcValidator,
},
},
"aws_additional_allowed_principals": schema.ListAttribute{
Description: "AWS additional allowed principals. " + common.ValueCannotBeChangedStringDescription,
ElementType: types.StringType,
Optional: true,
},
},
}
}
Expand Down Expand Up @@ -539,13 +564,38 @@ func createHcpClusterObject(ctx context.Context,
if err != nil {
return nil, err
}

var ingressHostedZoneId, route53RoleArn, vpceRoleArn, internalCommunicationHostedZoneId *string
if state.SharedVpc != nil &&
!common.IsStringAttributeUnknownOrEmpty(state.SharedVpc.IngressPrivateHostedZoneId) &&
!common.IsStringAttributeUnknownOrEmpty(state.SharedVpc.Route53RoleArn) &&
!common.IsStringAttributeUnknownOrEmpty(state.SharedVpc.InternalCommunicationPrivateHostedZoneId) &&
!common.IsStringAttributeUnknownOrEmpty(state.SharedVpc.VpceRoleArn) {
route53RoleArn = state.SharedVpc.Route53RoleArn.ValueStringPointer()
ingressHostedZoneId = state.SharedVpc.IngressPrivateHostedZoneId.ValueStringPointer()
vpceRoleArn = state.SharedVpc.VpceRoleArn.ValueStringPointer()
internalCommunicationHostedZoneId = state.SharedVpc.InternalCommunicationPrivateHostedZoneId.ValueStringPointer()
}

awsAdditionalAllowedPrincipals, err := common.StringListToArray(ctx, state.AWSAdditionalAllowedPrincipals)
if err != nil {
return nil, err
}

if err := ocmClusterResource.CreateAWSBuilder(rosaTypes.Hcp, awsTags, ec2MetadataHttpTokens,
kmsKeyARN, etcdKmsKeyArn,
isPrivate, awsAccountID, awsBillingAccountId, stsBuilder, awsSubnetIDs, nil, nil,
awsAdditionalComputeSecurityGroupIds, nil, nil); err != nil {
isPrivate, awsAccountID, awsBillingAccountId, stsBuilder, awsSubnetIDs,
ingressHostedZoneId, route53RoleArn, internalCommunicationHostedZoneId, vpceRoleArn,
awsAdditionalComputeSecurityGroupIds, nil, nil, awsAdditionalAllowedPrincipals); err != nil {
return nil, err
}

if !common.IsStringAttributeUnknownOrEmpty(state.BaseDNSDomain) {
dnsBuilder := cmv1.NewDNS()
dnsBuilder.BaseDomain(state.BaseDNSDomain.ValueString())
builder.DNS(dnsBuilder)
}

if err := ocmClusterResource.SetAPIPrivacy(isPrivate, isPrivate, stsBuilder != nil); err != nil {
return nil, err
}
Expand Down Expand Up @@ -751,7 +801,7 @@ func (r *ClusterRosaHcpResource) Create(ctx context.Context, request resource.Cr
object = add.Body()

// Save initial state:
err = populateRosaHcpClusterState(ctx, object, state, common.DefaultHttpClient{})
err = populateRosaHcpClusterState(ctx, object, state)
if err != nil {
response.Diagnostics.AddError(
"Can't populate cluster state",
Expand Down Expand Up @@ -794,7 +844,7 @@ func (r *ClusterRosaHcpResource) Create(ctx context.Context, request resource.Cr
}

// Save the state post wait completion:
err = populateRosaHcpClusterState(ctx, object, state, common.DefaultHttpClient{})
err = populateRosaHcpClusterState(ctx, object, state)
if err != nil {
response.Diagnostics.AddError(
"Can't populate cluster state",
Expand Down Expand Up @@ -843,7 +893,7 @@ func (r *ClusterRosaHcpResource) Read(ctx context.Context, request resource.Read
object := get.Body()

// Save the state:
err = populateRosaHcpClusterState(ctx, object, state, common.DefaultHttpClient{})
err = populateRosaHcpClusterState(ctx, object, state)
if err != nil {
response.Diagnostics.AddError(
"Can't populate cluster state",
Expand Down Expand Up @@ -899,6 +949,11 @@ func validateNoImmutableAttChange(state, plan *ClusterRosaHcpState) diag.Diagnos
diags.AddError(common.AssertionErrorSummaryMessage, fmt.Sprintf(common.AssertionErrorDetailsMessage, "admin_credentials", state.AdminCredentials, plan.AdminCredentials))
}

common.ValidateStateAndPlanEquals(state.BaseDNSDomain, plan.BaseDNSDomain, "base_dns_domain", &diags)
if !reflect.DeepEqual(state.SharedVpc, plan.SharedVpc) {
diags.AddError(common.AssertionErrorSummaryMessage, fmt.Sprintf(common.AssertionErrorDetailsMessage, "shared_vpc", *state.SharedVpc, *plan.SharedVpc))
}

return diags

}
Expand Down Expand Up @@ -1036,7 +1091,7 @@ func (r *ClusterRosaHcpResource) Update(ctx context.Context, request resource.Up
object := update.Body()

// Update the state:
err = populateRosaHcpClusterState(ctx, object, plan, common.DefaultHttpClient{})
err = populateRosaHcpClusterState(ctx, object, plan)
if err != nil {
response.Diagnostics.AddError(
"Can't populate cluster state",
Expand Down Expand Up @@ -1300,7 +1355,7 @@ func (r *ClusterRosaHcpResource) ImportState(ctx context.Context, request resour
}

// populateRosaHcpClusterState copies the data from the API object to the Terraform state.
func populateRosaHcpClusterState(ctx context.Context, object *cmv1.Cluster, state *ClusterRosaHcpState, httpClient common.HttpClient) error {
func populateRosaHcpClusterState(ctx context.Context, object *cmv1.Cluster, state *ClusterRosaHcpState) error {
state.ID = types.StringValue(object.ID())
state.ExternalID = types.StringValue(object.ExternalID())
object.API()
Expand Down Expand Up @@ -1333,6 +1388,7 @@ func populateRosaHcpClusterState(ctx context.Context, object *cmv1.Cluster, stat
state.APIURL = types.StringValue(object.API().URL())
state.ConsoleURL = types.StringValue(object.Console().URL())
state.Domain = types.StringValue(fmt.Sprintf("%s.%s", object.DomainPrefix(), object.DNS().BaseDomain()))
state.BaseDNSDomain = types.StringValue(object.DNS().BaseDomain())

if azs, ok := object.Nodes().GetAvailabilityZones(); ok {
listValue, err := common.StringArrayToList(azs)
Expand Down Expand Up @@ -1534,6 +1590,30 @@ func populateRosaHcpClusterState(ctx context.Context, object *cmv1.Cluster, stat
return err
}

if awsObj, ok := object.GetAWS(); ok {
ingressHostedZoneId := awsObj.PrivateHostedZoneID()
route53RoleArn := awsObj.PrivateHostedZoneRoleARN()
internalCommunicationHostedZoneId := awsObj.HcpInternalCommunicationHostedZoneId()
vpceRoleArn := awsObj.VpcEndpointRoleArn()
if len(ingressHostedZoneId) > 0 && len(route53RoleArn) > 0 &&
len(internalCommunicationHostedZoneId) > 0 && len(vpceRoleArn) > 0 {
state.SharedVpc = &sharedvpc.SharedVpc{
IngressPrivateHostedZoneId: types.StringValue(ingressHostedZoneId),
InternalCommunicationPrivateHostedZoneId: types.StringValue(internalCommunicationHostedZoneId),
Route53RoleArn: types.StringValue(route53RoleArn),
VpceRoleArn: types.StringValue(vpceRoleArn),
}
}

if additionalAllowedPrincipals, ok := awsObj.GetAdditionalAllowedPrincipals(); ok {
awsAdditionalAllowedPrincipals, err := common.StringArrayToList(additionalAllowedPrincipals)
if err != nil {
return err
}
state.AWSAdditionalAllowedPrincipals = awsAdditionalAllowedPrincipals
}
}

return nil
}

Expand Down
Loading

0 comments on commit 9515aba

Please sign in to comment.