You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
helm/pkg/postrender/exec.go

109 lines
3.2 KiB

/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package postrender
import (
"bytes"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/helmpath"
)
type execRender struct {
binaryPath string
}
// NewExec returns a PostRenderer implementation that calls the provided binary.
// It returns an error if the binary cannot be found. If the provided path does
// not contain any separators, it will search first in the plugins directory,
// then in $PATH, otherwise it will resolve any relative paths to a fully
// qualified path
func NewExec(binaryPath string) (PostRenderer, error) {
fullPath, err := getFullPath(binaryPath)
if err != nil {
return nil, err
}
return &execRender{fullPath}, nil
}
// Run the configured binary for the post render
func (p *execRender) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) {
cmd := exec.Command(p.binaryPath)
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, err
}
var postRendered = &bytes.Buffer{}
var stderr = &bytes.Buffer{}
cmd.Stdout = postRendered
cmd.Stderr = stderr
go func() {
defer stdin.Close()
io.Copy(stdin, renderedManifests)
}()
err = cmd.Run()
if err != nil {
return nil, errors.Wrapf(err, "error while running command %s. error output:\n%s", p.binaryPath, stderr.String())
}
return postRendered, nil
}
// getFullPath returns the full filepath to the binary to execute. If the path
// does not contain any separators, it will search first in the plugins
// directory (or directories if multiple are specified. In which case, it will
// return the first result), then in $PATH, otherwise it will resolve any
// relative paths to a fully qualified path
func getFullPath(binaryPath string) (string, error) {
// Manually check the plugin dir first
if !strings.Contains(binaryPath, string(filepath.Separator)) {
// First check the plugin dir
pluginDir := helmpath.DataPath("plugins") // Default location
// If location for plugins is explicitly set, check there
if v, ok := os.LookupEnv("HELM_PLUGINS"); ok {
pluginDir = v
}
// The plugins variable can actually contain multple paths, so loop through those
for _, p := range filepath.SplitList(pluginDir) {
_, err := os.Stat(filepath.Join(p, binaryPath))
if err != nil && !os.IsNotExist(err) {
return "", err
} else if err == nil {
binaryPath = filepath.Join(p, binaryPath)
break
}
}
}
// Now check for the binary using the given path or check if it exists in
// the path and is executable
checkedPath, err := exec.LookPath(binaryPath)
if err != nil {
return "", errors.Wrapf(err, "unable to find binary at %s", binaryPath)
}
return filepath.Abs(checkedPath)
}