Merge pull request #623 from bigkevmcd/resource-quantity

Use resource.Quantity instead of a string for storageRequestSize in the Cluster definition.
This commit is contained in:
Kevin McDermott
2026-01-22 13:13:06 +00:00
committed by GitHub
11 changed files with 68 additions and 35 deletions

View File

@@ -4,7 +4,7 @@ kind: CustomResourceDefinition
metadata:
annotations:
helm.sh/resource-policy: keep
controller-gen.kubebuilder.io/version: v0.18.0
controller-gen.kubebuilder.io/version: v0.20.0
name: clusters.k3k.io
spec:
group: k3k.io
@@ -434,11 +434,18 @@ spec:
This field is only relevant in "dynamic" mode.
type: string
storageRequestSize:
anyOf:
- type: integer
- type: string
default: 2G
description: |-
StorageRequestSize is the requested size for the PVC.
This field is only relevant in "dynamic" mode.
type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
x-kubernetes-validations:
- message: storageRequestSize is immutable
rule: self == oldSelf
type:
default: dynamic
description: Type specifies the persistence mode.

View File

@@ -4,7 +4,7 @@ kind: CustomResourceDefinition
metadata:
annotations:
helm.sh/resource-policy: keep
controller-gen.kubebuilder.io/version: v0.18.0
controller-gen.kubebuilder.io/version: v0.20.0
name: virtualclusterpolicies.k3k.io
spec:
group: k3k.io

View File

