From d2535e50d528b3f2c8557242215cabed25b5e840 Mon Sep 17 00:00:00 2001
From: Shiming Zhang
Date: Mon, 8 Apr 2024 10:28:36 +0800
Subject: [PATCH] Add kwokctl component api
---
.../v1alpha1/kwokctl_component_types.go | 43 +++++++
.../v1alpha1/kwokctl_configuration_types.go | 4 +
.../config/v1alpha1/zz_generated.deepcopy.go | 31 +++++
pkg/apis/internalversion/conversion.go | 22 ++++
.../kwokctl_component_types.go | 34 +++++
.../kwokctl_configuration_types.go | 3 +
.../zz_generated.conversion.go | 37 ++++++
.../internalversion/zz_generated.deepcopy.go | 22 ++++
pkg/config/config.go | 6 +
pkg/kwokctl/cmd/create/cluster/cluster.go | 25 ++++
pkg/kwokctl/components/utils.go | 26 ++++
pkg/kwokctl/runtime/binary/cluster.go | 6 +-
pkg/kwokctl/runtime/binary/component.go | 103 +++++++++++++++
pkg/kwokctl/runtime/component.go | 47 +++++++
pkg/kwokctl/runtime/compose/component.go | 121 ++++++++++++++++++
pkg/kwokctl/runtime/config.go | 3 +
pkg/kwokctl/runtime/kind/component.go | 99 ++++++++++++++
pkg/utils/gotpl/funcs.go | 11 ++
pkg/utils/gotpl/renderer.go | 6 +-
pkg/utils/slices/slices.go | 15 +++
site/content/en/docs/generated/apis.md | 88 +++++++++++++
.../docs/generated/kwokctl_create_cluster.md | 2 +
22 files changed, 750 insertions(+), 4 deletions(-)
create mode 100644 pkg/apis/config/v1alpha1/kwokctl_component_types.go
create mode 100644 pkg/apis/internalversion/kwokctl_component_types.go
create mode 100644 pkg/kwokctl/runtime/binary/component.go
create mode 100644 pkg/kwokctl/runtime/component.go
create mode 100644 pkg/kwokctl/runtime/compose/component.go
create mode 100644 pkg/kwokctl/runtime/kind/component.go
diff --git a/pkg/apis/config/v1alpha1/kwokctl_component_types.go b/pkg/apis/config/v1alpha1/kwokctl_component_types.go
new file mode 100644
index 0000000000..1597547ad5
--- /dev/null
+++ b/pkg/apis/config/v1alpha1/kwokctl_component_types.go
@@ -0,0 +1,43 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+ "encoding/json"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+const (
+ // KwokctlComponentKind is the kind of the kwokctl component.
+ KwokctlComponentKind = "KwokctlComponent"
+)
+
+// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
+
+// KwokctlComponent holds information about the kwokctl component.
+type KwokctlComponent struct {
+ //+k8s:conversion-gen=false
+ metav1.TypeMeta `json:",inline"`
+ // Standard list metadata.
+ // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+ // Parameters is the parameters for the kwokctl component configuration.
+ Parameters json.RawMessage `json:"parameters,omitempty"`
+ // Template is the template for the kwokctl component configuration.
+ Template string `json:"template,omitempty"`
+}
diff --git a/pkg/apis/config/v1alpha1/kwokctl_configuration_types.go b/pkg/apis/config/v1alpha1/kwokctl_configuration_types.go
index 538e64f05b..acc78f61e4 100644
--- a/pkg/apis/config/v1alpha1/kwokctl_configuration_types.go
+++ b/pkg/apis/config/v1alpha1/kwokctl_configuration_types.go
@@ -505,6 +505,10 @@ type Component struct {
// MetricsDiscovery is the metrics discovery of the component.
MetricsDiscovery *ComponentMetric `json:"metricsDiscovery,omitempty"`
+ // Address is the address of the component.
+ // +optional
+ Address string `json:"address,omitempty"`
+
// Version is the version of the component.
// +optional
Version string `json:"version,omitempty"`
diff --git a/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
index 1b5f881f11..77e7acf897 100644
--- a/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
@@ -239,6 +239,37 @@ func (in *KwokConfigurationOptions) DeepCopy() *KwokConfigurationOptions {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *KwokctlComponent) DeepCopyInto(out *KwokctlComponent) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ if in.Parameters != nil {
+ in, out := &in.Parameters, &out.Parameters
+ *out = make(json.RawMessage, len(*in))
+ copy(*out, *in)
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KwokctlComponent.
+func (in *KwokctlComponent) DeepCopy() *KwokctlComponent {
+ if in == nil {
+ return nil
+ }
+ out := new(KwokctlComponent)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *KwokctlComponent) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KwokctlConfiguration) DeepCopyInto(out *KwokctlConfiguration) {
*out = *in
diff --git a/pkg/apis/internalversion/conversion.go b/pkg/apis/internalversion/conversion.go
index 22bd715673..9f3e55b953 100644
--- a/pkg/apis/internalversion/conversion.go
+++ b/pkg/apis/internalversion/conversion.go
@@ -68,6 +68,28 @@ func ConvertToInternalKwokctlResource(in *configv1alpha1.KwokctlResource) (*Kwok
return &out, nil
}
+// ConvertToV1alpha1KwokctlComponent converts an internal version KwokctlComponent to a v1alpha1.KwokctlComponent.
+func ConvertToV1alpha1KwokctlComponent(in *KwokctlComponent) (*configv1alpha1.KwokctlComponent, error) {
+ var out configv1alpha1.KwokctlComponent
+ out.APIVersion = configv1alpha1.GroupVersion.String()
+ out.Kind = configv1alpha1.KwokctlComponentKind
+ err := Convert_internalversion_KwokctlComponent_To_v1alpha1_KwokctlComponent(in, &out, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &out, nil
+}
+
+// ConvertToInternalKwokctlComponent converts a v1alpha1.KwokctlComponent to an internal version.
+func ConvertToInternalKwokctlComponent(in *configv1alpha1.KwokctlComponent) (*KwokctlComponent, error) {
+ var out KwokctlComponent
+ err := Convert_v1alpha1_KwokctlComponent_To_internalversion_KwokctlComponent(in, &out, nil)
+ if err != nil {
+ return nil, err
+ }
+ return &out, nil
+}
+
// ConvertToV1alpha1KwokConfiguration converts an internal version KwokConfiguration to a v1alpha1.KwokConfiguration.
func ConvertToV1alpha1KwokConfiguration(in *KwokConfiguration) (*configv1alpha1.KwokConfiguration, error) {
var out configv1alpha1.KwokConfiguration
diff --git a/pkg/apis/internalversion/kwokctl_component_types.go b/pkg/apis/internalversion/kwokctl_component_types.go
new file mode 100644
index 0000000000..3bc6849a4e
--- /dev/null
+++ b/pkg/apis/internalversion/kwokctl_component_types.go
@@ -0,0 +1,34 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package internalversion
+
+import (
+ "encoding/json"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// KwokctlComponent provides component definition for kwokctl.
+type KwokctlComponent struct {
+ // Standard list metadata.
+ // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
+ metav1.ObjectMeta
+ // Parameters is the parameters for the kwokctl component configuration.
+ Parameters json.RawMessage
+ // Template is the template for the kwokctl component configuration.
+ Template string
+}
diff --git a/pkg/apis/internalversion/kwokctl_configuration_types.go b/pkg/apis/internalversion/kwokctl_configuration_types.go
index 43c875e250..28d3ff061d 100644
--- a/pkg/apis/internalversion/kwokctl_configuration_types.go
+++ b/pkg/apis/internalversion/kwokctl_configuration_types.go
@@ -338,6 +338,9 @@ type Component struct {
// MetricsDiscovery is the metrics discovery of the component.
MetricsDiscovery *ComponentMetric
+ // Address is the address of the component.
+ Address string
+
// Version is the version of the component.
Version string
}
diff --git a/pkg/apis/internalversion/zz_generated.conversion.go b/pkg/apis/internalversion/zz_generated.conversion.go
index 0e8df25da1..f25cc9cc8d 100644
--- a/pkg/apis/internalversion/zz_generated.conversion.go
+++ b/pkg/apis/internalversion/zz_generated.conversion.go
@@ -340,6 +340,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
+ if err := s.AddGeneratedConversionFunc((*KwokctlComponent)(nil), (*configv1alpha1.KwokctlComponent)(nil), func(a, b interface{}, scope conversion.Scope) error {
+ return Convert_internalversion_KwokctlComponent_To_v1alpha1_KwokctlComponent(a.(*KwokctlComponent), b.(*configv1alpha1.KwokctlComponent), scope)
+ }); err != nil {
+ return err
+ }
+ if err := s.AddGeneratedConversionFunc((*configv1alpha1.KwokctlComponent)(nil), (*KwokctlComponent)(nil), func(a, b interface{}, scope conversion.Scope) error {
+ return Convert_v1alpha1_KwokctlComponent_To_internalversion_KwokctlComponent(a.(*configv1alpha1.KwokctlComponent), b.(*KwokctlComponent), scope)
+ }); err != nil {
+ return err
+ }
if err := s.AddGeneratedConversionFunc((*KwokctlConfiguration)(nil), (*configv1alpha1.KwokctlConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_internalversion_KwokctlConfiguration_To_v1alpha1_KwokctlConfiguration(a.(*KwokctlConfiguration), b.(*configv1alpha1.KwokctlConfiguration), scope)
}); err != nil {
@@ -1071,6 +1081,7 @@ func autoConvert_internalversion_Component_To_v1alpha1_Component(in *Component,
}
out.Metric = (*configv1alpha1.ComponentMetric)(unsafe.Pointer(in.Metric))
out.MetricsDiscovery = (*configv1alpha1.ComponentMetric)(unsafe.Pointer(in.MetricsDiscovery))
+ out.Address = in.Address
out.Version = in.Version
return nil
}
@@ -1104,6 +1115,7 @@ func autoConvert_v1alpha1_Component_To_internalversion_Component(in *configv1alp
}
out.Metric = (*ComponentMetric)(unsafe.Pointer(in.Metric))
out.MetricsDiscovery = (*ComponentMetric)(unsafe.Pointer(in.MetricsDiscovery))
+ out.Address = in.Address
out.Version = in.Version
return nil
}
@@ -1564,6 +1576,31 @@ func Convert_v1alpha1_KwokConfigurationOptions_To_internalversion_KwokConfigurat
return autoConvert_v1alpha1_KwokConfigurationOptions_To_internalversion_KwokConfigurationOptions(in, out, s)
}
+func autoConvert_internalversion_KwokctlComponent_To_v1alpha1_KwokctlComponent(in *KwokctlComponent, out *configv1alpha1.KwokctlComponent, s conversion.Scope) error {
+ out.ObjectMeta = in.ObjectMeta
+ out.Parameters = *(*json.RawMessage)(unsafe.Pointer(&in.Parameters))
+ out.Template = in.Template
+ return nil
+}
+
+// Convert_internalversion_KwokctlComponent_To_v1alpha1_KwokctlComponent is an autogenerated conversion function.
+func Convert_internalversion_KwokctlComponent_To_v1alpha1_KwokctlComponent(in *KwokctlComponent, out *configv1alpha1.KwokctlComponent, s conversion.Scope) error {
+ return autoConvert_internalversion_KwokctlComponent_To_v1alpha1_KwokctlComponent(in, out, s)
+}
+
+func autoConvert_v1alpha1_KwokctlComponent_To_internalversion_KwokctlComponent(in *configv1alpha1.KwokctlComponent, out *KwokctlComponent, s conversion.Scope) error {
+ // INFO: in.TypeMeta opted out of conversion generation
+ out.ObjectMeta = in.ObjectMeta
+ out.Parameters = *(*json.RawMessage)(unsafe.Pointer(&in.Parameters))
+ out.Template = in.Template
+ return nil
+}
+
+// Convert_v1alpha1_KwokctlComponent_To_internalversion_KwokctlComponent is an autogenerated conversion function.
+func Convert_v1alpha1_KwokctlComponent_To_internalversion_KwokctlComponent(in *configv1alpha1.KwokctlComponent, out *KwokctlComponent, s conversion.Scope) error {
+ return autoConvert_v1alpha1_KwokctlComponent_To_internalversion_KwokctlComponent(in, out, s)
+}
+
func autoConvert_internalversion_KwokctlConfiguration_To_v1alpha1_KwokctlConfiguration(in *KwokctlConfiguration, out *configv1alpha1.KwokctlConfiguration, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
if err := Convert_internalversion_KwokctlConfigurationOptions_To_v1alpha1_KwokctlConfigurationOptions(&in.Options, &out.Options, s); err != nil {
diff --git a/pkg/apis/internalversion/zz_generated.deepcopy.go b/pkg/apis/internalversion/zz_generated.deepcopy.go
index 8ced5c9c71..39e64d99cd 100644
--- a/pkg/apis/internalversion/zz_generated.deepcopy.go
+++ b/pkg/apis/internalversion/zz_generated.deepcopy.go
@@ -695,6 +695,28 @@ func (in *KwokConfigurationOptions) DeepCopy() *KwokConfigurationOptions {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *KwokctlComponent) DeepCopyInto(out *KwokctlComponent) {
+ *out = *in
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ if in.Parameters != nil {
+ in, out := &in.Parameters, &out.Parameters
+ *out = make(json.RawMessage, len(*in))
+ copy(*out, *in)
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KwokctlComponent.
+func (in *KwokctlComponent) DeepCopy() *KwokctlComponent {
+ if in == nil {
+ return nil
+ }
+ out := new(KwokctlComponent)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KwokctlConfiguration) DeepCopyInto(out *KwokctlConfiguration) {
*out = *in
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 84b8298f68..b84f90ec26 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -106,6 +106,12 @@ var configHandlers = map[string]configHandler{
MutateToInternal: mutateToInternalConfig(internalversion.ConvertToInternalKwokctlResource),
MutateToVersiond: mutateToVersiondConfig(internalversion.ConvertToV1alpha1KwokctlResource),
},
+ configv1alpha1.KwokctlComponentKind: {
+ Unmarshal: unmarshalConfig[*configv1alpha1.KwokctlComponent],
+ Marshal: marshalConfig,
+ MutateToInternal: mutateToInternalConfig(internalversion.ConvertToInternalKwokctlComponent),
+ MutateToVersiond: mutateToVersiondConfig(internalversion.ConvertToV1alpha1KwokctlComponent),
+ },
v1alpha1.StageKind: {
Unmarshal: unmarshalConfig[*v1alpha1.Stage],
Marshal: marshalConfig,
diff --git a/pkg/kwokctl/cmd/create/cluster/cluster.go b/pkg/kwokctl/cmd/create/cluster/cluster.go
index d652b33a81..1ceca119d0 100644
--- a/pkg/kwokctl/cmd/create/cluster/cluster.go
+++ b/pkg/kwokctl/cmd/create/cluster/cluster.go
@@ -33,6 +33,7 @@ import (
"sigs.k8s.io/kwok/pkg/log"
"sigs.k8s.io/kwok/pkg/utils/kubeconfig"
"sigs.k8s.io/kwok/pkg/utils/path"
+ "sigs.k8s.io/kwok/pkg/utils/slices"
)
type flagpole struct {
@@ -42,6 +43,9 @@ type flagpole struct {
Kubeconfig string
ExtraArgs []string
+ ComponentArgs []string
+ EnableComponents []string
+
*internalversion.KwokctlConfiguration
}
@@ -145,6 +149,9 @@ func NewCommand(ctx context.Context) *cobra.Command {
cmd.Flags().Float64Var(&flags.Options.HeartbeatFactor, "heartbeat-factor", flags.Options.HeartbeatFactor, "Scale factor for all about heartbeat")
cmd.Flags().StringArrayVar(&flags.ExtraArgs, "extra-args", flags.ExtraArgs, "Pass a single extra arg key-value pair to the component in the format `component=key=value`")
+ cmd.Flags().StringArrayVar(&flags.ComponentArgs, "component-args", flags.ComponentArgs, "Set component args for the cluster, format: .key1=value1 .key2=value2")
+ cmd.Flags().StringArrayVar(&flags.EnableComponents, "enable-components", flags.EnableComponents, "Enable components for the cluster")
+
return cmd
}
@@ -286,6 +293,24 @@ func runE(ctx context.Context, flags *flagpole) error {
cleanUp()
return err
}
+
+ // Enable components
+ for _, componentName := range flags.EnableComponents {
+ componentPrefix := fmt.Sprintf(".%s.", componentName)
+ args := slices.FilterAndMap(flags.ComponentArgs, func(s string) (string, bool) {
+ if !strings.HasPrefix(s, componentPrefix) {
+ return "", false
+ }
+ return s[len(componentPrefix)-1:], true
+ })
+ err := rt.SetComponents(ctx, componentName, args...)
+ if err != nil {
+ logger.Error("Failed to set components", err, "component", componentName)
+ cleanUp()
+ return err
+ }
+ }
+
err = rt.Save(ctx)
if err != nil {
logger.Error("Failed to save config", err)
diff --git a/pkg/kwokctl/components/utils.go b/pkg/kwokctl/components/utils.go
index 21d26febc9..71c764becc 100644
--- a/pkg/kwokctl/components/utils.go
+++ b/pkg/kwokctl/components/utils.go
@@ -89,3 +89,29 @@ var (
func GetRuntimeMode(runtime string) string {
return runtimeTypeMap[runtime]
}
+
+// PatchComponent patches a component.
+func PatchComponent(c internalversion.Component, patch internalversion.ComponentPatches) internalversion.Component {
+ for _, a := range patch.ExtraArgs {
+ c.Args = append(c.Args, fmt.Sprintf("--%s=%s", a.Key, a.Value))
+ }
+
+ for _, v := range patch.ExtraVolumes {
+ c.Volumes = append(c.Volumes, internalversion.Volume{
+ Name: v.Name,
+ HostPath: v.HostPath,
+ PathType: v.PathType,
+ MountPath: v.MountPath,
+ ReadOnly: v.ReadOnly,
+ })
+ }
+
+ for _, e := range patch.ExtraEnvs {
+ c.Envs = append(c.Envs, internalversion.Env{
+ Name: e.Name,
+ Value: e.Value,
+ })
+ }
+
+ return c
+}
diff --git a/pkg/kwokctl/runtime/binary/cluster.go b/pkg/kwokctl/runtime/binary/cluster.go
index 8ec2556366..c948c9f541 100644
--- a/pkg/kwokctl/runtime/binary/cluster.go
+++ b/pkg/kwokctl/runtime/binary/cluster.go
@@ -822,8 +822,12 @@ func (c *Cluster) startComponent(ctx context.Context, component internalversion.
ctx = exec.WithUser(ctx, &uid, &gid)
}
+ workdir := component.WorkDir
+ if workdir == "" {
+ workdir = c.Workdir()
+ }
logger.Debug("Starting component")
- return c.ForkExec(ctx, component.WorkDir, component.Binary, component.Args...)
+ return c.ForkExec(ctx, workdir, component.Binary, component.Args...)
}
func (c *Cluster) startComponents(ctx context.Context) error {
diff --git a/pkg/kwokctl/runtime/binary/component.go b/pkg/kwokctl/runtime/binary/component.go
new file mode 100644
index 0000000000..eecd4e3a16
--- /dev/null
+++ b/pkg/kwokctl/runtime/binary/component.go
@@ -0,0 +1,103 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package binary
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "sigs.k8s.io/kwok/pkg/apis/internalversion"
+ "sigs.k8s.io/kwok/pkg/config"
+ "sigs.k8s.io/kwok/pkg/kwokctl/runtime"
+ "sigs.k8s.io/kwok/pkg/kwokctl/scale"
+ "sigs.k8s.io/kwok/pkg/utils/gotpl"
+ "sigs.k8s.io/kwok/pkg/utils/path"
+ "sigs.k8s.io/kwok/pkg/utils/slices"
+)
+
+// SetComponents returns the components of cluster
+func (c *Cluster) SetComponents(ctx context.Context, name string, args ...string) error {
+ conf, err := c.Config(ctx)
+ if err != nil {
+ return err
+ }
+ _, ok := slices.Find(conf.Components, func(component internalversion.Component) bool {
+ return component.Name == name
+ })
+ if ok {
+ return fmt.Errorf("component %s is already exists", name)
+ }
+
+ kcp := config.FilterWithTypeFromContext[*internalversion.KwokctlComponent](ctx)
+ renderer := gotpl.NewRenderer(gotpl.FuncMap{
+ "ClusterName": c.Name,
+ "Workdir": c.Workdir,
+ "Runtime": func() string {
+ return c.Runtime(ctx)
+ },
+ "Mode": func() string {
+ return c.Mode(ctx)
+ },
+ "Address": func() string {
+ return c.ComponentAddress(ctx, name)
+ },
+ "PkiDir": func() string {
+ return path.Join(c.Workdir(), runtime.PkiName)
+ },
+ "Kubeconfig": func() string {
+ return c.GetWorkdirPath(runtime.InHostKubeconfigName)
+ },
+ "Config": func() *internalversion.KwokctlConfiguration {
+ return conf
+ },
+ })
+
+ krc, ok := slices.Find(kcp, func(krc *internalversion.KwokctlComponent) bool {
+ return krc.Name == name
+ })
+ if !ok {
+ return fmt.Errorf("component %s is not exists", name)
+ }
+
+ param, err := scale.NewParameters(ctx, krc.Parameters, args)
+ if err != nil {
+ return err
+ }
+
+ componentData, err := renderer.ToJSON(krc.Template, param)
+ if err != nil {
+ return err
+ }
+ var component internalversion.Component
+ err = json.Unmarshal(componentData, &component)
+ if err != nil {
+ return err
+ }
+ component.Name = name
+
+ binaryPath, err := c.EnsureBinary(ctx, component.Name, component.Binary)
+ if err != nil {
+ return err
+ }
+
+ component.Binary = binaryPath
+
+ conf.Components = append(conf.Components, component)
+
+ return c.SetConfig(ctx, conf)
+}
diff --git a/pkg/kwokctl/runtime/component.go b/pkg/kwokctl/runtime/component.go
new file mode 100644
index 0000000000..912003ef2e
--- /dev/null
+++ b/pkg/kwokctl/runtime/component.go
@@ -0,0 +1,47 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package runtime
+
+import (
+ "context"
+
+ "sigs.k8s.io/kwok/pkg/kwokctl/components"
+ "sigs.k8s.io/kwok/pkg/utils/net"
+)
+
+func (c *Cluster) Runtime(ctx context.Context) string {
+ config, err := c.Config(ctx)
+ if err != nil {
+ return ""
+ }
+ conf := &config.Options
+
+ return conf.Runtime
+}
+
+func (c *Cluster) Mode(ctx context.Context) string {
+ return components.GetRuntimeMode(c.Runtime(ctx))
+}
+
+func (c *Cluster) ComponentAddress(ctx context.Context, name string) string {
+ switch c.Mode(ctx) {
+ case components.RuntimeModeContainer:
+ return c.Name() + "-" + name
+ default:
+ return net.LocalAddress
+ }
+}
diff --git a/pkg/kwokctl/runtime/compose/component.go b/pkg/kwokctl/runtime/compose/component.go
new file mode 100644
index 0000000000..f6a8d40bd3
--- /dev/null
+++ b/pkg/kwokctl/runtime/compose/component.go
@@ -0,0 +1,121 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package compose
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "sigs.k8s.io/kwok/pkg/apis/internalversion"
+ "sigs.k8s.io/kwok/pkg/config"
+ "sigs.k8s.io/kwok/pkg/consts"
+ "sigs.k8s.io/kwok/pkg/kwokctl/runtime"
+ "sigs.k8s.io/kwok/pkg/kwokctl/scale"
+ "sigs.k8s.io/kwok/pkg/utils/gotpl"
+ "sigs.k8s.io/kwok/pkg/utils/slices"
+)
+
+// SetComponents returns the components of cluster
+func (c *Cluster) SetComponents(ctx context.Context, name string, args ...string) error {
+ conf, err := c.Config(ctx)
+ if err != nil {
+ return err
+ }
+ _, ok := slices.Find(conf.Components, func(component internalversion.Component) bool {
+ return component.Name == name
+ })
+ if ok {
+ return fmt.Errorf("component %s is already exists", name)
+ }
+
+ kcp := config.FilterWithTypeFromContext[*internalversion.KwokctlComponent](ctx)
+ renderer := gotpl.NewRenderer(gotpl.FuncMap{
+ "ClusterName": c.Name,
+ "Workdir": c.Workdir,
+ "Runtime": func() string {
+ return c.Runtime(ctx)
+ },
+ "Mode": func() string {
+ return c.Mode(ctx)
+ },
+ "Address": func() string {
+ return c.ComponentAddress(ctx, name)
+ },
+ "PkiDir": func() string {
+ return "/etc/kubernetes/pki"
+ },
+ "Kubeconfig": func() string {
+ return "/root/.kube/config"
+ },
+ "Config": func() *internalversion.KwokctlConfiguration {
+ return conf
+ },
+ })
+
+ krc, ok := slices.Find(kcp, func(krc *internalversion.KwokctlComponent) bool {
+ return krc.Name == name
+ })
+ if !ok {
+ return fmt.Errorf("component %s is not exists", name)
+ }
+
+ param, err := scale.NewParameters(ctx, krc.Parameters, args)
+ if err != nil {
+ return err
+ }
+
+ componentData, err := renderer.ToJSON(krc.Template, param)
+ if err != nil {
+ return err
+ }
+ var component internalversion.Component
+ err = json.Unmarshal(componentData, &component)
+ if err != nil {
+ return err
+ }
+ component.Name = name
+
+ volumes := []internalversion.Volume{
+ {
+ HostPath: c.GetWorkdirPath(runtime.InClusterKubeconfigName),
+ MountPath: "/root/.kube/config",
+ },
+ {
+ HostPath: c.GetWorkdirPath(runtime.PkiName),
+ MountPath: "/etc/kubernetes/pki/",
+ },
+ }
+
+ if name == consts.ComponentKwokController {
+ volumes = append(volumes, internalversion.Volume{
+ HostPath: c.GetWorkdirPath(runtime.ConfigName),
+ MountPath: "/root/.kwok/kwok.yaml",
+ })
+ }
+
+ component.Volumes = append(component.Volumes, volumes...)
+
+ err = c.EnsureImage(ctx, c.runtime, component.Image)
+ if err != nil {
+ return err
+ }
+
+ conf.Components = append(conf.Components, component)
+
+ return c.SetConfig(ctx, conf)
+}
diff --git a/pkg/kwokctl/runtime/config.go b/pkg/kwokctl/runtime/config.go
index d8d0141c1c..9d9f4675bd 100644
--- a/pkg/kwokctl/runtime/config.go
+++ b/pkg/kwokctl/runtime/config.go
@@ -70,6 +70,9 @@ type Runtime interface {
// ListComponents list the components of cluster
ListComponents(ctx context.Context) ([]internalversion.Component, error)
+ // SetComponents returns the components of cluster
+ SetComponents(ctx context.Context, name string, args ...string) error
+
// InspectComponent inspect the component
InspectComponent(ctx context.Context, name string) (ComponentStatus, error)
diff --git a/pkg/kwokctl/runtime/kind/component.go b/pkg/kwokctl/runtime/kind/component.go
new file mode 100644
index 0000000000..245eaa554f
--- /dev/null
+++ b/pkg/kwokctl/runtime/kind/component.go
@@ -0,0 +1,99 @@
+/*
+Copyright 2024 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package kind
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "sigs.k8s.io/kwok/pkg/apis/internalversion"
+ "sigs.k8s.io/kwok/pkg/config"
+ "sigs.k8s.io/kwok/pkg/kwokctl/scale"
+ "sigs.k8s.io/kwok/pkg/utils/gotpl"
+ "sigs.k8s.io/kwok/pkg/utils/slices"
+)
+
+// SetComponents returns the components of cluster
+func (c *Cluster) SetComponents(ctx context.Context, name string, args ...string) error {
+ conf, err := c.Config(ctx)
+ if err != nil {
+ return err
+ }
+ _, ok := slices.Find(conf.Components, func(component internalversion.Component) bool {
+ return component.Name == name
+ })
+ if ok {
+ return fmt.Errorf("component %s is already exists", name)
+ }
+
+ kcp := config.FilterWithTypeFromContext[*internalversion.KwokctlComponent](ctx)
+ renderer := gotpl.NewRenderer(gotpl.FuncMap{
+ "ClusterName": c.Name,
+ "Workdir": c.Workdir,
+ "Runtime": func() string {
+ return c.Runtime(ctx)
+ },
+ "Mode": func() string {
+ return c.Mode(ctx)
+ },
+ "Address": func() string {
+ return c.ComponentAddress(ctx, name)
+ },
+ "PkiDir": func() string {
+ return "/etc/kubernetes/pki"
+ },
+ "Kubeconfig": func() string {
+ return "/root/.kube/config"
+ },
+ "Config": func() *internalversion.KwokctlConfiguration {
+ return conf
+ },
+ })
+
+ krc, ok := slices.Find(kcp, func(krc *internalversion.KwokctlComponent) bool {
+ return krc.Name == name
+ })
+ if !ok {
+ return fmt.Errorf("component %s is not exists", name)
+ }
+
+ param, err := scale.NewParameters(ctx, krc.Parameters, args)
+ if err != nil {
+ return err
+ }
+
+ componentData, err := renderer.ToJSON(krc.Template, param)
+ if err != nil {
+ return err
+ }
+ var component internalversion.Component
+ err = json.Unmarshal(componentData, &component)
+ if err != nil {
+ return err
+ }
+ component.Name = name
+
+ err = c.EnsureImage(ctx, c.runtime, component.Image)
+ if err != nil {
+ return err
+ }
+
+ conf.Components = append(conf.Components, component)
+
+ return c.SetConfig(ctx, conf)
+}
diff --git a/pkg/utils/gotpl/funcs.go b/pkg/utils/gotpl/funcs.go
index 73303be445..302292d5ad 100644
--- a/pkg/utils/gotpl/funcs.go
+++ b/pkg/utils/gotpl/funcs.go
@@ -17,6 +17,8 @@ limitations under the License.
package gotpl
import (
+ "runtime"
+
"github.com/Masterminds/sprig/v3"
)
@@ -24,3 +26,12 @@ var (
// genericFuncs is generic template functions.
genericFuncs = sprig.TxtFuncMap()
)
+
+func init() {
+ genericFuncs["GOOS"] = func() string {
+ return runtime.GOOS
+ }
+ genericFuncs["GOARCH"] = func() string {
+ return runtime.GOARCH
+ }
+}
diff --git a/pkg/utils/gotpl/renderer.go b/pkg/utils/gotpl/renderer.go
index 88ef1f9f38..808d65e626 100644
--- a/pkg/utils/gotpl/renderer.go
+++ b/pkg/utils/gotpl/renderer.go
@@ -100,7 +100,7 @@ func (r *renderer) ToText(text string, original interface{}) ([]byte, error) {
err := r.render(buf, text, original)
if err != nil {
- return nil, fmt.Errorf("%w: %s", err, buf.String())
+ return nil, fmt.Errorf("%w: %s", err, strings.TrimRight(buf.String(), "\x00"))
}
return slices.Clone(buf.Bytes()), nil
}
@@ -112,12 +112,12 @@ func (r *renderer) ToJSON(text string, original interface{}) ([]byte, error) {
err := r.render(buf, text, original)
if err != nil {
- return nil, fmt.Errorf("%w: %s", err, buf.String())
+ return nil, fmt.Errorf("%w: %s", err, strings.TrimRight(buf.String(), "\x00"))
}
out, err := yaml.YAMLToJSON(buf.Bytes())
if err != nil {
- return nil, fmt.Errorf("%w: %s", err, buf.String())
+ return nil, fmt.Errorf("%w: %s", err, strings.TrimRight(buf.String(), "\x00"))
}
return out, nil
}
diff --git a/pkg/utils/slices/slices.go b/pkg/utils/slices/slices.go
index 65be179d50..b0dd356a90 100644
--- a/pkg/utils/slices/slices.go
+++ b/pkg/utils/slices/slices.go
@@ -16,6 +16,11 @@ limitations under the License.
package slices
+import (
+ "cmp"
+ "sort"
+)
+
// Map returns a new slice containing the results of applying the given function
func Map[S ~[]T, T any, O any](s S, f func(T) O) []O {
out := make([]O, len(s))
@@ -129,3 +134,13 @@ func GroupBy[S ~[]T, T any, K comparable](s S, f func(T) K) map[K][]T {
}
return out
}
+
+// Sort returns a new slice containing the elements of the slice in sorted order.
+func Sort[S ~[]T, T cmp.Ordered](s S) []T {
+ out := make([]T, len(s))
+ copy(out, s)
+ sort.Slice(out, func(i, j int) bool {
+ return cmp.Less(out[i], out[j])
+ })
+ return out
+}
diff --git a/site/content/en/docs/generated/apis.md b/site/content/en/docs/generated/apis.md
index 28ced467f3..d9f85d8a8c 100644
--- a/site/content/en/docs/generated/apis.md
+++ b/site/content/en/docs/generated/apis.md
@@ -137,6 +137,9 @@ Resource Types:
KwokConfiguration
+KwokctlComponent
+
+
KwokctlConfiguration
@@ -206,6 +209,79 @@ KwokConfigurationOptions
+
+KwokctlComponent
+ #
+
+
+
KwokctlComponent holds information about the kwokctl component.
+
+
+
+
+Field |
+Description |
+
+
+
+
+
+apiVersion
+string
+ |
+
+
+config.kwok.x-k8s.io/v1alpha1
+
+ |
+
+
+
+kind
+string
+ |
+KwokctlComponent |
+
+
+
+metadata
+
+
+Kubernetes meta/v1.ObjectMeta
+
+
+ |
+
+ Standard list metadata.
+More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
+Refer to the Kubernetes API documentation for the fields of the
+metadata field.
+ |
+
+
+
+parameters
+
+encoding/json.RawMessage
+
+ |
+
+ Parameters is the parameters for the kwokctl component configuration.
+ |
+
+
+
+template
+
+string
+
+ |
+
+ Template is the template for the kwokctl component configuration.
+ |
+
+
+
KwokctlConfiguration
#
@@ -1992,6 +2068,18 @@ ComponentMetric
+address
+
+string
+
+ |
+
+(Optional)
+ Address is the address of the component.
+ |
+
+
+
version
string
diff --git a/site/content/en/docs/generated/kwokctl_create_cluster.md b/site/content/en/docs/generated/kwokctl_create_cluster.md
index 03afd5509c..600f0ac67b 100644
--- a/site/content/en/docs/generated/kwokctl_create_cluster.md
+++ b/site/content/en/docs/generated/kwokctl_create_cluster.md
@@ -9,6 +9,7 @@ kwokctl create cluster [flags]
### Options
```
+ --component-args stringArray Set component args for the cluster, format: .key1=value1 .key2=value2
--controller-port uint32 Port of kwok-controller given to the host
--dashboard-image string Image of dashboard, only for docker/podman/nerdctl/kind/kind-podman runtime
'${KWOK_DASHBOARD_IMAGE_PREFIX}/dashboard:${KWOK_DASHBOARD_VERSION}'
@@ -17,6 +18,7 @@ kwokctl create cluster [flags]
--disable-kube-controller-manager Disable the kube-controller-manager
--disable-kube-scheduler Disable the kube-scheduler
--disable-qps-limits Disable QPS limits for components
+ --enable-components stringArray Enable components for the cluster
--enable-crds strings List of CRDs to enable
--enable-metrics-server Enable the metrics-server
--etcd-binary string Binary of etcd, only for binary runtime (default "https://github.com/etcd-io/etcd/releases/download/v3.5.11/etcd-v3.5.11-linux-amd64.tar.gz#etcd")
|