diff --git a/data/data/aws/cluster/main.tf b/data/data/aws/cluster/main.tf index 1b9ee39b1fb..c6fa36a2971 100644 --- a/data/data/aws/cluster/main.tf +++ b/data/data/aws/cluster/main.tf @@ -60,6 +60,7 @@ module "iam" { module "dns" { source = "./route53" + count = var.aws_api_ext_lb_name == null ? 1 : 0 api_external_lb_dns_name = module.vpc.aws_lb_api_external_dns_name api_external_lb_zone_id = module.vpc.aws_lb_api_external_zone_id @@ -75,6 +76,8 @@ module "dns" { region = var.aws_region publish_strategy = var.aws_publish_strategy custom_endpoints = var.custom_endpoints + api_ext_lb_name = var.aws_api_ext_lb_name + api_int_lb_name = var.aws_api_int_lb_name } module "vpc" { diff --git a/data/data/aws/cluster/vpc/common.tf b/data/data/aws/cluster/vpc/common.tf index c296555fbd5..d9b019228bd 100644 --- a/data/data/aws/cluster/vpc/common.tf +++ b/data/data/aws/cluster/vpc/common.tf @@ -3,6 +3,7 @@ locals { public_endpoints = var.publish_strategy == "External" ? true : false + byo_lbs = var.aws_api_ext_lb_name == null ? false : true description = "Created By OpenShift Installer" # CIDR block distribution: @@ -63,4 +64,12 @@ data "aws_subnet" "edge_public" { count = var.edge_zones == null ? 0 : length(var.edge_zones) id = var.edge_zones == null ? null : aws_subnet.edge_public_subnet[count.index].id -} \ No newline at end of file +} + +data "aws_lb" "ext" { + name = local.byo_lbs ? var.api_ext_lb_name : aws_lb.api_external[0].name +} + +data "aws_lb" "int" { + name = local.byo_lbs ? var.api_int_lb_name : aws_lb.api_internal[0].name +} diff --git a/data/data/aws/cluster/vpc/master-elb.tf b/data/data/aws/cluster/vpc/master-elb.tf index 6fff3d8b283..049e6b057b0 100644 --- a/data/data/aws/cluster/vpc/master-elb.tf +++ b/data/data/aws/cluster/vpc/master-elb.tf @@ -1,4 +1,6 @@ resource "aws_lb" "api_internal" { + count = local.byo_lbs ? 0 : 1 + name = "${var.cluster_id}-int" load_balancer_type = "network" subnets = data.aws_subnet.private.*.id @@ -20,7 +22,7 @@ resource "aws_lb" "api_internal" { } resource "aws_lb" "api_external" { - count = local.public_endpoints ? 1 : 0 + count = local.public_endpoints && ! local.byo_lbs ? 1 : 0 name = "${var.cluster_id}-ext" load_balancer_type = "network" @@ -122,7 +124,7 @@ resource "aws_lb_target_group" "services" { } resource "aws_lb_listener" "api_internal_api" { - load_balancer_arn = aws_lb.api_internal.arn + load_balancer_arn = data.aws_lb.int.arn protocol = "TCP" port = "6443" @@ -133,7 +135,7 @@ resource "aws_lb_listener" "api_internal_api" { } resource "aws_lb_listener" "api_internal_services" { - load_balancer_arn = aws_lb.api_internal.arn + load_balancer_arn = data.aws_lb.int.arn protocol = "TCP" port = "22623" @@ -146,7 +148,7 @@ resource "aws_lb_listener" "api_internal_services" { resource "aws_lb_listener" "api_external_api" { count = local.public_endpoints ? 1 : 0 - load_balancer_arn = aws_lb.api_external[0].arn + load_balancer_arn = data.aws_lb.ext.arn protocol = "TCP" port = "6443" diff --git a/data/data/aws/cluster/vpc/outputs.tf b/data/data/aws/cluster/vpc/outputs.tf index 84279ae0bb6..14565a9d5ec 100644 --- a/data/data/aws/cluster/vpc/outputs.tf +++ b/data/data/aws/cluster/vpc/outputs.tf @@ -61,18 +61,18 @@ output "aws_lb_target_group_arns_length" { } output "aws_lb_api_external_dns_name" { - value = local.public_endpoints ? aws_lb.api_external[0].dns_name : null + value = local.public_endpoints ? data.aws_lb.ext.dns_name : null } output "aws_lb_api_external_zone_id" { - value = local.public_endpoints ? aws_lb.api_external[0].zone_id : null + value = local.public_endpoints ? data.aws_lb.ext.zone_id : null } output "aws_lb_api_internal_dns_name" { - value = aws_lb.api_internal.dns_name + value = data.aws_lb.int.dns_name } output "aws_lb_api_internal_zone_id" { - value = aws_lb.api_internal.zone_id + value = data.aws_lb.int.zone_id } diff --git a/data/data/aws/cluster/vpc/variables.tf b/data/data/aws/cluster/vpc/variables.tf index d30d8d503ca..b46ce19a7d6 100644 --- a/data/data/aws/cluster/vpc/variables.tf +++ b/data/data/aws/cluster/vpc/variables.tf @@ -53,4 +53,16 @@ variable "public_subnets" { variable "private_subnets" { type = list(string) description = "Existing private subnets into which the cluster should be installed." -} \ No newline at end of file +} + +variable "ext_lb" { + type = string + description = "external lb name in byo scenario" + default = null +} + +variable "int_lb" { + type = string + description = "int lb name in byo scenario" + default = null +} diff --git a/data/data/aws/variables-aws.tf b/data/data/aws/variables-aws.tf index a2c25959ece..22f81539916 100644 --- a/data/data/aws/variables-aws.tf +++ b/data/data/aws/variables-aws.tf @@ -199,4 +199,16 @@ variable "aws_worker_iam_role_name" { type = string description = "The name of the IAM role that will be attached to worker instances." default = "" -} \ No newline at end of file +} + +variable "aws_api_ext_lb_name" { + type = string + description = "The name of the external load balancer created by the user." + default = null +} + +variable "aws_api_int_lb_name" { + type = string + description = "The name of the internal load balancer created by the user." + default = null +} diff --git a/data/data/install.openshift.io_installconfigs.yaml b/data/data/install.openshift.io_installconfigs.yaml index 12cf1140463..f3325e31e8c 100644 --- a/data/data/install.openshift.io_installconfigs.yaml +++ b/data/data/install.openshift.io_installconfigs.yaml @@ -2063,6 +2063,33 @@ spec: items: type: string type: array + userConfiguredDNSLB: + description: "UserConfiguredDNSLB contains all the API and API-Int + LB information. \n This field is used to Enable the use of a + custom DNS solution when the DNS provided by the underlying + cloud platform cannot be used. When Enabled, the user can provide + information about user created API and API-Int LBs using this + field." + properties: + apiIntLBName: + description: ApiIntLBName is the name of the API-Int NLB created + by the user. + type: string + apiLBName: + description: ApiLBName is the name of the API NLB created + by the user. + type: string + userDNS: + default: Disabled + description: UserDNS specifies whether the customer is responsible + for configuring DNS entries for API and API-Int prior to + cluster installation. + enum: + - "" + - Enabled + - Disabled + type: string + type: object userTags: additionalProperties: type: string diff --git a/pkg/asset/cluster/tfvars.go b/pkg/asset/cluster/tfvars.go index c7c1f097901..5acbc8c0d57 100644 --- a/pkg/asset/cluster/tfvars.go +++ b/pkg/asset/cluster/tfvars.go @@ -287,6 +287,12 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error { awsMP.Set(mp.Platform.AWS) masterIAMRoleName = awsMP.IAMRole } + apiIntLBName := "" + apiLBName := "" + if installConfig.Config.AWS.UserConfiguredDNSLB.UserDNS == "Enabled" { + apiIntLBName = installConfig.Config.AWS.UserConfiguredDNSLB.APIIntLBName + apiLBName = installConfig.Config.AWS.UserConfiguredDNSLB.APILBName + } // AWS Zones is used to determine which route table the edge zone will be associated. allZones, err := installConfig.AWS.AllZones(ctx) @@ -314,6 +320,8 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error { WorkerIAMRoleName: workerIAMRoleName, Architecture: installConfig.Config.ControlPlane.Architecture, Proxy: installConfig.Config.Proxy, + APILBName: apiLBName, + APIIntLBName: apiIntLBName, }) if err != nil { return errors.Wrapf(err, "failed to get %s Terraform variables", platform) diff --git a/pkg/asset/manifests/dns.go b/pkg/asset/manifests/dns.go index 6bc07a9d368..b2ff6742a44 100644 --- a/pkg/asset/manifests/dns.go +++ b/pkg/asset/manifests/dns.go @@ -101,6 +101,14 @@ func (d *DNS) Generate(dependencies asset.Parents) error { Tags: map[string]string{"type": "private"}, } case awstypes.Name: + // We donot want to configure Route53 when the user provided DNS is in use. + // so, do not set PrivateZone and PublicZone fields in the DNS manifest. + if installConfig.Config.AWS.UserConfiguredDNSLB.UserDNS == "Enabled" { + config.Spec.PublicZone = &configv1.DNSZone{ID: ""} + config.Spec.PrivateZone = &configv1.DNSZone{ID: ""} + break + } + if installConfig.Config.Publish == types.ExternalPublishingStrategy { sess, err := installConfig.AWS.Session(context.TODO()) if err != nil { diff --git a/pkg/explain/printer_test.go b/pkg/explain/printer_test.go index 6cb0e91e86c..1cf9db73bdf 100644 --- a/pkg/explain/printer_test.go +++ b/pkg/explain/printer_test.go @@ -186,6 +186,10 @@ func Test_PrintFields(t *testing.T) { subnets <[]string> Subnets specifies existing subnets (by ID) where cluster resources will be created. Leave unset to have the installer create subnets in a new VPC on your behalf. + userConfiguredDNSLB + UserConfiguredDNSLB contains all the API and API-Int LB information. + This field is used to Enable the use of a custom DNS solution when the DNS provided by the underlying cloud platform cannot be used. When Enabled, the user can provide information about user created API and API-Int LBs using this field. + userTags UserTags additional keys and values that the installer will add as tags to all resources that it creates. Resources created by the cluster itself may not include these tags.`, }, { diff --git a/pkg/tfvars/aws/aws.go b/pkg/tfvars/aws/aws.go index 7052bf824d4..6e20e46fc02 100644 --- a/pkg/tfvars/aws/aws.go +++ b/pkg/tfvars/aws/aws.go @@ -45,6 +45,8 @@ type config struct { WorkerIAMRoleName string `json:"aws_worker_iam_role_name,omitempty"` MasterMetadataAuthentication string `json:"aws_master_instance_metadata_authentication,omitempty"` BootstrapMetadataAuthentication string `json:"aws_bootstrap_instance_metadata_authentication,omitempty"` + APILBName string `json:"aws_api_ext_lb_name,omitempty"` + APIIntLBName string `json:"aws_api_int_lb_name,omitempty"` } // TFVarsSources contains the parameters to be converted into Terraform variables @@ -72,6 +74,8 @@ type TFVarsSources struct { Architecture types.Architecture Proxy *types.Proxy + + APILBName, APIIntLBName string } // TFVars generates AWS-specific Terraform variables launching the cluster. @@ -193,6 +197,8 @@ func TFVars(sources TFVarsSources) ([]byte, error) { IgnitionBucket: sources.IgnitionBucket, MasterIAMRoleName: sources.MasterIAMRoleName, WorkerIAMRoleName: sources.WorkerIAMRoleName, + APILBName: sources.APILBName, + APIIntLBName: sources.APIIntLBName, } stubIgn, err := bootstrap.GenerateIgnitionShimWithCertBundleAndProxy(sources.IgnitionPresignedURL, sources.AdditionalTrustBundle, sources.Proxy) diff --git a/pkg/types/aws/platform.go b/pkg/types/aws/platform.go index 131d4a491f5..873cae32cf7 100644 --- a/pkg/types/aws/platform.go +++ b/pkg/types/aws/platform.go @@ -101,6 +101,16 @@ type Platform struct { // // +optional LBType configv1.AWSLBType `json:"lbType,omitempty"` + + // UserConfiguredDNSLB contains all the API and API-Int LB information. + // + // This field is used to Enable the use of a custom DNS solution when + // the DNS provided by the underlying cloud platform cannot be used. + // When Enabled, the user can provide information about user created + // API and API-Int LBs using this field. + // + // +optional + UserConfiguredDNSLB UserConfiguredDNSLB `json:"userConfiguredDNSLB,omitempty"` } // ServiceEndpoint store the configuration for services to @@ -118,6 +128,35 @@ type ServiceEndpoint struct { URL string `json:"url"` } +// UserDNSConfiguration specifies whether the csutomer is using their own DNS +// +kubebuilder:validation:Enum="";Enabled;Disabled +type UserDNSConfiguration string + +const ( + // UserDNSEnabled indicates that user has pre-configured their own DNS. + UserDNSEnabled UserDNSConfiguration = "Enabled" + // UserDNSDisabled indicates that user has not pre-configured their own DNS. + // Installer would continue to be responsible for configuring the cloud DNS. + UserDNSDisabled UserDNSConfiguration = "Disabled" +) + +// UserConfiguredDNSLB is used to specify the customer's intent in using a +// custom DNS solution. It contains informmation about the pre-created LB +// resources for API and API-Int. +type UserConfiguredDNSLB struct { + // UserDNS specifies whether the customer is responsible for + // configuring DNS entries for API and API-Int prior to cluster + // installation. + // +kubebuilder:default=Disabled + UserDNS UserDNSConfiguration `json:"userDNS,omitempty"` + + // ApiLBName is the name of the API NLB created by the user. + APILBName string `json:"apiLBName,omitempty"` + + // ApiIntLBName is the name of the API-Int NLB created by the user. + APIIntLBName string `json:"apiIntLBName,omitempty"` +} + // IsSecretRegion returns true if the region is part of either the ISO or ISOB partitions. func IsSecretRegion(region string) bool { partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), region) diff --git a/pkg/types/aws/validation/platform.go b/pkg/types/aws/validation/platform.go index e90513db724..4c5465000b3 100644 --- a/pkg/types/aws/validation/platform.go +++ b/pkg/types/aws/validation/platform.go @@ -27,7 +27,7 @@ var openshiftNamespaceRegex = regexp.MustCompile(`^([^/]*\.)?openshift.io/`) const userTagLimit = 25 // ValidatePlatform checks that the specified platform is valid. -func ValidatePlatform(p *aws.Platform, cm types.CredentialsMode, fldPath *field.Path) field.ErrorList { +func ValidatePlatform(p *aws.Platform, cm types.CredentialsMode, publish types.PublishingStrategy, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if p.Region == "" { @@ -58,6 +58,7 @@ func ValidatePlatform(p *aws.Platform, cm types.CredentialsMode, fldPath *field. allErrs = append(allErrs, ValidateMachinePool(p, p.DefaultMachinePlatform, fldPath.Child("defaultMachinePlatform"))...) } + allErrs = append(allErrs, validateUserConfiguredDNSLB(p.UserConfiguredDNSLB, publish, fldPath.Child("userConfiguredDNSLB"))...) return allErrs } @@ -169,3 +170,27 @@ func validateServiceURL(uri string) error { return nil } + +func validateUserConfiguredDNSLB(userConfig aws.UserConfiguredDNSLB, publish types.PublishingStrategy, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + switch { + case userConfig.UserDNS == aws.UserDNSEnabled: + if userConfig.APIIntLBName == "" { + allErrs = append(allErrs, + field.Required(fldPath, "have to provide pre-created API-Int LB Name when user configured DNS is Enabled")) + } + if publish == types.ExternalPublishingStrategy && userConfig.APILBName == "" { + allErrs = append(allErrs, + field.Required(fldPath, "have to provide pre-created API LB Name when user configured DNS is Enabled and the publishing strategy is External")) + } + case userConfig.UserDNS == aws.UserDNSDisabled || userConfig.UserDNS == "": + if userConfig.APILBName != "" || userConfig.APIIntLBName != "" { + allErrs = append(allErrs, + field.Invalid(fldPath, userConfig, "API and API-Int LB Names will be ignored when user configured DNS is Disabled or unspecified")) + } + default: + allErrs = append(allErrs, + field.Invalid(fldPath, userConfig.UserDNS, "provided for userDNS")) + } + return allErrs +} diff --git a/pkg/types/aws/validation/platform_test.go b/pkg/types/aws/validation/platform_test.go index 41de264bf9d..9c760a8ba4c 100644 --- a/pkg/types/aws/validation/platform_test.go +++ b/pkg/types/aws/validation/platform_test.go @@ -19,6 +19,7 @@ func TestValidatePlatform(t *testing.T) { platform *aws.Platform expected string credMode types.CredentialsMode + publish types.PublishingStrategy }{ { name: "minimal", @@ -205,10 +206,84 @@ func TestValidatePlatform(t *testing.T) { }, expected: `^test-path\.hostedZoneRole: Forbidden: when specifying a hostedZoneRole, either Passthrough or Manual credential mode must be specified$`, }, + { + name: "valid userConfiguredDNSLB default", + platform: &aws.Platform{ + Region: "us-east-1", + UserConfiguredDNSLB: aws.UserConfiguredDNSLB{ + UserDNS: "Disabled", + }, + }, + }, + { + name: "UserDNS Enabled with valid LB Names", + platform: &aws.Platform{ + Region: "us-east-1", + UserConfiguredDNSLB: aws.UserConfiguredDNSLB{ + UserDNS: "Enabled", + APILBName: "api-lb-name", + APIIntLBName: "api-int-lb-name", + }, + }, + }, + { + name: "valid UserDNS but no LB Names", + platform: &aws.Platform{ + Region: "us-east-1", + UserConfiguredDNSLB: aws.UserConfiguredDNSLB{ + UserDNS: "Enabled", + }, + }, + expected: `^test-path\.userConfiguredDNSLB: Invalid value: aws.UserConfiguredDNSLB{UserDNS:"Enabled", APILBName:"", APIIntLBName:""}: have to provide pre-created API and API-Int LB Names when user configured DNS is Enabled$`, + }, + { + name: "invalid userDNS value", + platform: &aws.Platform{ + Region: "us-east-1", + UserConfiguredDNSLB: aws.UserConfiguredDNSLB{ + UserDNS: "Test", + }, + }, + expected: `^test-path\.userConfiguredDNSLB: Invalid value: "Test": provided for userDNS$`, + }, + { + name: "UserDNS Enabled with PublishingStrategy External", + platform: &aws.Platform{ + Region: "us-east-1", + UserConfiguredDNSLB: aws.UserConfiguredDNSLB{ + UserDNS: "Enabled", + APIIntLBName: "api-int-lb-name", + }, + }, + publish: "External", + expected: `^test-path\.userConfiguredDNSLB: Required value: have to provide pre-created API LB Name when user configured DNS is Enabled and the publishing strategy is External$`, + }, + { + name: "UserDNS Enabled with PublishingStrategy External", + platform: &aws.Platform{ + Region: "us-east-1", + UserConfiguredDNSLB: aws.UserConfiguredDNSLB{ + UserDNS: "Enabled", + APIIntLBName: "api-int-lb-name", + }, + }, + publish: "Internal", + }, + { + name: "UserDNS Enabled with PublishingStrategy External", + platform: &aws.Platform{ + Region: "us-east-1", + UserConfiguredDNSLB: aws.UserConfiguredDNSLB{ + UserDNS: "Enabled", + APILBName: "api-lb-name", + }, + }, + expected: `^test-path\.userConfiguredDNSLB: Required value: have to provide pre-created API-Int LB Name when user configured DNS is Enabled$`, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - err := ValidatePlatform(tc.platform, tc.credMode, field.NewPath("test-path")).ToAggregate() + err := ValidatePlatform(tc.platform, tc.credMode, tc.publish, field.NewPath("test-path")).ToAggregate() if tc.expected == "" { assert.NoError(t, err) } else { diff --git a/pkg/types/validation/installconfig.go b/pkg/types/validation/installconfig.go index a7155e5d62e..a9fb06207e1 100644 --- a/pkg/types/validation/installconfig.go +++ b/pkg/types/validation/installconfig.go @@ -726,7 +726,7 @@ func validatePlatform(platform *types.Platform, usingAgentMethod bool, fldPath * } if platform.AWS != nil { validate(aws.Name, platform.AWS, func(f *field.Path) field.ErrorList { - return awsvalidation.ValidatePlatform(platform.AWS, c.CredentialsMode, f) + return awsvalidation.ValidatePlatform(platform.AWS, c.CredentialsMode, c.Publish, f) }) } if platform.Azure != nil {