pkg/tool/exec: allow passing env vars in exec.Run
Closes #260
https://github.com/cuelang/cue/pull/260
GitOrigin-RevId: 25e566f00afa109e7d6ef1762a18b0f466e819d9
Change-Id: I80de4b7d278abf87323b516e564125f582c97e9a
Closes #264
https://github.com/cuelang/cue/pull/264
GitOrigin-RevId: 097c5a03b5665e1677e91c1f64070f28787b0658
Change-Id: I937463c5fdb6b562b457fa550d2840705ebee91d
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4680
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/pkg/tool/exec/doc.go b/pkg/tool/exec/doc.go
index 7a8eb45..d7cb622 100644
--- a/pkg/tool/exec/doc.go
+++ b/pkg/tool/exec/doc.go
@@ -11,12 +11,11 @@
// // cmd is the command to run.
// cmd: string | [string, ...string]
//
-// // install is an optional command to install the binaries needed
-// // to run the command.
-// install?: string | [string, ...string]
-//
// // env defines the environment variables to use for this system.
-// env: [string]: string
+// // If the value is a list, the entries mus be of the form key=value,
+// // where the last value takes precendence in the case of multiple
+// // occurrances of the same key.
+// env: [string]: string | [...=~"="]
//
// // stdout captures the output from stdout if it is of type bytes or string.
// // The default value of null indicates it is redirected to the stdout of the
diff --git a/pkg/tool/exec/exec.cue b/pkg/tool/exec/exec.cue
index f827fd6..cd9d797 100644
--- a/pkg/tool/exec/exec.cue
+++ b/pkg/tool/exec/exec.cue
@@ -21,12 +21,11 @@
// cmd is the command to run.
cmd: string | [string, ...string]
- // install is an optional command to install the binaries needed
- // to run the command.
- install?: string | [string, ...string]
-
// env defines the environment variables to use for this system.
- env: [string]: string
+ // If the value is a list, the entries mus be of the form key=value,
+ // where the last value takes precendence in the case of multiple
+ // occurrances of the same key.
+ env: [string]: string | [...=~"="]
// stdout captures the output from stdout if it is of type bytes or string.
// The default value of null indicates it is redirected to the stdout of the
diff --git a/pkg/tool/exec/exec.go b/pkg/tool/exec/exec.go
index 0846b16..7c93e4b 100644
--- a/pkg/tool/exec/exec.go
+++ b/pkg/tool/exec/exec.go
@@ -42,48 +42,12 @@
}
func (c *execCmd) Run(ctx *task.Context) (res interface{}, err error) {
+ cmd, doc, err := mkCommand(ctx)
+ if err != nil {
+ return cue.Value{}, err
+ }
+
// TODO: set environment variables, if defined.
- var bin string
- var args []string
- doc := ""
- v := ctx.Lookup("cmd")
- if ctx.Err != nil {
- return nil, ctx.Err
- }
- switch v.Kind() {
- case cue.StringKind:
- str := ctx.String("cmd")
- doc = str
- list := strings.Fields(str)
- bin = list[0]
- args = append(args, list[1:]...)
-
- case cue.ListKind:
- list, _ := v.List()
- if !list.Next() {
- return cue.Value{}, errors.New("empty command list")
- }
- bin, err = list.Value().String()
- if err != nil {
- return cue.Value{}, err
- }
- doc += bin
- for list.Next() {
- str, err := list.Value().String()
- if err != nil {
- return cue.Value{}, err
- }
- args = append(args, str)
- doc += " " + str
- }
- }
-
- if bin == "" {
- return cue.Value{}, errors.New("empty command")
- }
-
- cmd := exec.CommandContext(ctx.Context, bin, args...)
-
stream := func(name string) (stream cue.Value, ok bool) {
c := ctx.Obj.Lookup(name)
// Although the schema defines a default versions, older implementations
@@ -100,7 +64,7 @@
if v, ok := stream("stdin"); !ok {
cmd.Stdin = ctx.Stdin
} else if cmd.Stdin, err = v.Reader(); err != nil {
- return nil, fmt.Errorf("cue: %v", err)
+ return nil, errors.Wrapf(err, v.Pos(), "invalid input")
}
_, captureOut := stream("stdout")
if !captureOut {
@@ -130,3 +94,73 @@
}
return update, err
}
+
+func mkCommand(ctx *task.Context) (c *exec.Cmd, doc string, err error) {
+ var bin string
+ var args []string
+
+ v := ctx.Lookup("cmd")
+ if ctx.Err != nil {
+ return nil, "", ctx.Err
+ }
+
+ switch v.Kind() {
+ case cue.StringKind:
+ str := ctx.String("cmd")
+ doc = str
+ list := strings.Fields(str)
+ bin = list[0]
+ args = append(args, list[1:]...)
+
+ case cue.ListKind:
+ list, _ := v.List()
+ if !list.Next() {
+ return nil, "", errors.New("empty command list")
+ }
+ bin, err = list.Value().String()
+ if err != nil {
+ return nil, "", err
+ }
+ doc += bin
+ for list.Next() {
+ str, err := list.Value().String()
+ if err != nil {
+ return nil, "", err
+ }
+ args = append(args, str)
+ doc += " " + str
+ }
+ }
+
+ if bin == "" {
+ return nil, "", errors.New("empty command")
+ }
+
+ cmd := exec.CommandContext(ctx.Context, bin, args...)
+
+ env := ctx.Obj.Lookup("env")
+
+ // List case.
+ for iter, _ := env.List(); iter.Next(); {
+ str, err := iter.Value().String()
+ if err != nil {
+ return nil, "", errors.Wrapf(err, v.Pos(),
+ "invalid environment variable value %q", v)
+ }
+ cmd.Env = append(cmd.Env, str)
+ }
+
+ // Struct case.
+ for iter, _ := ctx.Obj.Lookup("env").Fields(); iter.Next(); {
+ label := iter.Label()
+ v := iter.Value()
+ str, err := v.String()
+ if err != nil {
+ return nil, "", errors.Wrapf(err, v.Pos(),
+ "invalid environment variable value %q", v)
+ }
+ cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", label, str))
+ }
+
+ return cmd, doc, nil
+}
diff --git a/pkg/tool/exec/exec_test.go b/pkg/tool/exec/exec_test.go
new file mode 100644
index 0000000..fb50513
--- /dev/null
+++ b/pkg/tool/exec/exec_test.go
@@ -0,0 +1,71 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package exec
+
+import (
+ "context"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+
+ "cuelang.org/go/cue"
+ "cuelang.org/go/internal/task"
+)
+
+func TestEnv(t *testing.T) {
+ testCases := []struct {
+ desc string
+ val string
+ env []string
+ }{{
+ desc: "mapped",
+ val: `
+ cmd: "echo"
+ env: {
+ WHO: "World"
+ WHAT: "Hello"
+ WHEN: "Now!"
+ }
+ `,
+ env: []string{"WHO=World", "WHAT=Hello", "WHEN=Now!" },
+ }, {
+ val: `
+ cmd: "echo"
+ env: [ "WHO=World", "WHAT=Hello", "WHEN=Now!" ]
+ `,
+ env: []string{ "WHO=World", "WHAT=Hello", "WHEN=Now!" },
+ }}
+ for _, tc := range testCases {
+ t.Run("", func(t *testing.T) {
+ var r cue.Runtime
+ inst, err := r.Compile(tc.desc, tc.val)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cmd, _, err := mkCommand(&task.Context{
+ Context: context.Background(),
+ Obj: inst.Value(),
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !cmp.Equal(cmd.Env, tc.env) {
+ t.Error(cmp.Diff(cmd.Env, tc.env))
+ }
+ })
+ }
+}