mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-25 17:12:50 +00:00
258 lines
7.8 KiB
Go
258 lines
7.8 KiB
Go
/*
|
|
Copyright 2024 The KubeSphere 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 options
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
cliflag "k8s.io/component-base/cli/flag"
|
|
"k8s.io/klog/v2"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
kkcorev1 "github.com/kubesphere/kubekey/v4/pkg/apis/core/v1"
|
|
)
|
|
|
|
var defaultConfig = &kkcorev1.Config{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: kkcorev1.SchemeGroupVersion.String(),
|
|
Kind: "Config",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{Name: "default"}}
|
|
var defaultInventory = &kkcorev1.Inventory{
|
|
TypeMeta: metav1.TypeMeta{
|
|
APIVersion: kkcorev1.SchemeGroupVersion.String(),
|
|
Kind: "Inventory",
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{Name: "default"}}
|
|
|
|
type commonOptions struct {
|
|
// Playbook which to execute.
|
|
Playbook string
|
|
// HostFile is the path of host file
|
|
InventoryFile string
|
|
// ConfigFile is the path of config file
|
|
ConfigFile string
|
|
// Set value in config
|
|
Set []string
|
|
// WorkDir is the baseDir which command find any resource (project etc.)
|
|
WorkDir string
|
|
// Artifact is the path of offline package for kubekey.
|
|
Artifact string
|
|
// Debug mode, after a successful execution of Pipeline, will retain runtime data, which includes task execution status and parameters.
|
|
Debug bool
|
|
// Namespace for all resources.
|
|
Namespace string
|
|
}
|
|
|
|
func newCommonOptions() commonOptions {
|
|
o := commonOptions{
|
|
Namespace: metav1.NamespaceDefault,
|
|
}
|
|
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
klog.ErrorS(err, "get current dir error")
|
|
o.WorkDir = "/tmp/kubekey"
|
|
} else {
|
|
o.WorkDir = filepath.Join(wd, "kubekey")
|
|
}
|
|
|
|
return o
|
|
}
|
|
|
|
func (o *commonOptions) flags() cliflag.NamedFlagSets {
|
|
fss := cliflag.NamedFlagSets{}
|
|
gfs := fss.FlagSet("generic")
|
|
gfs.StringVar(&o.WorkDir, "workdir", o.WorkDir, "the base Dir for kubekey. Default current dir. ")
|
|
gfs.StringVarP(&o.Artifact, "artifact", "a", "", "Path to a KubeKey artifact")
|
|
gfs.StringVarP(&o.ConfigFile, "config", "c", o.ConfigFile, "the config file path. support *.yaml ")
|
|
gfs.StringArrayVar(&o.Set, "set", o.Set, "set value in config. format --set key=val or --set k1=v1,k2=v2")
|
|
gfs.StringVarP(&o.InventoryFile, "inventory", "i", o.InventoryFile, "the host list file path. support *.ini")
|
|
gfs.BoolVarP(&o.Debug, "debug", "d", o.Debug, "Debug mode, after a successful execution of Pipeline, will retain runtime data, which includes task execution status and parameters.")
|
|
gfs.StringVarP(&o.Namespace, "namespace", "n", o.Namespace, "the namespace which pipeline will be executed, all reference resources(pipeline, config, inventory, task) should in the same namespace")
|
|
|
|
return fss
|
|
}
|
|
|
|
func (o *commonOptions) completeRef(pipeline *kkcorev1.Pipeline) (*kkcorev1.Config, *kkcorev1.Inventory, error) {
|
|
if !filepath.IsAbs(o.WorkDir) {
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("get current dir error: %w", err)
|
|
}
|
|
o.WorkDir = filepath.Join(wd, o.WorkDir)
|
|
}
|
|
// complete config
|
|
config, err := o.genConfig()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("generate config error: %w", err)
|
|
}
|
|
pipeline.Spec.ConfigRef = &corev1.ObjectReference{
|
|
Kind: config.Kind,
|
|
Namespace: config.Namespace,
|
|
Name: config.Name,
|
|
UID: config.UID,
|
|
APIVersion: config.APIVersion,
|
|
ResourceVersion: config.ResourceVersion,
|
|
}
|
|
// complete inventory
|
|
inventory, err := o.genInventory()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("generate inventory error: %w", err)
|
|
}
|
|
pipeline.Spec.InventoryRef = &corev1.ObjectReference{
|
|
Kind: inventory.Kind,
|
|
Namespace: inventory.Namespace,
|
|
Name: inventory.Name,
|
|
UID: inventory.UID,
|
|
APIVersion: inventory.APIVersion,
|
|
ResourceVersion: inventory.ResourceVersion,
|
|
}
|
|
|
|
return config, inventory, nil
|
|
}
|
|
|
|
// genConfig generate config by ConfigFile and set value by command args.
|
|
func (o *commonOptions) genConfig() (*kkcorev1.Config, error) {
|
|
config := defaultConfig.DeepCopy()
|
|
if o.ConfigFile != "" {
|
|
cdata, err := os.ReadFile(o.ConfigFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read config file error: %w", err)
|
|
}
|
|
config = &kkcorev1.Config{}
|
|
if err := yaml.Unmarshal(cdata, config); err != nil {
|
|
return nil, fmt.Errorf("unmarshal config file error: %w", err)
|
|
}
|
|
}
|
|
// set value by command args
|
|
if o.Namespace != "" {
|
|
config.Namespace = o.Namespace
|
|
}
|
|
if wd, err := config.GetValue("work_dir"); err == nil && wd != nil {
|
|
// if work_dir is defined in config, use it. otherwise use current dir.
|
|
if workDir, ok := wd.(string); ok {
|
|
o.WorkDir = workDir
|
|
}
|
|
} else if err := config.SetValue("work_dir", o.WorkDir); err != nil {
|
|
return nil, fmt.Errorf("work_dir to config error: %w", err)
|
|
}
|
|
if o.Artifact != "" {
|
|
// override artifact_file in config
|
|
if err := config.SetValue("artifact_file", o.Artifact); err != nil {
|
|
return nil, fmt.Errorf("artifact file to config error: %w", err)
|
|
}
|
|
}
|
|
for _, s := range o.Set {
|
|
for _, setVal := range strings.Split(unescapeString(s), ",") {
|
|
i := strings.Index(setVal, "=")
|
|
if i == 0 || i == -1 {
|
|
return nil, errors.New("--set value should be k=v")
|
|
}
|
|
if err := setValue(config, setVal[:i], setVal[i+1:]); err != nil {
|
|
return nil, fmt.Errorf("--set value to config error: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
// genConfig generate config by ConfigFile and set value by command args.
|
|
func (o *commonOptions) genInventory() (*kkcorev1.Inventory, error) {
|
|
inventory := defaultInventory.DeepCopy()
|
|
if o.InventoryFile != "" {
|
|
cdata, err := os.ReadFile(o.InventoryFile)
|
|
if err != nil {
|
|
klog.V(4).ErrorS(err, "read config file error")
|
|
|
|
return nil, err
|
|
}
|
|
inventory = &kkcorev1.Inventory{}
|
|
if err := yaml.Unmarshal(cdata, inventory); err != nil {
|
|
klog.V(4).ErrorS(err, "unmarshal config file error")
|
|
|
|
return nil, err
|
|
}
|
|
}
|
|
// set value by command args
|
|
if o.Namespace != "" {
|
|
inventory.Namespace = o.Namespace
|
|
}
|
|
|
|
return inventory, nil
|
|
}
|
|
|
|
// setValue set key: val in config.
|
|
// If val is json string. convert to map or slice
|
|
// If val is TRUE,YES,Y. convert to bool type true.
|
|
// If val is FALSE,NO,N. convert to bool type false.
|
|
func setValue(config *kkcorev1.Config, key, val string) error {
|
|
switch {
|
|
case strings.HasPrefix(val, "{") && strings.HasSuffix(val, "{"):
|
|
var value map[string]any
|
|
err := json.Unmarshal([]byte(val), &value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return config.SetValue(key, value)
|
|
case strings.HasPrefix(val, "[") && strings.HasSuffix(val, "]"):
|
|
var value []any
|
|
err := json.Unmarshal([]byte(val), &value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return config.SetValue(key, value)
|
|
case strings.EqualFold(val, "TRUE") || strings.EqualFold(val, "YES") || strings.EqualFold(val, "Y"):
|
|
return config.SetValue(key, true)
|
|
case strings.EqualFold(val, "FALSE") || strings.EqualFold(val, "NO") || strings.EqualFold(val, "N"):
|
|
return config.SetValue(key, false)
|
|
default:
|
|
return config.SetValue(key, val)
|
|
}
|
|
}
|
|
|
|
// unescapeString handles common escape sequences
|
|
func unescapeString(s string) string {
|
|
replacements := map[string]string{
|
|
`\\`: `\`,
|
|
`\"`: `"`,
|
|
`\'`: `'`,
|
|
`\n`: "\n",
|
|
`\r`: "\r",
|
|
`\t`: "\t",
|
|
`\b`: "\b",
|
|
`\f`: "\f",
|
|
}
|
|
|
|
// Iterate over the replacements map and replace escape sequences in the string
|
|
for o, n := range replacements {
|
|
s = strings.ReplaceAll(s, o, n)
|
|
}
|
|
|
|
return s
|
|
}
|