github.com/Datadog/cnab-go@v0.3.3-beta1.0.20191007143216-bba4b7e723d0/driver/command/command.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"os/exec"
    10  	"strings"
    11  
    12  	"github.com/deislabs/cnab-go/driver"
    13  )
    14  
    15  // Driver relies upon a system command to provide a driver implementation
    16  type Driver struct {
    17  	Name string
    18  }
    19  
    20  // Run executes the command
    21  func (d *Driver) Run(op *driver.Operation) (driver.OperationResult, error) {
    22  	return d.exec(op)
    23  }
    24  
    25  // Handles executes the driver with `--handles` and parses the results
    26  func (d *Driver) Handles(dt string) bool {
    27  	out, err := exec.Command(d.cliName(), "--handles").CombinedOutput()
    28  	if err != nil {
    29  		fmt.Printf("%s --handles: %s", d.cliName(), err)
    30  		return false
    31  	}
    32  	types := strings.Split(string(out), ",")
    33  	for _, tt := range types {
    34  		if dt == strings.TrimSpace(tt) {
    35  			return true
    36  		}
    37  	}
    38  	return false
    39  }
    40  
    41  func (d *Driver) cliName() string {
    42  	return "cnab-" + strings.ToLower(d.Name)
    43  }
    44  
    45  func (d *Driver) exec(op *driver.Operation) (driver.OperationResult, error) {
    46  	// We need to do two things here: We need to make it easier for the
    47  	// command to access data, and we need to make it easy for the command
    48  	// to pass that data on to the image it invokes. So we do some data
    49  	// duplication.
    50  
    51  	// Construct an environment for the subprocess by cloning our
    52  	// environment and adding in all the extra env vars.
    53  	pairs := os.Environ()
    54  	added := []string{}
    55  	for k, v := range op.Environment {
    56  		pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
    57  		added = append(added, k)
    58  	}
    59  	// CNAB_VARS is a list of variables we added to the env. This is to make
    60  	// it easier for shell script drivers to clone the env vars.
    61  	pairs = append(pairs, fmt.Sprintf("CNAB_VARS=%s", strings.Join(added, ",")))
    62  
    63  	data, err := json.Marshal(op)
    64  	if err != nil {
    65  		return driver.OperationResult{}, err
    66  	}
    67  	args := []string{}
    68  	cmd := exec.Command(d.cliName(), args...)
    69  	cmd.Dir, err = os.Getwd()
    70  	if err != nil {
    71  		return driver.OperationResult{}, err
    72  	}
    73  	cmd.Env = pairs
    74  	cmd.Stdin = bytes.NewBuffer(data)
    75  
    76  	// Make stdout and stderr from driver available immediately
    77  
    78  	stdout, err := cmd.StdoutPipe()
    79  	if err != nil {
    80  		return driver.OperationResult{}, fmt.Errorf("Setting up output handling for driver (%s) failed: %v", d.Name, err)
    81  	}
    82  
    83  	go func() {
    84  
    85  		// Errors not handled here as they only prevent output from the driver being shown, errors in the command execution are handled when command is executed
    86  
    87  		io.Copy(op.Out, stdout)
    88  	}()
    89  	stderr, err := cmd.StderrPipe()
    90  	if err != nil {
    91  		return driver.OperationResult{}, fmt.Errorf("Setting up error output handling for driver (%s) failed: %v", d.Name, err)
    92  	}
    93  	go func() {
    94  
    95  		// Errors not handled here as they only prevent output from the driver being shown, errors in the command execution are handled when command is executed
    96  
    97  		io.Copy(op.Out, stderr)
    98  	}()
    99  
   100  	if err = cmd.Start(); err != nil {
   101  		return driver.OperationResult{}, fmt.Errorf("Start of driver (%s) failed: %v", d.Name, err)
   102  	}
   103  
   104  	return driver.OperationResult{}, cmd.Wait()
   105  }