github.com/splunk/qbec@v0.15.2/vm/internal/ds/exec/exec.go (about)

     1  /*
     2     Copyright 2021 Splunk Inc.
     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 exec provides a data source implementation that can execute external commands and return
    18  // its standard output for import or importstr use.
    19  package exec
    20  
    21  import (
    22  	"encoding/json"
    23  	"fmt"
    24  	"os"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"time"
    28  
    29  	"github.com/pkg/errors"
    30  	"github.com/splunk/qbec/vm/datasource"
    31  	"github.com/splunk/qbec/vm/internal/ds"
    32  )
    33  
    34  // Scheme is scheme supported by this data source
    35  const (
    36  	Scheme = "exec"
    37  )
    38  
    39  // Config is the configuration of the data source.
    40  type Config struct {
    41  	Command    string            `json:"command"`              // the executable that is run
    42  	Args       []string          `json:"args,omitempty"`       // arguments to be passed to the command
    43  	Env        map[string]string `json:"env,omitempty"`        // environment for the command
    44  	Stdin      string            `json:"stdin,omitempty"`      // standard input to pass to the command
    45  	Timeout    string            `json:"timeout,omitempty"`    // command timeout as a duration string
    46  	InheritEnv bool              `json:"inheritEnv,omitempty"` // Inherit env from the parent(qbec) process
    47  
    48  	timeout time.Duration // internal representation
    49  }
    50  
    51  func findExecutable(cmd string) (string, error) {
    52  	if !filepath.IsAbs(cmd) {
    53  		p, err := filepath.Abs(cmd)
    54  		if err == nil {
    55  			stat, err := os.Stat(cmd)
    56  			if err == nil {
    57  				if m := stat.Mode(); !m.IsDir() && m&0111 != 0 {
    58  					return p, nil
    59  				}
    60  			}
    61  		}
    62  	}
    63  	return exec.LookPath(cmd)
    64  }
    65  
    66  func (c *Config) assertValid() error {
    67  	if c.Command == "" {
    68  		return fmt.Errorf("command not specified")
    69  	}
    70  	if c.Timeout != "" {
    71  		t, err := time.ParseDuration(c.Timeout)
    72  		if err != nil {
    73  			return fmt.Errorf("invalid timeout '%s': %v", c.Timeout, err)
    74  		}
    75  		c.timeout = t
    76  	}
    77  	exe, err := findExecutable(c.Command)
    78  	if err != nil {
    79  		return fmt.Errorf("invalid command '%s': %v", c.Command, err)
    80  	}
    81  	c.Command = exe
    82  	return nil
    83  }
    84  
    85  func (c *Config) initDefaults() {
    86  	if c.timeout == 0 {
    87  		c.timeout = time.Minute
    88  	}
    89  }
    90  
    91  type execSource struct {
    92  	name      string
    93  	configVar string
    94  	runner    *runner
    95  }
    96  
    97  // New creates a new exec data source
    98  func New(name string, configVar string) ds.DataSourceWithLifecycle {
    99  	return &execSource{
   100  		name:      name,
   101  		configVar: configVar,
   102  	}
   103  }
   104  
   105  // Name implements the interface method
   106  func (d *execSource) Name() string {
   107  	return d.name
   108  }
   109  
   110  // Init implements the interface method.
   111  func (d *execSource) Init(p datasource.ConfigProvider) (fErr error) {
   112  	defer func() {
   113  		fErr = errors.Wrapf(fErr, "init data source %s", d.name) // nil wraps as nil
   114  	}()
   115  	cfgJSON, err := p(d.configVar)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	var c Config
   120  	err = json.Unmarshal([]byte(cfgJSON), &c)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	err = c.assertValid()
   125  	if err != nil {
   126  		return err
   127  	}
   128  	c.initDefaults()
   129  	d.runner = newRunner(&c)
   130  	return nil
   131  }
   132  
   133  // Resolve implements the interface method.
   134  func (d *execSource) Resolve(path string) (string, error) {
   135  	return d.runner.runWithEnv(map[string]string{
   136  		"__DS_NAME__": d.name,
   137  		"__DS_PATH__": path,
   138  	})
   139  }
   140  
   141  // Close implements the interface method.
   142  func (d *execSource) Close() error {
   143  	return d.runner.close()
   144  }