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
Original file line number Diff line number Diff line change
Expand Up @@ -860,8 +860,8 @@ func (r *HostedControlPlaneReconciler) reconcileAPIServerService(ctx context.Con
}

if serviceStrategy.Type == hyperv1.Route {
externalRoute := manifests.KubeAPIServerExternalRoute(hcp.Namespace)
if util.IsPublicHCP(hcp) {
externalRoute := manifests.KubeAPIServerExternalRoute(hcp.Namespace)
if _, err := createOrUpdate(ctx, r.Client, externalRoute, func() error {
kas.ReconcileRoute(externalRoute, serviceStrategy.Route.Hostname)
if externalRoute.Annotations == nil {
Expand All @@ -872,6 +872,18 @@ func (r *HostedControlPlaneReconciler) reconcileAPIServerService(ctx context.Con
}); err != nil {
return fmt.Errorf("failed to reconcile apiserver external route: %w", err)
}
} else {
// Remove the external route if it exists
err := r.Get(ctx, client.ObjectKeyFromObject(externalRoute), externalRoute)
if err != nil {
if !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to check whether kube-apiserver external route exists: %w", err)
}
} else {
if err := r.Delete(ctx, externalRoute); err != nil {
return fmt.Errorf("failed to delete kube-apiserver external route: %w", err)
}
}
}

// We do not need to enumerate all possible addresses, because we use the KAS as default backend through a custom
Expand Down Expand Up @@ -2569,6 +2581,7 @@ func (r *HostedControlPlaneReconciler) reconcileRouter(ctx context.Context, hcp
}

var canonicalHostname string
pubSvc := manifests.RouterPublicService(hcp.Namespace)
if util.IsPrivateHCP(hcp) {
svc := manifests.PrivateRouterService(hcp.Namespace)
if _, err := createOrUpdate(ctx, r.Client, svc, func() error {
Expand All @@ -2579,10 +2592,22 @@ func (r *HostedControlPlaneReconciler) reconcileRouter(ctx context.Context, hcp
if (!util.IsPublicHCP(hcp) || !exposeAPIThroughRouter) && len(svc.Status.LoadBalancer.Ingress) > 0 {
canonicalHostname = svc.Status.LoadBalancer.Ingress[0].Hostname
}
if !util.IsPublicHCP(hcp) {
// Remove the public router Service if it exists
err := r.Get(ctx, client.ObjectKeyFromObject(pubSvc), pubSvc)
if err != nil {
if !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to check whether public router service exists: %w", err)
}
} else {
if err := r.Delete(ctx, pubSvc); err != nil {
return fmt.Errorf("failed to delete public router service: %w", err)
}
}
}
}

if util.IsPublicHCP(hcp) && exposeAPIThroughRouter {
pubSvc := manifests.RouterPublicService(hcp.Namespace)
if _, err := createOrUpdate(ctx, r.Client, pubSvc, func() error {
return ingress.ReconcileRouterService(pubSvc, config.OwnerRefFrom(hcp), util.APIPortWithDefault(hcp, config.DefaultAPIServerPort), false)
}); err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,22 @@ func validateStructDeepEqual(x reflect.Value, y reflect.Value, path *field.Path,
return errs
}

func validateEndpointAccess(new *hyperv1.PlatformSpec, old *hyperv1.PlatformSpec) error {
if old.Type != hyperv1.AWSPlatform || new.Type != hyperv1.AWSPlatform || old.AWS == nil || new.AWS == nil {
return nil
}
if old.AWS.EndpointAccess == new.AWS.EndpointAccess {
return nil
}
if old.AWS.EndpointAccess == hyperv1.Public || new.AWS.EndpointAccess == hyperv1.Public {
return fmt.Errorf("transitioning from EndpointAccess %s to %s is not allowed", old.AWS.EndpointAccess, new.AWS.EndpointAccess)
}
// Clear EndpointAccess for further validation
old.AWS.EndpointAccess = ""
new.AWS.EndpointAccess = ""
return nil
}

// validateStructEqual uses introspection to walk through the fields of a struct and check
// for differences. Any differences are flagged as an invalid change to an immutable field.
func validateStructEqual(x any, y any, path *field.Path) field.ErrorList {
Expand Down Expand Up @@ -269,6 +285,10 @@ func validateHostedClusterUpdate(new *hyperv1.HostedCluster, old *hyperv1.Hosted
old.Spec.Networking.APIServer.Port = new.Spec.Networking.APIServer.Port
}

if err := validateEndpointAccess(&new.Spec.Platform, &old.Spec.Platform); err != nil {
return err
}

errs := validateStructEqual(new.Spec, old.Spec, field.NewPath("HostedCluster.spec"))

return errs.ToAggregate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,3 +452,133 @@ func TestValidateHostedClusterCreate(t *testing.T) {
})
}
}

func Test_validateEndpointAccess(t *testing.T) {
type args struct {
new *hyperv1.PlatformSpec
old *hyperv1.PlatformSpec
}
tests := []struct {
name string
args args
wantErr bool
}{

{
name: "non-AWS should pass",
args: args{
new: &hyperv1.PlatformSpec{
Type: hyperv1.AgentPlatform,
},
old: &hyperv1.PlatformSpec{
Type: hyperv1.AgentPlatform,
},
},
},
{
name: "nil AWS platform passes",
args: args{
new: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
old: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
},
},
{
name: "no EndpointAccess changes passes",
args: args{
new: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
AWS: &hyperv1.AWSPlatformSpec{
EndpointAccess: hyperv1.Public,
},
},
old: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
AWS: &hyperv1.AWSPlatformSpec{
EndpointAccess: hyperv1.Public,
},
},
},
},
{
name: "Private to PublicAndPrivate passes",
args: args{
new: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
AWS: &hyperv1.AWSPlatformSpec{
EndpointAccess: hyperv1.PublicAndPrivate,
},
},
old: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
AWS: &hyperv1.AWSPlatformSpec{
EndpointAccess: hyperv1.Private,
},
},
},
},
{
name: "PublicAndPrivate to Private passes",
args: args{
new: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
AWS: &hyperv1.AWSPlatformSpec{
EndpointAccess: hyperv1.Private,
},
},
old: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
AWS: &hyperv1.AWSPlatformSpec{
EndpointAccess: hyperv1.PublicAndPrivate,
},
},
},
},
{
name: "Public to Private fails",
args: args{
new: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
AWS: &hyperv1.AWSPlatformSpec{
EndpointAccess: hyperv1.Private,
},
},
old: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
AWS: &hyperv1.AWSPlatformSpec{
EndpointAccess: hyperv1.Public,
},
},
},
wantErr: true,
},
{
name: "Public to PublicAndPrivate fails",
args: args{
new: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
AWS: &hyperv1.AWSPlatformSpec{
EndpointAccess: hyperv1.PublicAndPrivate,
},
},
old: &hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
AWS: &hyperv1.AWSPlatformSpec{
EndpointAccess: hyperv1.Public,
},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validateEndpointAccess(tt.args.new, tt.args.old); (err != nil) != tt.wantErr {
t.Errorf("validateEndpointAccess() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}