diff --git a/pkg/asset/manifests/azure/cluster.go b/pkg/asset/manifests/azure/cluster.go index fe8307e7b7..e9d135d2d9 100644 --- a/pkg/asset/manifests/azure/cluster.go +++ b/pkg/asset/manifests/azure/cluster.go @@ -55,6 +55,9 @@ func GenerateClusterAssets(installConfig *installconfig.InstallConfig, clusterID }, }, NetworkSpec: capz.NetworkSpec{ + NetworkClassSpec: capz.NetworkClassSpec{ + PrivateDNSZoneName: installConfig.Config.ClusterDomain(), + }, Vnet: capz.VnetSpec{ ID: installConfig.Config.Azure.VirtualNetwork, VnetClassSpec: capz.VnetClassSpec{ @@ -63,6 +66,15 @@ func GenerateClusterAssets(installConfig *installconfig.InstallConfig, clusterID }, }, }, + APIServerLB: capz.LoadBalancerSpec{ + Name: fmt.Sprintf("%s-internal", clusterID.InfraID), + BackendPool: capz.BackendPool{ + Name: fmt.Sprintf("%s-internal", clusterID.InfraID), + }, + LoadBalancerClassSpec: capz.LoadBalancerClassSpec{ + Type: capz.Internal, + }, + }, Subnets: capz.Subnets{ { SubnetClassSpec: capz.SubnetClassSpec{ diff --git a/pkg/infrastructure/azure/azure.go b/pkg/infrastructure/azure/azure.go index 1ac2971998..6a49a685f5 100644 --- a/pkg/infrastructure/azure/azure.go +++ b/pkg/infrastructure/azure/azure.go @@ -1,6 +1,8 @@ package azure import ( + "context" + "github.com/openshift/installer/pkg/infrastructure/clusterapi" azuretypes "github.com/openshift/installer/pkg/types/azure" ) @@ -12,3 +14,8 @@ type Provider struct{} // Name gives the name of the provider, Azure. func (*Provider) Name() string { return azuretypes.Name } + +// InfraReady sets the DNS currently after the ignition is done. +func (p *Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput) error { + return createDNSEntries(ctx, in) +} diff --git a/pkg/infrastructure/azure/dns.go b/pkg/infrastructure/azure/dns.go new file mode 100644 index 0000000000..4d5ff2a936 --- /dev/null +++ b/pkg/infrastructure/azure/dns.go @@ -0,0 +1,196 @@ +package azure + +import ( + "context" + "fmt" + + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns" + "k8s.io/utils/ptr" + capz "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/openshift/installer/pkg/asset/manifests/capiutils" + "github.com/openshift/installer/pkg/infrastructure/clusterapi" + "github.com/openshift/installer/pkg/types" +) + +type recordListType string + +const ( + cname recordListType = "Cname" + arecord recordListType = "ARecord" + aaaarecord recordListType = "AaaaRecord" +) + +type recordList struct { + Name string + RecordType armdns.RecordType + RecordSet armdns.RecordSet +} + +type recordPrivateList struct { + Name string + RecordType armprivatedns.RecordType + RecordSet armprivatedns.RecordSet +} + +// Create DNS entries for azure. +func createDNSEntries(ctx context.Context, in clusterapi.InfraReadyInput) error { + private := in.InstallConfig.Config.Publish == types.InternalPublishingStrategy + baseDomainResourceGroup := in.InstallConfig.Config.Azure.BaseDomainResourceGroupName + zone := in.InstallConfig.Config.BaseDomain + privatezone := in.InstallConfig.Config.ClusterDomain() + apiExternalName := fmt.Sprintf("api.%s", in.InstallConfig.Config.ObjectMeta.Name) + + resourceGroup := fmt.Sprintf("%s-rg", in.InfraID) + if in.InstallConfig.Config.Azure.ResourceGroupName != "" { + resourceGroup = in.InstallConfig.Config.Azure.ResourceGroupName + } + azureTags := make(map[string]*string) + for k, v := range in.InstallConfig.Config.Azure.UserTags { + azureTags[k] = ptr.To(v) + } + azureCluster := &capz.AzureCluster{} + key := client.ObjectKey{ + Name: in.InfraID, + Namespace: capiutils.Namespace, + } + if err := in.Client.Get(ctx, key, azureCluster); err != nil && azureCluster != nil { + return fmt.Errorf("failed to get Azure cluster: %w", err) + } + + if len(azureCluster.Spec.NetworkSpec.APIServerLB.FrontendIPs) == 0 { + return fmt.Errorf("failed to get Azure cluster LB frontend IPs") + } + ipIlb := azureCluster.Spec.NetworkSpec.APIServerLB.FrontendIPs[0].PrivateIPAddress + // useIPv6 := false + // for _, network := range in.InstallConfig.Config.Networking.ServiceNetwork { + // if network.IP.To4() == nil { + // useIPv6 = true + // } + // } + + privateRecords := []recordPrivateList{} + ttl := int64(300) + recordType := arecord + // if useIPv6 { + // recordType = aaaarecord + // } + privateRecords = append(privateRecords, createPrivateRecordSet("api-int", azureTags, ttl, recordType, ipIlb, "")) + privateRecords = append(privateRecords, createPrivateRecordSet("api", azureTags, ttl, recordType, ipIlb, "")) + + session, err := in.InstallConfig.Azure.Session() + if err != nil { + return fmt.Errorf("failed to create session: %w", err) + } + subscriptionID := session.Credentials.SubscriptionID + tokenCreds, err := azidentity.NewClientSecretCredential(session.Credentials.TenantID, session.Credentials.ClientID, session.Credentials.ClientSecret, nil) + if err != nil { + return fmt.Errorf("failed to create identity: %w", err) + } + recordSetClient, err := armdns.NewRecordSetsClient(subscriptionID, tokenCreds, nil) + if err != nil { + return fmt.Errorf("failed to create public record client: %w", err) + } + privateRecordSetClient, err := armprivatedns.NewRecordSetsClient(subscriptionID, tokenCreds, nil) + if err != nil { + return fmt.Errorf("failed to create private record client: %w", err) + } + + // Create the records for api and api-int in the private zone and api. for public zone. + // CAPI currently creates a record called "apiserver" instead of "api" so creating "api" for the installer in the private zone. + if !private { + cnameRecordName := apiExternalName + // apiExternalNameV6 := fmt.Sprintf("v6-api.%s", infraID) + // if useIPv6 { + // cnameRecordName = apiExternalNameV6 + // } + // TODO: Populate with public LB FQDN. Placeholder text as value. + publicRecords := createRecordSet(cnameRecordName, azureTags, ttl, cname, "", in.InstallConfig.Config.ClusterDomain()) + _, err = recordSetClient.CreateOrUpdate(ctx, baseDomainResourceGroup, zone, publicRecords.Name, publicRecords.RecordType, publicRecords.RecordSet, nil) + if err != nil { + return fmt.Errorf("failed to create public record set: %w", err) + } + } + + for _, record := range privateRecords { + _, err = privateRecordSetClient.CreateOrUpdate(ctx, resourceGroup, privatezone, record.RecordType, record.Name, record.RecordSet, nil) + if err != nil { + return fmt.Errorf("failed to create private record set: %w", err) + } + } + + return nil +} + +func createPrivateRecordSet(lbType string, azureTags map[string]*string, ttl int64, rType recordListType, ipAddress string, recordName string) (record recordPrivateList) { + record = recordPrivateList{ + Name: lbType, + RecordSet: armprivatedns.RecordSet{ + Properties: &armprivatedns.RecordSetProperties{ + TTL: &ttl, + Metadata: azureTags, + }, + }, + } + + switch rType { + case cname: + record.RecordType = armprivatedns.RecordTypeCNAME + record.RecordSet.Properties.CnameRecord = &armprivatedns.CnameRecord{ + Cname: &recordName, + } + case arecord: + record.RecordType = armprivatedns.RecordTypeA + record.RecordSet.Properties.ARecords = []*armprivatedns.ARecord{ + { + IPv4Address: &ipAddress, + }, + } + case aaaarecord: + record.RecordType = armprivatedns.RecordTypeAAAA + record.RecordSet.Properties.AaaaRecords = []*armprivatedns.AaaaRecord{ + { + IPv6Address: &ipAddress, + }, + } + } + return record +} + +func createRecordSet(lbType string, azureTags map[string]*string, ttl int64, rType recordListType, ipAddress string, recordName string) (record recordList) { + record = recordList{ + Name: lbType, + RecordSet: armdns.RecordSet{ + Properties: &armdns.RecordSetProperties{ + TTL: &ttl, + Metadata: azureTags, + }, + }, + } + + switch rType { + case cname: + record.RecordType = armdns.RecordTypeCNAME + record.RecordSet.Properties.CnameRecord = &armdns.CnameRecord{ + Cname: &recordName, + } + case arecord: + record.RecordType = armdns.RecordTypeA + record.RecordSet.Properties.ARecords = []*armdns.ARecord{ + { + IPv4Address: &ipAddress, + }, + } + case aaaarecord: + record.RecordType = armdns.RecordTypeAAAA + record.RecordSet.Properties.AaaaRecords = []*armdns.AaaaRecord{ + { + IPv6Address: &ipAddress, + }, + } + } + return record +}