Skip to content
Closed
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 @@ -21,6 +21,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"net/netip"
"path/filepath"
Expand Down Expand Up @@ -690,7 +691,7 @@ func (c *ControlPlaneController) detectJoinHost(ctx context.Context, scope *Cont
if found && k0sAPIPort > 0 {
port = strconv.Itoa(int(k0sAPIPort))
}
host := fmt.Sprintf("https://%s:%s", scope.Cluster.Spec.ControlPlaneEndpoint.Host, port)
host := joinHostPortURL(scope.Cluster.Spec.ControlPlaneEndpoint.Host, port)

_, err = httpClient.Get(fmt.Sprintf("%s/v1beta1/ca", host))
if err == nil {
Expand All @@ -702,7 +703,13 @@ func (c *ControlPlaneController) detectJoinHost(ctx context.Context, scope *Cont
return "", fmt.Errorf("failed to get first controller IP: %w", err)
}

return fmt.Sprintf("https://%s:%s", firstControllerIP, port), nil
return joinHostPortURL(firstControllerIP, port), nil
}

// joinHostPortURL builds an https URL for the given host and port. It uses
// net.JoinHostPort so that IPv6 literals are bracketed correctly.
func joinHostPortURL(host, port string) string {
return "https://" + net.JoinHostPort(host, port)
}

func (c *ControlPlaneController) findFirstControllerIP(ctx context.Context, firstControllerMachine *clusterv1.Machine) (string, error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,22 @@ func TestController_genK0sCommands(t *testing.T) {
})
}
}

func Test_joinHostPortURL(t *testing.T) {
tests := []struct {
name string
host string
port string
want string
}{
{"hostname", "cp.example.com", "9443", "https://cp.example.com:9443"},
{"ipv4", "10.0.0.1", "9443", "https://10.0.0.1:9443"},
{"ipv6", "2001:db8::1", "9443", "https://[2001:db8::1]:9443"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, joinHostPortURL(tt.host, tt.port))
})
}
}
12 changes: 10 additions & 2 deletions internal/controller/bootstrap/worker_bootstrap_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import (
"errors"
"fmt"
"maps"
"net"
"path/filepath"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -529,9 +531,9 @@ func (r *Controller) getK0sToken(ctx context.Context, scope *Scope) (string, err
}

var joinToken string
joinURL := fmt.Sprintf("https://%s:%d", scope.Cluster.Spec.ControlPlaneEndpoint.Host, scope.Cluster.Spec.ControlPlaneEndpoint.Port)
joinURL := controlPlaneJoinURL(scope.Cluster.Spec.ControlPlaneEndpoint.Host, int64(scope.Cluster.Spec.ControlPlaneEndpoint.Port))
if scope.ingressSpec != nil {
joinURL = fmt.Sprintf("https://%s:%d", scope.ingressSpec.APIHost, scope.ingressSpec.Port)
joinURL = controlPlaneJoinURL(scope.ingressSpec.APIHost, scope.ingressSpec.Port)
}

joinToken, err := kutil.CreateK0sJoinToken(ca.KeyPair.Cert, token, joinURL, "kubelet-bootstrap")
Expand All @@ -541,6 +543,12 @@ func (r *Controller) getK0sToken(ctx context.Context, scope *Scope) (string, err
return joinToken, nil
}

// controlPlaneJoinURL builds the k0s join URL for the given host and port.
// It uses net.JoinHostPort so that IPv6 literals are bracketed correctly.
func controlPlaneJoinURL(host string, port int64) string {
return "https://" + net.JoinHostPort(host, strconv.FormatInt(port, 10))
}

func createIngressCommands(scope *Scope) []string {
if scope.ingressSpec == nil {
return []string{}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,22 @@ func Test_getWindowsCommands(t *testing.T) {
}

}

func Test_controlPlaneJoinURL(t *testing.T) {
tests := []struct {
name string
host string
port int64
want string
}{
{"hostname", "cp.example.com", 443, "https://cp.example.com:443"},
{"ipv4", "10.0.0.1", 6443, "https://10.0.0.1:6443"},
{"ipv6", "2001:db8::1", 443, "https://[2001:db8::1]:443"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, controlPlaneJoinURL(tt.host, tt.port))
})
}
}
Loading