Skip to content

Commit aa83b6f

Browse files
authored
Merge pull request #4335 from twz123/apispec-defaults
Proper defaulting and validation for the API spec
2 parents bd12eff + 2b2ab9c commit aa83b6f

File tree

5 files changed

+85
-35
lines changed

5 files changed

+85
-35
lines changed

inttest/common/bootloosesuite.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -817,15 +817,15 @@ func (s *BootlooseSuite) GetKubeConfig(node string, k0sKubeconfigArgs ...string)
817817

818818
hostURL, err := url.Parse(cfg.Host)
819819
if err != nil {
820-
return nil, fmt.Errorf("can't parse port value `%s`: %w", cfg.Host, err)
820+
return nil, fmt.Errorf("can't parse Kubernetes API server host %q: %w", cfg.Host, err)
821821
}
822-
port, err := strconv.ParseInt(hostURL.Port(), 10, 32)
822+
port, err := strconv.ParseUint(hostURL.Port(), 10, 16)
823823
if err != nil {
824-
return nil, fmt.Errorf("can't parse port value `%s`: %w", hostURL.Port(), err)
824+
return nil, fmt.Errorf("can't parse Kubernetes API server port %q: %w", hostURL.Port(), err)
825825
}
826826
hostPort, err := machine.HostPort(int(port))
827827
if err != nil {
828-
return nil, fmt.Errorf("bootloose machine has to have %d port mapped: %w", port, err)
828+
return nil, fmt.Errorf("can't find host port for Kubernetes API server port %d: %w", port, err)
829829
}
830830
cfg.Host = fmt.Sprintf("localhost:%d", hostPort)
831831
return cfg, nil

pkg/apis/k0s/v1beta1/api.go

+61-19
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"encoding/json"
2021
"fmt"
2122
"net"
2223

2324
"github.com/k0sproject/k0s/internal/pkg/iface"
2425
"github.com/k0sproject/k0s/internal/pkg/stringslice"
2526

27+
"k8s.io/apimachinery/pkg/util/validation"
2628
"k8s.io/apimachinery/pkg/util/validation/field"
2729

2830
"github.com/asaskevich/govalidator"
@@ -32,37 +34,43 @@ var _ Validateable = (*APISpec)(nil)
3234

3335
// APISpec defines the settings for the K0s API
3436
type APISpec struct {
35-
// Local address on which to bind an API
36-
Address string `json:"address"`
37+
// Address on which to connect to the API server.
38+
// +optional
39+
Address string `json:"address,omitempty"`
3740

3841
// The loadbalancer address (for k0s controllers running behind a loadbalancer)
42+
// +optional
3943
ExternalAddress string `json:"externalAddress,omitempty"`
44+
4045
// Map of key-values (strings) for any extra arguments to pass down to Kubernetes api-server process
46+
// +optional
4147
ExtraArgs map[string]string `json:"extraArgs,omitempty"`
48+
4249
// Custom port for k0s-api server to listen on (default: 9443)
50+
// +kubebuilder:validation:Minimum=1
51+
// +kubebuilder:validation:Maximum=65535
52+
// +kubebuilder:default=9443
53+
// +optional
4354
K0sAPIPort int `json:"k0sApiPort,omitempty"`
4455

4556
// Custom port for kube-api server to listen on (default: 6443)
46-
Port int `json:"port"`
57+
// +kubebuilder:validation:Minimum=1
58+
// +kubebuilder:validation:Maximum=65535
59+
// +kubebuilder:default=6443
60+
// +optional
61+
Port int `json:"port,omitempty"`
4762

4863
// List of additional addresses to push to API servers serving the certificate
49-
SANs []string `json:"sans"`
64+
// +optional
65+
SANs []string `json:"sans,omitempty"`
5066
}
5167

52-
const defaultKasPort = 6443
53-
5468
// DefaultAPISpec default settings for api
5569
func DefaultAPISpec() *APISpec {
56-
// Collect all nodes addresses for sans
57-
addresses, _ := iface.AllAddresses()
58-
publicAddress, _ := iface.FirstPublicAddress()
59-
return &APISpec{
60-
Port: defaultKasPort,
61-
K0sAPIPort: 9443,
62-
SANs: addresses,
63-
Address: publicAddress,
64-
ExtraArgs: make(map[string]string),
65-
}
70+
a := new(APISpec)
71+
a.setDefaults()
72+
a.SANs, _ = iface.AllAddresses()
73+
return a
6674
}
6775

6876
// APIAddress ...
@@ -131,13 +139,47 @@ func (a *APISpec) Validate() []error {
131139
errors = append(errors, field.Invalid(path, san, "invalid IP address / DNS name"))
132140
}
133141

142+
if a.ExternalAddress != "" {
143+
validateIPAddressOrDNSName(field.NewPath("externalAddress"), a.ExternalAddress)
144+
}
145+
146+
for _, msg := range validation.IsValidPortNum(a.K0sAPIPort) {
147+
errors = append(errors, field.Invalid(field.NewPath("k0sApiPort"), a.K0sAPIPort, msg))
148+
}
149+
150+
for _, msg := range validation.IsValidPortNum(a.Port) {
151+
errors = append(errors, field.Invalid(field.NewPath("port"), a.Port, msg))
152+
}
153+
134154
sansPath := field.NewPath("sans")
135155
for idx, san := range a.SANs {
136156
validateIPAddressOrDNSName(sansPath.Index(idx), san)
137157
}
138158

