mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-26 01:22:51 +00:00
fix: add gather_facts interface to connector
Signed-off-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
parent
934c731a57
commit
1a4f7d6122
|
|
@ -70,7 +70,7 @@ func (o *KubekeyRunOptions) Flags() cliflag.NamedFlagSets {
|
|||
|
||||
tfs := fss.FlagSet("tags")
|
||||
tfs.StringArrayVar(&o.Tags, "tags", o.Tags, "the tags of playbook which to execute")
|
||||
tfs.StringArrayVar(&o.SkipTags, "skip_tags", o.SkipTags, "the tags of playbook which skip execute")
|
||||
tfs.StringArrayVar(&o.SkipTags, "skip-tags", o.SkipTags, "the tags of playbook which skip execute")
|
||||
|
||||
return fss
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,3 +119,8 @@ func NewConnector(host string, vars map[string]any) (Connector, error) {
|
|||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GatherFacts get host info.
|
||||
type GatherFacts interface {
|
||||
Info(ctx context.Context) (map[string]any, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
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 connector
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// convertBytesToMap with split string, only convert line which contain split
|
||||
func convertBytesToMap(bs []byte, split string) map[string]string {
|
||||
config := make(map[string]string)
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(bs))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.SplitN(line, split, 2)
|
||||
if len(parts) == 2 {
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
config[key] = value
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// convertBytesToSlice with split string. only convert line which contain split.
|
||||
// group by empty line
|
||||
func convertBytesToSlice(bs []byte, split string) []map[string]string {
|
||||
var config []map[string]string
|
||||
currentMap := make(map[string]string)
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(bs))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if len(line) > 0 {
|
||||
parts := strings.SplitN(line, split, 2)
|
||||
if len(parts) == 2 {
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
currentMap[key] = value
|
||||
}
|
||||
} else {
|
||||
// If encountering an empty line, add the current map to config and create a new map
|
||||
if len(currentMap) > 0 {
|
||||
config = append(config, currentMap)
|
||||
currentMap = make(map[string]string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last map if not already added
|
||||
if len(currentMap) > 0 {
|
||||
config = append(config, currentMap)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package executor
|
||||
package connector
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
|
@ -29,6 +29,8 @@ import (
|
|||
_const "github.com/kubesphere/kubekey/v4/pkg/const"
|
||||
)
|
||||
|
||||
var _ Connector = &kubernetesConnector{}
|
||||
|
||||
type kubernetesConnector struct {
|
||||
clusterName string
|
||||
kubeconfig string
|
||||
|
|
|
|||
|
|
@ -17,16 +17,22 @@ limitations under the License.
|
|||
package connector
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
var _ Connector = &localConnector{}
|
||||
var _ GatherFacts = &localConnector{}
|
||||
|
||||
type localConnector struct {
|
||||
Cmd exec.Interface
|
||||
}
|
||||
|
|
@ -69,3 +75,52 @@ func (c *localConnector) ExecuteCommand(ctx context.Context, cmd string) ([]byte
|
|||
klog.V(4).InfoS("exec local command", "cmd", cmd)
|
||||
return c.Cmd.CommandContext(ctx, "/bin/sh", "-c", cmd).CombinedOutput()
|
||||
}
|
||||
|
||||
func (c *localConnector) Info(ctx context.Context) (map[string]any, error) {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
// os information
|
||||
osVars := make(map[string]any)
|
||||
var osRelease bytes.Buffer
|
||||
if err := c.FetchFile(ctx, "/etc/os-release", &osRelease); err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch os-release: %w", err)
|
||||
}
|
||||
osVars["release"] = convertBytesToMap(osRelease.Bytes(), "=")
|
||||
kernel, err := c.ExecuteCommand(ctx, "uname -r")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get kernel version error: %w", err)
|
||||
}
|
||||
osVars["kernel_version"] = string(bytes.TrimSuffix(kernel, []byte("\n")))
|
||||
hn, err := c.ExecuteCommand(ctx, "hostname")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get hostname error: %w", err)
|
||||
}
|
||||
osVars["hostname"] = string(bytes.TrimSuffix(hn, []byte("\n")))
|
||||
arch, err := c.ExecuteCommand(ctx, "arch")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get arch error: %w", err)
|
||||
}
|
||||
osVars["architecture"] = string(bytes.TrimSuffix(arch, []byte("\n")))
|
||||
|
||||
// process information
|
||||
procVars := make(map[string]any)
|
||||
var cpu bytes.Buffer
|
||||
if err := c.FetchFile(ctx, "/proc/cpuinfo", &cpu); err != nil {
|
||||
return nil, fmt.Errorf("get cpuinfo error: %w", err)
|
||||
}
|
||||
procVars["cpuInfo"] = convertBytesToSlice(cpu.Bytes(), ":")
|
||||
var mem bytes.Buffer
|
||||
if err := c.FetchFile(ctx, "/proc/meminfo", &mem); err != nil {
|
||||
return nil, fmt.Errorf("get meminfo error: %w", err)
|
||||
}
|
||||
procVars["memInfo"] = convertBytesToMap(mem.Bytes(), ":")
|
||||
|
||||
return map[string]any{
|
||||
"os": osVars,
|
||||
"process": procVars,
|
||||
}, nil
|
||||
default:
|
||||
klog.V(4).ErrorS(nil, "Unsupported platform", "platform", runtime.GOOS)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package connector
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -30,6 +31,9 @@ import (
|
|||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var _ Connector = &sshConnector{}
|
||||
var _ GatherFacts = &sshConnector{}
|
||||
|
||||
type sshConnector struct {
|
||||
Host string
|
||||
Port int
|
||||
|
|
@ -133,3 +137,46 @@ func (c *sshConnector) ExecuteCommand(ctx context.Context, cmd string) ([]byte,
|
|||
|
||||
return session.CombinedOutput(cmd)
|
||||
}
|
||||
|
||||
func (c *sshConnector) Info(ctx context.Context) (map[string]any, error) {
|
||||
// os information
|
||||
osVars := make(map[string]any)
|
||||
var osRelease bytes.Buffer
|
||||
if err := c.FetchFile(ctx, "/etc/os-release", &osRelease); err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch os-release: %w", err)
|
||||
}
|
||||
osVars["release"] = convertBytesToMap(osRelease.Bytes(), "=")
|
||||
kernel, err := c.ExecuteCommand(ctx, "uname -r")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get kernel version error: %w", err)
|
||||
}
|
||||
osVars["kernel_version"] = string(bytes.TrimSuffix(kernel, []byte("\n")))
|
||||
hn, err := c.ExecuteCommand(ctx, "hostname")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get hostname error: %w", err)
|
||||
}
|
||||
osVars["hostname"] = string(bytes.TrimSuffix(hn, []byte("\n")))
|
||||
arch, err := c.ExecuteCommand(ctx, "arch")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get arch error: %w", err)
|
||||
}
|
||||
osVars["architecture"] = string(bytes.TrimSuffix(arch, []byte("\n")))
|
||||
|
||||
// process information
|
||||
procVars := make(map[string]any)
|
||||
var cpu bytes.Buffer
|
||||
if err := c.FetchFile(ctx, "/proc/cpuinfo", &cpu); err != nil {
|
||||
return nil, fmt.Errorf("get cpuinfo error: %w", err)
|
||||
}
|
||||
procVars["cpuInfo"] = convertBytesToSlice(cpu.Bytes(), ":")
|
||||
var mem bytes.Buffer
|
||||
if err := c.FetchFile(ctx, "/proc/meminfo", &mem); err != nil {
|
||||
return nil, fmt.Errorf("get meminfo error: %w", err)
|
||||
}
|
||||
procVars["memInfo"] = convertBytesToMap(mem.Bytes(), ":")
|
||||
|
||||
return map[string]any{
|
||||
"os": osVars,
|
||||
"process": procVars,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ workdir/
|
|||
| | | |-- namespace/
|
||||
| | | | |-- inventory.yaml
|
||||
|
|
||||
|-- artifact/
|
||||
|-- kubekey/
|
||||
*/
|
||||
|
||||
// workDir is the user-specified working directory. By default, it is the same as the directory where the kubekey command is executed.
|
||||
|
|
@ -114,6 +114,6 @@ const RuntimePipelineVariableDir = "variable"
|
|||
|
||||
// inventory.yaml is the data of Inventory resource
|
||||
|
||||
// "artifact" is the default directory name under the working directory. It is used to store
|
||||
// "kubekey" is the default directory name under the working directory. It is used to store
|
||||
// files required when executing the kubekey command (such as: docker, etcd, image packages, etc.).
|
||||
// These files will be downloaded locally and distributed to remote nodes.
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import (
|
|||
kkcorev1 "github.com/kubesphere/kubekey/v4/pkg/apis/core/v1"
|
||||
kubekeyv1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1"
|
||||
kubekeyv1alpha1 "github.com/kubesphere/kubekey/v4/pkg/apis/kubekey/v1alpha1"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/connector"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/converter"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/converter/tmpl"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/modules"
|
||||
|
|
@ -112,7 +113,7 @@ func (e executor) Exec(ctx context.Context) error {
|
|||
// when gather_fact is set. get host's information from remote.
|
||||
if play.GatherFacts {
|
||||
for _, h := range hosts {
|
||||
gfv, err := getGatherFact(ctx, h, e.variable)
|
||||
gfv, err := e.getGatherFact(ctx, h, e.variable)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get gather fact error: %w", err)
|
||||
}
|
||||
|
|
@ -191,6 +192,32 @@ func (e executor) Exec(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// getGatherFact get host info
|
||||
func (e executor) getGatherFact(ctx context.Context, hostname string, vars variable.Variable) (map[string]any, error) {
|
||||
v, err := vars.Get(variable.GetParamVariable(hostname))
|
||||
if err != nil {
|
||||
klog.V(4).ErrorS(err, "Get host variable error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := connector.NewConnector(hostname, v.(map[string]any))
|
||||
if err != nil {
|
||||
klog.V(4).ErrorS(err, "New connector error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
if err := conn.Init(ctx); err != nil {
|
||||
klog.V(4).ErrorS(err, "Init connection error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close(ctx)
|
||||
|
||||
if gf, ok := conn.(connector.GatherFacts); ok {
|
||||
return gf.Info(ctx)
|
||||
}
|
||||
klog.V(4).ErrorS(nil, "gather fact is not defined in this connector", "hostname", hostname)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e executor) execBlock(ctx context.Context, options execBlockOptions) error {
|
||||
for _, at := range options.blocks {
|
||||
if !at.Taggable.IsEnabled(e.pipeline.Spec.Tags, e.pipeline.Spec.SkipTags) {
|
||||
|
|
|
|||
|
|
@ -1,147 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 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 executor
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"github.com/kubesphere/kubekey/v4/pkg/connector"
|
||||
"github.com/kubesphere/kubekey/v4/pkg/variable"
|
||||
)
|
||||
|
||||
// getGatherFact get host info
|
||||
func getGatherFact(ctx context.Context, hostname string, vars variable.Variable) (map[string]any, error) {
|
||||
v, err := vars.Get(variable.GetParamVariable(hostname))
|
||||
if err != nil {
|
||||
klog.V(4).ErrorS(err, "Get host variable error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := connector.NewConnector(hostname, v.(map[string]any))
|
||||
if err != nil {
|
||||
klog.V(4).ErrorS(err, "New connector error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
if err := conn.Init(ctx); err != nil {
|
||||
klog.V(4).ErrorS(err, "Init connection error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close(ctx)
|
||||
|
||||
// os information
|
||||
osVars := make(map[string]any)
|
||||
var osRelease bytes.Buffer
|
||||
if err := conn.FetchFile(ctx, "/etc/os-release", &osRelease); err != nil {
|
||||
klog.V(4).ErrorS(err, "Fetch os-release error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
osVars["release"] = convertBytesToMap(osRelease.Bytes(), "=")
|
||||
kernel, err := conn.ExecuteCommand(ctx, "uname -r")
|
||||
if err != nil {
|
||||
klog.V(4).ErrorS(err, "Get kernel version error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
osVars["kernel_version"] = string(bytes.TrimSuffix(kernel, []byte("\n")))
|
||||
hn, err := conn.ExecuteCommand(ctx, "hostname")
|
||||
if err != nil {
|
||||
klog.V(4).ErrorS(err, "Get hostname error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
osVars["hostname"] = string(bytes.TrimSuffix(hn, []byte("\n")))
|
||||
arch, err := conn.ExecuteCommand(ctx, "arch")
|
||||
if err != nil {
|
||||
klog.V(4).ErrorS(err, "Get arch error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
osVars["architecture"] = string(bytes.TrimSuffix(arch, []byte("\n")))
|
||||
|
||||
// process information
|
||||
procVars := make(map[string]any)
|
||||
var cpu bytes.Buffer
|
||||
if err := conn.FetchFile(ctx, "/proc/cpuinfo", &cpu); err != nil {
|
||||
klog.V(4).ErrorS(err, "Fetch cpuinfo error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
procVars["cpuInfo"] = convertBytesToSlice(cpu.Bytes(), ":")
|
||||
var mem bytes.Buffer
|
||||
if err := conn.FetchFile(ctx, "/proc/meminfo", &mem); err != nil {
|
||||
klog.V(4).ErrorS(err, "Fetch meminfo error", "hostname", hostname)
|
||||
return nil, err
|
||||
}
|
||||
procVars["memInfo"] = convertBytesToMap(mem.Bytes(), ":")
|
||||
|
||||
return map[string]any{
|
||||
"os": osVars,
|
||||
"process": procVars,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// convertBytesToMap with split string, only convert line which contain split
|
||||
func convertBytesToMap(bs []byte, split string) map[string]string {
|
||||
config := make(map[string]string)
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(bs))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
parts := strings.SplitN(line, split, 2)
|
||||
if len(parts) == 2 {
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
config[key] = value
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// convertBytesToSlice with split string. only convert line which contain split.
|
||||
// group by empty line
|
||||
func convertBytesToSlice(bs []byte, split string) []map[string]string {
|
||||
var config []map[string]string
|
||||
currentMap := make(map[string]string)
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewBuffer(bs))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if len(line) > 0 {
|
||||
parts := strings.SplitN(line, split, 2)
|
||||
if len(parts) == 2 {
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
currentMap[key] = value
|
||||
}
|
||||
} else {
|
||||
// If encountering an empty line, add the current map to config and create a new map
|
||||
if len(currentMap) > 0 {
|
||||
config = append(config, currentMap)
|
||||
currentMap = make(map[string]string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last map if not already added
|
||||
if len(currentMap) > 0 {
|
||||
config = append(config, currentMap)
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
Loading…
Reference in New Issue