...

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  	"os/exec"
    20  	"reflect"
    21  	"strings"
    22  
    23  	"github.com/pkg/errors"
    24  )
    25  
    26  // ExecTag stands for the path of executable file in command.
    27  // If we want this util works ,
    28  // we must add Exec in the struct and use NewExec() to initialize it,
    29  // because the default way to initialize Exec means None in code.
    30  const ExecTag = "exec"
    31  
    32  // SubCommandTag stands for the sub command in common command.
    33  // We can use it in struct fields as a tag.
    34  // Just like MatchExtension below
    35  //
    36  //	type Iptables Struct {
    37  //		Exec
    38  //		MatchExtension Match `sub_command:""`
    39  //	}
    40  //
    41  //	type Match Struct {
    42  //		Exec
    43  //		Port string `para:"-p"`
    44  //	}
    45  //
    46  // Field with SubcommandTag needs to be a struct with Exec.
    47  const SubCommandTag = "sub_command"
    48  
    49  // ParaTag stands for parameters in command.
    50  // We can use it in struct fields as a tag.
    51  // Just like Port below
    52  //
    53  //	type Iptables Struct {
    54  //		Exec
    55  //		Port string `para:"-p"`
    56  //	}
    57  //
    58  // If the field is not string type or []string type , it will bring an error.
    59  // If the tag value like "-p" is empty string ,
    60  // the para will just add the field value into the command just as some single value parameter in command.
    61  // If the value of field is empty string or empty string slice or empty slice, the field and tag will all be skipped.
    62  const ParaTag = "para"
    63  
    64  // Exec is the interface of a command.
    65  // We need to inherit it in the struct of command.
    66  // User must add ExecTag as the tag of Exec field.
    67  // Example:
    68  //
    69  //	type Iptables struct {
    70  //		Exec           `exec:"iptables"`
    71  //		Tables         string `para:"-t"`
    72  //	}
    73  type Exec struct {
    74  	active bool
    75  }
    76  
    77  func NewExec() Exec {
    78  	return Exec{active: true}
    79  }
    80  
    81  func ToCommand(i interface{}) (*exec.Cmd, error) {
    82  	path, args, err := Marshal(i)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	return exec.Command(path, args...), nil
    87  }
    88  
    89  func Marshal(i interface{}) (string, []string, error) {
    90  	value := reflect.ValueOf(i)
    91  	return marshal(value)
    92  }
    93  
    94  func marshal(value reflect.Value) (string, []string, error) {
    95  	//var options []string
    96  	if path, ok := SearchKey(value); ok {
    97  		// Field(0).String is Exec.Path
    98  
    99  		if path == "" {
   100  			return "", nil, nil
   101  		}
   102  		args := make([]string, 0)
   103  		for i := 0; i < value.NumField(); i++ {
   104  			if _, ok := value.Type().Field(i).Tag.Lookup(SubCommandTag); ok {
   105  				subPath, subArgs, err := marshal(value.Field(i))
   106  				if err != nil {
   107  					return "", nil, err
   108  				}
   109  				if subPath != "" {
   110  					args = append(args, subPath)
   111  				}
   112  				args = append(args, subArgs...)
   113  			}
   114  			if paraName, ok := value.Type().Field(i).Tag.Lookup(ParaTag); ok {
   115  				if value.Type().Field(i).Type.Name() == "string" {
   116  					if value.Field(i).String() != "" {
   117  						if paraName != "" {
   118  							args = append(args, paraName)
   119  						}
   120  						args = append(args, value.Field(i).String())
   121  					}
   122  				} else if value.Field(i).Kind() == reflect.Slice {
   123  					if slicePara, ok := value.Field(i).Interface().([]string); ok {
   124  						if strings.Join(slicePara, "") != "" {
   125  							if paraName != "" {
   126  								args = append(args, paraName)
   127  							}
   128  							args = append(args, slicePara...)
   129  						}
   130  					} else {
   131  						return "", nil, errors.Errorf("invalid parameter slice type %s :parameter slice must be string slice", value.Field(i).String())
   132  					}
   133  				} else {
   134  					return "", nil, errors.Errorf("invalid parameter type %s : parameter must be string or string slice", value.Type().Field(i).Type.Name())
   135  				}
   136  			}
   137  		}
   138  		return path, args, nil
   139  	}
   140  	return "", nil, nil
   141  }
   142  
   143  func SearchKey(value reflect.Value) (string, bool) {
   144  	for i := 0; i < value.NumField(); i++ {
   145  		if path, ok := value.Type().Field(i).Tag.Lookup(ExecTag); ok {
   146  			if value.Field(i).Field(0).Bool() {
   147  				return path, ok
   148  			}
   149  		}
   150  	}
   151  	return "", false
   152  }
   153