github.com/kristofferahl/go-centry@v1.5.0/cmd/centry/script.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"os/exec"
     6  	"strings"
     7  	"syscall"
     8  
     9  	"github.com/kristofferahl/go-centry/internal/pkg/cmd"
    10  	"github.com/kristofferahl/go-centry/internal/pkg/config"
    11  	"github.com/kristofferahl/go-centry/internal/pkg/shell"
    12  	"github.com/sirupsen/logrus"
    13  	"github.com/urfave/cli/v2"
    14  )
    15  
    16  // ScriptCommand is a Command implementation that applies stuff
    17  type ScriptCommand struct {
    18  	Context       *Context
    19  	Log           *logrus.Entry
    20  	Command       config.Command
    21  	GlobalOptions *cmd.OptionsSet
    22  	Script        shell.Script
    23  	Function      shell.Function
    24  }
    25  
    26  // GetCommandInvocation returns the command invocation string
    27  func (sc *ScriptCommand) GetCommandInvocation() string {
    28  	return strings.Replace(sc.Function.Name, sc.Script.FunctionNamespaceSplitChar(), " ", -1)
    29  }
    30  
    31  // GetCommandInvocationPath returns the command invocation path
    32  func (sc *ScriptCommand) GetCommandInvocationPath() []string {
    33  	return strings.Split(sc.GetCommandInvocation(), " ")
    34  }
    35  
    36  // ToCLICommand returns a CLI command
    37  func (sc *ScriptCommand) ToCLICommand() *cli.Command {
    38  	cmdKeys := sc.GetCommandInvocationPath()
    39  	cmdName := cmdKeys[len(cmdKeys)-1]
    40  	cmdHidden := sc.Command.Hidden || sc.Function.Hidden
    41  	return withCommandDefaults(&cli.Command{
    42  		Name:      cmdName,
    43  		Usage:     sc.Command.Description,
    44  		UsageText: sc.Command.Help,
    45  		Hidden:    cmdHidden,
    46  		Action: func(c *cli.Context) error {
    47  			err := validateOptions(c, sc, cmdName)
    48  			if err != nil {
    49  				return err
    50  			}
    51  
    52  			ec := sc.Run(c, c.Args().Slice())
    53  			if ec > 0 {
    54  				return cli.Exit("Command exited with non zero exit code", ec)
    55  			}
    56  			return nil
    57  		},
    58  		Flags: optionsSetToFlags(sc.Function.Options),
    59  	})
    60  }
    61  
    62  // Run builds the source and executes it
    63  func (sc *ScriptCommand) Run(c *cli.Context, args []string) int {
    64  	sc.Log.Debugf("executing command \"%v\"", sc.Function.Name)
    65  
    66  	var source []string
    67  	switch sc.Script.Language() {
    68  	case "bash":
    69  		source = generateBashSource(c, sc, args)
    70  		sc.Log.Debugf("generated bash source\n%s\n", source)
    71  	default:
    72  		sc.Log.Errorf("unsupported script language %s", sc.Script.Language())
    73  		return 1
    74  	}
    75  
    76  	err := sc.Script.Executable().Run(sc.Context.io, source)
    77  	if err != nil {
    78  		exitCode := 1
    79  
    80  		if exiterr, ok := err.(*exec.ExitError); ok {
    81  			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
    82  				exitCode = status.ExitStatus()
    83  			}
    84  		}
    85  
    86  		sc.Log.Debugf("script exited with error, %v", err)
    87  		return exitCode
    88  	}
    89  
    90  	sc.Log.Debugf("finished executing command %s...", sc.Function.Name)
    91  	return 0
    92  }
    93  
    94  func validateOptions(c *cli.Context, sc *ScriptCommand, cmdName string) error {
    95  	if err := validateOptionsSet(c, sc.GlobalOptions, cmdName, "global", sc.Log.WithField("option-valiation", "global")); err != nil {
    96  		return err
    97  	}
    98  	if err := validateOptionsSet(c, sc.Function.Options, cmdName, "command", sc.Log.WithField("option-valiation", "command")); err != nil {
    99  		return err
   100  	}
   101  	return nil
   102  }
   103  
   104  func generateBashSource(c *cli.Context, sc *ScriptCommand, args []string) []string {
   105  	source := []string{}
   106  	source = append(source, "#!/usr/bin/env bash")
   107  
   108  	source = append(source, "")
   109  	source = append(source, "# Set working directory")
   110  	source = append(source, fmt.Sprintf("cd %s || exit 1", sc.Context.manifest.BasePath))
   111  
   112  	source = append(source, "")
   113  	source = append(source, "# Set centry metadata")
   114  	source = append(source, fmt.Sprintf("export %s='%s'", "CENTRY_SCRIPT_FUNCTION", sc.Function.Name))
   115  	source = append(source, fmt.Sprintf("export %s='%s'", "CENTRY_SCRIPT_PATH", sc.Script.RelativePath()))
   116  	source = append(source, fmt.Sprintf("export %s='%s'", "CENTRY_COMMAND_NAME", sc.Command.Name))
   117  
   118  	source = append(source, "")
   119  	source = append(source, "# Set environment variables from global options")
   120  	conf := sc.Context.manifest.Config
   121  	for _, v := range optionsSetToEnvVars(c, sc.GlobalOptions, conf.EnvironmentPrefix) {
   122  		if v.Value != "" {
   123  			value := v.Value
   124  			if v.IsString() {
   125  				value = fmt.Sprintf("'%s'", v.Value)
   126  			}
   127  			source = append(source, fmt.Sprintf("export %s=%s", v.Name, value))
   128  		}
   129  	}
   130  
   131  	source = append(source, "")
   132  	source = append(source, "# Set environment variables from options defined by command")
   133  
   134  	for _, v := range optionsSetToEnvVars(c, sc.Function.Options, conf.EnvironmentPrefix) {
   135  		if v.Value != "" {
   136  			value := v.Value
   137  			if v.IsString() {
   138  				value = fmt.Sprintf("'%s'", v.Value)
   139  			}
   140  			source = append(source, fmt.Sprintf("export %s=%s", v.Name, value))
   141  		}
   142  	}
   143  
   144  	source = append(source, "")
   145  	source = append(source, "# Sourcing scripts")
   146  	for _, s := range sc.Context.manifest.Scripts {
   147  		source = append(source, fmt.Sprintf("source %s", s))
   148  	}
   149  
   150  	source = append(source, "")
   151  	source = append(source, "# Sourcing command")
   152  	source = append(source, fmt.Sprintf("source %s", sc.Script.FullPath()))
   153  
   154  	source = append(source, "")
   155  	source = append(source, "# Executing command")
   156  	source = append(source, fmt.Sprintf("%s %s", sc.Function.Name, strings.Join(args, " ")))
   157  
   158  	return []string{
   159  		"-c",
   160  		strings.Join(source, "\n"),
   161  	}
   162  }