github.com/solo-io/cue@v0.4.7/pkg/tool/exec/exec.go (about)

     1  // Copyright 2019 CUE 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  package exec
    16  
    17  //go:generate go run gen.go
    18  //go:generate gofmt -s -w .
    19  
    20  import (
    21  	"fmt"
    22  	"os/exec"
    23  	"strings"
    24  
    25  	"github.com/solo-io/cue/cue"
    26  	"github.com/solo-io/cue/cue/errors"
    27  	"github.com/solo-io/cue/internal/task"
    28  )
    29  
    30  func init() {
    31  	task.Register("tool/exec.Run", newExecCmd)
    32  
    33  	// For backwards compatibility.
    34  	task.Register("exec", newExecCmd)
    35  }
    36  
    37  type execCmd struct{}
    38  
    39  func newExecCmd(v cue.Value) (task.Runner, error) {
    40  	return &execCmd{}, nil
    41  }
    42  
    43  func (c *execCmd) Run(ctx *task.Context) (res interface{}, err error) {
    44  	cmd, doc, err := mkCommand(ctx)
    45  	if err != nil {
    46  		return cue.Value{}, err
    47  	}
    48  
    49  	// TODO: set environment variables, if defined.
    50  	stream := func(name string) (stream cue.Value, ok bool) {
    51  		c := ctx.Obj.Lookup(name)
    52  		// Although the schema defines a default versions, older implementations
    53  		// may not use it yet.
    54  		if !c.Exists() {
    55  			return
    56  		}
    57  		if err := c.Null(); ctx.Err != nil || err == nil {
    58  			return
    59  		}
    60  		return c, true
    61  	}
    62  
    63  	if v, ok := stream("stdin"); !ok {
    64  		cmd.Stdin = ctx.Stdin
    65  	} else if cmd.Stdin, err = v.Reader(); err != nil {
    66  		return nil, errors.Wrapf(err, v.Pos(), "invalid input")
    67  	}
    68  	_, captureOut := stream("stdout")
    69  	if !captureOut {
    70  		cmd.Stdout = ctx.Stdout
    71  	}
    72  	_, captureErr := stream("stderr")
    73  	if !captureErr {
    74  		cmd.Stderr = ctx.Stderr
    75  	}
    76  
    77  	update := map[string]interface{}{}
    78  	if captureOut {
    79  		var stdout []byte
    80  		stdout, err = cmd.Output()
    81  		update["stdout"] = string(stdout)
    82  	} else {
    83  		err = cmd.Run()
    84  	}
    85  	update["success"] = err == nil
    86  	if err != nil {
    87  		if exit := (*exec.ExitError)(nil); errors.As(err, &exit) && captureErr {
    88  			update["stderr"] = string(exit.Stderr)
    89  		} else {
    90  			update = nil
    91  		}
    92  		err = fmt.Errorf("command %q failed: %v", doc, err)
    93  	}
    94  	return update, err
    95  }
    96  
    97  func mkCommand(ctx *task.Context) (c *exec.Cmd, doc string, err error) {
    98  	var bin string
    99  	var args []string
   100  
   101  	v := ctx.Lookup("cmd")
   102  	if ctx.Err != nil {
   103  		return nil, "", ctx.Err
   104  	}
   105  
   106  	switch v.Kind() {
   107  	case cue.StringKind:
   108  		str := ctx.String("cmd")
   109  		doc = str
   110  		list := strings.Fields(str)
   111  		bin = list[0]
   112  		args = append(args, list[1:]...)
   113  
   114  	case cue.ListKind:
   115  		list, _ := v.List()
   116  		if !list.Next() {
   117  			return nil, "", errors.New("empty command list")
   118  		}
   119  		bin, err = list.Value().String()
   120  		if err != nil {
   121  			return nil, "", err
   122  		}
   123  		doc += bin
   124  		for list.Next() {
   125  			str, err := list.Value().String()
   126  			if err != nil {
   127  				return nil, "", err
   128  			}
   129  			args = append(args, str)
   130  			doc += " " + str
   131  		}
   132  	}
   133  
   134  	if bin == "" {
   135  		return nil, "", errors.New("empty command")
   136  	}
   137  
   138  	cmd := exec.CommandContext(ctx.Context, bin, args...)
   139  
   140  	cmd.Dir, _ = ctx.Obj.Lookup("dir").String()
   141  
   142  	env := ctx.Obj.Lookup("env")
   143  
   144  	// List case.
   145  	for iter, _ := env.List(); iter.Next(); {
   146  		str, err := iter.Value().String()
   147  		if err != nil {
   148  			return nil, "", errors.Wrapf(err, v.Pos(),
   149  				"invalid environment variable value %q", v)
   150  		}
   151  		cmd.Env = append(cmd.Env, str)
   152  	}
   153  
   154  	// Struct case.
   155  	for iter, _ := ctx.Obj.Lookup("env").Fields(); iter.Next(); {
   156  		label := iter.Label()
   157  		v := iter.Value()
   158  		var str string
   159  		switch v.Kind() {
   160  		case cue.StringKind:
   161  			str, _ = v.String()
   162  		case cue.IntKind, cue.FloatKind, cue.NumberKind:
   163  			str = fmt.Sprint(v)
   164  		default:
   165  			return nil, "", errors.Newf(v.Pos(),
   166  				"invalid environment variable value %q", v)
   167  		}
   168  		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", label, str))
   169  	}
   170  
   171  	return cmd, doc, nil
   172  }