From e5460d255ea61f86de92e76faee3702394306877 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Sat, 18 Jan 2025 20:48:21 +0200 Subject: [PATCH] feat(installer): add `enabled` option --- README.md | 22 ++------------ appconfig/appconfig_test.go | 48 ++++++++++++++++++++++++++++- appconfig/installer_data.go | 1 + docs/installer-types.md | 7 +++++ installer/filter.go | 28 +++++++++++++++++ installer/filter_test.go | 60 +++++++++++++++++++++++++++++++++++++ installer/installer.go | 13 ++++++++ 7 files changed, 158 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 616575f..4682350 100644 --- a/README.md +++ b/README.md @@ -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`** diff --git a/appconfig/appconfig_test.go b/appconfig/appconfig_test.go index 0ccdb06..a9aa933 100644 --- a/appconfig/appconfig_test.go +++ b/appconfig/appconfig_test.go @@ -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() diff --git a/appconfig/installer_data.go b/appconfig/installer_data.go index 3d9f5e5..32c27e4 100644 --- a/appconfig/installer_data.go +++ b/appconfig/installer_data.go @@ -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"` diff --git a/docs/installer-types.md b/docs/installer-types.md index 91e18b2..a481818 100644 --- a/docs/installer-types.md +++ b/docs/installer-types.md @@ -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) diff --git a/installer/filter.go b/installer/filter.go index 342c618..73baeb9 100644 --- a/installer/filter.go +++ b/installer/filter.go @@ -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 +} diff --git a/installer/filter_test.go b/installer/filter_test.go index 4b8eae8..e2a4001 100644 --- a/installer/filter_test.go +++ b/installer/filter_test.go @@ -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) + }) + } +} diff --git a/installer/installer.go b/installer/installer.go index fcd5ae3..57ff60f 100644 --- a/installer/installer.go +++ b/installer/installer.go @@ -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 {