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 }