diff --git a/kustomize/kwokctl/component/dashboard.yaml b/kustomize/kwokctl/component/dashboard.yaml new file mode 100644 index 000000000..4be590a6a --- /dev/null +++ b/kustomize/kwokctl/component/dashboard.yaml @@ -0,0 +1,54 @@ +apiVersion: config.kwok.x-k8s.io/v1alpha1 +kind: KwokctlComponent +metadata: + name: dashboard +parameters: + image: "" + binary: "" + version: "2.7.0" + bindAddress: "0.0.0.0" + port: 8000 +template: |- + {{ $version := + ( or + ( env "KWOK_DASHBOARD_VERSION" ) + .version + ) + }} + {{ $image := + ( or + .image + ( env "KWOK_DASHBOARD_IMAGE" ) + ( join + "" + ( list + ( or + ( env "KWOK_DASHBOARD_IMAGE_PREFIX" ) + "docker.io/kubernetesui" + ) + "/dashboard:v" + $version + ) + ) + ) + }} + image: {{ $image }} + binary: {{ .binary }} + links: + - kube-apiserver + ports: + - name: http + port: {{ .port }} + hostPort: {{ .port }} + protocol: TCP + args: + - --insecure-bind-address={{ .bindAddress }} + - --insecure-port={{ .port }} + - --bind-address=127.0.0.1 + - --port=0 + - --enable-insecure-login + - --enable-skip-login + - --disable-settings-authorizer + - --metrics-provider=none + - --system-banner=Welcome to {{ ClusterName }} + - --kubeconfig={{ Kubeconfig }} 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 000000000..1597547ad --- /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 4dd7eb114..eeef6615a 100644 --- a/pkg/apis/config/v1alpha1/kwokctl_configuration_types.go +++ b/pkg/apis/config/v1alpha1/kwokctl_configuration_types.go @@ -523,6 +523,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 80c889090..9fe9d315f 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 147c1ecd3..b2aeac28b 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 000000000..3bc6849a4 --- /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 5d3a35454..953b6c4c8 100644 --- a/pkg/apis/internalversion/kwokctl_configuration_types.go +++ b/pkg/apis/internalversion/kwokctl_configuration_types.go @@ -355,6 +355,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 f0a056fde..3cb788e60 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 03a962e75..fff3774eb 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 089702c5f..8bd513cd4 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -110,6 +110,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/cluster_component.go b/pkg/kwokctl/runtime/binary/cluster_component.go new file mode 100644 index 000000000..37c0cb376 --- /dev/null +++ b/pkg/kwokctl/runtime/binary/cluster_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/utils/gotpl" + "sigs.k8s.io/kwok/pkg/utils/path" + "sigs.k8s.io/kwok/pkg/utils/slices" + "sigs.k8s.io/kwok/pkg/utils/expression" +) + +// 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 := expression.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 000000000..912003ef2 --- /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/cluster_component.go b/pkg/kwokctl/runtime/compose/cluster_component.go new file mode 100644 index 000000000..a3e8fd76d --- /dev/null +++ b/pkg/kwokctl/runtime/compose/cluster_component.go @@ -0,0 +1,113 @@ +/* +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/kwokctl/runtime" + "sigs.k8s.io/kwok/pkg/utils/gotpl" + "sigs.k8s.io/kwok/pkg/utils/slices" + "sigs.k8s.io/kwok/pkg/utils/expression" +) + +// 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 := expression.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/", + }, + } + + 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/cluster_component.go b/pkg/kwokctl/runtime/kind/cluster_component.go new file mode 100644 index 000000000..dc750cdd5 --- /dev/null +++ b/pkg/kwokctl/runtime/kind/cluster_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/utils/gotpl" + "sigs.k8s.io/kwok/pkg/utils/slices" + "sigs.k8s.io/kwok/pkg/utils/expression" +) + +// 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 := expression.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 40c2c0810..d4e3435e2 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 26996a957..fe794dc86 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 bef9ae20f..3690ae3cb 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