Feature: karmadactl init supports deployment through configuration files
Signed-off-by: tiansuo114 <1729765480@qq.com> fix lint Signed-off-by: tiansuo114 <1729765480@qq.com>
This commit is contained in:
parent
f7d6da341e
commit
9b9847e3f7
|
@ -36,7 +36,6 @@ jobs:
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
|
|
||||||
- name: run karmadactl init test
|
- name: run karmadactl init test
|
||||||
run: |
|
run: |
|
||||||
export CLUSTER_VERSION=kindest/node:${{ matrix.k8s }}
|
export CLUSTER_VERSION=kindest/node:${{ matrix.k8s }}
|
||||||
|
@ -48,7 +47,7 @@ jobs:
|
||||||
export KUBECONFIG=${HOME}/karmada/karmada-apiserver.config
|
export KUBECONFIG=${HOME}/karmada/karmada-apiserver.config
|
||||||
GO111MODULE=on go install github.com/onsi/ginkgo/v2/ginkgo
|
GO111MODULE=on go install github.com/onsi/ginkgo/v2/ginkgo
|
||||||
ginkgo -v --race --trace -p --focus="[BasicPropagation] propagation testing deployment propagation testing" ./test/e2e/
|
ginkgo -v --race --trace -p --focus="[BasicPropagation] propagation testing deployment propagation testing" ./test/e2e/
|
||||||
- name: export logs
|
- name: export logs
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
export ARTIFACTS_PATH=${{ github.workspace }}/karmadactl-test-logs/${{ matrix.k8s }}/
|
export ARTIFACTS_PATH=${{ github.workspace }}/karmadactl-test-logs/${{ matrix.k8s }}/
|
||||||
|
|
|
@ -40,6 +40,7 @@ missing_license_header_files="$($ADDLICENSE_BIN \
|
||||||
-ignore "**/*.yml" \
|
-ignore "**/*.yml" \
|
||||||
-ignore "**/*.json" \
|
-ignore "**/*.json" \
|
||||||
-ignore ".idea/**" \
|
-ignore ".idea/**" \
|
||||||
|
-ignore ".git/**"
|
||||||
.)" || true
|
.)" || true
|
||||||
|
|
||||||
if [[ "$missing_license_header_files" ]]; then
|
if [[ "$missing_license_header_files" ]]; then
|
||||||
|
|
|
@ -76,7 +76,10 @@ var (
|
||||||
%[1]s init --karmada-apiserver-replicas 3 --etcd-replicas 3 --etcd-storage-mode PVC --storage-classes-name {StorageClassesName}
|
%[1]s init --karmada-apiserver-replicas 3 --etcd-replicas 3 --etcd-storage-mode PVC --storage-classes-name {StorageClassesName}
|
||||||
|
|
||||||
# Specify external IPs(load balancer or HA IP) which used to sign the certificate
|
# Specify external IPs(load balancer or HA IP) which used to sign the certificate
|
||||||
%[1]s init --cert-external-ip 10.235.1.2 --cert-external-dns www.karmada.io`)
|
%[1]s init --cert-external-ip 10.235.1.2 --cert-external-dns www.karmada.io
|
||||||
|
|
||||||
|
# Install Karmada using a configuration file
|
||||||
|
%[1]s init --config /path/to/your/config/file.yaml`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewCmdInit install Karmada on Kubernetes
|
// NewCmdInit install Karmada on Kubernetes
|
||||||
|
@ -149,6 +152,7 @@ func NewCmdInit(parentCommand string) *cobra.Command {
|
||||||
flags.StringVar(&opts.ExternalEtcdKeyPrefix, "external-etcd-key-prefix", "", "The key prefix to be configured to kube-apiserver through --etcd-prefix.")
|
flags.StringVar(&opts.ExternalEtcdKeyPrefix, "external-etcd-key-prefix", "", "The key prefix to be configured to kube-apiserver through --etcd-prefix.")
|
||||||
// karmada
|
// karmada
|
||||||
flags.StringVar(&opts.CRDs, "crds", kubernetes.DefaultCrdURL, "Karmada crds resource.(local file e.g. --crds /root/crds.tar.gz)")
|
flags.StringVar(&opts.CRDs, "crds", kubernetes.DefaultCrdURL, "Karmada crds resource.(local file e.g. --crds /root/crds.tar.gz)")
|
||||||
|
flags.StringVar(&opts.KarmadaInitFilePath, "config", "", "Karmada init file path")
|
||||||
flags.StringVarP(&opts.KarmadaAPIServerAdvertiseAddress, "karmada-apiserver-advertise-address", "", "", "The IP address the Karmada API Server will advertise it's listening on. If not set, the address on the master node will be used.")
|
flags.StringVarP(&opts.KarmadaAPIServerAdvertiseAddress, "karmada-apiserver-advertise-address", "", "", "The IP address the Karmada API Server will advertise it's listening on. If not set, the address on the master node will be used.")
|
||||||
flags.Int32VarP(&opts.KarmadaAPIServerNodePort, "port", "p", 32443, "Karmada apiserver service node port")
|
flags.Int32VarP(&opts.KarmadaAPIServerNodePort, "port", "p", 32443, "Karmada apiserver service node port")
|
||||||
flags.StringVarP(&opts.KarmadaDataPath, "karmada-data", "d", "/etc/karmada", "Karmada data path. kubeconfig cert and crds files")
|
flags.StringVarP(&opts.KarmadaDataPath, "karmada-data", "d", "/etc/karmada", "Karmada data path. kubeconfig cert and crds files")
|
||||||
|
@ -166,6 +170,7 @@ func NewCmdInit(parentCommand string) *cobra.Command {
|
||||||
flags.StringVarP(&opts.KarmadaAggregatedAPIServerImage, "karmada-aggregated-apiserver-image", "", kubernetes.DefaultKarmadaAggregatedAPIServerImage, "Karmada aggregated apiserver image")
|
flags.StringVarP(&opts.KarmadaAggregatedAPIServerImage, "karmada-aggregated-apiserver-image", "", kubernetes.DefaultKarmadaAggregatedAPIServerImage, "Karmada aggregated apiserver image")
|
||||||
flags.Int32VarP(&opts.KarmadaAggregatedAPIServerReplicas, "karmada-aggregated-apiserver-replicas", "", 1, "Karmada aggregated apiserver replica set")
|
flags.Int32VarP(&opts.KarmadaAggregatedAPIServerReplicas, "karmada-aggregated-apiserver-replicas", "", 1, "Karmada aggregated apiserver replica set")
|
||||||
flags.IntVarP(&opts.WaitComponentReadyTimeout, "wait-component-ready-timeout", "", cmdinitoptions.WaitComponentReadyTimeout, "Wait for karmada component ready timeout. 0 means wait forever")
|
flags.IntVarP(&opts.WaitComponentReadyTimeout, "wait-component-ready-timeout", "", cmdinitoptions.WaitComponentReadyTimeout, "Wait for karmada component ready timeout. 0 means wait forever")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 The Karmada 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
yamlserializer "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
|
||||||
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadInitConfiguration loads the InitConfiguration from the specified file path.
|
||||||
|
// It delegates the actual loading to the loadInitConfigurationFromFile function.
|
||||||
|
func LoadInitConfiguration(cfgPath string) (*KarmadaInitConfig, error) {
|
||||||
|
var config *KarmadaInitConfig
|
||||||
|
var err error
|
||||||
|
|
||||||
|
config, err = loadInitConfigurationFromFile(cfgPath)
|
||||||
|
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadInitConfigurationFromFile reads the file at the specified path and converts it into an InitConfiguration.
|
||||||
|
// It reads the file contents and then converts the bytes to an InitConfiguration.
|
||||||
|
func loadInitConfigurationFromFile(cfgPath string) (*KarmadaInitConfig, error) {
|
||||||
|
klog.V(1).Infof("loading configuration from %q", cfgPath)
|
||||||
|
|
||||||
|
b, err := os.ReadFile(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read config from %q: %v", cfgPath, err)
|
||||||
|
}
|
||||||
|
gvkmap, err := ParseGVKYamlMap(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return documentMapToInitConfiguration(gvkmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseGVKYamlMap parses a single YAML document into a map of GroupVersionKind to byte slices.
|
||||||
|
// This function is a simplified version that handles only a single YAML document.
|
||||||
|
func ParseGVKYamlMap(yamlBytes []byte) (map[schema.GroupVersionKind][]byte, error) {
|
||||||
|
gvkmap := make(map[schema.GroupVersionKind][]byte)
|
||||||
|
|
||||||
|
gvk, err := yamlserializer.DefaultMetaFactory.Interpret(yamlBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to interpret YAML document: %w", err)
|
||||||
|
}
|
||||||
|
if len(gvk.Group) == 0 || len(gvk.Version) == 0 || len(gvk.Kind) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid configuration for GroupVersionKind %+v: kind and apiVersion is mandatory information that must be specified", gvk)
|
||||||
|
}
|
||||||
|
gvkmap[*gvk] = yamlBytes
|
||||||
|
|
||||||
|
return gvkmap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentMapToInitConfiguration processes a map of GroupVersionKind to byte slices to extract the InitConfiguration.
|
||||||
|
// It iterates over the map, checking for the "InitConfiguration" kind, group, and version, and unmarshals its content into an InitConfiguration object.
|
||||||
|
func documentMapToInitConfiguration(gvkmap map[schema.GroupVersionKind][]byte) (*KarmadaInitConfig, error) {
|
||||||
|
var initcfg *KarmadaInitConfig
|
||||||
|
|
||||||
|
gvks := make([]schema.GroupVersionKind, 0, len(gvkmap))
|
||||||
|
for gvk := range gvkmap {
|
||||||
|
gvks = append(gvks, gvk)
|
||||||
|
}
|
||||||
|
sort.Slice(gvks, func(i, j int) bool {
|
||||||
|
return gvks[i].String() < gvks[j].String()
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, gvk := range gvks {
|
||||||
|
fileContent := gvkmap[gvk]
|
||||||
|
if gvk.Kind == "KarmadaInitConfig" {
|
||||||
|
if gvk.Group != GroupName || gvk.Version != SchemeGroupVersion.Version {
|
||||||
|
return nil, fmt.Errorf("invalid Group or Version: expected group %q and version %q, but got group %q and version %q", GroupName, SchemeGroupVersion.Version, gvk.Group, gvk.Version)
|
||||||
|
}
|
||||||
|
initcfg = &KarmadaInitConfig{}
|
||||||
|
if err := yaml.Unmarshal(fileContent, initcfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if initcfg == nil {
|
||||||
|
return nil, fmt.Errorf("no KarmadaInitConfig kind was found in the YAML file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return initcfg, nil
|
||||||
|
}
|
|
@ -0,0 +1,426 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 The Karmada 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testConfig = `
|
||||||
|
apiVersion: config.karmada.io/v1alpha1
|
||||||
|
kind: KarmadaInitConfig
|
||||||
|
metadata:
|
||||||
|
name: karmada-init
|
||||||
|
spec:
|
||||||
|
certificates:
|
||||||
|
caCertFile: "/etc/karmada/pki/ca.crt"
|
||||||
|
caKeyFile: "/etc/karmada/pki/ca.key"
|
||||||
|
externalDNS:
|
||||||
|
- "localhost"
|
||||||
|
- "example.com"
|
||||||
|
externalIP:
|
||||||
|
- "192.168.1.2"
|
||||||
|
- "172.16.1.2"
|
||||||
|
validityPeriod: "8760h0m0s"
|
||||||
|
etcd:
|
||||||
|
local:
|
||||||
|
Repository: "registry.k8s.io/etcd"
|
||||||
|
Tag: "latest"
|
||||||
|
dataPath: "/var/lib/karmada-etcd"
|
||||||
|
initImage:
|
||||||
|
repository: "alpine"
|
||||||
|
tag: "3.19.1"
|
||||||
|
nodeSelectorLabels:
|
||||||
|
karmada.io/etcd: "true"
|
||||||
|
pvcSize: "5Gi"
|
||||||
|
replicas: 3
|
||||||
|
storageClassesName: "fast"
|
||||||
|
storageMode: "PVC"
|
||||||
|
external:
|
||||||
|
endpoints:
|
||||||
|
- "https://example.com:8443"
|
||||||
|
caFile: "/path/to/your/ca.crt"
|
||||||
|
certFile: "/path/to/your/cert.crt"
|
||||||
|
keyFile: "/path/to/your/key.key"
|
||||||
|
keyPrefix: "ext-"
|
||||||
|
hostCluster:
|
||||||
|
apiEndpoint: "https://kubernetes.example.com"
|
||||||
|
kubeconfig: "/root/.kube/config"
|
||||||
|
context: "karmada-host"
|
||||||
|
domain: "cluster.local"
|
||||||
|
images:
|
||||||
|
imagePullPolicy: "IfNotPresent"
|
||||||
|
imagePullSecrets:
|
||||||
|
- "PullSecret1"
|
||||||
|
- "PullSecret2"
|
||||||
|
kubeImageMirrorCountry: "cn"
|
||||||
|
kubeImageRegistry: "registry.cn-hangzhou.aliyuncs.com/google_containers"
|
||||||
|
kubeImageTag: "v1.29.6"
|
||||||
|
privateRegistry:
|
||||||
|
registry: "my.private.registry"
|
||||||
|
components:
|
||||||
|
karmadaAPIServer:
|
||||||
|
repository: "karmada/kube-apiserver"
|
||||||
|
tag: "v1.29.6"
|
||||||
|
replicas: 1
|
||||||
|
advertiseAddress: "192.168.1.100"
|
||||||
|
serviceType: "NodePort"
|
||||||
|
networking:
|
||||||
|
namespace: "karmada-system"
|
||||||
|
port: 32443
|
||||||
|
karmadaAggregatedAPIServer:
|
||||||
|
repository: "karmada/karmada-aggregated-apiserver"
|
||||||
|
tag: "v0.0.0-master"
|
||||||
|
replicas: 1
|
||||||
|
kubeControllerManager:
|
||||||
|
repository: "karmada/kube-controller-manager"
|
||||||
|
tag: "v1.29.6"
|
||||||
|
replicas: 1
|
||||||
|
karmadaControllerManager:
|
||||||
|
repository: "karmada/karmada-controller-manager"
|
||||||
|
tag: "v0.0.0-master"
|
||||||
|
replicas: 1
|
||||||
|
karmadaScheduler:
|
||||||
|
repository: "karmada/karmada-scheduler"
|
||||||
|
tag: "v0.0.0-master"
|
||||||
|
replicas: 1
|
||||||
|
karmadaWebhook:
|
||||||
|
repository: "karmada/karmada-webhook"
|
||||||
|
tag: "v0.0.0-master"
|
||||||
|
replicas: 1
|
||||||
|
karmadaDataPath: "/etc/karmada"
|
||||||
|
karmadaPKIPath: "/etc/karmada/pki"
|
||||||
|
karmadaCRDs: "https://github.com/karmada-io/karmada/releases/download/test/crds.tar.gz"
|
||||||
|
waitComponentReadyTimeout: 120
|
||||||
|
`
|
||||||
|
|
||||||
|
const invalidTestConfig = `
|
||||||
|
apiVersion: v1alpha1
|
||||||
|
kind: KarmadaInitConfig
|
||||||
|
metadata:
|
||||||
|
name: karmada-init
|
||||||
|
spec:
|
||||||
|
waitComponentReadyTimeout: "invalid-int"
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestLoadInitConfiguration(t *testing.T) {
|
||||||
|
expectedConfig := &KarmadaInitConfig{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "KarmadaInitConfig",
|
||||||
|
APIVersion: "config.karmada.io/v1alpha1",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "karmada-init",
|
||||||
|
},
|
||||||
|
Spec: KarmadaInitSpec{
|
||||||
|
WaitComponentReadyTimeout: 120,
|
||||||
|
KarmadaDataPath: "/etc/karmada",
|
||||||
|
KarmadaPKIPath: "/etc/karmada/pki",
|
||||||
|
KarmadaCRDs: "https://github.com/karmada-io/karmada/releases/download/test/crds.tar.gz",
|
||||||
|
Certificates: Certificates{
|
||||||
|
CACertFile: "/etc/karmada/pki/ca.crt",
|
||||||
|
CAKeyFile: "/etc/karmada/pki/ca.key",
|
||||||
|
ExternalDNS: []string{
|
||||||
|
"localhost",
|
||||||
|
"example.com",
|
||||||
|
},
|
||||||
|
ExternalIP: []string{
|
||||||
|
"192.168.1.2",
|
||||||
|
"172.16.1.2",
|
||||||
|
},
|
||||||
|
ValidityPeriod: metav1.Duration{Duration: parseDuration("8760h")},
|
||||||
|
},
|
||||||
|
Etcd: Etcd{
|
||||||
|
Local: &LocalEtcd{
|
||||||
|
CommonSettings: CommonSettings{
|
||||||
|
Image: Image{
|
||||||
|
Repository: "registry.k8s.io/etcd",
|
||||||
|
Tag: "latest",
|
||||||
|
},
|
||||||
|
Replicas: 3,
|
||||||
|
},
|
||||||
|
InitImage: Image{
|
||||||
|
Repository: "alpine",
|
||||||
|
Tag: "3.19.1",
|
||||||
|
},
|
||||||
|
DataPath: "/var/lib/karmada-etcd",
|
||||||
|
PVCSize: "5Gi",
|
||||||
|
NodeSelectorLabels: map[string]string{
|
||||||
|
"karmada.io/etcd": "true",
|
||||||
|
},
|
||||||
|
StorageClassesName: "fast",
|
||||||
|
StorageMode: "PVC",
|
||||||
|
},
|
||||||
|
External: &ExternalEtcd{
|
||||||
|
Endpoints: []string{
|
||||||
|
"https://example.com:8443",
|
||||||
|
},
|
||||||
|
CAFile: "/path/to/your/ca.crt",
|
||||||
|
CertFile: "/path/to/your/cert.crt",
|
||||||
|
KeyFile: "/path/to/your/key.key",
|
||||||
|
KeyPrefix: "ext-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HostCluster: HostCluster{
|
||||||
|
APIEndpoint: "https://kubernetes.example.com",
|
||||||
|
Kubeconfig: "/root/.kube/config",
|
||||||
|
Context: "karmada-host",
|
||||||
|
Domain: "cluster.local",
|
||||||
|
},
|
||||||
|
Images: Images{
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
ImagePullSecrets: []string{"PullSecret1", "PullSecret2"},
|
||||||
|
KubeImageMirrorCountry: "cn",
|
||||||
|
KubeImageRegistry: "registry.cn-hangzhou.aliyuncs.com/google_containers",
|
||||||
|
KubeImageTag: "v1.29.6",
|
||||||
|
PrivateRegistry: &ImageRegistry{
|
||||||
|
Registry: "my.private.registry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Components: KarmadaComponents{
|
||||||
|
KarmadaAPIServer: &KarmadaAPIServer{
|
||||||
|
CommonSettings: CommonSettings{
|
||||||
|
Image: Image{
|
||||||
|
Repository: "karmada/kube-apiserver",
|
||||||
|
Tag: "v1.29.6",
|
||||||
|
},
|
||||||
|
Replicas: 1,
|
||||||
|
},
|
||||||
|
AdvertiseAddress: "192.168.1.100",
|
||||||
|
Networking: Networking{
|
||||||
|
Namespace: "karmada-system",
|
||||||
|
Port: 32443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KarmadaAggregatedAPIServer: &KarmadaAggregatedAPIServer{
|
||||||
|
CommonSettings: CommonSettings{
|
||||||
|
Image: Image{
|
||||||
|
Repository: "karmada/karmada-aggregated-apiserver",
|
||||||
|
Tag: "v0.0.0-master",
|
||||||
|
},
|
||||||
|
Replicas: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KubeControllerManager: &KubeControllerManager{
|
||||||
|
CommonSettings: CommonSettings{
|
||||||
|
Image: Image{
|
||||||
|
Repository: "karmada/kube-controller-manager",
|
||||||
|
Tag: "v1.29.6",
|
||||||
|
},
|
||||||
|
Replicas: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KarmadaControllerManager: &KarmadaControllerManager{
|
||||||
|
CommonSettings: CommonSettings{
|
||||||
|
Image: Image{
|
||||||
|
Repository: "karmada/karmada-controller-manager",
|
||||||
|
Tag: "v0.0.0-master",
|
||||||
|
},
|
||||||
|
Replicas: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KarmadaScheduler: &KarmadaScheduler{
|
||||||
|
CommonSettings: CommonSettings{
|
||||||
|
Image: Image{
|
||||||
|
Repository: "karmada/karmada-scheduler",
|
||||||
|
Tag: "v0.0.0-master",
|
||||||
|
},
|
||||||
|
Replicas: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KarmadaWebhook: &KarmadaWebhook{
|
||||||
|
CommonSettings: CommonSettings{
|
||||||
|
Image: Image{
|
||||||
|
Repository: "karmada/karmada-webhook",
|
||||||
|
Tag: "v0.0.0-master",
|
||||||
|
},
|
||||||
|
Replicas: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Test Load Valid Configuration", func(t *testing.T) {
|
||||||
|
tmpFile, err := os.CreateTemp("", "test-config-*.yaml")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
|
||||||
|
_, err = tmpFile.Write([]byte(testConfig))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = tmpFile.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
config, err := LoadInitConfiguration(tmpFile.Name())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedConfig, config)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Load Invalid Configuration", func(t *testing.T) {
|
||||||
|
tmpFile, err := os.CreateTemp("", "invalid-config-*.yaml")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer os.Remove(tmpFile.Name())
|
||||||
|
|
||||||
|
_, err = tmpFile.Write([]byte(invalidTestConfig))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = tmpFile.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = LoadInitConfiguration(tmpFile.Name())
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Load Non-Existent Configuration", func(t *testing.T) {
|
||||||
|
_, err := LoadInitConfiguration("non-existent-file.yaml")
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGVKYamlMap(t *testing.T) {
|
||||||
|
t.Run("Test Parse Valid GVK Yaml", func(t *testing.T) {
|
||||||
|
gvkmap, err := ParseGVKYamlMap([]byte(testConfig))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, gvkmap)
|
||||||
|
|
||||||
|
// Check if the GVK is correct
|
||||||
|
for gvk := range gvkmap {
|
||||||
|
assert.Equal(t, "config.karmada.io", gvk.Group)
|
||||||
|
assert.Equal(t, "v1alpha1", gvk.Version)
|
||||||
|
assert.Equal(t, "KarmadaInitConfig", gvk.Kind)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Parse Invalid GVK Yaml - Incorrect Group/Version/Kind", func(t *testing.T) {
|
||||||
|
invalidGVKConfig := `
|
||||||
|
apiVersion: invalid.group/v1beta1
|
||||||
|
kind: InvalidKind
|
||||||
|
metadata:
|
||||||
|
name: invalid-config
|
||||||
|
spec:
|
||||||
|
key: value
|
||||||
|
`
|
||||||
|
gvkmap, err := ParseGVKYamlMap([]byte(invalidGVKConfig))
|
||||||
|
assert.NoError(t, err, "Expected error due to invalid Group/Version/Kind")
|
||||||
|
|
||||||
|
for gvk := range gvkmap {
|
||||||
|
assert.Equal(t, "invalid.group", gvk.Group)
|
||||||
|
assert.Equal(t, "v1beta1", gvk.Version)
|
||||||
|
assert.Equal(t, "InvalidKind", gvk.Kind)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Parse Invalid Yaml - Bad Formatting", func(t *testing.T) {
|
||||||
|
// This YAML has invalid formatting (bad indentation)
|
||||||
|
invalidFormattedYAML := `
|
||||||
|
apiVersion: config.karmada.io/v1alpha1
|
||||||
|
kind: KarmadaInitConfig
|
||||||
|
metadata:
|
||||||
|
name: invalid-format
|
||||||
|
spec:
|
||||||
|
certificates:
|
||||||
|
caCertFile: /etc/karmada/pki/ca.crt
|
||||||
|
caKeyFile: /etc/karmada/pki/ca.key
|
||||||
|
externalDNS
|
||||||
|
- "localhost"
|
||||||
|
`
|
||||||
|
_, err := ParseGVKYamlMap([]byte(invalidFormattedYAML))
|
||||||
|
assert.Error(t, err, "Expected error due to incorrect YAML formatting")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Parse Empty Yaml", func(t *testing.T) {
|
||||||
|
_, err := ParseGVKYamlMap([]byte{})
|
||||||
|
assert.Error(t, err, "Expected error due to empty YAML")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDocumentMapToInitConfiguration(t *testing.T) {
|
||||||
|
t.Run("Test Valid GVK Map to InitConfiguration", func(t *testing.T) {
|
||||||
|
gvkmap, err := ParseGVKYamlMap([]byte(testConfig))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
config, err := documentMapToInitConfiguration(gvkmap)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, config)
|
||||||
|
assert.Equal(t, "KarmadaInitConfig", config.Kind)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Invalid GVK Map with Missing Kind", func(t *testing.T) {
|
||||||
|
// Create a GVK map with an invalid Kind
|
||||||
|
invalidGVK := map[schema.GroupVersionKind][]byte{
|
||||||
|
{Group: "config.karmada.io", Version: "v1alpha1", Kind: "InvalidKind"}: []byte(testConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := documentMapToInitConfiguration(invalidGVK)
|
||||||
|
assert.Error(t, err, "Expected error due to missing KarmadaInitConfig kind")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Invalid GVK with Wrong Group and Version", func(t *testing.T) {
|
||||||
|
invalidGVKConfig := `
|
||||||
|
apiVersion: wrong.group/v0alpha1
|
||||||
|
kind: KarmadaInitConfig
|
||||||
|
metadata:
|
||||||
|
name: invalid-config
|
||||||
|
`
|
||||||
|
gvkmap, err := ParseGVKYamlMap([]byte(invalidGVKConfig))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = documentMapToInitConfiguration(gvkmap)
|
||||||
|
assert.Error(t, err, "Expected error due to incorrect Group or Version")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Multiple GVKs with Only One KarmadaInitConfig", func(t *testing.T) {
|
||||||
|
multiGVKConfig := `
|
||||||
|
apiVersion: config.karmada.io/v1alpha1
|
||||||
|
kind: KarmadaInitConfig
|
||||||
|
metadata:
|
||||||
|
name: valid-config
|
||||||
|
---
|
||||||
|
apiVersion: other.group/v1beta1
|
||||||
|
kind: OtherConfig
|
||||||
|
metadata:
|
||||||
|
name: other-config
|
||||||
|
`
|
||||||
|
gvkmap, err := ParseGVKYamlMap([]byte(multiGVKConfig))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
config, err := documentMapToInitConfiguration(gvkmap)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, config)
|
||||||
|
assert.Equal(t, "KarmadaInitConfig", config.Kind)
|
||||||
|
|
||||||
|
// Ensure the other config is ignored
|
||||||
|
assert.Len(t, gvkmap, 1, fmt.Sprintf("Expect only 1 GVKs in the map, but got %d", len(gvkmap)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDuration parses a duration string and returns the corresponding time.Duration value.
|
||||||
|
// If the parsing fails, it returns a duration of 0.
|
||||||
|
func parseDuration(durationStr string) time.Duration {
|
||||||
|
duration, err := time.ParseDuration(durationStr)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return duration
|
||||||
|
}
|
|
@ -0,0 +1,354 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 The Karmada 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupName is the group name use in this package
|
||||||
|
const GroupName = "config.karmada.io"
|
||||||
|
|
||||||
|
// SchemeGroupVersion is group version used to register these objects
|
||||||
|
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
|
||||||
|
|
||||||
|
// KarmadaInitConfig defines the configuration for initializing Karmada
|
||||||
|
type KarmadaInitConfig struct {
|
||||||
|
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
|
||||||
|
|
||||||
|
// Spec defines the desired state for initializing Karmada
|
||||||
|
// +optional
|
||||||
|
Spec KarmadaInitSpec `json:"spec,omitempty" yaml:"spec,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KarmadaInitSpec is the specification part of KarmadaInitConfig, containing all configurable options
|
||||||
|
type KarmadaInitSpec struct {
|
||||||
|
// Certificates configures the certificate information required by Karmada
|
||||||
|
// +optional
|
||||||
|
Certificates Certificates `json:"certificates,omitempty" yaml:"certificates,omitempty"`
|
||||||
|
|
||||||
|
// Etcd configures the information of the Etcd cluster
|
||||||
|
// +optional
|
||||||
|
Etcd Etcd `json:"etcd,omitempty" yaml:"etcd,omitempty"`
|
||||||
|
|
||||||
|
// HostCluster configures the information of the host cluster
|
||||||
|
// +optional
|
||||||
|
HostCluster HostCluster `json:"hostCluster,omitempty" yaml:"hostCluster,omitempty"`
|
||||||
|
|
||||||
|
// Images configures image-related information
|
||||||
|
// +optional
|
||||||
|
Images Images `json:"images,omitempty" yaml:"images,omitempty"`
|
||||||
|
|
||||||
|
// Components configures information about Karmada components
|
||||||
|
// +optional
|
||||||
|
Components KarmadaComponents `json:"components,omitempty" yaml:"components,omitempty"`
|
||||||
|
|
||||||
|
// KarmadaCRDs configures the Karmada CRDs to be installed
|
||||||
|
// +optional
|
||||||
|
KarmadaCRDs string `json:"karmadaCRDs,omitempty" yaml:"karmadaCRDs,omitempty"`
|
||||||
|
|
||||||
|
// KarmadaDataPath configures the data directory for Karmada
|
||||||
|
// +optional
|
||||||
|
KarmadaDataPath string `json:"karmadaDataPath,omitempty" yaml:"karmadaDataPath,omitempty"`
|
||||||
|
|
||||||
|
// KarmadaPKIPath configures the PKI directory for Karmada
|
||||||
|
// +optional
|
||||||
|
KarmadaPKIPath string `json:"karmadaPKIPath,omitempty" yaml:"karmadaPKIPath,omitempty"`
|
||||||
|
|
||||||
|
// WaitComponentReadyTimeout configures the timeout (in seconds) for waiting for components to be ready
|
||||||
|
// +optional
|
||||||
|
WaitComponentReadyTimeout int `json:"waitComponentReadyTimeout,omitempty" yaml:"waitComponentReadyTimeout,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificates defines the configuration related to certificates
|
||||||
|
type Certificates struct {
|
||||||
|
// CACertFile is the path to the root CA certificate file
|
||||||
|
// +optional
|
||||||
|
CACertFile string `json:"caCertFile,omitempty" yaml:"caCertFile,omitempty"`
|
||||||
|
|
||||||
|
// CAKeyFile is the path to the root CA key file
|
||||||
|
// +optional
|
||||||
|
CAKeyFile string `json:"caKeyFile,omitempty" yaml:"caKeyFile,omitempty"`
|
||||||
|
|
||||||
|
// ExternalDNS is the list of external DNS names for the certificate
|
||||||
|
// +optional
|
||||||
|
ExternalDNS []string `json:"externalDNS,omitempty" yaml:"externalDNS,omitempty"`
|
||||||
|
|
||||||
|
// ExternalIP is the list of external IPs for the certificate
|
||||||
|
// +optional
|
||||||
|
ExternalIP []string `json:"externalIP,omitempty" yaml:"externalIP,omitempty"`
|
||||||
|
|
||||||
|
// ValidityPeriod is the validity period of the certificate
|
||||||
|
// +optional
|
||||||
|
ValidityPeriod metav1.Duration `json:"validityPeriod,omitempty" yaml:"validityPeriod,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Etcd defines the configuration of the Etcd cluster
|
||||||
|
type Etcd struct {
|
||||||
|
// Local indicates using a local Etcd cluster
|
||||||
|
// +optional
|
||||||
|
Local *LocalEtcd `json:"local,omitempty" yaml:"local,omitempty"`
|
||||||
|
|
||||||
|
// External indicates using an external Etcd cluster
|
||||||
|
// +optional
|
||||||
|
External *ExternalEtcd `json:"external,omitempty" yaml:"external,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalEtcd defines the configuration of a local Etcd cluster
|
||||||
|
type LocalEtcd struct {
|
||||||
|
// CommonSettings contains common settings like image and resources
|
||||||
|
CommonSettings `json:",inline" yaml:",inline"`
|
||||||
|
|
||||||
|
// DataPath is the data storage path for Etcd
|
||||||
|
// +optional
|
||||||
|
DataPath string `json:"dataPath,omitempty" yaml:"dataPath,omitempty"`
|
||||||
|
|
||||||
|
// InitImage is the image for the Etcd init container
|
||||||
|
// +optional
|
||||||
|
InitImage Image `json:"initImage,omitempty" yaml:"initImage,omitempty"`
|
||||||
|
|
||||||
|
// NodeSelectorLabels are the node selector labels for the Etcd pods
|
||||||
|
// +optional
|
||||||
|
NodeSelectorLabels map[string]string `json:"nodeSelectorLabels,omitempty" yaml:"nodeSelectorLabels,omitempty"`
|
||||||
|
|
||||||
|
// PVCSize is the size of the PersistentVolumeClaim for Etcd
|
||||||
|
// +optional
|
||||||
|
PVCSize string `json:"pvcSize,omitempty" yaml:"pvcSize,omitempty"`
|
||||||
|
|
||||||
|
// StorageMode is the storage mode for Etcd (e.g., emptyDir, hostPath, PVC)
|
||||||
|
// +optional
|
||||||
|
StorageMode string `json:"storageMode,omitempty" yaml:"storageMode,omitempty"`
|
||||||
|
|
||||||
|
// StorageClassesName is the name of the storage class for the Etcd PVC
|
||||||
|
// +optional
|
||||||
|
StorageClassesName string `json:"storageClassesName,omitempty" yaml:"storageClassesName,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalEtcd defines the configuration of an external Etcd cluster
|
||||||
|
type ExternalEtcd struct {
|
||||||
|
// Endpoints are the server addresses of the external Etcd cluster
|
||||||
|
// +required
|
||||||
|
Endpoints []string `json:"endpoints" yaml:"endpoints"`
|
||||||
|
|
||||||
|
// CAFile is the path to the CA certificate for the external Etcd cluster
|
||||||
|
// +optional
|
||||||
|
CAFile string `json:"caFile,omitempty" yaml:"caFile,omitempty"`
|
||||||
|
|
||||||
|
// CertFile is the path to the client certificate for the external Etcd cluster
|
||||||
|
// +optional
|
||||||
|
CertFile string `json:"certFile,omitempty" yaml:"certFile,omitempty"`
|
||||||
|
|
||||||
|
// KeyFile is the path to the client key for the external Etcd cluster
|
||||||
|
// +optional
|
||||||
|
KeyFile string `json:"keyFile,omitempty" yaml:"keyFile,omitempty"`
|
||||||
|
|
||||||
|
// KeyPrefix is the key prefix used in the external Etcd cluster
|
||||||
|
// +optional
|
||||||
|
KeyPrefix string `json:"keyPrefix,omitempty" yaml:"keyPrefix,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HostCluster defines the configuration of the host cluster
|
||||||
|
type HostCluster struct {
|
||||||
|
// APIEndpoint is the API server address of the host cluster
|
||||||
|
// +optional
|
||||||
|
APIEndpoint string `json:"apiEndpoint,omitempty" yaml:"apiEndpoint,omitempty"`
|
||||||
|
|
||||||
|
// Kubeconfig is the path to the kubeconfig file for the host cluster
|
||||||
|
// +optional
|
||||||
|
Kubeconfig string `json:"kubeconfig,omitempty" yaml:"kubeconfig,omitempty"`
|
||||||
|
|
||||||
|
// Context is the context name in the kubeconfig for the host cluster
|
||||||
|
// +optional
|
||||||
|
Context string `json:"context,omitempty" yaml:"context,omitempty"`
|
||||||
|
|
||||||
|
// Domain is the domain name of the host cluster
|
||||||
|
// +optional
|
||||||
|
Domain string `json:"domain,omitempty" yaml:"domain,omitempty"`
|
||||||
|
|
||||||
|
// SecretRef refers to the credentials needed to access the host cluster
|
||||||
|
// +optional
|
||||||
|
SecretRef *LocalSecretReference `json:"secretRef,omitempty" yaml:"secretRef,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Images defines the configuration related to images
|
||||||
|
type Images struct {
|
||||||
|
// ImagePullPolicy is the pull policy for images
|
||||||
|
// +optional
|
||||||
|
ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty" yaml:"imagePullPolicy,omitempty"`
|
||||||
|
|
||||||
|
// ImagePullSecrets are the secrets used for pulling images
|
||||||
|
// +optional
|
||||||
|
ImagePullSecrets []string `json:"imagePullSecrets,omitempty" yaml:"imagePullSecrets,omitempty"`
|
||||||
|
|
||||||
|
// KubeImageMirrorCountry is the country code for the Kubernetes image mirror
|
||||||
|
// +optional
|
||||||
|
KubeImageMirrorCountry string `json:"kubeImageMirrorCountry,omitempty" yaml:"kubeImageMirrorCountry,omitempty"`
|
||||||
|
|
||||||
|
// KubeImageRegistry is the registry for Kubernetes images
|
||||||
|
// +optional
|
||||||
|
KubeImageRegistry string `json:"kubeImageRegistry,omitempty" yaml:"kubeImageRegistry,omitempty"`
|
||||||
|
|
||||||
|
// KubeImageTag is the tag for Kubernetes images
|
||||||
|
// +optional
|
||||||
|
KubeImageTag string `json:"kubeImageTag,omitempty" yaml:"kubeImageTag,omitempty"`
|
||||||
|
|
||||||
|
// PrivateRegistry is the private image registry
|
||||||
|
// +optional
|
||||||
|
PrivateRegistry *ImageRegistry `json:"privateRegistry,omitempty" yaml:"privateRegistry,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KarmadaComponents defines the configuration for all Karmada components
|
||||||
|
type KarmadaComponents struct {
|
||||||
|
// KarmadaAPIServer is the configuration for the Karmada API Server
|
||||||
|
// +optional
|
||||||
|
KarmadaAPIServer *KarmadaAPIServer `json:"karmadaAPIServer,omitempty" yaml:"karmadaAPIServer,omitempty"`
|
||||||
|
|
||||||
|
// KarmadaAggregatedAPIServer is the configuration for the Karmada Aggregated API Server
|
||||||
|
// +optional
|
||||||
|
KarmadaAggregatedAPIServer *KarmadaAggregatedAPIServer `json:"karmadaAggregatedAPIServer,omitempty" yaml:"karmadaAggregatedAPIServer,omitempty"`
|
||||||
|
|
||||||
|
// KubeControllerManager is the configuration for the Kube Controller Manager
|
||||||
|
// +optional
|
||||||
|
KubeControllerManager *KubeControllerManager `json:"kubeControllerManager,omitempty" yaml:"kubeControllerManager,omitempty"`
|
||||||
|
|
||||||
|
// KarmadaControllerManager is the configuration for the Karmada Controller Manager
|
||||||
|
// +optional
|
||||||
|
KarmadaControllerManager *KarmadaControllerManager `json:"karmadaControllerManager,omitempty" yaml:"karmadaControllerManager,omitempty"`
|
||||||
|
|
||||||
|
// KarmadaScheduler is the configuration for the Karmada Scheduler
|
||||||
|
// +optional
|
||||||
|
KarmadaScheduler *KarmadaScheduler `json:"karmadaScheduler,omitempty" yaml:"karmadaScheduler,omitempty"`
|
||||||
|
|
||||||
|
// KarmadaWebhook is the configuration for the Karmada Webhook
|
||||||
|
// +optional
|
||||||
|
KarmadaWebhook *KarmadaWebhook `json:"karmadaWebhook,omitempty" yaml:"karmadaWebhook,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Networking defines network-related configuration
|
||||||
|
type Networking struct {
|
||||||
|
// Namespace is the Kubernetes namespace where Karmada is deployed
|
||||||
|
// +optional
|
||||||
|
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||||
|
|
||||||
|
// Port is the port number for the Karmada API Server
|
||||||
|
// +optional
|
||||||
|
Port int32 `json:"port,omitempty" yaml:"port,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommonSettings defines common settings for components
|
||||||
|
type CommonSettings struct {
|
||||||
|
// Image specifies the image to use for the component
|
||||||
|
Image `json:",inline" yaml:",inline"`
|
||||||
|
|
||||||
|
// Replicas is the number of replicas for the component
|
||||||
|
// +optional
|
||||||
|
Replicas int32 `json:"replicas,omitempty" yaml:"replicas,omitempty"`
|
||||||
|
|
||||||
|
// Resources defines resource requests and limits for the component
|
||||||
|
// +optional
|
||||||
|
Resources corev1.ResourceRequirements `json:"resources,omitempty" yaml:"resources,omitempty"`
|
||||||
|
|
||||||
|
// NodeSelector defines node selection constraints
|
||||||
|
// +optional
|
||||||
|
NodeSelector map[string]string `json:"nodeSelector,omitempty" yaml:"nodeSelector,omitempty"`
|
||||||
|
|
||||||
|
// Tolerations define pod tolerations
|
||||||
|
// +optional
|
||||||
|
Tolerations []corev1.Toleration `json:"tolerations,omitempty" yaml:"tolerations,omitempty"`
|
||||||
|
|
||||||
|
// Affinity defines pod affinity rules
|
||||||
|
// +optional
|
||||||
|
Affinity *corev1.Affinity `json:"affinity,omitempty" yaml:"affinity,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image defines image information
|
||||||
|
type Image struct {
|
||||||
|
// Repository is the repository for the image
|
||||||
|
// +optional
|
||||||
|
Repository string `json:"repository,omitempty" yaml:"repository,omitempty"`
|
||||||
|
|
||||||
|
// Tag is the tag for the image
|
||||||
|
// +optional
|
||||||
|
Tag string `json:"tag,omitempty" yaml:"tag,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KarmadaAPIServer defines the configuration for the Karmada API Server
|
||||||
|
type KarmadaAPIServer struct {
|
||||||
|
CommonSettings `json:",inline" yaml:",inline"`
|
||||||
|
|
||||||
|
// AdvertiseAddress is the address advertised by the API server
|
||||||
|
// +optional
|
||||||
|
AdvertiseAddress string `json:"advertiseAddress,omitempty" yaml:"advertiseAddress,omitempty"`
|
||||||
|
|
||||||
|
// Networking configures network-related information
|
||||||
|
// +optional
|
||||||
|
Networking Networking `json:"networking,omitempty" yaml:"networking,omitempty"`
|
||||||
|
|
||||||
|
// ServiceAnnotations are annotations added to the API server service
|
||||||
|
// +optional
|
||||||
|
ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty" yaml:"serviceAnnotations,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KarmadaAggregatedAPIServer defines the configuration for the Karmada Aggregated API Server
|
||||||
|
type KarmadaAggregatedAPIServer struct {
|
||||||
|
CommonSettings `json:",inline" yaml:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KubeControllerManager defines the configuration for the Kube Controller Manager
|
||||||
|
type KubeControllerManager struct {
|
||||||
|
CommonSettings `json:",inline" yaml:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KarmadaControllerManager defines the configuration for the Karmada Controller Manager
|
||||||
|
type KarmadaControllerManager struct {
|
||||||
|
CommonSettings `json:",inline" yaml:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KarmadaScheduler defines the configuration for the Karmada Scheduler
|
||||||
|
type KarmadaScheduler struct {
|
||||||
|
CommonSettings `json:",inline" yaml:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// KarmadaWebhook defines the configuration for the Karmada Webhook
|
||||||
|
type KarmadaWebhook struct {
|
||||||
|
CommonSettings `json:",inline" yaml:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalSecretReference is a reference to a secret within the same namespace
|
||||||
|
type LocalSecretReference struct {
|
||||||
|
// Name is the name of the referenced secret
|
||||||
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageRegistry represents an image registry
|
||||||
|
type ImageRegistry struct {
|
||||||
|
// Registry is the hostname of the image registry
|
||||||
|
// +required
|
||||||
|
Registry string `json:"registry" yaml:"registry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetImage generates the full image string in the format "Repository:Tag"
|
||||||
|
// by combining the image repository and tag fields.
|
||||||
|
func (i *Image) GetImage() string {
|
||||||
|
if i.Tag == "" || i.Repository == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return i.Repository + ":" + i.Tag
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ import (
|
||||||
netutils "k8s.io/utils/net"
|
netutils "k8s.io/utils/net"
|
||||||
|
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/cert"
|
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/cert"
|
||||||
|
initConfig "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/config"
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/karmada"
|
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/karmada"
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/options"
|
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/options"
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
||||||
|
@ -174,6 +175,7 @@ type CommandInitOption struct {
|
||||||
WaitComponentReadyTimeout int
|
WaitComponentReadyTimeout int
|
||||||
CaCertFile string
|
CaCertFile string
|
||||||
CaKeyFile string
|
CaKeyFile string
|
||||||
|
KarmadaInitFilePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *CommandInitOption) validateLocalEtcd(parentCommand string) error {
|
func (i *CommandInitOption) validateLocalEtcd(parentCommand string) error {
|
||||||
|
@ -219,6 +221,16 @@ func (i *CommandInitOption) isExternalEtcdProvided() bool {
|
||||||
|
|
||||||
// Validate Check that there are enough flags to run the command.
|
// Validate Check that there are enough flags to run the command.
|
||||||
func (i *CommandInitOption) Validate(parentCommand string) error {
|
func (i *CommandInitOption) Validate(parentCommand string) error {
|
||||||
|
if i.KarmadaInitFilePath != "" {
|
||||||
|
cfg, err := initConfig.LoadInitConfiguration(i.KarmadaInitFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load karmada init configuration: %v", err)
|
||||||
|
}
|
||||||
|
if err := i.parseInitConfig(cfg); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse karmada init configuration: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if i.KarmadaAPIServerAdvertiseAddress != "" {
|
if i.KarmadaAPIServerAdvertiseAddress != "" {
|
||||||
if netutils.ParseIPSloppy(i.KarmadaAPIServerAdvertiseAddress) == nil {
|
if netutils.ParseIPSloppy(i.KarmadaAPIServerAdvertiseAddress) == nil {
|
||||||
return fmt.Errorf("karmada apiserver advertise address is not valid")
|
return fmt.Errorf("karmada apiserver advertise address is not valid")
|
||||||
|
@ -276,8 +288,11 @@ func (i *CommandInitOption) Complete() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !i.isExternalEtcdProvided() && i.EtcdStorageMode == "hostPath" && i.EtcdNodeSelectorLabels != "" {
|
if !i.isExternalEtcdProvided() && i.EtcdStorageMode == "hostPath" && i.EtcdNodeSelectorLabels != "" {
|
||||||
if !i.isNodeExist(i.EtcdNodeSelectorLabels) {
|
labels := strings.Split(i.EtcdNodeSelectorLabels, ",")
|
||||||
return fmt.Errorf("no node found by label %s", i.EtcdNodeSelectorLabels)
|
for _, label := range labels {
|
||||||
|
if !i.isNodeExist(label) {
|
||||||
|
return fmt.Errorf("no node found by label %s", label)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return initializeDirectory(i.KarmadaDataPath)
|
return initializeDirectory(i.KarmadaDataPath)
|
||||||
|
@ -744,3 +759,225 @@ func generateServerURL(serverIP string, nodePort int32) (string, error) {
|
||||||
func SupportedStorageMode() []string {
|
func SupportedStorageMode() []string {
|
||||||
return []string{etcdStorageModeEmptyDir, etcdStorageModeHostPath, etcdStorageModePVC}
|
return []string{etcdStorageModeEmptyDir, etcdStorageModeHostPath, etcdStorageModePVC}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseEtcdNodeSelectorLabelsMap parse etcd node selector labels
|
||||||
|
func (i *CommandInitOption) parseEtcdNodeSelectorLabelsMap() error {
|
||||||
|
if i.EtcdNodeSelectorLabels == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Parse the label selector string into a LabelSelector object
|
||||||
|
selector, err := metav1.ParseToLabelSelector(i.EtcdNodeSelectorLabels)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("the etcdNodeSelector format is incorrect: %s", err)
|
||||||
|
}
|
||||||
|
// Convert the LabelSelector object into a map[string]string
|
||||||
|
labelMap, err := metav1.LabelSelectorAsMap(selector)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to convert etcdNodeSelector labels to map: %v", err)
|
||||||
|
}
|
||||||
|
i.EtcdNodeSelectorLabelsMap = labelMap
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseInitConfig parses fields from KarmadaInitConfig into CommandInitOption.
|
||||||
|
// It is responsible for delegating the parsing of various configuration sections,
|
||||||
|
// such as certificates, etcd, and control plane components.
|
||||||
|
func (i *CommandInitOption) parseInitConfig(cfg *initConfig.KarmadaInitConfig) error {
|
||||||
|
spec := cfg.Spec
|
||||||
|
|
||||||
|
i.parseGeneralConfig(spec)
|
||||||
|
i.parseCertificateConfig(spec.Certificates)
|
||||||
|
i.parseEtcdConfig(spec.Etcd)
|
||||||
|
i.parseControlPlaneConfig(spec.Components)
|
||||||
|
|
||||||
|
setIfNotEmpty(&i.KarmadaDataPath, spec.KarmadaDataPath)
|
||||||
|
setIfNotEmpty(&i.KarmadaPkiPath, spec.KarmadaPKIPath)
|
||||||
|
setIfNotEmpty(&i.HostClusterDomain, spec.HostCluster.Domain)
|
||||||
|
setIfNotEmpty(&i.CRDs, spec.KarmadaCRDs)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseGeneralConfig parses basic configuration related to the host cluster,
|
||||||
|
// such as namespace, kubeconfig, and image settings from the KarmadaInitConfigSpec.
|
||||||
|
func (i *CommandInitOption) parseGeneralConfig(spec initConfig.KarmadaInitSpec) {
|
||||||
|
setIfNotEmpty(&i.KubeConfig, spec.HostCluster.Kubeconfig)
|
||||||
|
setIfNotEmpty(&i.KubeImageTag, spec.Images.KubeImageTag)
|
||||||
|
setIfNotEmpty(&i.KubeImageRegistry, spec.Images.KubeImageRegistry)
|
||||||
|
setIfNotEmpty(&i.KubeImageMirrorCountry, spec.Images.KubeImageMirrorCountry)
|
||||||
|
|
||||||
|
if spec.Images.PrivateRegistry != nil {
|
||||||
|
setIfNotEmpty(&i.ImageRegistry, spec.Images.PrivateRegistry.Registry)
|
||||||
|
}
|
||||||
|
setIfNotEmpty(&i.ImagePullPolicy, string(spec.Images.ImagePullPolicy))
|
||||||
|
setIfNotEmpty(&i.Context, spec.HostCluster.Context)
|
||||||
|
|
||||||
|
if len(spec.Images.ImagePullSecrets) != 0 {
|
||||||
|
i.PullSecrets = spec.Images.ImagePullSecrets
|
||||||
|
}
|
||||||
|
setIfNotZero(&i.WaitComponentReadyTimeout, spec.WaitComponentReadyTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCertificateConfig parses certificate-related configuration, including CA files,
|
||||||
|
// external DNS, and external IP from the Certificates configuration block.
|
||||||
|
func (i *CommandInitOption) parseCertificateConfig(certificates initConfig.Certificates) {
|
||||||
|
setIfNotEmpty(&i.CaKeyFile, certificates.CAKeyFile)
|
||||||
|
setIfNotEmpty(&i.CaCertFile, certificates.CACertFile)
|
||||||
|
|
||||||
|
if len(certificates.ExternalDNS) > 0 {
|
||||||
|
i.ExternalDNS = joinStringSlice(certificates.ExternalDNS)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(certificates.ExternalIP) > 0 {
|
||||||
|
i.ExternalIP = joinStringSlice(certificates.ExternalIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if certificates.ValidityPeriod.Duration != 0 {
|
||||||
|
i.CertValidity = certificates.ValidityPeriod.Duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEtcdConfig handles the parsing of both local and external Etcd configurations.
|
||||||
|
func (i *CommandInitOption) parseEtcdConfig(etcd initConfig.Etcd) {
|
||||||
|
if etcd.Local != nil {
|
||||||
|
i.parseLocalEtcdConfig(etcd.Local)
|
||||||
|
} else if etcd.External != nil {
|
||||||
|
i.parseExternalEtcdConfig(etcd.External)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLocalEtcdConfig parses the local Etcd settings, including image information,
|
||||||
|
// data path, PVC size, and node selector labels.
|
||||||
|
func (i *CommandInitOption) parseLocalEtcdConfig(localEtcd *initConfig.LocalEtcd) {
|
||||||
|
setIfNotEmpty(&i.EtcdImage, localEtcd.CommonSettings.Image.GetImage())
|
||||||
|
setIfNotEmpty(&i.EtcdInitImage, localEtcd.InitImage.GetImage())
|
||||||
|
setIfNotEmpty(&i.EtcdHostDataPath, localEtcd.DataPath)
|
||||||
|
setIfNotEmpty(&i.EtcdPersistentVolumeSize, localEtcd.PVCSize)
|
||||||
|
|
||||||
|
if len(localEtcd.NodeSelectorLabels) != 0 {
|
||||||
|
i.EtcdNodeSelectorLabels = mapToString(localEtcd.NodeSelectorLabels)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIfNotEmpty(&i.EtcdStorageMode, localEtcd.StorageMode)
|
||||||
|
setIfNotEmpty(&i.StorageClassesName, localEtcd.StorageClassesName)
|
||||||
|
setIfNotZeroInt32(&i.EtcdReplicas, localEtcd.Replicas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExternalEtcdConfig parses the external Etcd configuration, including CA file,
|
||||||
|
// client certificates, and endpoints.
|
||||||
|
func (i *CommandInitOption) parseExternalEtcdConfig(externalEtcd *initConfig.ExternalEtcd) {
|
||||||
|
setIfNotEmpty(&i.ExternalEtcdCACertPath, externalEtcd.CAFile)
|
||||||
|
setIfNotEmpty(&i.ExternalEtcdClientCertPath, externalEtcd.CertFile)
|
||||||
|
setIfNotEmpty(&i.ExternalEtcdClientKeyPath, externalEtcd.KeyFile)
|
||||||
|
|
||||||
|
if len(externalEtcd.Endpoints) > 0 {
|
||||||
|
i.ExternalEtcdServers = strings.Join(externalEtcd.Endpoints, ",")
|
||||||
|
}
|
||||||
|
setIfNotEmpty(&i.ExternalEtcdKeyPrefix, externalEtcd.KeyPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseControlPlaneConfig parses the configuration for various control plane components,
|
||||||
|
// including API Server, Controller Manager, Scheduler, and Webhook.
|
||||||
|
func (i *CommandInitOption) parseControlPlaneConfig(components initConfig.KarmadaComponents) {
|
||||||
|
i.parseKarmadaAPIServerConfig(components.KarmadaAPIServer)
|
||||||
|
i.parseKarmadaControllerManagerConfig(components.KarmadaControllerManager)
|
||||||
|
i.parseKarmadaSchedulerConfig(components.KarmadaScheduler)
|
||||||
|
i.parseKarmadaWebhookConfig(components.KarmadaWebhook)
|
||||||
|
i.parseKarmadaAggregatedAPIServerConfig(components.KarmadaAggregatedAPIServer)
|
||||||
|
i.parseKubeControllerManagerConfig(components.KubeControllerManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKarmadaAPIServerConfig parses the configuration for the Karmada API Server component,
|
||||||
|
// including image and replica settings, as well as advertise address.
|
||||||
|
func (i *CommandInitOption) parseKarmadaAPIServerConfig(apiServer *initConfig.KarmadaAPIServer) {
|
||||||
|
if apiServer != nil {
|
||||||
|
setIfNotZeroInt32(&i.KarmadaAPIServerNodePort, apiServer.Networking.Port)
|
||||||
|
setIfNotEmpty(&i.Namespace, apiServer.Networking.Namespace)
|
||||||
|
setIfNotEmpty(&i.KarmadaAPIServerImage, apiServer.CommonSettings.Image.GetImage())
|
||||||
|
setIfNotZeroInt32(&i.KarmadaAPIServerReplicas, apiServer.CommonSettings.Replicas)
|
||||||
|
setIfNotEmpty(&i.KarmadaAPIServerAdvertiseAddress, apiServer.AdvertiseAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKarmadaControllerManagerConfig parses the configuration for the Karmada Controller Manager,
|
||||||
|
// including image and replica settings.
|
||||||
|
func (i *CommandInitOption) parseKarmadaControllerManagerConfig(manager *initConfig.KarmadaControllerManager) {
|
||||||
|
if manager != nil {
|
||||||
|
setIfNotEmpty(&i.KarmadaControllerManagerImage, manager.CommonSettings.Image.GetImage())
|
||||||
|
setIfNotZeroInt32(&i.KarmadaControllerManagerReplicas, manager.CommonSettings.Replicas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKarmadaSchedulerConfig parses the configuration for the Karmada Scheduler,
|
||||||
|
// including image and replica settings.
|
||||||
|
func (i *CommandInitOption) parseKarmadaSchedulerConfig(scheduler *initConfig.KarmadaScheduler) {
|
||||||
|
if scheduler != nil {
|
||||||
|
setIfNotEmpty(&i.KarmadaSchedulerImage, scheduler.CommonSettings.Image.GetImage())
|
||||||
|
setIfNotZeroInt32(&i.KarmadaSchedulerReplicas, scheduler.CommonSettings.Replicas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKarmadaWebhookConfig parses the configuration for the Karmada Webhook,
|
||||||
|
// including image and replica settings.
|
||||||
|
func (i *CommandInitOption) parseKarmadaWebhookConfig(webhook *initConfig.KarmadaWebhook) {
|
||||||
|
if webhook != nil {
|
||||||
|
setIfNotEmpty(&i.KarmadaWebhookImage, webhook.CommonSettings.Image.GetImage())
|
||||||
|
setIfNotZeroInt32(&i.KarmadaWebhookReplicas, webhook.CommonSettings.Replicas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKarmadaAggregatedAPIServerConfig parses the configuration for the Karmada Aggregated API Server,
|
||||||
|
// including image and replica settings.
|
||||||
|
func (i *CommandInitOption) parseKarmadaAggregatedAPIServerConfig(aggregatedAPIServer *initConfig.KarmadaAggregatedAPIServer) {
|
||||||
|
if aggregatedAPIServer != nil {
|
||||||
|
setIfNotEmpty(&i.KarmadaAggregatedAPIServerImage, aggregatedAPIServer.CommonSettings.Image.GetImage())
|
||||||
|
setIfNotZeroInt32(&i.KarmadaAggregatedAPIServerReplicas, aggregatedAPIServer.CommonSettings.Replicas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKubeControllerManagerConfig parses the configuration for the Kube Controller Manager,
|
||||||
|
// including image and replica settings.
|
||||||
|
func (i *CommandInitOption) parseKubeControllerManagerConfig(manager *initConfig.KubeControllerManager) {
|
||||||
|
if manager != nil {
|
||||||
|
setIfNotEmpty(&i.KubeControllerManagerImage, manager.CommonSettings.Image.GetImage())
|
||||||
|
setIfNotZeroInt32(&i.KubeControllerManagerReplicas, manager.CommonSettings.Replicas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapToString converts a map to a comma-separated key=value string.
|
||||||
|
func mapToString(m map[string]string) string {
|
||||||
|
var builder strings.Builder
|
||||||
|
for k, v := range m {
|
||||||
|
if builder.Len() > 0 {
|
||||||
|
builder.WriteString(",")
|
||||||
|
}
|
||||||
|
builder.WriteString(fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setIfNotEmpty checks if the source string is not empty, and if so, assigns its value to the destination string.
|
||||||
|
func setIfNotEmpty(dest *string, src string) {
|
||||||
|
if src != "" {
|
||||||
|
*dest = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setIfNotZero checks if the source integer is not zero, and if so, assigns its value to the destination integer.
|
||||||
|
func setIfNotZero(dest *int, src int) {
|
||||||
|
if src != 0 {
|
||||||
|
*dest = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setIfNotZeroInt32 checks if the source int32 is not zero, and if so, assigns its value to the destination int32.
|
||||||
|
func setIfNotZeroInt32(dest *int32, src int32) {
|
||||||
|
if src != 0 {
|
||||||
|
*dest = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// joinStringSlice joins a slice of strings into a single string separated by commas.
|
||||||
|
func joinStringSlice(slice []string) string {
|
||||||
|
return strings.Join(slice, ",")
|
||||||
|
}
|
||||||
|
|
|
@ -20,13 +20,16 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
|
||||||
|
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/config"
|
||||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -496,3 +499,237 @@ func TestKarmadaSchedulerImage(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommandInitOption_parseEtcdNodeSelectorLabelsMap(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
opt CommandInitOption
|
||||||
|
wantErr bool
|
||||||
|
expected map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid labels",
|
||||||
|
opt: CommandInitOption{
|
||||||
|
EtcdNodeSelectorLabels: "kubernetes.io/os=linux,hello=world",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
expected: map[string]string{
|
||||||
|
"kubernetes.io/os": "linux",
|
||||||
|
"hello": "world",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid labels without equal sign",
|
||||||
|
opt: CommandInitOption{
|
||||||
|
EtcdNodeSelectorLabels: "invalidlabel",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Labels with extra spaces",
|
||||||
|
opt: CommandInitOption{
|
||||||
|
EtcdNodeSelectorLabels: " key1 = value1 , key2=value2 ",
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
expected: map[string]string{
|
||||||
|
"key1": "value1",
|
||||||
|
"key2": "value2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := tt.opt.parseEtcdNodeSelectorLabelsMap()
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseEtcdNodeSelectorLabelsMap() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !tt.wantErr && !reflect.DeepEqual(tt.opt.EtcdNodeSelectorLabelsMap, tt.expected) {
|
||||||
|
t.Errorf("parseEtcdNodeSelectorLabelsMap() = %v, want %v", tt.opt.EtcdNodeSelectorLabelsMap, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInitConfig(t *testing.T) {
|
||||||
|
cfg := &config.KarmadaInitConfig{
|
||||||
|
Spec: config.KarmadaInitSpec{
|
||||||
|
WaitComponentReadyTimeout: 200,
|
||||||
|
KarmadaDataPath: "/etc/karmada",
|
||||||
|
KarmadaPKIPath: "/etc/karmada/pki",
|
||||||
|
KarmadaCRDs: "https://github.com/karmada-io/karmada/releases/download/test/crds.tar.gz",
|
||||||
|
Certificates: config.Certificates{
|
||||||
|
CACertFile: "/path/to/ca.crt",
|
||||||
|
CAKeyFile: "/path/to/ca.key",
|
||||||
|
ExternalDNS: []string{"dns1", "dns2"},
|
||||||
|
ExternalIP: []string{"1.2.3.4", "5.6.7.8"},
|
||||||
|
ValidityPeriod: metav1.Duration{Duration: parseDuration("8760h")},
|
||||||
|
},
|
||||||
|
Etcd: config.Etcd{
|
||||||
|
Local: &config.LocalEtcd{
|
||||||
|
CommonSettings: config.CommonSettings{
|
||||||
|
Image: config.Image{
|
||||||
|
Repository: "etcd-image",
|
||||||
|
Tag: "latest",
|
||||||
|
},
|
||||||
|
Replicas: 3,
|
||||||
|
},
|
||||||
|
InitImage: config.Image{
|
||||||
|
Repository: "init-image",
|
||||||
|
Tag: "latest",
|
||||||
|
},
|
||||||
|
DataPath: "/data/dir",
|
||||||
|
PVCSize: "5Gi",
|
||||||
|
NodeSelectorLabels: map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
StorageClassesName: "fast",
|
||||||
|
StorageMode: "PVC",
|
||||||
|
},
|
||||||
|
External: &config.ExternalEtcd{
|
||||||
|
CAFile: "/etc/ssl/certs/ca-certificates.crt",
|
||||||
|
CertFile: "/path/to/certificate.pem",
|
||||||
|
KeyFile: "/path/to/privatekey.pem",
|
||||||
|
Endpoints: []string{"https://example.com:8443"},
|
||||||
|
KeyPrefix: "ext-",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HostCluster: config.HostCluster{
|
||||||
|
Kubeconfig: "/path/to/kubeconfig",
|
||||||
|
Context: "test-context",
|
||||||
|
Domain: "cluster.local",
|
||||||
|
},
|
||||||
|
Images: config.Images{
|
||||||
|
KubeImageTag: "v1.21.0",
|
||||||
|
KubeImageRegistry: "registry",
|
||||||
|
KubeImageMirrorCountry: "cn",
|
||||||
|
ImagePullPolicy: corev1.PullIfNotPresent,
|
||||||
|
ImagePullSecrets: []string{"secret1", "secret2"},
|
||||||
|
PrivateRegistry: &config.ImageRegistry{
|
||||||
|
Registry: "test-registry",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Components: config.KarmadaComponents{
|
||||||
|
KarmadaAPIServer: &config.KarmadaAPIServer{
|
||||||
|
CommonSettings: config.CommonSettings{
|
||||||
|
Image: config.Image{
|
||||||
|
Repository: "apiserver-image",
|
||||||
|
Tag: "latest",
|
||||||
|
},
|
||||||
|
Replicas: 2,
|
||||||
|
},
|
||||||
|
AdvertiseAddress: "192.168.1.1",
|
||||||
|
Networking: config.Networking{
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
Port: 32443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KarmadaControllerManager: &config.KarmadaControllerManager{
|
||||||
|
CommonSettings: config.CommonSettings{
|
||||||
|
Image: config.Image{
|
||||||
|
Repository: "controller-manager-image",
|
||||||
|
Tag: "latest",
|
||||||
|
},
|
||||||
|
Replicas: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KarmadaScheduler: &config.KarmadaScheduler{
|
||||||
|
CommonSettings: config.CommonSettings{
|
||||||
|
Image: config.Image{
|
||||||
|
Repository: "scheduler-image",
|
||||||
|
Tag: "latest",
|
||||||
|
},
|
||||||
|
Replicas: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KarmadaWebhook: &config.KarmadaWebhook{
|
||||||
|
CommonSettings: config.CommonSettings{
|
||||||
|
Image: config.Image{
|
||||||
|
Repository: "webhook-image",
|
||||||
|
Tag: "latest",
|
||||||
|
},
|
||||||
|
Replicas: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KarmadaAggregatedAPIServer: &config.KarmadaAggregatedAPIServer{
|
||||||
|
CommonSettings: config.CommonSettings{
|
||||||
|
Image: config.Image{
|
||||||
|
Repository: "aggregated-apiserver-image",
|
||||||
|
Tag: "latest",
|
||||||
|
},
|
||||||
|
Replicas: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KubeControllerManager: &config.KubeControllerManager{
|
||||||
|
CommonSettings: config.CommonSettings{
|
||||||
|
Image: config.Image{
|
||||||
|
Repository: "kube-controller-manager-image",
|
||||||
|
Tag: "latest",
|
||||||
|
},
|
||||||
|
Replicas: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := &CommandInitOption{}
|
||||||
|
err := opt.parseInitConfig(cfg)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test-namespace", opt.Namespace)
|
||||||
|
assert.Equal(t, "/path/to/kubeconfig", opt.KubeConfig)
|
||||||
|
assert.Equal(t, "test-registry", opt.ImageRegistry)
|
||||||
|
assert.Equal(t, 200, opt.WaitComponentReadyTimeout)
|
||||||
|
assert.Equal(t, "dns1,dns2", opt.ExternalDNS)
|
||||||
|
assert.Equal(t, "1.2.3.4,5.6.7.8", opt.ExternalIP)
|
||||||
|
assert.Equal(t, parseDuration("8760h"), opt.CertValidity)
|
||||||
|
assert.Equal(t, "etcd-image:latest", opt.EtcdImage)
|
||||||
|
assert.Equal(t, "init-image:latest", opt.EtcdInitImage)
|
||||||
|
assert.Equal(t, "/data/dir", opt.EtcdHostDataPath)
|
||||||
|
assert.Equal(t, "5Gi", opt.EtcdPersistentVolumeSize)
|
||||||
|
assert.Equal(t, "key=value", opt.EtcdNodeSelectorLabels)
|
||||||
|
assert.Equal(t, "fast", opt.StorageClassesName)
|
||||||
|
assert.Equal(t, "PVC", opt.EtcdStorageMode)
|
||||||
|
assert.Equal(t, int32(3), opt.EtcdReplicas)
|
||||||
|
assert.Equal(t, "apiserver-image:latest", opt.KarmadaAPIServerImage)
|
||||||
|
assert.Equal(t, "192.168.1.1", opt.KarmadaAPIServerAdvertiseAddress)
|
||||||
|
assert.Equal(t, int32(2), opt.KarmadaAPIServerReplicas)
|
||||||
|
assert.Equal(t, "registry", opt.KubeImageRegistry)
|
||||||
|
assert.Equal(t, "cn", opt.KubeImageMirrorCountry)
|
||||||
|
assert.Equal(t, "IfNotPresent", opt.ImagePullPolicy)
|
||||||
|
assert.Equal(t, []string{"secret1", "secret2"}, opt.PullSecrets)
|
||||||
|
assert.Equal(t, "https://github.com/karmada-io/karmada/releases/download/test/crds.tar.gz", opt.CRDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInitConfig_MissingFields(t *testing.T) {
|
||||||
|
cfg := &config.KarmadaInitConfig{
|
||||||
|
Spec: config.KarmadaInitSpec{
|
||||||
|
Components: config.KarmadaComponents{
|
||||||
|
KarmadaAPIServer: &config.KarmadaAPIServer{
|
||||||
|
Networking: config.Networking{
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := &CommandInitOption{}
|
||||||
|
err := opt.parseInitConfig(cfg)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test-namespace", opt.Namespace)
|
||||||
|
assert.Empty(t, opt.KubeConfig)
|
||||||
|
assert.Empty(t, opt.KubeImageTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDuration parses a duration string and returns the corresponding time.Duration value.
|
||||||
|
// If the parsing fails, it returns a duration of 0.
|
||||||
|
func parseDuration(durationStr string) time.Duration {
|
||||||
|
duration, err := time.ParseDuration(durationStr)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return duration
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultMetaFactory is a default factory for versioning objects in JSON or
|
||||||
|
// YAML. The object in memory and in the default serialization will use the
|
||||||
|
// "kind" and "apiVersion" fields.
|
||||||
|
var DefaultMetaFactory = SimpleMetaFactory{}
|
||||||
|
|
||||||
|
// SimpleMetaFactory provides default methods for retrieving the type and version of objects
|
||||||
|
// that are identified with an "apiVersion" and "kind" fields in their JSON
|
||||||
|
// serialization. It may be parameterized with the names of the fields in memory, or an
|
||||||
|
// optional list of base structs to search for those fields in memory.
|
||||||
|
type SimpleMetaFactory struct{}
|
||||||
|
|
||||||
|
// Interpret will return the APIVersion and Kind of the JSON wire-format
|
||||||
|
// encoding of an object, or an error.
|
||||||
|
func (SimpleMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) {
|
||||||
|
gvk := runtime.TypeMeta{}
|
||||||
|
if err := yaml.Unmarshal(data, &gvk); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not interpret GroupVersionKind; unmarshal error: %v", err)
|
||||||
|
}
|
||||||
|
gv, err := schema.ParseGroupVersion(gvk.APIVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: gvk.Kind}, nil
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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 yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// yamlSerializer converts YAML passed to the Decoder methods to JSON.
|
||||||
|
type yamlSerializer struct {
|
||||||
|
// the nested serializer
|
||||||
|
runtime.Serializer
|
||||||
|
}
|
||||||
|
|
||||||
|
// yamlSerializer implements Serializer
|
||||||
|
var _ runtime.Serializer = yamlSerializer{}
|
||||||
|
|
||||||
|
// NewDecodingSerializer adds YAML decoding support to a serializer that supports JSON.
|
||||||
|
func NewDecodingSerializer(jsonSerializer runtime.Serializer) runtime.Serializer {
|
||||||
|
return &yamlSerializer{jsonSerializer}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c yamlSerializer) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
|
||||||
|
out, err := yaml.ToJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
data = out
|
||||||
|
return c.Serializer.Decode(data, gvk, into)
|
||||||
|
}
|
|
@ -980,6 +980,7 @@ k8s.io/apimachinery/pkg/runtime/serializer/protobuf
|
||||||
k8s.io/apimachinery/pkg/runtime/serializer/recognizer
|
k8s.io/apimachinery/pkg/runtime/serializer/recognizer
|
||||||
k8s.io/apimachinery/pkg/runtime/serializer/streaming
|
k8s.io/apimachinery/pkg/runtime/serializer/streaming
|
||||||
k8s.io/apimachinery/pkg/runtime/serializer/versioning
|
k8s.io/apimachinery/pkg/runtime/serializer/versioning
|
||||||
|
k8s.io/apimachinery/pkg/runtime/serializer/yaml
|
||||||
k8s.io/apimachinery/pkg/selection
|
k8s.io/apimachinery/pkg/selection
|
||||||
k8s.io/apimachinery/pkg/types
|
k8s.io/apimachinery/pkg/types
|
||||||
k8s.io/apimachinery/pkg/util/cache
|
k8s.io/apimachinery/pkg/util/cache
|
||||||
|
|
Loading…
Reference in New Issue