feat(installer): add enabled option

This commit is contained in:
2025-01-18 20:48:21 +02:00
parent dfab9ecaf9
commit e5460d255e
7 changed files with 158 additions and 21 deletions

View File

@@ -175,33 +175,24 @@ actions. Steps can be of **several types**, such as `brew`, `rsync`, `shell`, an
### Supported `type` of Installers
For a full list with all the supported options, see [the docs](./docs/installer-types.md).
- **`rsync`**
- Copy files from `source` to `destination` using rsync.
- **Options**:
- `opts.source`: Source directory/file.
- `opts.destination`: Destination directory/file.
- `opts.flags`: Additional rsync flags (e.g., `--delete`, `--exclude`).
- **`group`**
- Executes a logical group of steps in sequence.
- Allows nesting multiple steps together.
- **Options**:
- `steps`: List of nested steps.
- **`brew`**
- Installs packages using Homebrew.
- **Options**:
- `opts.tap`: Name of the tap to install the package from.
- **`shell`**
- Executes arbitrary shell commands.
- **Options**:
- `opts.command`: The command to execute for installing.
- `opts.update_command`: The command to execute for updating.
- **`npm`/`pnpm`/`yarn`**
@@ -214,9 +205,6 @@ actions. Steps can be of **several types**, such as `brew`, `rsync`, `shell`, an
- Clones a git repository to a local directory.
- If `name` is a full git URL (https or SSH), the repository is cloned directly. If it is a
repository path, e.g. `chenasraf/sofmani`, GitHub is assumed.
- **Options**:
- `opts.destination`: The local directory to clone the repository to.
- `opts.ref`: The branch, tag, or commit to checkout after cloning.
- **`manifest`**
@@ -225,12 +213,6 @@ actions. Steps can be of **several types**, such as `brew`, `rsync`, `shell`, an
installers.
- `debug` and `check_updates` will be inherited by the loaded config.
- `env` and `defaults` will be merged into the loaded config, overriding any existing values.
- **Options**:
- `opts.source`: The local file, or remote git URL (https or SSH) containing the manifest.
- `opts.path`: The path to the manifest file within the repository. If `opts.source` is a local
file, `opts.path` will be appended to it.
- `opts.ref`: The branch, tag, or commit to checkout after cloning if `opts.source` is a git
URL. For local manifests, this value will be ignored.
- **`apt`**

View File

