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 }