1 /* 2 Copyright The Helm Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package postrender 18 19 import ( 20 "bytes" 21 "io" 22 "os/exec" 23 "path/filepath" 24 25 "github.com/pkg/errors" 26 ) 27 28 type execRender struct { 29 binaryPath string 30 args []string 31 } 32 33 // NewExec returns a PostRenderer implementation that calls the provided binary. 34 // It returns an error if the binary cannot be found. If the path does not 35 // contain any separators, it will search in $PATH, otherwise it will resolve 36 // any relative paths to a fully qualified path 37 func NewExec(binaryPath string, args ...string) (PostRenderer, error) { 38 fullPath, err := getFullPath(binaryPath) 39 if err != nil { 40 return nil, err 41 } 42 return &execRender{fullPath, args}, nil 43 } 44 45 // Run the configured binary for the post render 46 func (p *execRender) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { 47 cmd := exec.Command(p.binaryPath, p.args...) 48 stdin, err := cmd.StdinPipe() 49 if err != nil { 50 return nil, err 51 } 52 53 var postRendered = &bytes.Buffer{} 54 var stderr = &bytes.Buffer{} 55 cmd.Stdout = postRendered 56 cmd.Stderr = stderr 57 58 go func() { 59 defer stdin.Close() 60 io.Copy(stdin, renderedManifests) 61 }() 62 err = cmd.Run() 63 if err != nil { 64 return nil, errors.Wrapf(err, "error while running command %s. error output:\n%s", p.binaryPath, stderr.String()) 65 } 66 67 return postRendered, nil 68 } 69 70 // getFullPath returns the full filepath to the binary to execute. If the path 71 // does not contain any separators, it will search in $PATH, otherwise it will 72 // resolve any relative paths to a fully qualified path 73 func getFullPath(binaryPath string) (string, error) { 74 // NOTE(thomastaylor312): I am leaving this code commented out here. During 75 // the implementation of post-render, it was brought up that if we are 76 // relying on plugins, we should actually use the plugin system so it can 77 // properly handle multiple OSs. This will be a feature add in the future, 78 // so I left this code for reference. It can be deleted or reused once the 79 // feature is implemented 80 81 // Manually check the plugin dir first 82 // if !strings.Contains(binaryPath, string(filepath.Separator)) { 83 // // First check the plugin dir 84 // pluginDir := helmpath.DataPath("plugins") // Default location 85 // // If location for plugins is explicitly set, check there 86 // if v, ok := os.LookupEnv("HELM_PLUGINS"); ok { 87 // pluginDir = v 88 // } 89 // // The plugins variable can actually contain multiple paths, so loop through those 90 // for _, p := range filepath.SplitList(pluginDir) { 91 // _, err := os.Stat(filepath.Join(p, binaryPath)) 92 // if err != nil && !os.IsNotExist(err) { 93 // return "", err 94 // } else if err == nil { 95 // binaryPath = filepath.Join(p, binaryPath) 96 // break 97 // } 98 // } 99 // } 100 101 // Now check for the binary using the given path or check if it exists in 102 // the path and is executable 103 checkedPath, err := exec.LookPath(binaryPath) 104 if err != nil { 105 return "", errors.Wrapf(err, "unable to find binary at %s", binaryPath) 106 } 107 108 return filepath.Abs(checkedPath) 109 } 110