@@ -60,7 +60,7 @@ func TestInstallerPlatformEnviron(t *testing.T) {
assert.ElementsMatch(t, expected, data.Environ())
}
func TestParseConfig(t *testing.T) {
func TestParseJsonConfig(t *testing.T) {
// Create a temporary config file
file, err := os.CreateTemp("", "config.*.json")
assert.NoError(t, err)
@@ -78,6 +78,52 @@ func TestParseConfig(t *testing.T) {
assert.False(t, config.CheckUpdates)
}
func TestParseYamlConfig(t *testing.T) {
// Create a temporary config file
file, err := os.CreateTemp("", "config.*.yaml")
assert.NoError(t, err)
defer os.Remove(file.Name())
_, err = file.WriteString(`
debug: true
check_updates: false
`)
assert.NoError(t, err)
file.Close()
// Test parsing the config file
overrides := AppCliConfig{ConfigFile: file.Name()}
config, err := ParseConfig(&overrides)
assert.NoError(t, err)
assert.True(t, config.Debug)
assert.False(t, config.CheckUpdates)
}
func TestParseYamlConfigEnabled(t *testing.T) {
// Create a temporary config file
file, err := os.CreateTemp("", "config.*.yaml")
assert.NoError(t, err)
defer os.Remove(file.Name())
_, err = file.WriteString(`
debug: true
check_updates: false
install:
- name: test
type: shell
enabled: true
`)
assert.NoError(t, err)
file.Close()
// Test parsing the config file
overrides := AppCliConfig{ConfigFile: file.Name()}
config, err := ParseConfig(&overrides)
assert.NoError(t, err)
assert.True(t, config.Debug)
assert.False(t, config.CheckUpdates)
}
func TestFindConfigFile(t *testing.T) {
// Create a temporary config file
dir := t.TempDir()

View File

@@ -9,6 +9,7 @@ import (
)
type InstallerData struct {
Enabled *string `json:"enabled" yaml:"enabled"`
Name *string `json:"name" yaml:"name"`
Type InstallerType `json:"type" yaml:"type"`
Tags *string `json:"tags" yaml:"tags"`

View File

@@ -20,6 +20,13 @@ These fields are shared by all installer types. Some fields may vary in behavior
- **Description**: Type of the step. See [supported types](#supported-type-of-installers) for a
comprehensive list of supported values.
- **`enabled`**
- **Type**: String or Boolean (optional)
- **Description**: Enable or disable the step. Disabled steps are not run. This can either be a
static boolean (`true` or `false`), or a command that returns a success status code for true, or
a failure for false.
- **`tags`**
- **Type** String (optional)

View File

@@ -3,6 +3,7 @@ package installer
import (
"strings"
"github.com/chenasraf/sofmani/utils"
"github.com/samber/lo"
)
@@ -57,3 +58,30 @@ func isFilteredIn(installer IInstaller, filter string) bool {
}
return strings.Contains(*data.Name, filter)
}
func InstallerIsEnabled(i IInstaller) (bool, error) {
enabledCmd := i.GetData().Enabled
if enabledCmd == nil {
return true, nil
}
if strings.ToLower(*enabledCmd) == "true" {
return true, nil
}
if strings.ToLower(*enabledCmd) == "false" {
return false, nil
}
shell := utils.GetOSShell(i.GetData().EnvShell)
args := utils.GetOSShellArgs(*enabledCmd)
err, success := utils.RunCmdGetSuccess(i.GetData().Environ(), shell, args...)
if err != nil {
return false, err
}
return success, nil
}

View File

@@ -4,6 +4,7 @@ import (
"testing"
"github.com/chenasraf/sofmani/appconfig"
"github.com/chenasraf/sofmani/logger"
"github.com/stretchr/testify/assert"
)
@@ -95,3 +96,62 @@ func TestFilterInstaller(t *testing.T) {
})
}
}
func TestInstallerIsEnabled(t *testing.T) {
logger.InitLogger(true)
tests := []struct {
name string
installer IInstaller
expected bool
expectErr bool
}{
{
name: "Enabled is nil",
installer: &MockInstaller{
data: &appconfig.InstallerData{Enabled: nil},
},
expected: true,
},
{
name: "Enabled is true",
installer: &MockInstaller{
data: &appconfig.InstallerData{Enabled: strPtr("true")},
},
expected: true,
},
{
name: "Enabled is false",
installer: &MockInstaller{
data: &appconfig.InstallerData{Enabled: strPtr("false")},
},
expected: false,
},
{
name: "Enabled is a command that succeeds",
installer: &MockInstaller{
data: &appconfig.InstallerData{Enabled: strPtr("exit 0")},
},
expected: true,
},
{
name: "Enabled is a command that fails",
installer: &MockInstaller{
data: &appconfig.InstallerData{Enabled: strPtr("exit 1")},
},
expected: false,
expectErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := InstallerIsEnabled(tt.installer)
if tt.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.expected, result)
})
}
}

View File

@@ -152,6 +152,19 @@ func RunInstaller(config *appconfig.AppConfig, installer IInstaller) error {
logger.Debug("%s is filtered, skipping", name)
return nil
}
enabled, err := InstallerIsEnabled(installer)
if err != nil {
logger.Error("Failed to check if %s is enabled: %s", name, err)
return nil
}
if !enabled {
logger.Debug("%s is disabled, skipping", name)
return nil
}
logger.Debug("Checking %s (%s)", name, info.Type)
err, installed := installer.CheckIsInstalled()
if err != nil {