mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-25 17:12:50 +00:00
fix: add index to groups (#2649)
Signed-off-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
parent
e3dec872f6
commit
c71814aa09
|
|
@ -113,7 +113,7 @@ linters-settings:
|
|||
cyclop:
|
||||
# The maximal code complexity to report.
|
||||
# Default: 10
|
||||
max-complexity: 20
|
||||
max-complexity: 25
|
||||
# Should ignore tests.
|
||||
# Default: false
|
||||
skip-tests: true
|
||||
|
|
|
|||
|
|
@ -1,8 +1,22 @@
|
|||
- name: Check Connect
|
||||
hosts:
|
||||
- all
|
||||
gather_facts: true
|
||||
tasks:
|
||||
- name: get_arch
|
||||
debug:
|
||||
msg: "{{ .os.architecture }}"
|
||||
- name: get host info
|
||||
ignore_errors: true
|
||||
setup: {}
|
||||
- name: set result
|
||||
run_once: true
|
||||
vars:
|
||||
architectures:
|
||||
amd64:
|
||||
- amd64
|
||||
- x86_64
|
||||
arm64:
|
||||
- arm64
|
||||
- aarch64
|
||||
result: |
|
||||
{{- range $k,$v := .hostvars }}
|
||||
{{ $k }}: {{ if $.architectures.amd64 | has ($v.os.architecture | default "") }}amd64{{ else if $.architectures.arm64 | has .os.architecture }}arm64{{ else }}{{ $v.os.architecture | default "" }}{{ end }}
|
||||
{{- end }}
|
||||
|
||||
|
|
@ -31,11 +31,6 @@ var (
|
|||
CapkkVolumeProject = Environment{env: "CAPKK_VOLUME_PROJECT"}
|
||||
// CapkkVolumeWorkdir specifies the working directory for capkk playbook
|
||||
CapkkVolumeWorkdir = Environment{env: "CAPKK_VOLUME_WORKDIR"}
|
||||
|
||||
// TaskNameGatherFacts the task name for gather_facts in playbook
|
||||
TaskNameGatherFacts = Environment{env: "TASK_GATHER_FACTS", def: "gather_facts"}
|
||||
// TaskNameGetArch the task name for get_arch in playbook, used to get host architecture
|
||||
TaskNameGetArch = Environment{env: "", def: "get_arch"}
|
||||
)
|
||||
|
||||
// Getenv retrieves the value of the environment variable. If the environment variable is not set,
|
||||
|
|
|
|||
|
|
@ -246,8 +246,7 @@ func (e playbookExecutor) dealGatherFacts(ctx context.Context, gatherFacts bool,
|
|||
// skip
|
||||
return nil
|
||||
}
|
||||
// run as option
|
||||
|
||||
// run setup task
|
||||
return (&taskExecutor{option: e.option, task: &kkcorev1alpha1.Task{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: e.playbook.Name + "-",
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ func ModuleCommand(ctx context.Context, options ExecOptions) (string, string) {
|
|||
}
|
||||
// execute command
|
||||
var stdout, stderr string
|
||||
data, err := conn.ExecuteCommand(ctx, command)
|
||||
data, err := conn.ExecuteCommand(ctx, string(command))
|
||||
if err != nil {
|
||||
stderr = err.Error()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,13 +47,19 @@ Return Values:
|
|||
|
||||
// ModuleResult handles the "result" module, setting result variables during playbook execution
|
||||
func ModuleResult(ctx context.Context, options ExecOptions) (string, string) {
|
||||
var node yaml.Node
|
||||
// get host variable
|
||||
ha, err := options.getAllVariables()
|
||||
if err != nil {
|
||||
return "", err.Error()
|
||||
}
|
||||
arg, _ := variable.Extension2String(ha, options.Args)
|
||||
var result any
|
||||
// Unmarshal the YAML document into a root node.
|
||||
if err := yaml.Unmarshal(options.Args.Raw, &node); err != nil {
|
||||
if err := yaml.Unmarshal(arg, &result); err != nil {
|
||||
return "", fmt.Sprintf("failed to unmarshal YAML error: %v", err)
|
||||
}
|
||||
|
||||
if err := options.Variable.Merge(variable.MergeResultVariable(node, options.Host)); err != nil {
|
||||
if err := options.Variable.Merge(variable.MergeResultVariable(result)); err != nil {
|
||||
return "", fmt.Sprintf("result error: %v", err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -399,30 +399,26 @@ func Extension2Slice(ctx map[string]any, ext runtime.RawExtension) []any {
|
|||
klog.ErrorS(err, "extension2string error", "input", string(ext.Raw))
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(val), &data); err == nil {
|
||||
if err := json.Unmarshal(val, &data); err == nil {
|
||||
return data
|
||||
}
|
||||
|
||||
return []any{val}
|
||||
}
|
||||
|
||||
// Extension2String convert runtime.RawExtension to string.
|
||||
// if runtime.RawExtension contains tmpl syntax, parse it.
|
||||
func Extension2String(ctx map[string]any, ext runtime.RawExtension) (string, error) {
|
||||
// Extension2String converts a runtime.RawExtension to a string, optionally parsing it as a template.
|
||||
// If the extension is empty, it returns nil. If the string is quoted, it unquotes it first.
|
||||
// Finally, it parses the string as a template using the provided context.
|
||||
func Extension2String(ctx map[string]any, ext runtime.RawExtension) ([]byte, error) {
|
||||
if len(ext.Raw) == 0 {
|
||||
return "", nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var input = string(ext.Raw)
|
||||
// try to escape string
|
||||
if ns, err := strconv.Unquote(string(ext.Raw)); err == nil {
|
||||
// try to escape string if it's quoted
|
||||
if ns, err := strconv.Unquote(input); err == nil {
|
||||
input = ns
|
||||
}
|
||||
|
||||
result, err := tmpl.Parse(ctx, input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(result), nil
|
||||
return tmpl.Parse(ctx, input)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,13 +139,19 @@ type variable struct {
|
|||
sync.RWMutex
|
||||
}
|
||||
|
||||
// value is the specific data contained in the variable
|
||||
// resultKey is the key used to store global result variables in the value struct.
|
||||
// It is used as the only key in the Result map to hold result data set by result tasks.
|
||||
const resultKey = "result"
|
||||
|
||||
// value holds all variable data for a playbook execution.
|
||||
// It contains configuration, inventory, per-host variables, and global result variables.
|
||||
type value struct {
|
||||
Config kkcorev1.Config
|
||||
Inventory kkcorev1.Inventory
|
||||
// Hosts store the variable for running tasks on specific hosts
|
||||
Config kkcorev1.Config // Playbook configuration
|
||||
Inventory kkcorev1.Inventory // Playbook inventory
|
||||
// Hosts stores variables for each host, including remote and runtime variables.
|
||||
Hosts map[string]host
|
||||
// result store the variable which set by result task.
|
||||
// Result stores global result variables set by result tasks.
|
||||
// The map always contains a single key (resultKey) for easy cloning and merging.
|
||||
Result map[string]any
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -223,6 +223,6 @@ var GetResultVariable = func() GetFunc {
|
|||
return nil, errors.New("variable type error")
|
||||
}
|
||||
|
||||
return vv.value.Result, nil
|
||||
return vv.value.Result[resultKey], nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,32 +118,14 @@ var MergeHostsRuntimeVariable = func(node yaml.Node, hostname string, hosts ...s
|
|||
// 1. Gets all variables for the host to create a parsing context
|
||||
// 2. Parses the YAML node using that context
|
||||
// 3. Sets the parsed data as the global result variables (accessible across all hosts)
|
||||
var MergeResultVariable = func(node yaml.Node, hostname string) MergeFunc {
|
||||
if node.IsZero() {
|
||||
// skip
|
||||
return emptyMergeFunc
|
||||
}
|
||||
|
||||
var MergeResultVariable = func(result any) MergeFunc {
|
||||
return func(v Variable) error {
|
||||
vv, ok := v.(*variable)
|
||||
if !ok {
|
||||
return errors.New("variable type error")
|
||||
}
|
||||
|
||||
// Avoid nested locking: prepare context for parsing outside locking region
|
||||
curVars, err := v.Get(GetAllVariable(hostname))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, ok := curVars.(map[string]any)
|
||||
if !ok {
|
||||
return errors.Errorf("host %s variables type error, expect map[string]any", hostname)
|
||||
}
|
||||
result, err := parseYamlNode(ctx, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vv.value.Result = CombineVariables(vv.value.Result, result)
|
||||
vv.value.Result = CombineVariables(vv.value.Result, map[string]any{resultKey: result})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ func TestMergeResultVariable(t *testing.T) {
|
|||
},
|
||||
},
|
||||
data: map[string]any{
|
||||
"v1": "{{ .k1 }}",
|
||||
"v1": "v1",
|
||||
"v2": "vv",
|
||||
},
|
||||
except: value{
|
||||
|
|
@ -154,8 +154,10 @@ func TestMergeResultVariable(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Result: map[string]any{
|
||||
"v1": "v1",
|
||||
"v2": "vv",
|
||||
resultKey: map[string]any{
|
||||
"v1": "v1",
|
||||
"v2": "vv",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -163,12 +165,7 @@ func TestMergeResultVariable(t *testing.T) {
|
|||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
node, err := converter.ConvertMap2Node(tc.data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err = tc.variable.Merge(MergeResultVariable(node, tc.host)); err != nil {
|
||||
if err := tc.variable.Merge(MergeResultVariable(tc.data)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,16 @@ limitations under the License.
|
|||
|
||||
package api
|
||||
|
||||
const (
|
||||
// ResultSucceed indicates a successful operation result.
|
||||
ResultSucceed = "success"
|
||||
// ResultFailed indicates a failed operation result.
|
||||
ResultFailed = "failed"
|
||||
)
|
||||
|
||||
// SUCCESS is a global variable representing a successful operation result with a default success message.
|
||||
// It can be used as a standard response for successful API calls.
|
||||
var SUCCESS = Result{Message: "success"}
|
||||
var SUCCESS = Result{Message: ResultSucceed}
|
||||
|
||||
// Result represents a basic API response structure containing a message field.
|
||||
// The Message field is typically used to convey error or success information.
|
||||
|
|
@ -37,18 +44,25 @@ type ListResult[T any] struct {
|
|||
// InventoryHostTable represents a host entry in an inventory with its configuration details.
|
||||
// It includes network information, SSH credentials, group membership, and architecture.
|
||||
type InventoryHostTable struct {
|
||||
Name string `json:"name"` // Hostname of the inventory host
|
||||
Status string `json:"status"` // Current status of the host
|
||||
InternalIPV4 string `json:"internalIPV4"` // IPv4 address of the host
|
||||
InternalIPV6 string `json:"internalIPV6"` // IPv6 address of the host
|
||||
SSHHost string `json:"sshHost"` // SSH hostname for connection
|
||||
SSHPort string `json:"sshPort"` // SSH port for connection
|
||||
SSHUser string `json:"sshUser"` // SSH username for authentication
|
||||
SSHPassword string `json:"sshPassword"` // SSH password for authentication
|
||||
SSHPrivateKey string `json:"sshPrivateKey"` // SSH private key for authentication
|
||||
Vars map[string]any `json:"vars"` // Additional host variables
|
||||
Groups []string `json:"groups"` // Groups the host belongs to
|
||||
Arch string `json:"arch"` // Architecture of the host
|
||||
Name string `json:"name"` // Hostname of the inventory host
|
||||
Status string `json:"status"` // Current status of the host
|
||||
InternalIPV4 string `json:"internalIPV4"` // IPv4 address of the host
|
||||
InternalIPV6 string `json:"internalIPV6"` // IPv6 address of the host
|
||||
SSHHost string `json:"sshHost"` // SSH hostname for connection
|
||||
SSHPort string `json:"sshPort"` // SSH port for connection
|
||||
SSHUser string `json:"sshUser"` // SSH username for authentication
|
||||
SSHPassword string `json:"sshPassword"` // SSH password for authentication
|
||||
SSHPrivateKey string `json:"sshPrivateKey"` // SSH private key for authentication
|
||||
Vars map[string]any `json:"vars"` // Additional host variables
|
||||
Groups []InventoryHostGroups `json:"groups"` // Groups the host belongs to
|
||||
Arch string `json:"arch"` // Architecture of the host
|
||||
}
|
||||
|
||||
// InventoryHostGroups represents the group information for a host in the inventory.
|
||||
// Role is the name of the group, and Index is the index of the group to which the host belongs.
|
||||
type InventoryHostGroups struct {
|
||||
Role string `json:"role"` // the groups name
|
||||
Index int `json:"index"` // the index of groups which hosts belong to
|
||||
}
|
||||
|
||||
// SchemaTable represents schema metadata for a resource.
|
||||
|
|
|
|||
|
|
@ -318,10 +318,12 @@ func (h *coreHandler) inventoryInfo(request *restful.Request, response *restful.
|
|||
// listInventoryHosts lists all hosts in an inventory with their details
|
||||
// It includes information about SSH configuration, IP addresses, and group membership
|
||||
func (h *coreHandler) listInventoryHosts(request *restful.Request, response *restful.Response) {
|
||||
// Parse query parameters from the request
|
||||
queryParam := query.ParseQueryParameter(request)
|
||||
namespace := request.PathParameter("namespace")
|
||||
name := request.PathParameter("inventory")
|
||||
|
||||
// Retrieve the inventory object from the cluster
|
||||
inventory := &kkcorev1.Inventory{}
|
||||
err := h.client.Get(request.Request.Context(), ctrlclient.ObjectKey{Namespace: namespace, Name: name}, inventory)
|
||||
if err != nil {
|
||||
|
|
@ -329,15 +331,19 @@ func (h *coreHandler) listInventoryHosts(request *restful.Request, response *res
|
|||
return
|
||||
}
|
||||
|
||||
// Retrieve the list of tasks associated with the inventory
|
||||
taskList := &kkcorev1alpha1.TaskList{}
|
||||
_ = h.client.List(request.Request.Context(), taskList, ctrlclient.InNamespace(namespace), ctrlclient.MatchingFields{
|
||||
"playbook.name": inventory.Annotations[kkcorev1.HostCheckPlaybookAnnotation],
|
||||
})
|
||||
// get host-check playbook if annotation exists
|
||||
playbook := &kkcorev1.Playbook{}
|
||||
if playbookName, ok := inventory.Annotations[kkcorev1.HostCheckPlaybookAnnotation]; ok {
|
||||
if err := h.client.Get(request.Request.Context(), ctrlclient.ObjectKey{Name: playbookName, Namespace: inventory.Namespace}, playbook); err != nil {
|
||||
klog.Warningf("cannot found host-check playbook for inventory %q", ctrlclient.ObjectKeyFromObject(inventory))
|
||||
}
|
||||
}
|
||||
|
||||
// buildHostItem constructs an InventoryHostTable from the hostname and raw extension
|
||||
buildHostItem := func(hostname string, raw runtime.RawExtension) api.InventoryHostTable {
|
||||
// Convert the raw extension to a map of variables
|
||||
vars := variable.Extension2Variables(raw)
|
||||
// Extract relevant fields from the variables
|
||||
internalIPV4, _ := variable.StringVar(nil, vars, _const.VariableIPv4)
|
||||
internalIPV6, _ := variable.StringVar(nil, vars, _const.VariableIPv6)
|
||||
sshHost, _ := variable.StringVar(nil, vars, _const.VariableConnector, _const.VariableConnectorHost)
|
||||
|
|
@ -351,6 +357,7 @@ func (h *coreHandler) listInventoryHosts(request *restful.Request, response *res
|
|||
delete(vars, _const.VariableIPv6)
|
||||
delete(vars, _const.VariableConnector)
|
||||
|
||||
// Return the constructed InventoryHostTable
|
||||
return api.InventoryHostTable{
|
||||
Name: hostname,
|
||||
InternalIPV4: internalIPV4,
|
||||
|
|
@ -361,49 +368,61 @@ func (h *coreHandler) listInventoryHosts(request *restful.Request, response *res
|
|||
SSHPassword: sshPassword,
|
||||
SSHPrivateKey: sshPrivateKey,
|
||||
Vars: vars,
|
||||
Groups: []string{},
|
||||
Groups: []api.InventoryHostGroups{},
|
||||
}
|
||||
}
|
||||
|
||||
// Convert inventory groups for host membership lookup
|
||||
groups := variable.ConvertGroup(*inventory)
|
||||
|
||||
// Helper to check if a host is in a group and get its index
|
||||
getGroupIndex := func(groupName, hostName string) int {
|
||||
for i, h := range inventory.Spec.Groups[groupName].Hosts {
|
||||
if h == hostName {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// fillGroups adds group names to the InventoryHostTable item if the host is a member
|
||||
fillGroups := func(item *api.InventoryHostTable) {
|
||||
for groupName, hosts := range groups {
|
||||
if groupName == _const.VariableGroupsAll || groupName == _const.VariableUnGrouped {
|
||||
// Skip special groups
|
||||
if groupName == _const.VariableGroupsAll || groupName == _const.VariableUnGrouped || groupName == "k8s_cluster" {
|
||||
continue
|
||||
}
|
||||
// If the host is in the group, add the group info to the item
|
||||
if slices.Contains(hosts, item.Name) {
|
||||
item.Groups = append(item.Groups, groupName)
|
||||
g := api.InventoryHostGroups{
|
||||
Role: groupName,
|
||||
Index: getGroupIndex(groupName, item.Name),
|
||||
}
|
||||
item.Groups = append(item.Groups, g)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fillTaskInfo populates status and architecture info for the host from task results
|
||||
fillTaskInfo := func(item *api.InventoryHostTable) {
|
||||
for _, task := range taskList.Items {
|
||||
switch task.Name {
|
||||
case _const.Getenv(_const.TaskNameGatherFacts):
|
||||
for _, result := range task.Status.HostResults {
|
||||
if result.Host == item.Name {
|
||||
item.Status = result.Stdout
|
||||
break
|
||||
}
|
||||
}
|
||||
case _const.Getenv(_const.TaskNameGetArch):
|
||||
for _, result := range task.Status.HostResults {
|
||||
if result.Host == item.Name {
|
||||
item.Arch = result.Stdout
|
||||
break
|
||||
}
|
||||
}
|
||||
// fillByPlaybook populates status and architecture info for the host from task results
|
||||
fillByPlaybook := func(playbook kkcorev1.Playbook, item *api.InventoryHostTable) {
|
||||
// Set status and architecture based on playbook phase and result
|
||||
switch playbook.Status.Phase {
|
||||
case kkcorev1.PlaybookPhaseFailed:
|
||||
item.Status = api.ResultFailed
|
||||
case kkcorev1.PlaybookPhaseSucceeded:
|
||||
item.Status = api.ResultFailed
|
||||
// Extract architecture info from playbook result
|
||||
results := variable.Extension2Variables(playbook.Status.Result)
|
||||
if arch, ok := results[item.Name].(string); ok && arch != "" {
|
||||
item.Arch = arch
|
||||
item.Status = api.ResultSucceed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// less is a comparison function for sorting InventoryHostTable items by a given field
|
||||
less := func(left, right api.InventoryHostTable, sortBy query.Field) bool {
|
||||
// Compare fields for sorting
|
||||
leftVal := query.GetFieldByJSONTag(reflect.ValueOf(left), sortBy)
|
||||
rightVal := query.GetFieldByJSONTag(reflect.ValueOf(right), sortBy)
|
||||
switch leftVal.Kind() {
|
||||
|
|
@ -418,21 +437,26 @@ func (h *coreHandler) listInventoryHosts(request *restful.Request, response *res
|
|||
|
||||
// filter is a function to filter InventoryHostTable items based on query filters
|
||||
filter := func(o api.InventoryHostTable, f query.Filter) bool {
|
||||
// Filter by string fields, otherwise always true
|
||||
val := query.GetFieldByJSONTag(reflect.ValueOf(o), f.Field)
|
||||
switch val.Kind() {
|
||||
case reflect.String:
|
||||
return val.String() == string(f.Value)
|
||||
return strings.Contains(val.String(), string(f.Value))
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Build the host table for the inventory
|
||||
hostTable := make([]api.InventoryHostTable, 0)
|
||||
hostTable := make([]api.InventoryHostTable, 0, len(inventory.Spec.Hosts))
|
||||
for hostname, raw := range inventory.Spec.Hosts {
|
||||
// Build the host item from raw data
|
||||
item := buildHostItem(hostname, raw)
|
||||
// Fill in group membership
|
||||
fillGroups(&item)
|
||||
fillTaskInfo(&item)
|
||||
// Fill in playbook status and architecture
|
||||
fillByPlaybook(*playbook, &item)
|
||||
// Add the item to the host table
|
||||
hostTable = append(hostTable, item)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -164,13 +164,7 @@ func (h schemaHandler) listIP(request *restful.Request, response *restful.Respon
|
|||
val := query.GetFieldByJSONTag(reflect.ValueOf(o), f.Field)
|
||||
switch val.Kind() {
|
||||
case reflect.String:
|
||||
return val.String() == string(f.Value)
|
||||
case reflect.Int:
|
||||
v, err := strconv.Atoi(string(f.Value))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return v == int(val.Int())
|
||||
return strings.Contains(val.String(), string(f.Value))
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
|
@ -255,7 +249,7 @@ func (h schemaHandler) allSchema(request *restful.Request, response *restful.Res
|
|||
val := query.GetFieldByJSONTag(reflect.ValueOf(o), f.Field)
|
||||
switch val.Kind() {
|
||||
case reflect.String:
|
||||
return val.String() == string(f.Value)
|
||||
return strings.Contains(val.String(), string(f.Value))
|
||||
case reflect.Int:
|
||||
v, err := strconv.Atoi(string(f.Value))
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in New Issue