139-
if a.ExternalAddress != "" {
140-
validateIPAddressOrDNSName(field.NewPath("externalAddress"), a.ExternalAddress)
141-
}
142159
return errors
143160
}
161+
162+
// Sets in some sane defaults when unmarshaling the data from JSON.
163+
func (a *APISpec) UnmarshalJSON(data []byte) error {
164+
type apiSpec APISpec
165+
jc := (*apiSpec)(a)
166+
167+
if err := json.Unmarshal(data, jc); err != nil {
168+
return err
169+
}
170+
171+
a.setDefaults()
172+
return nil
173+
}
174+
175+
func (a *APISpec) setDefaults() {
176+
if a.Address == "" {
177+
a.Address, _ = iface.FirstPublicAddress()
178+
}
179+
if a.K0sAPIPort == 0 {
180+
a.K0sAPIPort = 9443
181+
}
182+
if a.Port == 0 {
183+
a.Port = 6443
184+
}
185+
}

pkg/apis/k0s/v1beta1/api_test.go

+9-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1beta1
1818

1919
import (
20+
"errors"
2021
"testing"
2122

2223
"github.com/stretchr/testify/suite"
@@ -30,22 +31,23 @@ func (s *APISuite) TestValidation() {
3031
s.T().Run("defaults_are_valid", func(t *testing.T) {
3132
a := DefaultAPISpec()
3233

33-
s.Nil(a.Validate())
34+
s.NoError(errors.Join(a.Validate()...))
3435
})
3536

3637
s.T().Run("accepts_ipv6_as_address", func(t *testing.T) {
37-
a := APISpec{
38-
Address: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
39-
}
40-
41-
s.Nil(a.Validate())
38+
ipV6Addr := "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
39+
a := APISpec{Address: ipV6Addr}
40+
a.setDefaults()
4241

42+
s.Equal(ipV6Addr, a.Address)
43+
s.NoError(errors.Join(a.Validate()...))
4344
})
4445

4546
s.T().Run("invalid_api_address", func(t *testing.T) {
4647
a := APISpec{
4748
Address: "something.that.is.not.valid//(())",
4849
}
50+
a.setDefaults()
4951

5052
errors := a.Validate()
5153
s.NotNil(errors)
@@ -56,11 +58,11 @@ func (s *APISuite) TestValidation() {
5658

5759
s.T().Run("invalid_sans_address", func(t *testing.T) {
5860
a := APISpec{
59-
Address: "1.2.3.4",
6061
SANs: []string{
6162
"something.that.is.not.valid//(())",
6263
},
6364
}
65+
a.setDefaults()
6466

6567
errors := a.Validate()
6668
s.NotNil(errors)

pkg/apis/k0s/v1beta1/clusterconfig_types.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ const (
3939

4040
// ClusterSpec defines the desired state of ClusterConfig
4141
type ClusterSpec struct {
42-
API *APISpec `json:"api"`
42+
API *APISpec `json:"api,omitempty"`
4343
ControllerManager *ControllerManagerSpec `json:"controllerManager,omitempty"`
4444
Scheduler *SchedulerSpec `json:"scheduler,omitempty"`
45-
Storage *StorageSpec `json:"storage"`
46-
Network *Network `json:"network"`
45+
Storage *StorageSpec `json:"storage,omitempty"`
46+
Network *Network `json:"network,omitempty"`
4747
WorkerProfiles WorkerProfiles `json:"workerProfiles,omitempty"`
48-
Telemetry *ClusterTelemetry `json:"telemetry"`
48+
Telemetry *ClusterTelemetry `json:"telemetry,omitempty"`
4949
Install *InstallSpec `json:"installConfig,omitempty"`
5050
Images *ClusterImages `json:"images,omitempty"`
5151
Extensions *ClusterExtensions `json:"extensions,omitempty"`

static/manifests/v1beta1/CustomResourceDefinition/k0s.k0sproject.io_clusterconfigs.yaml

+7-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ spec:
4343
description: APISpec defines the settings for the K0s API
4444
properties:
4545
address:
46-
description: Local address on which to bind an API
46+
description: Address on which to connect to the API server.
4747
type: string
4848
externalAddress:
4949
description: The loadbalancer address (for k0s controllers running
@@ -56,12 +56,18 @@ spec:
5656
to pass down to Kubernetes api-server process
5757
type: object
5858
k0sApiPort:
59+
default: 9443
5960
description: 'Custom port for k0s-api server to listen on (default:
6061
9443)'
62+
maximum: 65535
63+
minimum: 1
6164
type: integer
6265
port:
66+
default: 6443
6367
description: 'Custom port for kube-api server to listen on (default:
6468
6443)'
69+
maximum: 65535
70+
minimum: 1
6571
type: integer
6672
sans:
6773
description: List of additional addresses to push to API servers

0 commit comments

Comments
 (0)