mirror of
https://github.com/chenasraf/sofmani.git
synced 2026-05-18 01:29:02 +00:00
feat: shell update command + env shell
This commit is contained in:
@@ -144,6 +144,7 @@ actions. Steps can be of **several types**, such as `brew`, `rsync`, `shell`, an
|
||||
| `post_install` | String (shell script) | Shell script to execute _after_ the step is installed. |
|
||||
| `pre_update` | String (shell script) | Shell script to execute _before_ the step is updated (if applicable). |
|
||||
| `post_update` | String (shell script) | Shell script to execute _after_ the step is updated (if applicable). |
|
||||
| `env_shell` | String (optional) | Shell to use for command executions (only applicable on Linux/macOS). Default: `bash`. |
|
||||
|
||||
### Supported `type` Installers
|
||||
|
||||
@@ -165,7 +166,8 @@ actions. Steps can be of **several types**, such as `brew`, `rsync`, `shell`, an
|
||||
|
||||
- Executes arbitrary shell commands.
|
||||
- **Options**:
|
||||
- `opts.command`: The command to execute.
|
||||
- `opts.command`: The command to execute for installing.
|
||||
- `opts.update_command`: The command to execute for updating.
|
||||
|
||||
4. **`group`**
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ type Installer struct {
|
||||
PreInstall *string `json:"pre_install" yaml:"pre_install"`
|
||||
PostUpdate *string `json:"post_update" yaml:"post_update"`
|
||||
PreUpdate *string `json:"pre_update" yaml:"pre_update"`
|
||||
EnvShell *string `json:"env_shell" yaml:"env_shell"`
|
||||
}
|
||||
|
||||
type InstallerType string
|
||||
|
||||
2
go.mod
2
go.mod
@@ -6,6 +6,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/eschao/config v0.1.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/samber/lo v1.47.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -13,5 +14,6 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
4
go.sum
4
go.sum
@@ -12,6 +12,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
@@ -20,6 +22,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -31,7 +31,7 @@ func (i *BrewInstaller) Update() error {
|
||||
// CheckNeedsUpdate implements IInstaller.
|
||||
func (i *BrewInstaller) CheckNeedsUpdate() (error, bool) {
|
||||
if i.GetInfo().CheckHasUpdate != nil {
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(), utils.GetOSShellArgs(*i.GetInfo().CheckHasUpdate)...)
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(i.GetInfo().EnvShell), utils.GetOSShellArgs(*i.GetInfo().CheckHasUpdate)...)
|
||||
}
|
||||
err, success := utils.RunCmdGetSuccess(i.Info.Environ(), "brew", "outdated", "--json", *i.Info.Name)
|
||||
if err != nil {
|
||||
|
||||
@@ -42,7 +42,7 @@ func (i *GroupInstaller) Update() error {
|
||||
// CheckNeedsUpdate implements IInstaller.
|
||||
func (i *GroupInstaller) CheckNeedsUpdate() (error, bool) {
|
||||
if i.GetInfo().CheckHasUpdate != nil {
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(), utils.GetOSShellArgs(*i.GetInfo().CheckHasUpdate)...)
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(i.GetInfo().EnvShell), utils.GetOSShellArgs(*i.GetInfo().CheckHasUpdate)...)
|
||||
}
|
||||
return nil, true
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func (i *GroupInstaller) CheckNeedsUpdate() (error, bool) {
|
||||
// CheckIsInstalled implements IInstaller.
|
||||
func (i *GroupInstaller) CheckIsInstalled() (error, bool) {
|
||||
if i.GetInfo().CheckInstalled != nil {
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(), utils.GetOSShellArgs(*i.GetInfo().CheckInstalled)...)
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(i.GetInfo().EnvShell), utils.GetOSShellArgs(*i.GetInfo().CheckInstalled)...)
|
||||
}
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetShellWhich(), i.GetBinName())
|
||||
}
|
||||
|
||||
@@ -43,7 +43,14 @@ func InstallerWithDefaults(
|
||||
if val, ok := (*defaults.Type)[installerType]; ok {
|
||||
logger.Debug("Applying defaults for %s", installerType)
|
||||
if val.Opts != nil {
|
||||
installer.Opts = val.Opts
|
||||
o := *val.Opts
|
||||
o2 := *installer.Opts
|
||||
for k, v := range o {
|
||||
o2[k] = v
|
||||
}
|
||||
}
|
||||
if val.EnvShell != nil {
|
||||
installer.EnvShell = val.EnvShell
|
||||
}
|
||||
if val.Platforms != nil {
|
||||
installer.Platforms = val.Platforms
|
||||
@@ -110,7 +117,7 @@ func RunInstaller(config *appconfig.AppConfig, installer IInstaller) error {
|
||||
logger.Info("%s (%s) has an update", name, info.Type)
|
||||
if info.PreUpdate != nil {
|
||||
logger.Debug("Running pre-update command for %s (%s)", name, info.Type)
|
||||
err := utils.RunCmdPassThrough(env, utils.GetOSShell(), utils.GetOSShellArgs(*info.PreUpdate)...)
|
||||
err := utils.RunCmdPassThrough(env, utils.GetOSShell(installer.GetInfo().EnvShell), utils.GetOSShellArgs(*info.PreUpdate)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -119,7 +126,7 @@ func RunInstaller(config *appconfig.AppConfig, installer IInstaller) error {
|
||||
installer.Update()
|
||||
if info.PostUpdate != nil {
|
||||
logger.Debug("Running post-update command for %s (%s)", name, info.Type)
|
||||
err := utils.RunCmdPassThrough(env, utils.GetOSShell(), utils.GetOSShellArgs(*info.PostUpdate)...)
|
||||
err := utils.RunCmdPassThrough(env, utils.GetOSShell(installer.GetInfo().EnvShell), utils.GetOSShellArgs(*info.PostUpdate)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -133,7 +140,7 @@ func RunInstaller(config *appconfig.AppConfig, installer IInstaller) error {
|
||||
logger.Info("Installing %s (%s)", name, installer.GetInfo().Type)
|
||||
if info.PreInstall != nil {
|
||||
logger.Debug("Running pre-install command for %s (%s)", name, info.Type)
|
||||
err := utils.RunCmdPassThrough(env, utils.GetOSShell(), utils.GetOSShellArgs(*info.PreInstall)...)
|
||||
err := utils.RunCmdPassThrough(env, utils.GetOSShell(installer.GetInfo().EnvShell), utils.GetOSShellArgs(*info.PreInstall)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -142,7 +149,7 @@ func RunInstaller(config *appconfig.AppConfig, installer IInstaller) error {
|
||||
err = installer.Install()
|
||||
if info.PostInstall != nil {
|
||||
logger.Debug("Running post-install command for %s (%s)", name, info.Type)
|
||||
err := utils.RunCmdPassThrough(env, utils.GetOSShell(), utils.GetOSShellArgs(*info.PostInstall)...)
|
||||
err := utils.RunCmdPassThrough(env, utils.GetOSShell(installer.GetInfo().EnvShell), utils.GetOSShellArgs(*info.PostInstall)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func (i *NpmInstaller) CheckNeedsUpdate() (error, bool) {
|
||||
// CheckIsInstalled implements IInstaller.
|
||||
func (i *NpmInstaller) CheckIsInstalled() (error, bool) {
|
||||
if i.GetInfo().CheckInstalled != nil {
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(), utils.GetOSShellArgs(*i.GetInfo().CheckInstalled)...)
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(i.GetInfo().EnvShell), utils.GetOSShellArgs(*i.GetInfo().CheckInstalled)...)
|
||||
}
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetShellWhich(), i.GetBinName())
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (i *RsyncInstaller) Update() error {
|
||||
// CheckNeedsUpdate implements IInstaller.
|
||||
func (i *RsyncInstaller) CheckNeedsUpdate() (error, bool) {
|
||||
if i.GetInfo().CheckHasUpdate != nil {
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(), utils.GetOSShellArgs(*i.GetInfo().CheckHasUpdate)...)
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(i.GetInfo().EnvShell), utils.GetOSShellArgs(*i.GetInfo().CheckHasUpdate)...)
|
||||
}
|
||||
return nil, true
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func (i *RsyncInstaller) CheckNeedsUpdate() (error, bool) {
|
||||
// CheckIsInstalled implements IInstaller.
|
||||
func (i *RsyncInstaller) CheckIsInstalled() (error, bool) {
|
||||
if i.GetInfo().CheckInstalled != nil {
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(), utils.GetOSShellArgs(*i.GetInfo().CheckInstalled)...)
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), utils.GetOSShell(i.GetInfo().EnvShell), utils.GetOSShellArgs(*i.GetInfo().CheckInstalled)...)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
package installer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/chenasraf/sofmani/appconfig"
|
||||
"github.com/chenasraf/sofmani/utils"
|
||||
)
|
||||
@@ -16,36 +11,27 @@ type ShellInstaller struct {
|
||||
}
|
||||
|
||||
type ShellOpts struct {
|
||||
Command *string
|
||||
Command *string
|
||||
UpdateCommand *string
|
||||
}
|
||||
|
||||
// Install implements IInstaller.
|
||||
func (i *ShellInstaller) Install() error {
|
||||
tmpdir := os.TempDir()
|
||||
tmpfile := i.getShellScript(tmpdir)
|
||||
commandStr, err := i.getScriptContents(*i.GetOpts().Command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(tmpfile, []byte(commandStr), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shell := utils.GetOSShell()
|
||||
args := utils.GetOSShellArgs(tmpfile)
|
||||
return utils.RunCmdPassThrough(i.Info.Environ(), shell, args...)
|
||||
return utils.RunCmdAsFile(i.Info.Environ(), *i.GetOpts().Command, i.GetInfo().EnvShell)
|
||||
}
|
||||
|
||||
// Update implements IInstaller.
|
||||
func (i *ShellInstaller) Update() error {
|
||||
if i.GetOpts().UpdateCommand != nil {
|
||||
return utils.RunCmdAsFile(i.Info.Environ(), *i.GetOpts().UpdateCommand, i.GetInfo().EnvShell)
|
||||
}
|
||||
return i.Install()
|
||||
}
|
||||
|
||||
// CheckNeedsUpdate implements IInstaller.
|
||||
func (i *ShellInstaller) CheckNeedsUpdate() (error, bool) {
|
||||
if i.GetInfo().CheckHasUpdate != nil {
|
||||
shell := utils.GetOSShell()
|
||||
shell := utils.GetOSShell(i.GetInfo().EnvShell)
|
||||
args := utils.GetOSShellArgs(*i.GetInfo().CheckHasUpdate)
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), shell, args...)
|
||||
}
|
||||
@@ -55,7 +41,7 @@ func (i *ShellInstaller) CheckNeedsUpdate() (error, bool) {
|
||||
// CheckIsInstalled implements IInstaller.
|
||||
func (i *ShellInstaller) CheckIsInstalled() (error, bool) {
|
||||
if i.GetInfo().CheckInstalled != nil {
|
||||
shell := utils.GetOSShell()
|
||||
shell := utils.GetOSShell(i.GetInfo().EnvShell)
|
||||
args := utils.GetOSShellArgs(*i.GetInfo().CheckInstalled)
|
||||
return utils.RunCmdGetSuccess(i.Info.Environ(), shell, args...)
|
||||
}
|
||||
@@ -74,6 +60,9 @@ func (i *ShellInstaller) GetOpts() *ShellOpts {
|
||||
if command, ok := (*info.Opts)["command"].(string); ok {
|
||||
opts.Command = &command
|
||||
}
|
||||
if updateCommand, ok := (*info.Opts)["update_command"].(string); ok {
|
||||
opts.UpdateCommand = &updateCommand
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
@@ -86,28 +75,6 @@ func (i *ShellInstaller) GetBinName() string {
|
||||
return *info.Name
|
||||
}
|
||||
|
||||
func (*ShellInstaller) getShellScript(dir string) string {
|
||||
var filename string
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
filename = "install.bat"
|
||||
case "linux", "darwin":
|
||||
filename = "install.sh"
|
||||
}
|
||||
tmpfile := filepath.Join(dir, filename)
|
||||
return tmpfile
|
||||
}
|
||||
|
||||
func (i *ShellInstaller) getScriptContents(script string) (string, error) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return *i.GetOpts().Command, nil
|
||||
case "linux", "darwin":
|
||||
return fmt.Sprintf("#!/usr/bin/i.Info.Environ() bash\n%s\n", script), nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
func NewShellInstaller(cfg *appconfig.AppConfig, installer *appconfig.Installer) *ShellInstaller {
|
||||
return &ShellInstaller{
|
||||
Config: cfg,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
|
||||
"github.com/chenasraf/sofmani/logger"
|
||||
)
|
||||
|
||||
const UNIX_DEFAULT_SHELL = "bash"
|
||||
|
||||
func RunCmdPassThrough(env []string, bin string, args ...string) error {
|
||||
logger.Debug("Running command: %s %v", bin, args)
|
||||
cmd := exec.Command(bin, args...)
|
||||
@@ -53,6 +57,49 @@ func RunCmdGetOutput(env []string, bin string, args ...string) ([]byte, error) {
|
||||
return out, err
|
||||
}
|
||||
|
||||
func getShellScript(dir string) string {
|
||||
var filename string
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
filename = "install.bat"
|
||||
case "linux", "darwin":
|
||||
filename = "install"
|
||||
}
|
||||
tmpfile := filepath.Join(dir, filename)
|
||||
return tmpfile
|
||||
}
|
||||
|
||||
func getScriptContents(script string, envShell *string) (string, error) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return script, nil
|
||||
case "linux", "darwin":
|
||||
if envShell == nil {
|
||||
shell := UNIX_DEFAULT_SHELL
|
||||
envShell = &shell
|
||||
}
|
||||
return fmt.Sprintf("#!/usr/bin/env %s\n%s\n", *envShell, script), nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
func RunCmdAsFile(env []string, contents string, envShell *string) error {
|
||||
tmpdir := os.TempDir()
|
||||
tmpfile := getShellScript(tmpdir)
|
||||
commandStr, err := getScriptContents(contents, envShell)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(tmpfile, []byte(commandStr), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shell := GetOSShell(envShell)
|
||||
args := GetOSShellArgs(tmpfile)
|
||||
return RunCmdPassThrough(env, shell, args...)
|
||||
}
|
||||
|
||||
func GetShellWhich() string {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
@@ -63,12 +110,15 @@ func GetShellWhich() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func GetOSShell() string {
|
||||
func GetOSShell(envShell *string) string {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return "cmd"
|
||||
case "linux", "darwin":
|
||||
return "sh"
|
||||
if envShell != nil {
|
||||
return *envShell
|
||||
}
|
||||
return UNIX_DEFAULT_SHELL
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
12
utils/map.go
Normal file
12
utils/map.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package utils
|
||||
|
||||
func MergeMap[K comparable, V any](me map[K]V, other map[K]V) map[K]V {
|
||||
copy := map[K]V{}
|
||||
for k, v := range me {
|
||||
copy[k] = v
|
||||
}
|
||||
for k, v := range other {
|
||||
copy[k] = v
|
||||
}
|
||||
return me
|
||||
}
|
||||
Reference in New Issue
Block a user