Skip to content
Merged
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
22 changes: 22 additions & 0 deletions data/data/install.openshift.io_installconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5805,6 +5805,28 @@ spec:
type: string
type: array
type: object
dns:
description: |-
DNS contains the dns zone information for the cluster. The DNS information can
only be supplied during Shared VPC (XPN) installs.
properties:
privateZone:
description: |-
PrivateZone contains the information for a private DNS zone. The Private DNS Zone can
only be supplied during Shared VPC (XPN) installs. The PrivateZone can exist or be
created in a second service project; a project other than the one matching projectID
or networkProjectID.
properties:
name:
description: Name is the name of the dns-managed zone.
type: string
projectID:
description: ProjectID is the project where the zone resides.
type: string
required:
- name
type: object
type: object
network:
description: |-
Network specifies an existing VPC where the cluster should be created
Expand Down
17 changes: 12 additions & 5 deletions pkg/asset/cluster/gcp/gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,22 @@ import (
func Metadata(config *types.InstallConfig) *gcp.Metadata {
// leave the private zone domain blank when not using a pre-created private zone
privateZoneDomain := fmt.Sprintf("%s.", config.ClusterDomain())
privateZoneProject := config.GCP.ProjectID
if config.GCP.Network == "" || config.GCP.NetworkProjectID == "" {
privateZoneDomain = ""
}
if config.GCP.DNS != nil && config.GCP.DNS.PrivateZone != nil {
if config.GCP.DNS.PrivateZone.ProjectID != "" {
privateZoneProject = config.GCP.DNS.PrivateZone.ProjectID
}
}

return &gcp.Metadata{
Region: config.Platform.GCP.Region,
ProjectID: config.Platform.GCP.ProjectID,
NetworkProjectID: config.Platform.GCP.NetworkProjectID,
PrivateZoneDomain: privateZoneDomain,
ServiceEndpoints: config.Platform.GCP.ServiceEndpoints,
Region: config.Platform.GCP.Region,
ProjectID: config.Platform.GCP.ProjectID,
NetworkProjectID: config.Platform.GCP.NetworkProjectID,
PrivateZoneDomain: privateZoneDomain,
PrivateZoneProjectID: privateZoneProject,
ServiceEndpoints: config.Platform.GCP.ServiceEndpoints,
}
}
2 changes: 1 addition & 1 deletion pkg/asset/cluster/tfvars/tfvars.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ func (t *TerraformVariables) Generate(ctx context.Context, parents asset.Parents
}

// Set the private zone
privateZoneName, err = manifests.GetGCPPrivateZoneName(ctx, client, installConfig, clusterID.InfraID)
privateZoneName, _, err = manifests.GetGCPPrivateZoneName(ctx, client, installConfig, clusterID.InfraID)
if err != nil {
return fmt.Errorf("failed to find gcp private dns zone: %w", err)
}
Expand Down
142 changes: 125 additions & 17 deletions pkg/asset/installconfig/gcp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type API interface {
GetMachineTypeWithZones(ctx context.Context, project, region, machineType string) (*compute.MachineType, sets.Set[string], error)
GetPublicDomains(ctx context.Context, project string) ([]string, error)
GetDNSZone(ctx context.Context, project, baseDomain string, isPublic bool) (*dns.ManagedZone, error)
GetDNSZoneFromParams(ctx context.Context, params gcptypes.DNSZoneParams) (*dns.ManagedZone, error)
GetDNSZoneByName(ctx context.Context, project, zoneName string) (*dns.ManagedZone, error)
GetSubnetworks(ctx context.Context, network, project, region string) ([]*compute.Subnetwork, error)
GetProjects(ctx context.Context) (map[string]string, error)
Expand All @@ -58,6 +59,7 @@ type API interface {
GetProjectTags(ctx context.Context, projectID string) (sets.Set[string], error)
GetNamespacedTagValue(ctx context.Context, tagNamespacedName string) (*cloudresourcemanager.TagValue, error)
GetKeyRing(ctx context.Context, kmsKeyRef *gcptypes.KMSKeyReference) (*kmspb.KeyRing, error)
UpdateDNSPrivateZoneLabels(ctx context.Context, baseDomain, project, zoneName string, labels map[string]string) error
}

// Client makes calls to the GCP API.
Expand Down Expand Up @@ -243,41 +245,138 @@ func (c *Client) GetPublicDomains(ctx context.Context, project string) ([]string
return publicZones, nil
}

func getDNSZoneByName(ctx context.Context, svc *dns.Service, project, zoneName string) (*dns.ManagedZone, error) {
returnedZone, err := svc.ManagedZones.Get(project, zoneName).Context(ctx).Do()
if err != nil {
return nil, fmt.Errorf("failed to get DNS Zones: %w", err)
}
return returnedZone, nil
}

// GetDNSZoneByName returns a DNS zone matching the `zoneName` if the DNS zone exists
// and can be seen (correct permissions for a private zone) in the project.
func (c *Client) GetDNSZoneByName(ctx context.Context, project, zoneName string) (*dns.ManagedZone, error) {
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()

svc, err := c.getDNSService(ctx)
if err != nil {
return nil, err
}
returnedZone, err := svc.ManagedZones.Get(project, zoneName).Context(ctx).Do()
return getDNSZoneByName(ctx, svc, project, zoneName)
}

// UpdateDNSPrivateZoneLabels will find a private DNS zone in the project with the name passed in. The labels
// for the zone will be updated to include the provided labels. The labels that match will be overwritten
// and all other labels will remain.
func (c *Client) UpdateDNSPrivateZoneLabels(ctx context.Context, baseDomain, project, zoneName string, labels map[string]string) error {
params := gcptypes.DNSZoneParams{
Project: project,
Name: zoneName,
BaseDomain: baseDomain,
IsPublic: false,
}
zone, err := c.GetDNSZoneFromParams(ctx, params)
if err != nil {
return nil, errors.Wrap(err, "failed to get DNS Zones")
return err
}
if zone == nil {
return fmt.Errorf("failed to find matching DNS zone for %s in project %s", zoneName, project)
}
return returnedZone, nil
}

// GetDNSZone returns a DNS zone for a basedomain.
func (c *Client) GetDNSZone(ctx context.Context, project, baseDomain string, isPublic bool) (*dns.ManagedZone, error) {
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
if zone.Labels == nil {
zone.Labels = make(map[string]string)
}

svc, err := c.getDNSService(ctx)
for key, value := range labels {
zone.Labels[key] = value
}

if zone.Description == "" {
// It is possible to create a managed zone without a description using the GCP web console.
// If the description is missing the managed zone modification will fail.
zone.Description = "Used by OpenShift Installer"
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: "Used by OpenShift" would have been sufficient.

}

dnsService, err := c.getDNSService(ctx)
if err != nil {
return nil, err
return fmt.Errorf("failed to get dns service during dns managed zone update: %w", err)
}

return UpdateDNSManagedZone(ctx, dnsService, project, zoneName, zone)
}

// UpdateDNSManagedZone will update a dns managed zone with the matching name and project. The new zone
// information is contained in the zone parameter.
func UpdateDNSManagedZone(ctx context.Context, svc *dns.Service, project, zoneName string, zone *dns.ManagedZone) error {
_, err := svc.ManagedZones.Update(project, zoneName, zone).Context(ctx).Do()
if err != nil {
return fmt.Errorf("failed updating DNS Zone %s in project %s: %w", zoneName, project, err)
}
if !strings.HasSuffix(baseDomain, ".") {
baseDomain = fmt.Sprintf("%s.", baseDomain)
return nil
}

func formatBaseDomain(domain string) string {
if !strings.HasSuffix(domain, ".") {
domain = fmt.Sprintf("%s.", domain)
}
return domain
}

// currently, only private and public are supported. All peering zones are private.
func getZoneVisibility(isPublic bool) string {
visibility := "private"
if isPublic {
visibility = "public"
}
return visibility
}

// GetDNSZoneFromParams allows the user to enter parameters found in `DNSZoneParams` to find a
// dns managed zone by name or by base domain.
func GetDNSZoneFromParams(ctx context.Context, svc *dns.Service, params gcptypes.DNSZoneParams) (*dns.ManagedZone, error) {
switch {
case params.Name == "" && params.BaseDomain != "":
return getDNSZone(ctx, svc, params.Project, params.BaseDomain, params.IsPublic)
case params.Name != "":
managedZone, err := getDNSZoneByName(ctx, svc, params.Project, params.Name)
if params.BaseDomain == "" {
return managedZone, err
}
if err != nil {
if IsNotFound(err) {
return nil, nil
}
return nil, err
}
if managedZone == nil {
return nil, nil
}
baseDomain := formatBaseDomain(params.BaseDomain)
if !strings.HasSuffix(managedZone.DnsName, baseDomain) {
return nil, fmt.Errorf("failed to find matching DNS zone for %s with DNS name %s", params.Name, params.BaseDomain)
}
visibility := getZoneVisibility(params.IsPublic)
if managedZone.Visibility != visibility {
return nil, fmt.Errorf("failed to find matching DNS zone for %s with visibility %s", params.Name, visibility)
}
return managedZone, nil
}
return nil, fmt.Errorf("invalid dns zone parameters, please provide a base domain or name")
}

// GetDNSZoneFromParams allows the user to enter parameters found in DNSZoneParams. The user must enter at
// least a base domain or a zone name to make a valid request. When both fields are populated extra validation
// steps occur to ensure that the correct zone is found.
func (c *Client) GetDNSZoneFromParams(ctx context.Context, params gcptypes.DNSZoneParams) (*dns.ManagedZone, error) {
svc, err := c.getDNSService(ctx)
if err != nil {
return nil, err
}
return GetDNSZoneFromParams(ctx, svc, params)
}

func getDNSZone(ctx context.Context, svc *dns.Service, project, baseDomain string, isPublic bool) (*dns.ManagedZone, error) {
baseDomain = formatBaseDomain(baseDomain)

// currently, only private and public are supported. All peering zones are private.
visibility := getZoneVisibility(isPublic)

req := svc.ManagedZones.List(project).DnsName(baseDomain).Context(ctx)
var res *dns.ManagedZone
Expand All @@ -290,7 +389,7 @@ func (c *Client) GetDNSZone(ctx context.Context, project, baseDomain string, isP
}
return nil
}); err != nil {
return nil, errors.Wrap(err, "failed to list DNS Zones")
return nil, fmt.Errorf("failed to list DNS Zones: %w", err)
}
if res == nil {
if isPublic {
Expand All @@ -305,6 +404,15 @@ func (c *Client) GetDNSZone(ctx context.Context, project, baseDomain string, isP
return res, nil
}

// GetDNSZone returns a DNS zone for a basedomain.
func (c *Client) GetDNSZone(ctx context.Context, project, baseDomain string, isPublic bool) (*dns.ManagedZone, error) {
svc, err := c.getDNSService(ctx)
if err != nil {
return nil, err
}
return getDNSZone(ctx, svc, project, baseDomain, isPublic)
}

// GetRecordSets returns all the records for a DNS zone.
func (c *Client) GetRecordSets(ctx context.Context, project, zone string) ([]*dns.ResourceRecordSet, error) {
ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
Expand Down
7 changes: 5 additions & 2 deletions pkg/asset/installconfig/gcp/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ func IsThrottled(err error) bool {

// IsNotFound checks whether a response from the GPC API was not found.
func IsNotFound(err error) bool {
gErr, ok := err.(*googleapi.Error)
return ok && gErr.Code == http.StatusNotFound
var gErr *googleapi.Error
if errors.As(err, &gErr) {
return gErr.Code == http.StatusNotFound
}
return false
}
29 changes: 29 additions & 0 deletions pkg/asset/installconfig/gcp/mock/gcpclient_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading