8 Commits

Author SHA1 Message Date
f777c78aad v0.9.0
All checks were successful
ci/woodpecker/push/publish-docs Pipeline was successful
ci/woodpecker/tag/gitea Pipeline was successful
ci/woodpecker/tag/publish-docs Pipeline was successful
ci/woodpecker/release/publish-docs Pipeline was successful
2025-02-28 17:52:28 -06:00
bb693dbb97 bump version
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-02-28 17:49:51 -06:00
7beda281e0 fixed constant typo
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-02-28 17:49:01 -06:00
1143d2850b added beginning of tests
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-02-28 17:40:32 -06:00
8900bd70a4 changed PackageOperation to enums 2025-02-28 17:39:08 -06:00
6db5f73bc0 fetch env file where config file is stored
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
2025-02-25 21:19:17 -06:00
a163c11129 v0.9.0
Some checks failed
ci/woodpecker/push/go-lint Pipeline failed
ci/woodpecker/push/publish-docs Pipeline was successful
2025-02-23 22:10:42 -06:00
2b4d191271 v0.9.0
Some checks are pending
ci/woodpecker/push/go-lint Pipeline is pending
2025-02-23 21:18:46 -06:00
26 changed files with 392 additions and 107 deletions

View File

@ -1,3 +0,0 @@
kind: Added
body: '`list` command with subcommands `cmds` and `lists`'
time: 2025-02-20T14:45:49.562361581-06:00

View File

@ -1,3 +0,0 @@
kind: Added
body: Deprecation and unsupported warnings for old config keys
time: 2025-02-20T14:50:14.048452348-06:00

View File

@ -1,3 +0,0 @@
kind: Added
body: CLI flag `--cmdStdOut` to output command's stdout/stderr to stdout
time: 2025-02-20T14:52:45.030582408-06:00

View File

@ -1,3 +0,0 @@
kind: Added
body: Command type `remoteScript`. See docs for more info.
time: 2025-02-21T00:30:12.276616792-06:00

View File

@ -1,3 +0,0 @@
kind: Changed
body: change to enums for Command type
time: 2025-02-23T15:30:12.033359922-06:00

View File

@ -1,3 +0,0 @@
kind: Changed
body: Cache now stores resources by URL hash for ease-of-lookup
time: 2025-02-23T15:33:05.33444642-06:00

View File

@ -1,3 +0,0 @@
kind: Fixed
body: Local command's `dir` full path is now found with home directory
time: 2025-02-20T14:48:43.475300515-06:00

12
.changes/v0.9.0.md Normal file
View File

@ -0,0 +1,12 @@
## v0.9.0 - 2025-02-28
### Added
* `list` command with subcommands `cmds` and `lists`
* Deprecation and unsupported warnings for old config keys
* CLI flag `--cmdStdOut` to output command's stdout/stderr to stdout
* Command type `remoteScript`. See docs for more info.
### Changed
* change to enums for Command type
* Cache now stores resources by URL hash for ease-of-lookup
* Changed PackageOperation to enums
### Fixed
* Local command's `dir` full path is now found with home directory

View File

