feat(config): add SSH capability with user/pass or key/pass

This commit is contained in:
DataHearth 2022-06-02 19:44:51 +02:00
parent f4bf8070ac
commit 46249857f3
9 changed files with 314 additions and 113 deletions

View File

@ -7,6 +7,8 @@ import (
"time" "time"
mapper "github.com/datahearth/config-mapper/internal" mapper "github.com/datahearth/config-mapper/internal"
"github.com/datahearth/config-mapper/internal/configuration"
"github.com/datahearth/config-mapper/internal/git"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -47,30 +49,36 @@ var saveCmd = &cobra.Command{
} }
func init() { func init() {
cobra.OnInitialize(mapper.InitConfig) cobra.OnInitialize(configuration.InitConfig)
rootCmd.AddCommand(initCmd) rootCmd.AddCommand(initCmd)
rootCmd.AddCommand(loadCmd) rootCmd.AddCommand(loadCmd)
rootCmd.AddCommand(saveCmd) rootCmd.AddCommand(saveCmd)
rootCmd.PersistentFlags().StringP("configuration-file", "c", "", "location of configuration file") rootCmd.PersistentFlags().StringP("configuration-file", "c", "", "location of configuration file")
rootCmd.PersistentFlags().String("ssh-user", "", "SSH username to retrieve configuration file")
rootCmd.PersistentFlags().String("ssh-password", "", "SSH password to retrieve configuration file")
rootCmd.PersistentFlags().String("ssh-key", "", "SSH key to retrieve configuration file (if a passphrase is needed, use the \"CONFIG_MAPPER_PASS\" env variable")
viper.BindPFlag("configuration-file", rootCmd.PersistentFlags().Lookup("configuration-file")) viper.BindPFlag("configuration-file", rootCmd.PersistentFlags().Lookup("configuration-file"))
viper.BindPFlag("ssh-user", rootCmd.PersistentFlags().Lookup("ssh-user"))
viper.BindPFlag("ssh-password", rootCmd.PersistentFlags().Lookup("ssh-password"))
viper.BindPFlag("ssh-key", rootCmd.PersistentFlags().Lookup("ssh-key"))
loadCmd.PersistentFlags().Bool("disable-files", false, "files will be ignored") loadCmd.Flags().Bool("disable-files", false, "files will be ignored")
loadCmd.PersistentFlags().Bool("disable-folders", false, "folders will be ignored") loadCmd.Flags().Bool("disable-folders", false, "folders will be ignored")
loadCmd.PersistentFlags().Bool("pkgs", false, "packages will be installed") loadCmd.Flags().Bool("pkgs", false, "packages will be installed")
viper.BindPFlag("load-disable-files", loadCmd.PersistentFlags().Lookup("disable-files")) viper.BindPFlag("load-disable-files", loadCmd.Flags().Lookup("disable-files"))
viper.BindPFlag("load-disable-folders", loadCmd.PersistentFlags().Lookup("disable-folders")) viper.BindPFlag("load-disable-folders", loadCmd.Flags().Lookup("disable-folders"))
viper.BindPFlag("load-enable-pkgs", loadCmd.PersistentFlags().Lookup("pkgs")) viper.BindPFlag("load-enable-pkgs", loadCmd.Flags().Lookup("pkgs"))
saveCmd.PersistentFlags().Bool("disable-files", false, "files will be ignored") saveCmd.Flags().Bool("disable-files", false, "files will be ignored")
saveCmd.PersistentFlags().Bool("disable-folders", false, "folders will be ignored") saveCmd.Flags().Bool("disable-folders", false, "folders will be ignored")
saveCmd.PersistentFlags().Bool("pkgs", false, "packages will be saved") saveCmd.Flags().Bool("pkgs", false, "packages will be saved")
saveCmd.Flags().BoolP("push", "p", false, "new configurations will be committed and pushed") saveCmd.Flags().BoolP("push", "p", false, "new configurations will be committed and pushed")
saveCmd.Flags().StringP("message", "m", strconv.FormatInt(time.Now().Unix(), 10), "combined with --push to set a commit message") saveCmd.Flags().StringP("message", "m", strconv.FormatInt(time.Now().Unix(), 10), "combined with --push to set a commit message")
saveCmd.Flags().Bool("disable-index", false, "configuration index will not be updated") saveCmd.Flags().Bool("disable-index", false, "configuration index will not be updated")
viper.BindPFlag("save-disable-files", saveCmd.PersistentFlags().Lookup("disable-files")) viper.BindPFlag("save-disable-files", saveCmd.Flags().Lookup("disable-files"))
viper.BindPFlag("save-disable-folders", saveCmd.PersistentFlags().Lookup("disable-folders")) viper.BindPFlag("save-disable-folders", saveCmd.Flags().Lookup("disable-folders"))
viper.BindPFlag("save-enable-pkgs", saveCmd.PersistentFlags().Lookup("pkgs")) viper.BindPFlag("save-enable-pkgs", saveCmd.Flags().Lookup("pkgs"))
viper.BindPFlag("push", saveCmd.Flags().Lookup("push")) viper.BindPFlag("push", saveCmd.Flags().Lookup("push"))
viper.BindPFlag("disable-index-update", saveCmd.Flags().Lookup("disable-index")) viper.BindPFlag("disable-index-update", saveCmd.Flags().Lookup("disable-index"))
viper.BindPFlag("message", saveCmd.Flags().Lookup("message")) viper.BindPFlag("message", saveCmd.Flags().Lookup("message"))
@ -84,7 +92,7 @@ func Execute() {
} }
func save(cmd *cobra.Command, args []string) { func save(cmd *cobra.Command, args []string) {
var c mapper.Configuration var c configuration.Configuration
if err := viper.Unmarshal(&c); err != nil { if err := viper.Unmarshal(&c); err != nil {
mapper.PrintError("failed to decode configuration: %v\n", err) mapper.PrintError("failed to decode configuration: %v\n", err)
os.Exit(1) os.Exit(1)
@ -96,7 +104,7 @@ func save(cmd *cobra.Command, args []string) {
os.Exit(1) os.Exit(1)
} }
r, err := mapper.NewRepository(c.Storage.Git, c.Storage.Path) r, err := git.NewRepository(c.Storage.Git, c.Storage.Path)
if err != nil { if err != nil {
mapper.PrintError("failed to open repository at %s: %v\n", c.Storage.Path, err) mapper.PrintError("failed to open repository at %s: %v\n", c.Storage.Path, err)
os.Exit(1) os.Exit(1)
@ -138,7 +146,7 @@ func save(cmd *cobra.Command, args []string) {
} }
func load(cmd *cobra.Command, args []string) { func load(cmd *cobra.Command, args []string) {
var c mapper.Configuration var c configuration.Configuration
if err := viper.Unmarshal(&c); err != nil { if err := viper.Unmarshal(&c); err != nil {
mapper.PrintError("failed to decode configuration: %v\n", err) mapper.PrintError("failed to decode configuration: %v\n", err)
os.Exit(1) os.Exit(1)
@ -150,7 +158,7 @@ func load(cmd *cobra.Command, args []string) {
os.Exit(1) os.Exit(1)
} }
r, err := mapper.NewRepository(c.Storage.Git, c.Storage.Path) r, err := git.NewRepository(c.Storage.Git, c.Storage.Path)
if err != nil { if err != nil {
mapper.PrintError("failed to open repository at %s: %v\n", c.Storage.Path, err) mapper.PrintError("failed to open repository at %s: %v\n", c.Storage.Path, err)
os.Exit(1) os.Exit(1)
@ -176,16 +184,15 @@ func load(cmd *cobra.Command, args []string) {
} }
func initCommand(cmd *cobra.Command, args []string) { func initCommand(cmd *cobra.Command, args []string) {
var config mapper.Configuration var c configuration.Configuration
if err := viper.Unmarshal(&c); err != nil {
if err := viper.Unmarshal(&config); err != nil {
mapper.PrintError("failed to decode configuration: %v\n", err) mapper.PrintError("failed to decode configuration: %v\n", err)
os.Exit(1) os.Exit(1)
} }
logger.Println("initializing config-mapper folder from configuration...") logger.Println("initializing config-mapper folder from configuration...")
if _, err := mapper.NewRepository(config.Storage.Git, config.Storage.Path); err != nil { if _, err := git.NewRepository(c.Storage.Git, c.Storage.Path); err != nil {
mapper.PrintError("failed to initialize folder: %v\n", err) mapper.PrintError("failed to initialize folder: %v\n", err)
os.Exit(1) os.Exit(1)
} }

View File

@ -0,0 +1,67 @@
package configuration
import (
"fmt"
"log"
"os"
"path"
"strings"
"github.com/fatih/color"
"github.com/spf13/viper"
)
var errLogger = log.New(os.Stderr, "", 0)
func InitConfig() {
h, err := os.UserHomeDir()
if err != nil {
errLogger.Fatalln(err)
}
if c := viper.GetString("configuration-file"); c != "" {
if strings.Contains(c, "ssh://") {
viper.AddConfigPath(h)
viper.SetConfigType("yml")
viper.SetConfigName(".config-mapper")
if err := loadConfigSSH(c); err != nil {
errLogger.Fatalln(err)
}
return
}
if strings.Contains(c, ".yml") {
viper.AddConfigPath(path.Dir(c))
} else {
viper.AddConfigPath(c)
}
}
if c := os.Getenv("CONFIG_MAPPER_CFG"); c != "" {
if strings.Contains(c, ".yml") {
viper.AddConfigPath(path.Dir(c))
} else {
viper.AddConfigPath(c)
}
}
viper.AddConfigPath(h)
viper.SetConfigType("yml")
viper.SetConfigName(".config-mapper")
viper.SetDefault("storage.location", fmt.Sprintf("%s/config-mapper", os.TempDir()))
viper.SetDefault("package-managers.installation-order", []string{"apt", "homebrew"})
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
color.Error.Write([]byte(color.RedString("no configuration file found: %v\n", err)))
} else {
color.Error.Write([]byte(color.RedString("failed to read config: %v\n", err)))
}
os.Exit(1)
}
viper.Set("configuration-file", viper.ConfigFileUsed())
}

View File

@ -1,13 +1,4 @@
package mapper package configuration
import (
"fmt"
"os"
"path"
"strings"
"github.com/spf13/viper"
)
type Configuration struct { type Configuration struct {
Storage Storage `mapstructure:"storage" yaml:"storage"` Storage Storage `mapstructure:"storage" yaml:"storage"`
@ -49,45 +40,3 @@ type PkgManagers struct {
Homebrew []string `mapstructure:"homebrew" yaml:"homebrew"` Homebrew []string `mapstructure:"homebrew" yaml:"homebrew"`
Aptitude []string `mapstructure:"apt-get" yaml:"apt-get"` Aptitude []string `mapstructure:"apt-get" yaml:"apt-get"`
} }
func InitConfig() {
h, err := os.UserHomeDir()
if err != nil {
errLogger.Printf("can't get home directory through $HOME variable: %v\n", err)
os.Exit(1)
}
if c := viper.GetString("configuration-file"); c != "" {
if strings.Contains(c, ".yml") {
viper.AddConfigPath(path.Dir(c))
} else {
viper.AddConfigPath(c)
}
}
if c := os.Getenv("CONFIG_MAPPER_CFG"); c != "" {
if strings.Contains(c, ".yml") {
viper.AddConfigPath(path.Dir(c))
} else {
viper.AddConfigPath(c)
}
}
viper.AddConfigPath(h)
viper.SetConfigType("yml")
viper.SetConfigName(".config-mapper")
viper.SetDefault("storage.location", fmt.Sprintf("%s/config-mapper", os.TempDir()))
viper.SetDefault("package-managers.installation-order", []string{"apt", "homebrew"})
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
PrintError(err.Error())
} else {
PrintError("failed to read config: %v\n", err)
}
os.Exit(1)
}
viper.Set("configuration-file", viper.ConfigFileUsed())
}

View File

@ -0,0 +1,171 @@
package configuration
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
osUser "os/user"
"strings"
"github.com/fatih/color"
"github.com/spf13/viper"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
)
func loadConfigSSH(uri string) error {
config, host, path, err := getSSHConfig(uri)
if err != nil {
return err
}
c, err := ssh.Dial("tcp", host, config)
if err != nil {
return err
}
defer c.Close()
s, err := c.NewSession()
if err != nil {
return err
}
defer s.Close()
buff := new(bytes.Buffer)
s.Stdout = buff
if err := s.Run(fmt.Sprintf("cat %s", path)); err != nil {
return err
}
if err := viper.ReadConfig(buff); err != nil {
return err
}
return nil
}
func getSSHConfig(uriFlag string) (*ssh.ClientConfig, string, string, error) {
var err error
var user, passwd, host, configPath, key string
uri := strings.Split(uriFlag, "ssh://")[1]
if key = viper.GetString("ssh-key"); key != "" {
uri, user, passwd, err = getCredentials(uri)
if err != nil {
return nil, "", "", err
}
host, configPath, err = getUriContent(uri)
if err != nil {
return nil, "", "", err
}
} else if user = viper.GetString("ssh-user"); user != "" {
host, configPath, err = getUriContent(uri)
if err != nil {
return nil, "", "", err
}
passwd = viper.GetString("ssh-password")
} else {
uri, user, passwd, err = getCredentials(uri)
if err != nil {
return nil, "", "", err
}
if passwd == "" {
passwd = viper.GetString("ssh-password")
}
host, configPath, err = getUriContent(uri)
if err != nil {
return nil, "", "", err
}
}
if user == "" {
color.Yellow("WARNING: no user was found in either the URI and flags. Current user will be used")
var currentUser *osUser.User
currentUser, err = osUser.Current()
user = currentUser.Username
}
var auth ssh.AuthMethod
if key != "" {
auth, err = createPubKeyAuth(key)
if err != nil {
return nil, "", "", err
}
} else {
auth = ssh.Password(passwd)
}
h, err := os.UserHomeDir()
if err != nil {
return nil, "", "", err
}
hostKeyCallback, err := knownhosts.New(fmt.Sprintf("%s/.ssh/known_hosts", h))
if err != nil {
return nil, "", "", err
}
if len(strings.SplitN(host, ":", 1)) == 1 {
host = fmt.Sprintf("%s:22", host)
}
return &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{auth},
HostKeyCallback: hostKeyCallback,
}, host, configPath, nil
}
// getCredentials takes an SSH URI and returns (splitted URI, user, passwd, error)
//
// "passwd" can be empty in case of a single credential in URI
func getCredentials(uri string) (string, string, string, error) {
uriContent := strings.SplitN(uri, "@", 2)
if len(uriContent) == 1 {
fmt.Printf("uriContent: %v\n", uriContent)
return "", "", "", errors.New("no credentials in URI")
}
credentials := strings.SplitN(uriContent[0], ":", 2)
if len(credentials) == 1 {
return uriContent[1], credentials[0], "", nil
}
return uriContent[1], credentials[0], credentials[1], nil
}
// getUriContent takes an SSH URI and returns (host, path, error)
func getUriContent(uri string) (string, string, error) {
uriContent := strings.Split(uri, ":")
if len(uriContent) < 2 {
return "", "", errors.New("ssh URI is malformed. It's missing either a host or path. E.g: \"ssh://localhost:/my/config/file.yml\"")
}
return uriContent[0], uriContent[1], nil
}
func createPubKeyAuth(key string) (ssh.AuthMethod, error) {
var signer ssh.Signer
privateKey, err := ioutil.ReadFile(key)
if err != nil {
return nil, err
}
if passphrase := os.Getenv("CONFIG_MAPPER_SSH_PASSPHRASE"); passphrase != "" {
signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(passphrase))
} else {
signer, err = ssh.ParsePrivateKey(privateKey)
}
if err != nil {
return nil, err
}
return ssh.PublicKeys(signer), nil
}

View File

@ -1,4 +1,4 @@
package mapper package git
import ( import (
"errors" "errors"
@ -6,6 +6,8 @@ import (
"os/exec" "os/exec"
"time" "time"
"github.com/datahearth/config-mapper/internal/configuration"
"github.com/datahearth/config-mapper/internal/misc"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport"
@ -14,8 +16,7 @@ import (
) )
var ( var (
ErrDirIsFile = errors.New("path is a file") ErrDirIsFile = errors.New("path is a file")
ErrInvalidEnv = errors.New("found invalid environment variable in path")
) )
type RepositoryActions interface { type RepositoryActions interface {
@ -38,18 +39,18 @@ type author struct {
email string email string
} }
func NewRepository(config Git, repoPath string) (RepositoryActions, error) { func NewRepository(config configuration.Git, repoPath string) (RepositoryActions, error) {
var auth transport.AuthMethod var auth transport.AuthMethod
if config.URL == "" { if config.URL == "" {
return nil, errors.New("a repository URI is needed (either using GIT protocol or HTTPS)") return nil, errors.New("a repository URI is needed (either using GIT protocol or HTTPS)")
} }
repoPath, err := absolutePath(repoPath) repoPath, err := misc.AbsolutePath(repoPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if config.SSH.Passphrase != "" && config.SSH.PrivateKey != "" { if config.SSH.Passphrase != "" && config.SSH.PrivateKey != "" {
privateKey, err := absolutePath(config.SSH.PrivateKey) privateKey, err := misc.AbsolutePath(config.SSH.PrivateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -5,6 +5,8 @@ import (
"io/fs" "io/fs"
"os" "os"
"strings" "strings"
"github.com/datahearth/config-mapper/internal/misc"
) )
type Index struct { type Index struct {
@ -24,7 +26,7 @@ type Indexer interface {
func NewIndexer(repoPath string) (Indexer, error) { func NewIndexer(repoPath string) (Indexer, error) {
perms := fs.FileMode(0755) perms := fs.FileMode(0755)
indexPath, err := absolutePath(fmt.Sprintf("%s/.index", repoPath)) indexPath, err := misc.AbsolutePath(fmt.Sprintf("%s/.index", repoPath))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -7,26 +7,29 @@ import (
"path" "path"
"strings" "strings"
"github.com/datahearth/config-mapper/internal/configuration"
"github.com/datahearth/config-mapper/internal/git"
"github.com/datahearth/config-mapper/internal/misc"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
type Items struct { type Items struct {
locations []OSLocation locations []configuration.OSLocation
storage string storage string
repository RepositoryActions repository git.RepositoryActions
indexer Indexer indexer Indexer
} }
type ItemsActions interface { type ItemsActions interface {
Action(action string) Action(action string)
AddItems(items []OSLocation) AddItems(items []configuration.OSLocation)
CleanUp(removedLines []string) error CleanUp(removedLines []string) error
} }
func NewItemsActions(items []OSLocation, storage string, repository RepositoryActions, indexer Indexer) ItemsActions { func NewItemsActions(items []configuration.OSLocation, storage string, repository git.RepositoryActions, indexer Indexer) ItemsActions {
if items == nil { if items == nil {
items = []OSLocation{} items = []configuration.OSLocation{}
} }
return &Items{ return &Items{
@ -43,7 +46,7 @@ func (e *Items) Action(action string) {
for i, l := range e.locations { for i, l := range e.locations {
var src string var src string
storagePath, systemPath, err := configPaths(l, e.storage) storagePath, systemPath, err := misc.ConfigPaths(l, e.storage)
if err != nil { if err != nil {
PrintError("[%d] failed to resolve item paths \"%v\": %v", i, l, err) PrintError("[%d] failed to resolve item paths \"%v\": %v", i, l, err)
continue continue
@ -103,18 +106,18 @@ func (e *Items) saveItem(src, dst string, index int) string {
return "" return ""
} }
} }
if err := copyFolder(src, dst, true); err != nil { if err := misc.CopyFolder(src, dst, true); err != nil {
PrintError("[%d] failed to save folder from \"%s\" to \"%s\": %v", index, src, dst, err) PrintError("[%d] failed to save folder from \"%s\" to \"%s\": %v", index, src, dst, err)
return "" return ""
} }
} else { } else {
if err := copyFile(src, dst); err != nil { if err := misc.CopyFile(src, dst); err != nil {
PrintError("[%d] failed to save file from \"%s\" to \"%s\": %v", index, src, dst, err) PrintError("[%d] failed to save file from \"%s\" to \"%s\": %v", index, src, dst, err)
return "" return ""
} }
} }
p, err := absolutePath(e.storage) p, err := misc.AbsolutePath(e.storage)
if err != nil { if err != nil {
PrintError("[%d] failed resolve absolute path from configuration storage: %v", index, err) PrintError("[%d] failed resolve absolute path from configuration storage: %v", index, err)
return "" return ""
@ -153,25 +156,25 @@ func (e *Items) loadItem(src, dst string, index int) {
return return
} }
} }
if err := copyFolder(src, dst, false); err != nil { if err := misc.CopyFolder(src, dst, false); err != nil {
PrintError("[%d] failed to load folder from \"%s\" to \"%s\": %v", index, src, dst, err) PrintError("[%d] failed to load folder from \"%s\" to \"%s\": %v", index, src, dst, err)
return return
} }
} else { } else {
if err := copyFile(src, dst); err != nil { if err := misc.CopyFile(src, dst); err != nil {
PrintError("[%d] failed to load file from \"%s\" to \"%s\": %v", index, src, dst, err) PrintError("[%d] failed to load file from \"%s\" to \"%s\": %v", index, src, dst, err)
return return
} }
} }
} }
func (e *Items) AddItems(items []OSLocation) { func (e *Items) AddItems(items []configuration.OSLocation) {
e.locations = append(e.locations, items...) e.locations = append(e.locations, items...)
} }
func (e *Items) CleanUp(removedLines []string) error { func (e *Items) CleanUp(removedLines []string) error {
for _, l := range removedLines { for _, l := range removedLines {
path, err := absolutePath(fmt.Sprintf("%s/%s", e.storage, l)) path, err := misc.AbsolutePath(fmt.Sprintf("%s/%s", e.storage, l))
if err != nil { if err != nil {
return err return err
} }
@ -183,3 +186,7 @@ func (e *Items) CleanUp(removedLines []string) error {
return nil return nil
} }
func PrintError(err string, values ...interface{}) {
color.Error.Write([]byte(color.RedString(err+"\n", values...)))
}

View File

@ -1,19 +1,18 @@
package mapper package misc
import ( import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path" "path"
"runtime" "runtime"
"strings" "strings"
"github.com/fatih/color" "github.com/datahearth/config-mapper/internal/configuration"
) )
func absolutePath(p string) (string, error) { func AbsolutePath(p string) (string, error) {
finalPath := p finalPath := p
if strings.Contains(finalPath, "~") { if strings.Contains(finalPath, "~") {
h, err := os.UserHomeDir() h, err := os.UserHomeDir()
@ -31,7 +30,8 @@ func absolutePath(p string) (string, error) {
if strings.Contains(s, "$") { if strings.Contains(s, "$") {
env := os.Getenv(s) env := os.Getenv(s)
if env == "" { if env == "" {
return "", ErrInvalidEnv return "", errors.New("found invalid environment variable in path")
} }
pathPart = env pathPart = env
} }
@ -49,12 +49,12 @@ func getPaths(p string, l string) (string, string, error) {
return "", "", errors.New("path incorrectly formatted. It requires \"source:destination\"") return "", "", errors.New("path incorrectly formatted. It requires \"source:destination\"")
} }
src, err := absolutePath(strings.Replace(paths[0], "$LOCATION", l, 1)) src, err := AbsolutePath(strings.Replace(paths[0], "$LOCATION", l, 1))
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
dst, err := absolutePath(paths[1]) dst, err := AbsolutePath(paths[1])
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
@ -62,7 +62,7 @@ func getPaths(p string, l string) (string, string, error) {
return src, dst, nil return src, dst, nil
} }
func copyFile(src, dst string) error { func CopyFile(src, dst string) error {
s, err := os.Stat(src) s, err := os.Stat(src)
if err != nil { if err != nil {
return err return err
@ -91,7 +91,7 @@ func copyFile(src, dst string) error {
return nil return nil
} }
func configPaths(os OSLocation, location string) (string, string, error) { func ConfigPaths(os configuration.OSLocation, location string) (string, string, error) {
var src, dst string var src, dst string
var err error var err error
@ -115,14 +115,14 @@ func configPaths(os OSLocation, location string) (string, string, error) {
var ignored map[string]bool var ignored map[string]bool
func copyFolder(src, dst string, checkIgnore bool) error { func CopyFolder(src, dst string, checkIgnore bool) error {
items, err := os.ReadDir(src) items, err := os.ReadDir(src)
if err != nil { if err != nil {
return err return err
} }
if checkIgnore { if checkIgnore {
f, err := ioutil.ReadFile(fmt.Sprintf("%s/.ignore", src)) f, err := os.ReadFile(fmt.Sprintf("%s/.ignore", src))
if err != nil && !errors.Is(err, io.EOF) { if err != nil && !errors.Is(err, io.EOF) {
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
return err return err
@ -158,21 +158,17 @@ func copyFolder(src, dst string, checkIgnore bool) error {
if err := os.MkdirAll(dstItem, info.Mode()); err != nil { if err := os.MkdirAll(dstItem, info.Mode()); err != nil {
return err return err
} }
if err := copyFolder(srcItem, dstItem, false); err != nil { if err := CopyFolder(srcItem, dstItem, false); err != nil {
return err return err
} }
continue continue
} }
if err := copyFile(srcItem, dstItem); err != nil { if err := CopyFile(srcItem, dstItem); err != nil {
return err return err
} }
} }
return nil return nil
} }
func PrintError(err string, values ...interface{}) {
color.Error.Write([]byte(color.RedString(err+"\n", values...)))
}

View File

@ -8,6 +8,7 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"github.com/datahearth/config-mapper/internal/configuration"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/spf13/viper" "github.com/spf13/viper"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@ -21,7 +22,7 @@ var (
ErrAptNotAvailable = errors.New("aptitude is not available on your system") ErrAptNotAvailable = errors.New("aptitude is not available on your system")
) )
func LoadPkgs(c PkgManagers) error { func LoadPkgs(c configuration.PkgManagers) error {
color.Blue("# Load folders into saved location") color.Blue("# Load folders into saved location")
for _, pkg := range c.InstallationOrder { for _, pkg := range c.InstallationOrder {
@ -42,7 +43,7 @@ func LoadPkgs(c PkgManagers) error {
return nil return nil
} }
func SavePkgs(cfg Configuration) error { func SavePkgs(cfg configuration.Configuration) error {
color.Blue("# Save user installed packages") color.Blue("# Save user installed packages")
for _, pkg := range cfg.PackageManagers.InstallationOrder { for _, pkg := range cfg.PackageManagers.InstallationOrder {
@ -60,7 +61,7 @@ func SavePkgs(cfg Configuration) error {
return nil return nil
} }
func SaveBrewPkgs(cfg Configuration) error { func SaveBrewPkgs(cfg configuration.Configuration) error {
if _, err := exec.LookPath("brew"); err != nil { if _, err := exec.LookPath("brew"); err != nil {
return err return err
} }