...

Source file src/cuelang.org/go/pkg/tool/exec/exec.go

Documentation: cuelang.org/go/pkg/tool/exec

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

View as plain text