feat(config): add SSH capability with user/pass or key/pass
This commit is contained in:
parent
f4bf8070ac
commit
46249857f3
49
cmd/cli.go
49
cmd/cli.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
|
@ -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())
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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...)))
|
||||||
|
}
|
||||||
|
|
|
@ -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...)))
|
|
||||||
}
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue