From 71436eaf32beeb9b65ec8512bede8fd67937f9c2 Mon Sep 17 00:00:00 2001
From: Shiming Zhang
Date: Mon, 26 Aug 2024 22:26:31 +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/runtime/binary/component.go | 103 +++++++++++++++
pkg/kwokctl/runtime/component.go | 47 +++++++
pkg/kwokctl/runtime/compose/component.go | 121 ++++++++++++++++++
pkg/kwokctl/runtime/kind/component.go | 99 ++++++++++++++
pkg/utils/gotpl/funcs.go | 10 ++
pkg/utils/gotpl/renderer.go | 6 +-
site/content/en/docs/generated/apis.md | 88 +++++++++++++
16 files changed, 673 insertions(+), 3 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 29b4782d14..afd334500f 100644
--- a/pkg/apis/config/v1alpha1/kwokctl_configuration_types.go
+++ b/pkg/apis/config/v1alpha1/kwokctl_configuration_types.go
@@ -507,6 +507,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 147c1ecd34..b2aeac28bc 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 410d799e3a..3ae6de854c 100644
--- a/pkg/apis/internalversion/kwokctl_configuration_types.go
+++ b/pkg/apis/internalversion/kwokctl_configuration_types.go
@@ -340,6 +340,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 65008ceaae..fc28d9f6e5 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
}
@@ -1566,6 +1578,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 3bbf08b6a7..a80ebc600e 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 02de27a194..0f6d654662 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -107,6 +107,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/runtime/binary/component.go b/pkg/kwokctl/runtime/binary/component.go
new file mode 100644
index 0000000000..d2f000d7a4
--- /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"
+)
+
+// AddComponent adds the component in to cluster
+func (c *Cluster) AddComponent(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..1965097d2b
--- /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"
+)
+
+// AddComponent adds the component in to cluster
+func (c *Cluster) AddComponent(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/kind/component.go b/pkg/kwokctl/runtime/kind/component.go
new file mode 100644
index 0000000000..ba8cbc5235
--- /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"
+)
+
+// AddComponent adds the component in to cluster
+func (c *Cluster) AddComponent(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 40c2c08105..d4e3435e23 100644
--- a/pkg/utils/gotpl/funcs.go
+++ b/pkg/utils/gotpl/funcs.go
@@ -19,6 +19,7 @@ package gotpl
import (
"encoding/json"
"fmt"
+ "runtime"
"strconv"
"strings"
"time"
@@ -36,6 +37,15 @@ var (
genericFuncs = sprig.TxtFuncMap()
)
+func init() {
+ genericFuncs["GOOS"] = func() string {
+ return runtime.GOOS
+ }
+ genericFuncs["GOARCH"] = func() string {
+ return runtime.GOARCH
+ }
+}
+
var (
startTime = time.Now().Format(time.RFC3339Nano)
diff --git a/pkg/utils/gotpl/renderer.go b/pkg/utils/gotpl/renderer.go
index 26996a9573..fe794dc868 100644
--- a/pkg/utils/gotpl/renderer.go
+++ b/pkg/utils/gotpl/renderer.go
@@ -101,7 +101,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
}
@@ -113,12 +113,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/site/content/en/docs/generated/apis.md b/site/content/en/docs/generated/apis.md
index 526dc5a77f..1269861761 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
#
@@ -2007,6 +2083,18 @@ ComponentMetric
+address
+
+string
+
+ |
+
+(Optional)
+ Address is the address of the component.
+ |
+
+
+
version
string
|