@ -6,6 +6,19 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie). and is generated by [Changie](https://github.com/miniscruff/changie).
## v0.9.0 - 2025-02-28
### Added
* `list` command with subcommands `cmds` and `lists`
* Deprecation and unsupported warnings for old config keys
* CLI flag `--cmdStdOut` to output command's stdout/stderr to stdout
* Command type `remoteScript`. See docs for more info.
### Changed
* change to enums for Command type
* Cache now stores resources by URL hash for ease-of-lookup
* Changed PackageOperation to enums
### Fixed
* Local command's `dir` full path is now found with home directory
## v0.8.0 - 2025-02-15 ## v0.8.0 - 2025-02-15
### Changed ### Changed
* Breaking: `cmd-lists` key changed to `cmdLists` * Breaking: `cmd-lists` key changed to `cmdLists`

View File

@ -7,8 +7,11 @@
], ],
"settings": { "settings": {
"cSpell.words": [ "cSpell.words": [
"Autorestic",
"changie",
"Cmds", "Cmds",
"CMDSTDOUT", "CMDSTDOUT",
"goreleaser",
"knadh", "knadh",
"koanf", "koanf",
"mattn", "mattn",

View File

@ -18,7 +18,11 @@ var (
func cron(cmd *cobra.Command, args []string) { func cron(cmd *cobra.Command, args []string) {
parseS3Config() parseS3Config()
opts := backy.NewOpts(cfgFile, backy.EnableCron(), backy.SetLogFile(logFile), backy.SetCmdStdOut(cmdStdOut)) opts := backy.NewOpts(cfgFile,
backy.EnableCron(),
backy.SetLogFile(logFile),
backy.SetCmdStdOut(cmdStdOut))
opts.InitConfig() opts.InitConfig()
opts.ReadConfig() opts.ReadConfig()

View File

@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const versionStr = "0.8.0" const versionStr = "0.9.0"
var ( var (
versionCmd = &cobra.Command{ versionCmd = &cobra.Command{

View File

@ -167,3 +167,39 @@ Global Flags:
Use "backy list [command] --help" for more information about a command. Use "backy list [command] --help" for more information about a command.
``` ```
## list cmds
```
List commands defined in config file
Usage:
backy list cmds [cmd1 cmd2 cmd3...] [flags]
Flags:
-h, --help help for cmds
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```
## list lists
```
List lists defined in config file
Usage:
backy list lists [list1 list2 ...] [flags]
Flags:
-h, --help help for lists
Global Flags:
--cmdStdOut Pass to print command output to stdout
-f, --config string config file to read from
--log-file string log file to write to
--s3-endpoint string Sets the S3 endpoint used for config file fetching. Overrides S3_ENDPOINT env variable.
-v, --verbose Sets verbose level
```

View File

@ -15,5 +15,5 @@ The `exec` subcommand can do some things that the configuration file can't do ye
The commands have to be defined in the config file. The hosts need to at least be in the ssh_config(5) file. The commands have to be defined in the config file. The hosts need to at least be in the ssh_config(5) file.
```sh ```sh
backy exec host [--commands command1 -commands command2 ... | -c command1 -c command2 ...] [--hosts host1 --hosts hosts2 ... | -m host1 -c host2 ...] [flags] backy exec host [--commands=command1 -commands=command2 ... | -c command1 -c command2 ...] [--hosts=host1 --hosts=hosts2 ... | -m host1 -c host2 ...] [flags]
``` ```

View File

@ -14,4 +14,4 @@ If using S3, you should use the s3 protocol URI: `s3://bucketName/key/path`. You
## Scripts ## Scripts
Scripts will be coming later. Remote script support is currently limited to http/https endpoints.

View File

@ -1,67 +1,83 @@
#!/bin/bash #!/bin/bash
CLI_PAGE="docs/content/cli/_index.md" CLI_PAGE="docs/content/cli/_index.md"
BACKYCOMMAND="go run backy.go"
{
echo "--- echo "---
title: "CLI" title: CLI
weight: 4 weight: 4
--- ---
This page lists documentation for the CLI. This page lists documentation for the CLI.
" > _index.md "
BACKYCOMMAND="go run backy.go" echo "## Backy "
echo " "
echo "## Backy " >> _index.md echo "\`\`\`"
echo " " >> _index.md eval "${BACKYCOMMAND} -h"
echo "\`\`\`" >> _index.md echo "\`\`\`"
eval "${BACKYCOMMAND} -h >> _index.md" echo " "
echo "\`\`\`" >> _index.md
echo " " >> _index.md
echo "# Subcommands" >> _index.md
echo "" >> _index.md
echo "## backup" >> _index.md echo "# Subcommands"
echo "" >> _index.md echo ""
echo "\`\`\`" >> _index.md
eval "${BACKYCOMMAND} backup -h >> _index.md"
echo "\`\`\`" >> _index.md
echo "" >> _index.md
echo "## cron" >> _index.md echo "## backup"
echo "" >> _index.md echo ""
echo "\`\`\`" >> _index.md echo "\`\`\`"
eval "${BACKYCOMMAND} cron -h >> _index.md" eval "${BACKYCOMMAND} backup -h"
echo "\`\`\`" >> _index.md echo "\`\`\`"
echo "" >> _index.md echo ""
echo "## exec" >> _index.md echo "## cron"
echo "" >> _index.md echo ""
echo "\`\`\`" >> _index.md echo "\`\`\`"
eval "${BACKYCOMMAND} exec -h >> _index.md" eval "${BACKYCOMMAND} cron -h"
echo "\`\`\`" >> _index.md echo "\`\`\`"
echo "" >> _index.md echo ""
echo "### exec host" >> _index.md echo "## exec"
echo "" >> _index.md echo ""
echo "\`\`\`" >> _index.md echo "\`\`\`"
eval "${BACKYCOMMAND} exec host -h >> _index.md" eval "${BACKYCOMMAND} exec -h"
echo "\`\`\`" >> _index.md echo "\`\`\`"
echo "" >> _index.md echo ""
echo "### exec host"
echo ""
echo "\`\`\`"
eval "${BACKYCOMMAND} exec host -h"
echo "\`\`\`"
echo ""
echo "## version" >> _index.md echo "## version"
echo "" >> _index.md echo ""
echo "\`\`\`" >> _index.md echo "\`\`\`"
eval "${BACKYCOMMAND} version -h >> _index.md" eval "${BACKYCOMMAND} version -h"
echo "\`\`\`" >> _index.md echo "\`\`\`"
echo "" >> _index.md echo ""
echo "## list" >> _index.md echo "## list"
echo "" >> _index.md echo ""
echo "\`\`\`" >> _index.md echo "\`\`\`"
eval "${BACKYCOMMAND} list -h >> _index.md" eval "${BACKYCOMMAND} list -h"
echo "\`\`\`" >> _index.md echo "\`\`\`"
echo "## list cmds"
echo ""
echo "\`\`\`"
eval "${BACKYCOMMAND} list cmds -h"
echo "\`\`\`"
echo "## list lists"
echo ""
echo "\`\`\`"
eval "${BACKYCOMMAND} list lists -h"
echo "\`\`\`"
} >> _index.md
mv _index.md "$CLI_PAGE" mv _index.md "$CLI_PAGE"

View File

@ -68,7 +68,7 @@ func (command *Command) RunCmd(cmdCtxLogger zerolog.Logger, opts *ConfigOpts) ([
} else { } else {
// Handle package operations // Handle package operations
if command.Type == PackageCT && command.PackageOperation == "checkVersion" { if command.Type == PackageCT && command.PackageOperation == PackOpCheckVersion {
cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Checking package versions") cmdCtxLogger.Info().Str("package", command.PackageName).Msg("Checking package versions")
// Execute the package version command // Execute the package version command

View File

@ -53,7 +53,7 @@ func (opts *ConfigOpts) InitConfig() {
cacheDir := homeCacheDir cacheDir := homeCacheDir
// Load metadata from file // Load metadata from file
opts.CachedData, err = remotefetcher.LoadMetadataFromFile(path.Join(backyHomeConfDir, "cache.yml")) opts.CachedData, err = remotefetcher.LoadMetadataFromFile(path.Join(backyHomeConfDir, "cache", "cache.yml"))
if err != nil { if err != nil {
fmt.Println("Error loading metadata:", err) fmt.Println("Error loading metadata:", err)
logging.ExitWithMSG(err.Error(), 1, &opts.Logger) logging.ExitWithMSG(err.Error(), 1, &opts.Logger)
@ -77,11 +77,13 @@ func (opts *ConfigOpts) InitConfig() {
if err != nil { if err != nil {
logging.ExitWithMSG(fmt.Sprintf("error initializing cache: %v", err), 1, nil) logging.ExitWithMSG(fmt.Sprintf("error initializing cache: %v", err), 1, nil)
} }
// Initialize the fetcher
// println("Creating new fetcher for source", opts.ConfigFilePath)
fetcher, err := remotefetcher.NewRemoteFetcher(opts.ConfigFilePath, opts.Cache)
// println("Created new fetcher for source", opts.ConfigFilePath)
fetcher, err := remotefetcher.NewRemoteFetcher(opts.ConfigFilePath, opts.Cache)
if isRemoteURL(opts.ConfigFilePath) {
p, _ := getRemoteDir(opts.ConfigFilePath)
opts.ConfigDir = p
}
if err != nil { if err != nil {
logging.ExitWithMSG(fmt.Sprintf("error initializing config fetcher: %v", err), 1, nil) logging.ExitWithMSG(fmt.Sprintf("error initializing config fetcher: %v", err), 1, nil)
} }
@ -274,20 +276,22 @@ func resolveProxyHosts(host *Host, opts *ConfigOpts) {
} }
func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) { func loadCommandLists(opts *ConfigOpts, backyKoanf *koanf.Koanf) {
var backyConfigFileDir string
var listConfigFiles []string var listConfigFiles []string
var u *url.URL var u *url.URL
var p string
// if config file is remote, use the directory of the remote file // if config file is remote, use the directory of the remote file
if isRemoteURL(opts.ConfigFilePath) { if isRemoteURL(opts.ConfigFilePath) {
_, u = getRemoteDir(opts.ConfigFilePath) p, u = getRemoteDir(opts.ConfigFilePath)
opts.ConfigDir = p
println(p)
// // Still use local list files if a remote config file is used, but use them last // // Still use local list files if a remote config file is used, but use them last
listConfigFiles = []string{u.JoinPath("lists.yml").String(), u.JoinPath("lists.yaml").String()} listConfigFiles = []string{u.JoinPath("lists.yml").String(), u.JoinPath("lists.yaml").String()}
} else { } else {
backyConfigFileDir = path.Dir(opts.ConfigFilePath) opts.ConfigDir = path.Dir(opts.ConfigFilePath)
listConfigFiles = []string{ listConfigFiles = []string{
// "./lists.yml", "./lists.yaml", // "./lists.yml", "./lists.yaml",
path.Join(backyConfigFileDir, "lists.yml"), path.Join(opts.ConfigDir, "lists.yml"),
path.Join(backyConfigFileDir, "lists.yaml"), path.Join(opts.ConfigDir, "lists.yaml"),
} }
} }
@ -327,7 +331,6 @@ func loadListConfigFile(filePath string, k *koanf.Koanf, opts *ConfigOpts) bool
if err != nil { if err != nil {
// if file not found, ignore // if file not found, ignore
if errors.Is(err, remotefetcher.ErrIgnoreFileNotFound) { if errors.Is(err, remotefetcher.ErrIgnoreFileNotFound) {
println("File not found", filePath)
return true return true
} }
@ -582,7 +585,7 @@ func processCmds(opts *ConfigOpts) error {
if cmd.PackageManager == "" { if cmd.PackageManager == "" {
return fmt.Errorf("package manager is required for package command %s", cmd.PackageName) return fmt.Errorf("package manager is required for package command %s", cmd.PackageName)
} }
if cmd.PackageOperation == "" { if cmd.PackageOperation.String() == "" {
return fmt.Errorf("package operation is required for package command %s", cmd.PackageName) return fmt.Errorf("package operation is required for package command %s", cmd.PackageName)
} }
if cmd.PackageName == "" { if cmd.PackageName == "" {
@ -591,15 +594,16 @@ func processCmds(opts *ConfigOpts) error {
var err error var err error
// Validate the operation // Validate the operation
switch cmd.PackageOperation { if cmd.PackageOperation.IsAPackageOperation() {
case "install", "remove", "upgrade", "checkVersion":
cmd.pkgMan, err = pkgman.PackageManagerFactory(cmd.PackageManager, pkgman.WithoutAuth()) cmd.pkgMan, err = pkgman.PackageManagerFactory(cmd.PackageManager, pkgman.WithoutAuth())
if err != nil { if err != nil {
return err return err
} }
default: } else {
return fmt.Errorf("unsupported package operation %s for command %s", cmd.PackageOperation, cmd.Name) return fmt.Errorf("unsupported package operation %s for command %s", cmd.PackageOperation, cmd.Name)
} }
} }
// Parse user commands // Parse user commands

View File

@ -0,0 +1,145 @@
// Code generated by "enumer -linecomment -yaml -text -json -type=PackageOperation"; DO NOT EDIT.
package backy
import (
"encoding/json"
"fmt"
"strings"
)
const _PackageOperationName = "installupgradepurgeremovecheckVersionisInstalled"
var _PackageOperationIndex = [...]uint8{0, 0, 7, 14, 19, 25, 37, 48}
const _PackageOperationLowerName = "installupgradepurgeremovecheckversionisinstalled"
func (i PackageOperation) String() string {
if i < 0 || i >= PackageOperation(len(_PackageOperationIndex)-1) {
return fmt.Sprintf("PackageOperation(%d)", i)
}
return _PackageOperationName[_PackageOperationIndex[i]:_PackageOperationIndex[i+1]]
}
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
func _PackageOperationNoOp() {
var x [1]struct{}
_ = x[DefaultPO-(0)]
_ = x[PackOpInstall-(1)]
_ = x[PackOpUpgrade-(2)]
_ = x[PackOpPurge-(3)]
_ = x[PackOpRemove-(4)]
_ = x[PackOpCheckVersion-(5)]
_ = x[PackOpIsInstalled-(6)]
}
var _PackageOperationValues = []PackageOperation{DefaultPO, PackOpInstall, PackOpUpgrade, PackOpPurge, PackOpRemove, PackOpCheckVersion, PackOpIsInstalled}
var _PackageOperationNameToValueMap = map[string]PackageOperation{
_PackageOperationName[0:0]: DefaultPO,
_PackageOperationLowerName[0:0]: DefaultPO,
_PackageOperationName[0:7]: PackOpInstall,
_PackageOperationLowerName[0:7]: PackOpInstall,
_PackageOperationName[7:14]: PackOpUpgrade,
_PackageOperationLowerName[7:14]: PackOpUpgrade,
_PackageOperationName[14:19]: PackOpPurge,
_PackageOperationLowerName[14:19]: PackOpPurge,
_PackageOperationName[19:25]: PackOpRemove,
_PackageOperationLowerName[19:25]: PackOpRemove,
_PackageOperationName[25:37]: PackOpCheckVersion,
_PackageOperationLowerName[25:37]: PackOpCheckVersion,
_PackageOperationName[37:48]: PackOpIsInstalled,
_PackageOperationLowerName[37:48]: PackOpIsInstalled,
}
var _PackageOperationNames = []string{
_PackageOperationName[0:0],
_PackageOperationName[0:7],
_PackageOperationName[7:14],
_PackageOperationName[14:19],
_PackageOperationName[19:25],
_PackageOperationName[25:37],
_PackageOperationName[37:48],
}
// PackageOperationString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func PackageOperationString(s string) (PackageOperation, error) {
if val, ok := _PackageOperationNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _PackageOperationNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to PackageOperation values", s)
}
// PackageOperationValues returns all values of the enum
func PackageOperationValues() []PackageOperation {
return _PackageOperationValues
}
// PackageOperationStrings returns a slice of all String values of the enum
func PackageOperationStrings() []string {
strs := make([]string, len(_PackageOperationNames))
copy(strs, _PackageOperationNames)
return strs
}
// IsAPackageOperation returns "true" if the value is listed in the enum definition. "false" otherwise
func (i PackageOperation) IsAPackageOperation() bool {
for _, v := range _PackageOperationValues {
if i == v {
return true
}
}
return false
}
// MarshalJSON implements the json.Marshaler interface for PackageOperation
func (i PackageOperation) MarshalJSON() ([]byte, error) {
return json.Marshal(i.String())
}
// UnmarshalJSON implements the json.Unmarshaler interface for PackageOperation
func (i *PackageOperation) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return fmt.Errorf("PackageOperation should be a string, got %s", data)
}
var err error
*i, err = PackageOperationString(s)
return err
}
// MarshalText implements the encoding.TextMarshaler interface for PackageOperation
func (i PackageOperation) MarshalText() ([]byte, error) {
return []byte(i.String()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface for PackageOperation
func (i *PackageOperation) UnmarshalText(text []byte) error {
var err error
*i, err = PackageOperationString(string(text))
return err
}
// MarshalYAML implements a YAML Marshaler for PackageOperation
func (i PackageOperation) MarshalYAML() (interface{}, error) {
return i.String(), nil
}
// UnmarshalYAML implements a YAML Unmarshaler for PackageOperation
func (i *PackageOperation) UnmarshalYAML(unmarshal func(interface{}) error) error {
var s string
if err := unmarshal(&s); err != nil {
return err
}
var err error
*i, err = PackageOperationString(s)
return err
}

View File

@ -538,7 +538,7 @@ func (command *Command) RunCmdSSH(cmdCtxLogger zerolog.Logger, opts *ConfigOpts)
case ScriptFileCT: case ScriptFileCT:
return command.runScriptFile(commandSession, cmdCtxLogger, &cmdOutBuf) return command.runScriptFile(commandSession, cmdCtxLogger, &cmdOutBuf)
case PackageCT: case PackageCT:
if command.PackageOperation == "checkVersion" { if command.PackageOperation == PackOpCheckVersion {
commandSession.Stderr = nil commandSession.Stderr = nil
// Execute the package version command remotely // Execute the package version command remotely
// Parse the output of package version command // Parse the output of package version command

View File

@ -106,7 +106,7 @@ type (
PackageVersion string `yaml:"packageVersion,omitempty"` PackageVersion string `yaml:"packageVersion,omitempty"`
// PackageOperation specifies the action for package-related commands (e.g., "install" or "remove") // PackageOperation specifies the action for package-related commands (e.g., "install" or "remove")
PackageOperation string `yaml:"packageOperation,omitempty"` PackageOperation PackageOperation `yaml:"packageOperation,omitempty"`
pkgMan pkgman.PackageManager pkgMan pkgman.PackageManager
@ -204,13 +204,12 @@ type (
CmdStdOut bool CmdStdOut bool
// Holds config file
ConfigFilePath string ConfigFilePath string
// Holds log file ConfigDir string
LogFilePath string LogFilePath string
// for command list file
CmdListFile string CmdListFile string
// use command lists using cron // use command lists using cron
@ -294,7 +293,10 @@ type (
ListName string // Name of the command list ListName string // Name of the command list
Error error // Error encountered, if any Error error // Error encountered, if any
} }
CommandType int
// use ints so we can use enums
CommandType int
PackageOperation int
) )
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=CommandType //go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=CommandType
@ -306,3 +308,14 @@ const (
PackageCT // package PackageCT // package
UserCT // user UserCT // user
) )
//go:generate go run github.com/dmarkham/enumer -linecomment -yaml -text -json -type=PackageOperation
const (
DefaultPO PackageOperation = iota //
PackOpInstall // install
PackOpUpgrade // upgrade
PackOpPurge // purge
PackOpRemove // remove
PackOpCheckVersion // checkVersion
PackOpIsInstalled // isInstalled
)

View File

@ -15,6 +15,7 @@ import (
"strings" "strings"
"git.andrewnw.xyz/CyberShell/backy/pkg/logging" "git.andrewnw.xyz/CyberShell/backy/pkg/logging"
"git.andrewnw.xyz/CyberShell/backy/pkg/remotefetcher"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/knadh/koanf/v2" "github.com/knadh/koanf/v2"
"github.com/rs/zerolog" "github.com/rs/zerolog"
@ -216,11 +217,31 @@ func getFullPathWithHomeDir(path string) (string, error) {
// loadEnv loads a .env file from the config file directory // loadEnv loads a .env file from the config file directory
func (opts *ConfigOpts) loadEnv() { func (opts *ConfigOpts) loadEnv() {
envFileInConfigDir := fmt.Sprintf("%s/.env", path.Dir(opts.ConfigFilePath))
var backyEnv map[string]string var backyEnv map[string]string
backyEnv, envFileErr := godotenv.Read(envFileInConfigDir) var envFileInConfigDir string
if envFileErr != nil { var envFileErr error
return if isRemoteURL(opts.ConfigFilePath) {
_, u := getRemoteDir(opts.ConfigFilePath)
envFileInConfigDir = u.JoinPath(".env").String()
envFetcher, err := remotefetcher.NewRemoteFetcher(envFileInConfigDir, opts.Cache)
if err != nil {
return
}
data, err := envFetcher.Fetch(envFileInConfigDir)
if err != nil {
return
}
backyEnv, envFileErr = godotenv.UnmarshalBytes(data)
if envFileErr != nil {
return
}
} else {
envFileInConfigDir = fmt.Sprintf("%s/.env", path.Dir(opts.ConfigFilePath))
backyEnv, envFileErr = godotenv.Read(envFileInConfigDir)
if envFileErr != nil {
return
}
} }
opts.backyEnv = backyEnv opts.backyEnv = backyEnv
@ -251,21 +272,18 @@ func expandEnvVars(backyEnv map[string]string, envVars []string) {
} }
} }
// getCommandTypeAndSetCommandInfo checks for command type and if the command has already been set
// Checks for types package and user
// Returns the modified Command with the package- or userManager command as Cmd and the package- or userOperation as args, plus any additional Args
func getCommandTypeAndSetCommandInfo(command *Command) *Command { func getCommandTypeAndSetCommandInfo(command *Command) *Command {
if command.Type == PackageCT && !command.packageCmdSet { if command.Type == PackageCT && !command.packageCmdSet {
command.packageCmdSet = true command.packageCmdSet = true
switch command.PackageOperation { switch command.PackageOperation {
case "install": case PackOpInstall:
command.Cmd, command.Args = command.pkgMan.Install(command.PackageName, command.PackageVersion, command.Args) command.Cmd, command.Args = command.pkgMan.Install(command.PackageName, command.PackageVersion, command.Args)
case "remove": case PackOpRemove:
command.Cmd, command.Args = command.pkgMan.Remove(command.PackageName, command.Args) command.Cmd, command.Args = command.pkgMan.Remove(command.PackageName, command.Args)
case "upgrade": case PackOpUpgrade:
command.Cmd, command.Args = command.pkgMan.Upgrade(command.PackageName, command.PackageVersion) command.Cmd, command.Args = command.pkgMan.Upgrade(command.PackageName, command.PackageVersion)
case "checkVersion": case PackOpCheckVersion:
command.Cmd, command.Args = command.pkgMan.CheckVersion(command.PackageName, command.PackageVersion) command.Cmd, command.Args = command.pkgMan.CheckVersion(command.PackageName, command.PackageVersion)
} }
} }

View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path"
"path/filepath" "path/filepath"
"sync" "sync"
@ -72,7 +73,7 @@ func (c *Cache) saveToFile() error {
for _, data := range c.store { for _, data := range c.store {
cacheData = append(cacheData, data) cacheData = append(cacheData, data)
} }
cacheData = unique(cacheData)
data, err := yaml.Marshal(cacheData) data, err := yaml.Marshal(cacheData)
if err != nil { if err != nil {
return err return err
@ -170,6 +171,7 @@ func (cf *CachedFetcher) Hash(data []byte) string {
func LoadMetadataFromFile(filePath string) ([]*CacheData, error) { func LoadMetadataFromFile(filePath string) ([]*CacheData, error) {
if _, err := os.Stat(filePath); os.IsNotExist(err) { if _, err := os.Stat(filePath); os.IsNotExist(err) {
// Create the file if it does not exist // Create the file if it does not exist
os.MkdirAll(path.Dir(filePath), 0700)
emptyData := []byte("[]") emptyData := []byte("[]")
err := os.WriteFile(filePath, emptyData, 0644) err := os.WriteFile(filePath, emptyData, 0644)
if err != nil { if err != nil {
@ -184,6 +186,7 @@ func LoadMetadataFromFile(filePath string) ([]*CacheData, error) {
var cacheData []*CacheData var cacheData []*CacheData
err = yaml.Unmarshal(data, &cacheData) err = yaml.Unmarshal(data, &cacheData)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -195,3 +198,23 @@ func HashURL(url string) string {
hash := sha256.Sum256([]byte(url)) hash := sha256.Sum256([]byte(url))
return hex.EncodeToString(hash[:]) return hex.EncodeToString(hash[:])
} }
func unique(cache []CacheData) []CacheData {
var unique []CacheData
type key struct{ value1, value2, value3, value4 string }
m := make(map[key]int)
for _, v := range cache {
k := key{v.URL, v.Hash, v.Path, v.Type}
if i, ok := m[k]; ok {
// Overwrite previous value per requirement in
// question to keep last matching value.
unique[i] = v
} else {
// Unique key found. Record position and collect
// in result.
m[k] = len(unique)
unique = append(unique, v)
}
}
return unique
}

View File

@ -59,6 +59,7 @@ func NewRemoteFetcher(source string, cache *Cache, options ...FetcherOption) (Re
URLHash := HashURL(source) URLHash := HashURL(source)
if cachedData, cacheMeta, exists := cache.Get(URLHash); exists { if cachedData, cacheMeta, exists := cache.Get(URLHash); exists {
println(cachedData)
return &CachedFetcher{data: cachedData, path: cacheMeta.Path, dataType: cacheMeta.Type}, nil return &CachedFetcher{data: cachedData, path: cacheMeta.Path, dataType: cacheMeta.Type}, nil
} }

View File

@ -49,6 +49,7 @@ func NewS3Fetcher(endpoint string, options ...FetcherOption) (*S3Fetcher, error)
println(err.Error()) println(err.Error())
return nil, err return nil, err
} }
// Initialize S3 client if not provided // Initialize S3 client if not provided
if cfg.S3Client == nil { if cfg.S3Client == nil {
s3Client, err = minio.New(s3Endpoint, &minio.Options{ s3Client, err = minio.New(s3Endpoint, &minio.Options{
@ -128,7 +129,6 @@ func (s *S3Fetcher) Hash(data []byte) string {
} }
func getS3Credentials(profile, host string, httpClient *http.Client) (*credentials.Credentials, error) { func getS3Credentials(profile, host string, httpClient *http.Client) (*credentials.Credentials, error) {
// println(s3utils.GetRegionFromURL(*u))
homeDir, hdirErr := homedir.Dir() homeDir, hdirErr := homedir.Dir()
if hdirErr != nil { if hdirErr != nil {
return nil, hdirErr return nil, hdirErr

18
tests/backy.yaml Normal file
View File

@ -0,0 +1,18 @@
commands:
echoTestPass:
cmd: echo
shell: bash
Args: hello world
runRemoteShellScriptSuccess:
cmd:
packageCommandSuccess:
packageName: docker-ce
Args:
- docker-ce-cli
packageManager: apt
packageOperation: install