...

Source file src/github.com/chaos-mesh/chaos-mesh/pkg/command/command.go

Documentation: github.com/chaos-mesh/chaos-mesh/pkg/command

     1  // Copyright 2021 Chaos Mesh Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  
    16  package command
    17  
    18  import (
    19  	"fmt"
    20  	"os/exec"
    21  	"reflect"
    22  	"strings"
    23  )
    24  
    25  // ExecTag stands for the path of executable file in command.
    26  // If we want this util works ,
    27  // we must add Exec in the struct and use NewExec() to initialize it,
    28  // because the default way to initialize Exec means None in code.
    29  const ExecTag = "exec"
    30  
    31  // SubCommandTag stands for the sub command in common command.
    32  // We can use it in struct fields as a tag.
    33  // Just like MatchExtension below
    34  // type Iptables Struct {
    35  // 	Exec
    36  //	MatchExtension Match `sub_command:""`
    37  // }
    38  //
    39  // type Match Struct {
    40  // 	Exec
    41  //	Port string `para:"-p"`
    42  // }
    43  // Field with SubcommandTag needs to be a struct with Exec.
    44  const SubCommandTag = "sub_command"
    45  
    46  // ParaTag stands for parameters in command.
    47  // We can use it in struct fields as a tag.
    48  // Just like Port below
    49  // type Iptables Struct {
    50  // 	Exec
    51  //	Port string `para:"-p"`
    52  // }
    53  // If the field is not string type or []string type , it will bring an error.
    54  // If the tag value like "-p" is empty string ,
    55  // the para will just add the field value into the command just as some single value parameter in command.
    56  // If the value of field is empty string or empty string slice or empty slice, the field and tag will all be skipped.
    57  const ParaTag = "para"
    58  
    59  // Exec is the interface of a command.
    60  // We need to inherit it in the struct of command.
    61  // User must add ExecTag as the tag of Exec field.
    62  // Example:
    63  // type Iptables struct {
    64  //	Exec           `exec:"iptables"`
    65  //	Tables         string `para:"-t"`
    66  // }
    67  type Exec struct {
    68  	active bool
    69  }
    70  
    71  func NewExec() Exec {
    72  	return Exec{active: true}
    73  }
    74  
    75  func ToCommand(i interface{}) (*exec.Cmd, error) {
    76  	path, args, err := Marshal(i)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	return exec.Command(path, args...), nil
    81  }
    82  
    83  func Marshal(i interface{}) (string, []string, error) {
    84  	value := reflect.ValueOf(i)
    85  	return marshal(value)
    86  }
    87  
    88  func marshal(value reflect.Value) (string, []string, error) {
    89  	//var options []string
    90  	if path, ok := SearchKey(value); ok {
    91  		// Field(0).String is Exec.Path
    92  
    93  		if path == "" {
    94  			return "", nil, nil
    95  		}
    96  		args := make([]string, 0)
    97  		for i := 0; i < value.NumField(); i++ {
    98  			if _, ok := value.Type().Field(i).Tag.Lookup(SubCommandTag); ok {
    99  				subPath, subArgs, err := marshal(value.Field(i))
   100  				if err != nil {
   101  					return "", nil, err
   102  				}
   103  				if subPath != "" {
   104  					args = append(args, subPath)
   105  				}
   106  				args = append(args, subArgs...)
   107  			}
   108  			if paraName, ok := value.Type().Field(i).Tag.Lookup(ParaTag); ok {
   109  				if value.Type().Field(i).Type.Name() == "string" {
   110  					if value.Field(i).String() != "" {
   111  						if paraName != "" {
   112  							args = append(args, paraName)
   113  						}
   114  						args = append(args, value.Field(i).String())
   115  					}
   116  				} else if value.Field(i).Kind() == reflect.Slice {
   117  					if slicePara, ok := value.Field(i).Interface().([]string); ok {
   118  						if strings.Join(slicePara, "") != "" {
   119  							if paraName != "" {
   120  								args = append(args, paraName)
   121  							}
   122  							args = append(args, slicePara...)
   123  						}
   124  					} else {
   125  						return "", nil, fmt.Errorf("invalid parameter slice type %s :parameter slice must be string slice", value.Field(i).String())
   126  					}
   127  				} else {
   128  					return "", nil, fmt.Errorf("invalid parameter type %s : parameter must be string or string slice", value.Type().Field(i).Type.Name())
   129  				}
   130  			}
   131  		}
   132  		return path, args, nil
   133  	}
   134  	return "", nil, nil
   135  }
   136  
   137  func SearchKey(value reflect.Value) (string, bool) {
   138  	for i := 0; i < value.NumField(); i++ {
   139  		if path, ok := value.Type().Field(i).Tag.Lookup(ExecTag); ok {
   140  			if value.Field(i).Field(0).Bool() {
   141  				return path, ok
   142  			}
   143  		}
   144  	}
   145  	return "", false
   146  }
   147