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
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: run karmadactl init test
|
||||
run: |
|
||||
export CLUSTER_VERSION=kindest/node:${{ matrix.k8s }}
|
||||
|
@ -48,7 +47,7 @@ jobs:
|
|||
export KUBECONFIG=${HOME}/karmada/karmada-apiserver.config
|
||||
GO111MODULE=on go install github.com/onsi/ginkgo/v2/ginkgo
|
||||
ginkgo -v --race --trace -p --focus="[BasicPropagation] propagation testing deployment propagation testing" ./test/e2e/
|
||||
- name: export logs
|
||||
- name: export logs
|
||||
if: always()
|
||||
run: |
|
||||
export ARTIFACTS_PATH=${{ github.workspace }}/karmadactl-test-logs/${{ matrix.k8s }}/
|
||||
|
|
|
@ -40,6 +40,7 @@ missing_license_header_files="$($ADDLICENSE_BIN \
|
|||
-ignore "**/*.yml" \
|
||||
-ignore "**/*.json" \
|
||||
-ignore ".idea/**" \
|
||||
-ignore ".git/**"
|
||||
.)" || true
|
||||
|
||||
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}
|
||||
|
||||
# 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
|
||||
|
@ -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.")
|
||||
// karmada
|
||||
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.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")
|
||||
|
@ -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.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")
|
||||
|
||||
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"
|
||||
|
||||
"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/options"
|
||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/utils"
|
||||
|
@ -174,6 +175,7 @@ type CommandInitOption struct {
|
|||
WaitComponentReadyTimeout int
|
||||
CaCertFile string
|
||||
CaKeyFile string
|
||||
KarmadaInitFilePath string
|
||||
}
|
||||
|
||||
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.
|
||||
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 netutils.ParseIPSloppy(i.KarmadaAPIServerAdvertiseAddress) == nil {
|
||||
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.isNodeExist(i.EtcdNodeSelectorLabels) {
|
||||
return fmt.Errorf("no node found by label %s", i.EtcdNodeSelectorLabels)
|
||||
labels := strings.Split(i.EtcdNodeSelectorLabels, ",")
|
||||
for _, label := range labels {
|
||||
if !i.isNodeExist(label) {
|
||||
return fmt.Errorf("no node found by label %s", label)
|
||||
}
|
||||
}
|
||||
}
|
||||
return initializeDirectory(i.KarmadaDataPath)
|
||||
|
@ -744,3 +759,225 @@ func generateServerURL(serverIP string, nodePort int32) (string, error) {
|
|||
func SupportedStorageMode() []string {
|
||||
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"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/config"
|
||||
"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/streaming
|
||||
k8s.io/apimachinery/pkg/runtime/serializer/versioning
|
||||
k8s.io/apimachinery/pkg/runtime/serializer/yaml
|
||||
k8s.io/apimachinery/pkg/selection
|
||||
k8s.io/apimachinery/pkg/types
|
||||
k8s.io/apimachinery/pkg/util/cache
|
||||
|
|
Loading…
Reference in New Issue