github.com/jdolitsky/cnab-go@v0.7.1-beta1/driver/command/command.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path"
    12  	"strings"
    13  
    14  	"github.com/deislabs/cnab-go/driver"
    15  )
    16  
    17  // Driver relies upon a system command to provide a driver implementation
    18  type Driver struct {
    19  	Name          string
    20  	outputDirName string
    21  }
    22  
    23  // Run executes the command
    24  func (d *Driver) Run(op *driver.Operation) (driver.OperationResult, error) {
    25  	return d.exec(op)
    26  }
    27  
    28  // Handles executes the driver with `--handles` and parses the results
    29  func (d *Driver) Handles(dt string) bool {
    30  	out, err := exec.Command(d.cliName(), "--handles").CombinedOutput()
    31  	if err != nil {
    32  		fmt.Printf("%s --handles: %s", d.cliName(), err)
    33  		return false
    34  	}
    35  	types := strings.Split(string(out), ",")
    36  	for _, tt := range types {
    37  		if dt == strings.TrimSpace(tt) {
    38  			return true
    39  		}
    40  	}
    41  	return false
    42  }
    43  
    44  func (d *Driver) cliName() string {
    45  	return "cnab-" + strings.ToLower(d.Name)
    46  }
    47  
    48  func (d *Driver) exec(op *driver.Operation) (driver.OperationResult, error) {
    49  	// We need to do two things here: We need to make it easier for the
    50  	// command to access data, and we need to make it easy for the command
    51  	// to pass that data on to the image it invokes. So we do some data
    52  	// duplication.
    53  
    54  	// Construct an environment for the subprocess by cloning our
    55  	// environment and adding in all the extra env vars.
    56  	pairs := os.Environ()
    57  	added := []string{}
    58  	for k, v := range op.Environment {
    59  		pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
    60  		added = append(added, k)
    61  	}
    62  	// Create a directory that can be used for outputs and then pass it as a command line argument
    63  	if len(op.Outputs) > 0 {
    64  		var err error
    65  		d.outputDirName, err = ioutil.TempDir("", "bundleoutput")
    66  		if err != nil {
    67  			return driver.OperationResult{}, err
    68  		}
    69  		defer os.RemoveAll(d.outputDirName)
    70  		// Set the env var CNAB_OUTPUT_DIR to the location of the folder
    71  		pairs = append(pairs, fmt.Sprintf("%s=%s", "CNAB_OUTPUT_DIR", d.outputDirName))
    72  		added = append(added, "CNAB_OUTPUT_DIR")
    73  	}
    74  
    75  	// CNAB_VARS is a list of variables we added to the env. This is to make
    76  	// it easier for shell script drivers to clone the env vars.
    77  	pairs = append(pairs, fmt.Sprintf("CNAB_VARS=%s", strings.Join(added, ",")))
    78  	data, err := json.Marshal(op)
    79  	if err != nil {
    80  		return driver.OperationResult{}, err
    81  	}
    82  
    83  	args := []string{}
    84  	cmd := exec.Command(d.cliName(), args...)
    85  	cmd.Dir, err = os.Getwd()
    86  	if err != nil {
    87  		return driver.OperationResult{}, err
    88  	}
    89  	cmd.Env = pairs
    90  	cmd.Stdin = bytes.NewBuffer(data)
    91  	// Make stdout and stderr from driver available immediately
    92  	stdout, err := cmd.StdoutPipe()
    93  	if err != nil {
    94  		return driver.OperationResult{}, fmt.Errorf("Setting up output handling for driver (%s) failed: %v", d.Name, err)
    95  	}
    96  
    97  	go func() {
    98  
    99  		// 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
   100  
   101  		io.Copy(op.Out, stdout)
   102  	}()
   103  	stderr, err := cmd.StderrPipe()
   104  	if err != nil {
   105  		return driver.OperationResult{}, fmt.Errorf("Setting up error output handling for driver (%s) failed: %v", d.Name, err)
   106  	}
   107  
   108  	go func() {
   109  
   110  		// 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
   111  
   112  		io.Copy(op.Out, stderr)
   113  	}()
   114  
   115  	if err = cmd.Start(); err != nil {
   116  		return driver.OperationResult{}, fmt.Errorf("Start of driver (%s) failed: %v", d.Name, err)
   117  	}
   118  
   119  	if err = cmd.Wait(); err != nil {
   120  		return driver.OperationResult{}, fmt.Errorf("Command driver (%s) failed executing bundle: %v", d.Name, err)
   121  	}
   122  
   123  	result, err := d.getOperationResult(op)
   124  	if err != nil {
   125  		return driver.OperationResult{}, fmt.Errorf("Command driver (%s) failed getting operation result: %v", d.Name, err)
   126  	}
   127  	return result, nil
   128  }
   129  func (d *Driver) getOperationResult(op *driver.Operation) (driver.OperationResult, error) {
   130  	opResult := driver.OperationResult{
   131  		Outputs: map[string]string{},
   132  	}
   133  	for _, item := range op.Outputs {
   134  		fileName := path.Join(d.outputDirName, item)
   135  		_, err := os.Stat(fileName)
   136  		if err != nil {
   137  			if os.IsNotExist(err) {
   138  				continue
   139  			}
   140  			return opResult, fmt.Errorf("Command driver (%s) failed checking for output file: %s Error: %v", d.Name, item, err)
   141  		}
   142  
   143  		contents, err := ioutil.ReadFile(fileName)
   144  		if err != nil {
   145  			return opResult, fmt.Errorf("Command driver (%s) failed reading output file: %s Error: %v", d.Name, item, err)
   146  		}
   147  
   148  		opResult.Outputs[item] = string(contents)
   149  	}
   150  	// Check if there are missing outputs and get default values if any
   151  	for name, output := range op.Bundle.Outputs {
   152  		if output.AppliesTo(op.Action) {
   153  			if _, exists := opResult.Outputs[output.Path]; !exists {
   154  				if outputDefinition, exists := op.Bundle.Definitions[output.Definition]; exists && outputDefinition.Default != nil {
   155  					opResult.Outputs[output.Path] = fmt.Sprintf("%v", outputDefinition.Default)
   156  				} else {
   157  					return opResult, fmt.Errorf("Command driver (%s) failed - required output %s for action %s is missing and has no default is defined", d.Name, name, op.Action)
   158  				}
   159  			}
   160  		}
   161  	}
   162  	return opResult, nil
   163  }