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 }