@@ -13,12 +13,13 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/util/retry"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
@@ -117,7 +118,10 @@ func createAction(appCtx *AppContext, config *CreateConfig) func(cmd *cobra.Comm
logrus.Infof("Creating cluster '%s' in namespace '%s'", name, namespace)
cluster := newCluster(name, namespace, config)
cluster, err := newCluster(name, namespace, config)
if err != nil {
return err
}
cluster.Spec.Expose = &v1beta1.ExposeConfig{
NodePort: &v1beta1.NodePortConfig{},
@@ -185,7 +189,17 @@ func createAction(appCtx *AppContext, config *CreateConfig) func(cmd *cobra.Comm
}
}
func newCluster(name, namespace string, config *CreateConfig) *v1beta1.Cluster {
func newCluster(name, namespace string, config *CreateConfig) (*v1beta1.Cluster, error) {
var storageRequestSize *resource.Quantity
if config.storageRequestSize != "" {
parsed, err := resource.ParseQuantity(config.storageRequestSize)
if err != nil {
return nil, err
}
storageRequestSize = ptr.To(parsed)
}
cluster := &v1beta1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@@ -211,7 +225,7 @@ func newCluster(name, namespace string, config *CreateConfig) *v1beta1.Cluster {
Persistence: v1beta1.PersistenceConfig{
Type: v1beta1.PersistenceMode(config.persistenceType),
StorageClassName: ptr.To(config.storageClassName),
StorageRequestSize: config.storageRequestSize,
StorageRequestSize: storageRequestSize,
},
MirrorHostNodes: config.mirrorHostNodes,
},
@@ -221,7 +235,7 @@ func newCluster(name, namespace string, config *CreateConfig) *v1beta1.Cluster {
}
if config.token != "" {
cluster.Spec.TokenSecretRef = &v1.SecretReference{
cluster.Spec.TokenSecretRef = &corev1.SecretReference{
Name: k3kcluster.TokenSecretName(name),
Namespace: namespace,
}
@@ -253,11 +267,11 @@ func newCluster(name, namespace string, config *CreateConfig) *v1beta1.Cluster {
}
}
return cluster
return cluster, nil
}
func env(envSlice []string) []v1.EnvVar {
var envVars []v1.EnvVar
func env(envSlice []string) []corev1.EnvVar {
var envVars []corev1.EnvVar
for _, env := range envSlice {
keyValue := strings.Split(env, "=")
@@ -265,7 +279,7 @@ func env(envSlice []string) []v1.EnvVar {
logrus.Fatalf("incorrect value for environment variable %s", env)
}
envVars = append(envVars, v1.EnvVar{
envVars = append(envVars, corev1.EnvVar{
Name: keyValue[0],
Value: keyValue[1],
})
@@ -352,8 +366,8 @@ func CreateCustomCertsSecrets(ctx context.Context, name, namespace, customCertsP
return nil
}
func caCertSecret(certName, clusterName, clusterNamespace string, cert, key []byte) *v1.Secret {
return &v1.Secret{
func caCertSecret(certName, clusterName, clusterNamespace string, cert, key []byte) *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
@@ -362,10 +376,10 @@ func caCertSecret(certName, clusterName, clusterNamespace string, cert, key []by
Name: controller.SafeConcatNameWithPrefix(clusterName, certName),
Namespace: clusterNamespace,
},
Type: v1.SecretTypeTLS,
Type: corev1.SecretTypeTLS,
Data: map[string][]byte{
v1.TLSCertKey: cert,
v1.TLSPrivateKeyKey: key,
corev1.TLSCertKey: cert,
corev1.TLSPrivateKeyKey: key,
},
}
}
@@ -433,7 +447,10 @@ func getClusterDetails(cluster *v1beta1.Cluster) (string, error) {
data.Persistence.Type = cluster.Spec.Persistence.Type
data.Persistence.StorageClassName = ptr.Deref(cluster.Spec.Persistence.StorageClassName, "")
data.Persistence.StorageRequestSize = cluster.Spec.Persistence.StorageRequestSize
if srs := cluster.Spec.Persistence.StorageRequestSize; srs != nil {
data.Persistence.StorageRequestSize = srs.String()
}
tmpl, err := template.New("clusterDetails").Parse(clusterDetailsTemplate)
if err != nil {

View File

@@ -4,6 +4,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/utils/ptr"
"github.com/rancher/k3k/pkg/apis/k3k.io/v1beta1"
@@ -66,7 +67,7 @@ func Test_printClusterDetails(t *testing.T) {
Persistence: v1beta1.PersistenceConfig{
Type: v1beta1.DynamicPersistenceMode,
StorageClassName: ptr.To("local-path"),
StorageRequestSize: "3gb",
StorageRequestSize: ptr.To(resource.MustParse("3G")),
},
},
Status: v1beta1.ClusterStatus{
@@ -81,7 +82,7 @@ func Test_printClusterDetails(t *testing.T) {
Persistence:
Type: dynamic
StorageClass: local-path
Size: 3gb`,
Size: 3G`,
},
}

View File

@@ -414,7 +414,7 @@ _Appears In:_
| *`type`* __xref:{anchor_prefix}-github-com-rancher-k3k-pkg-apis-k3k-io-v1beta1-persistencemode[$$PersistenceMode$$]__ | Type specifies the persistence mode. + | dynamic |
| *`storageClassName`* __string__ | StorageClassName is the name of the StorageClass to use for the PVC. +
This field is only relevant in "dynamic" mode. + | |
| *`storageRequestSize`* __string__ | StorageRequestSize is the requested size for the PVC. +
| *`storageRequestSize`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#quantity-resource-api[$$Quantity$$]__ | StorageRequestSize is the requested size for the PVC. +
This field is only relevant in "dynamic" mode. + | 2G |
|===

View File

@@ -313,7 +313,7 @@ _Appears in:_
| --- | --- | --- | --- |
| `type` _[PersistenceMode](#persistencemode)_ | Type specifies the persistence mode. | dynamic | |
| `storageClassName` _string_ | StorageClassName is the name of the StorageClass to use for the PVC.<br />This field is only relevant in "dynamic" mode. | | |
| `storageRequestSize` _string_ | StorageRequestSize is the requested size for the PVC.<br />This field is only relevant in "dynamic" mode. | 2G | |
| `storageRequestSize` _[Quantity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#quantity-resource-api)_ | StorageRequestSize is the requested size for the PVC.<br />This field is only relevant in "dynamic" mode. | 2G | |
#### PersistenceMode

View File

@@ -1,7 +1,9 @@
package v1beta1
import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -123,7 +125,7 @@ type ClusterSpec struct {
// The Secret must have a "token" field in its data.
//
// +optional
TokenSecretRef *v1.SecretReference `json:"tokenSecretRef,omitempty"`
TokenSecretRef *corev1.SecretReference `json:"tokenSecretRef,omitempty"`
// TLSSANs specifies subject alternative names for the K3s server certificate.
//
@@ -145,12 +147,12 @@ type ClusterSpec struct {
// ServerEnvs specifies list of environment variables to set in the server pod.
//
// +optional
ServerEnvs []v1.EnvVar `json:"serverEnvs,omitempty"`
ServerEnvs []corev1.EnvVar `json:"serverEnvs,omitempty"`
// AgentEnvs specifies list of environment variables to set in the agent pod.
//
// +optional
AgentEnvs []v1.EnvVar `json:"agentEnvs,omitempty"`
AgentEnvs []corev1.EnvVar `json:"agentEnvs,omitempty"`
// Addons specifies secrets containing raw YAML to deploy on cluster startup.
//
@@ -160,12 +162,12 @@ type ClusterSpec struct {
// ServerLimit specifies resource limits for server nodes.
//
// +optional
ServerLimit v1.ResourceList `json:"serverLimit,omitempty"`
ServerLimit corev1.ResourceList `json:"serverLimit,omitempty"`
// WorkerLimit specifies resource limits for agent nodes.
//
// +optional
WorkerLimit v1.ResourceList `json:"workerLimit,omitempty"`
WorkerLimit corev1.ResourceList `json:"workerLimit,omitempty"`
// MirrorHostNodes controls whether node objects from the host cluster
// are mirrored into the virtual cluster.
@@ -362,8 +364,9 @@ type PersistenceConfig struct {
// This field is only relevant in "dynamic" mode.
//
// +kubebuilder:default="2G"
// +kubebuilder:validation:XValidation:message="storageRequestSize is immutable",rule="self == oldSelf"
// +optional
StorageRequestSize string `json:"storageRequestSize,omitempty"`
StorageRequestSize *resource.Quantity `json:"storageRequestSize,omitempty"`
}
// ExposeConfig specifies options for exposing the API server.
@@ -582,13 +585,13 @@ type VirtualClusterPolicySpec struct {
// Quota specifies the resource limits for clusters within a clusterpolicy.
//
// +optional
Quota *v1.ResourceQuotaSpec `json:"quota,omitempty"`
Quota *corev1.ResourceQuotaSpec `json:"quota,omitempty"`
// Limit specifies the LimitRange that will be applied to all pods within the VirtualClusterPolicy
// to set defaults and constraints (min/max)
//
// +optional
Limit *v1.LimitRangeSpec `json:"limit,omitempty"`
Limit *corev1.LimitRangeSpec `json:"limit,omitempty"`
// DefaultNodeSelector specifies the node selector that applies to all clusters (server + agent) in the target Namespace.
//

View File

@@ -418,6 +418,11 @@ func (in *PersistenceConfig) DeepCopyInto(out *PersistenceConfig) {
*out = new(string)
**out = **in
}
if in.StorageRequestSize != nil {
in, out := &in.StorageRequestSize, &out.StorageRequestSize
x := (*in).DeepCopy()
*out = &x
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistenceConfig.

View File

@@ -4,6 +4,7 @@ import (
"context"
"time"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -66,7 +67,7 @@ var _ = Describe("Cluster Controller", Label("controller"), Label("Cluster"), fu
Expect(cluster.Spec.Sync.PriorityClasses.Enabled).To(BeFalse())
Expect(cluster.Spec.Persistence.Type).To(Equal(v1beta1.DynamicPersistenceMode))
Expect(cluster.Spec.Persistence.StorageRequestSize).To(Equal("2G"))
Expect(cluster.Spec.Persistence.StorageRequestSize.Equal(resource.MustParse("2G"))).To(BeTrue())
Expect(cluster.Status.Phase).To(Equal(v1beta1.ClusterUnknown))

View File

@@ -8,7 +8,6 @@ import (
"strings"
"text/template"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/ptr"
@@ -406,7 +405,7 @@ func (s *Server) setupDynamicPersistence() v1.PersistentVolumeClaim {
StorageClassName: s.cluster.Spec.Persistence.StorageClassName,
Resources: v1.VolumeResourceRequirements{
Requests: v1.ResourceList{
"storage": resource.MustParse(s.cluster.Spec.Persistence.StorageRequestSize),
"storage": *s.cluster.Spec.Persistence.StorageRequestSize,
},
},
},

View File

@@ -3,7 +3,7 @@
set -eou pipefail
CONTROLLER_TOOLS_VERSION=v0.18.0
CONTROLLER_TOOLS_VERSION=v0.20.0
# This will return non-zero until all of our objects in ./pkg/apis can generate valid crds.
# allowDangerousTypes is needed for struct that use floats