2023-11-17 12:47:37 +00:00
|
|
|
package project
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/fs"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/databricks/cli/libs/cmdio"
|
|
|
|
"github.com/databricks/cli/libs/log"
|
|
|
|
"github.com/databricks/cli/libs/process"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/pflag"
|
|
|
|
)
|
|
|
|
|
|
|
|
type proxy struct {
|
|
|
|
Entrypoint `yaml:",inline"`
|
|
|
|
Name string `yaml:"name"`
|
|
|
|
Description string `yaml:"description"`
|
|
|
|
TableTemplate string `yaml:"table_template,omitempty"`
|
|
|
|
Flags []flag `yaml:"flags,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cp *proxy) register(parent *cobra.Command) {
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: cp.Name,
|
|
|
|
Short: cp.Description,
|
|
|
|
RunE: cp.runE,
|
|
|
|
}
|
|
|
|
parent.AddCommand(cmd)
|
|
|
|
flags := cmd.Flags()
|
|
|
|
for _, flag := range cp.Flags {
|
|
|
|
flag.register(flags)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cp *proxy) runE(cmd *cobra.Command, _ []string) error {
|
|
|
|
err := cp.checkUpdates(cmd)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
args, err := cp.commandInput(cmd)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
envs, err := cp.Prepare(cmd)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("entrypoint: %w", err)
|
|
|
|
}
|
|
|
|
ctx := cmd.Context()
|
|
|
|
log.Debugf(ctx, "Forwarding subprocess: %s", strings.Join(args, " "))
|
|
|
|
if cp.TableTemplate != "" {
|
|
|
|
return cp.renderJsonAsTable(cmd, args, envs)
|
|
|
|
}
|
|
|
|
err = process.Forwarded(ctx, args,
|
|
|
|
cmd.InOrStdin(),
|
|
|
|
cmd.OutOrStdout(),
|
|
|
|
cmd.ErrOrStderr(),
|
|
|
|
process.WithEnvs(envs))
|
2023-11-29 19:08:27 +00:00
|
|
|
if errors.Is(err, fs.ErrNotExist) && cp.IsPythonProject() {
|
2023-11-17 12:47:37 +00:00
|
|
|
msg := "cannot find Python %s. Please re-run: databricks labs install %s"
|
|
|
|
return fmt.Errorf(msg, cp.MinPython, cp.Name)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// [EXPERIMENTAL] this interface contract may change in the future.
|
|
|
|
// See https://github.com/databricks/cli/issues/994
|
|
|
|
func (cp *proxy) renderJsonAsTable(cmd *cobra.Command, args []string, envs map[string]string) error {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
ctx := cmd.Context()
|
|
|
|
err := process.Forwarded(ctx, args,
|
|
|
|
cmd.InOrStdin(),
|
|
|
|
&buf,
|
|
|
|
cmd.ErrOrStderr(),
|
|
|
|
process.WithEnvs(envs))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
var anyVal any
|
|
|
|
err = json.Unmarshal(buf.Bytes(), &anyVal)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// IntelliJ eagerly replaces tabs with spaces, even though we're not asking for it
|
|
|
|
fixedTemplate := strings.ReplaceAll(cp.TableTemplate, "\\t", "\t")
|
|
|
|
return cmdio.RenderWithTemplate(ctx, anyVal, fixedTemplate)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cp *proxy) commandInput(cmd *cobra.Command) ([]string, error) {
|
|
|
|
flags := cmd.Flags()
|
|
|
|
commandInput := struct {
|
|
|
|
Command string `json:"command"`
|
|
|
|
Flags map[string]any `json:"flags"`
|
|
|
|
OutputType string `json:"output_type"`
|
|
|
|
}{
|
|
|
|
Command: cp.Name,
|
|
|
|
Flags: map[string]any{},
|
|
|
|
}
|
|
|
|
for _, f := range cp.Flags {
|
|
|
|
v, err := f.get(flags)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("get %s flag: %w", f.Name, err)
|
|
|
|
}
|
|
|
|
commandInput.Flags[f.Name] = v
|
|
|
|
}
|
|
|
|
logLevelFlag := flags.Lookup("log-level")
|
|
|
|
if logLevelFlag != nil {
|
|
|
|
commandInput.Flags["log_level"] = logLevelFlag.Value.String()
|
|
|
|
}
|
|
|
|
args := []string{}
|
|
|
|
ctx := cmd.Context()
|
2023-11-29 19:08:27 +00:00
|
|
|
if cp.IsPythonProject() {
|
2023-11-17 12:47:37 +00:00
|
|
|
args = append(args, cp.virtualEnvPython(ctx))
|
2023-11-29 19:08:27 +00:00
|
|
|
libDir := cp.EffectiveLibDir()
|
2023-11-17 12:47:37 +00:00
|
|
|
entrypoint := filepath.Join(libDir, cp.Main)
|
|
|
|
args = append(args, entrypoint)
|
|
|
|
}
|
|
|
|
raw, err := json.Marshal(commandInput)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("command input: %w", err)
|
|
|
|
}
|
|
|
|
args = append(args, string(raw))
|
|
|
|
return args, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type flag struct {
|
|
|
|
Name string `yaml:"name"`
|
|
|
|
Description string `yaml:"description"`
|
|
|
|
Default any `yaml:"default,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *flag) register(pf *pflag.FlagSet) {
|
|
|
|
var dflt string
|
|
|
|
if f.Default != nil {
|
|
|
|
dflt = fmt.Sprint(f.Default)
|
|
|
|
}
|
|
|
|
pf.String(f.Name, dflt, f.Description)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *flag) get(pf *pflag.FlagSet) (any, error) {
|
|
|
|
return pf.GetString(f.Name)
|
|
|
|
}
|