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.

    +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FieldDescription
    +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