Add new Client API
Signed-off-by: Helder Correia <174525+helderco@users.noreply.github.com>
This commit is contained in:
165
plan/task/clientcommand.go
Normal file
165
plan/task/clientcommand.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"cuelang.org/go/cue"
|
||||
"github.com/rs/zerolog/log"
|
||||
"go.dagger.io/dagger/compiler"
|
||||
"go.dagger.io/dagger/plancontext"
|
||||
"go.dagger.io/dagger/solver"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register("ClientCommand", func() Task { return &clientCommandTask{} })
|
||||
}
|
||||
|
||||
type clientCommandTask struct {
|
||||
}
|
||||
|
||||
func (t clientCommandTask) Run(ctx context.Context, pctx *plancontext.Context, s solver.Solver, v *compiler.Value) (*compiler.Value, error) {
|
||||
var opts struct {
|
||||
Name string
|
||||
Args []string
|
||||
}
|
||||
|
||||
if err := v.Decode(&opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
flags, err := v.Lookup("flags").Fields()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var flagArgs []string
|
||||
for _, flag := range flags {
|
||||
switch flag.Value.Kind() {
|
||||
case cue.BoolKind:
|
||||
if b, _ := flag.Value.Bool(); b {
|
||||
flagArgs = append(flagArgs, flag.Label())
|
||||
}
|
||||
case cue.StringKind:
|
||||
if s, _ := flag.Value.String(); s != "" {
|
||||
flagArgs = append(flagArgs, flag.Label(), s)
|
||||
}
|
||||
}
|
||||
}
|
||||
opts.Args = append(flagArgs, opts.Args...)
|
||||
|
||||
envs, err := v.Lookup("env").Fields()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
env := make([]string, len(envs))
|
||||
for _, envvar := range envs {
|
||||
s, err := t.getString(pctx, envvar.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env = append(env, fmt.Sprintf("%s=%s", envvar.Label(), s))
|
||||
}
|
||||
|
||||
lg := log.Ctx(ctx)
|
||||
lg.Debug().Str("name", opts.Name).Str("args", strings.Join(opts.Args, " ")).Msg("running client command")
|
||||
|
||||
cmd := exec.CommandContext(ctx, opts.Name, opts.Args...) //#nosec G204
|
||||
cmd.Env = append(os.Environ(), env...)
|
||||
|
||||
if i := v.Lookup("stdin"); i.Exists() {
|
||||
val, err := t.getString(pctx, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer stdin.Close()
|
||||
io.WriteString(stdin, val)
|
||||
}()
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stdoutVal, err := t.readPipe(&stdout, pctx, v.Lookup("stdout"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stderrVal, err := t.readPipe(&stderr, pctx, v.Lookup("stderr"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
// FIXME: stderr may be requested as a secret
|
||||
lg.Err(err).Msg(string(exitErr.Stderr))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return compiler.NewValue().FillFields(map[string]interface{}{
|
||||
"stdout": stdoutVal,
|
||||
"stderr": stderrVal,
|
||||
})
|
||||
}
|
||||
|
||||
func (t clientCommandTask) getString(pctx *plancontext.Context, v *compiler.Value) (string, error) {
|
||||
if plancontext.IsSecretValue(v) {
|
||||
secret, err := pctx.Secrets.FromValue(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return secret.PlainText(), nil
|
||||
}
|
||||
|
||||
s, err := v.String()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (t clientCommandTask) readPipe(pipe *io.ReadCloser, pctx *plancontext.Context, v *compiler.Value) (*compiler.Value, error) {
|
||||
slurp, err := io.ReadAll(*pipe)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
read := string(slurp)
|
||||
val, _ := v.Default()
|
||||
out := compiler.NewValue()
|
||||
|
||||
if plancontext.IsSecretValue(val) {
|
||||
secret := pctx.Secrets.New(read)
|
||||
return out.Fill(secret.MarshalCUE())
|
||||
}
|
||||
|
||||
return out.Fill(read)
|
||||
}
|
Reference in New Issue
Block a user