Compare commits
6 Commits
Author | SHA1 | Date |
---|---|---|
Antoine Langlois | 07e187e528 | |
Antoine Langlois | 02c82bc9d1 | |
DataHearth | ec82828509 | |
DataHearth | 29d91e0e73 | |
DataHearth | 3b3a4d59e0 | |
DataHearth | 81713eb583 |
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -3,6 +3,13 @@
|
|||
## [Unreleased]
|
||||
|
||||
|
||||
<a name="v0.3.0"></a>
|
||||
## [v0.3.0] - 2022-06-01
|
||||
### Features
|
||||
- **cli:** packages are disabled by default
|
||||
- **sync:** add .ignore file to filter folder's content
|
||||
|
||||
|
||||
<a name="v0.2.0"></a>
|
||||
## [v0.2.0] - 2022-05-23
|
||||
### Bug Fixes
|
||||
|
@ -53,5 +60,6 @@
|
|||
- **config:** add yaml tags for yaml.v3
|
||||
|
||||
|
||||
[Unreleased]: https://github.com/DataHearth/config-mapper/compare/v0.2.0...HEAD
|
||||
[Unreleased]: https://github.com/DataHearth/config-mapper/compare/v0.3.0...HEAD
|
||||
[v0.3.0]: https://github.com/DataHearth/config-mapper/compare/v0.2.0...v0.3.0
|
||||
[v0.2.0]: https://github.com/DataHearth/config-mapper/compare/v0.1.0...v0.2.0
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
VERSION 0.6
|
||||
|
||||
FROM golang:1.17-alpine3.15
|
||||
FROM golang:1.18-alpine3.15
|
||||
WORKDIR /config-mapper
|
||||
|
||||
build-macos:
|
||||
|
|
27
README.md
27
README.md
|
@ -56,7 +56,30 @@ config-mapper save
|
|||
|
||||
All defined files and folders will be copied inside your repository.
|
||||
|
||||
If you want to exclude one part of your configuration file (files, folders, package-managers), you can use these flags to ignore them `--disable-files` `--disable-folders` `--disable-pkgs`
|
||||
If you want to exclude one part of your configuration file (files, folders), you can use these flags to ignore them `--disable-files` `--disable-folders`. Note, package managers are disable by default. You can enable this option using the `--pkgs` flag.
|
||||
|
||||
You can also exclude files and folders from a given directory with a `.gitignore` like file named `.ignore`. Put it in the root directory of an included folder and add relative path to exclude (does not support glob for now). E.g:
|
||||
|
||||
```bash
|
||||
Permissions Size User Date Modified Name
|
||||
drwxr-xr-x - antoine 1 Jun 20:28 ../demo
|
||||
.rw-r--r-- 12 antoine 1 Jun 20:28 ├── .ignore
|
||||
drwxr-xr-x - antoine 1 Jun 20:28 ├── egg
|
||||
.rw-r--r-- 0 antoine 1 Jun 20:28 │ └── bar.py
|
||||
.rw-r--r-- 0 antoine 1 Jun 20:26 ├── example.py
|
||||
drwxr-xr-x - antoine 1 Jun 20:27 └── foo
|
||||
.rw-r--r-- 0 antoine 1 Jun 20:24 ├── bar
|
||||
.rw-r--r-- 0 antoine 1 Jun 20:27 └── demo.py
|
||||
```
|
||||
|
||||
`.ignore` content:
|
||||
```
|
||||
# bar file will be ignored
|
||||
foo/bar
|
||||
|
||||
# egg folder will be ignore
|
||||
egg
|
||||
```
|
||||
|
||||
If `homebrew` is provided in the `installation-order` (default: `["apt", "homebrew"]`), it will override the `homebrew` field with all user installed packages (`brew leaves --installed-on-request`). The same principle will be implemented with `aptitude`.
|
||||
|
||||
|
@ -107,6 +130,8 @@ The same ignore flags are used in the `save` command.
|
|||
|
||||
## TO-DO
|
||||
|
||||
- [X] add `.ignore` file to ignore content inside directory
|
||||
- [ ] optimisation over speed and memory
|
||||
- [ ] load configuration though SSH
|
||||
- [ ] save configuration though SSH
|
||||
- add more storage options
|
||||
|
|
14
cmd/cli.go
14
cmd/cli.go
|
@ -22,7 +22,7 @@ var rootCmd = &cobra.Command{
|
|||
Short: "Manage your systems configuration",
|
||||
Long: `config-mapper aims to help you manage your configurations between systems
|
||||
with a single configuration file.`,
|
||||
Version: "v0.1.0.beta0",
|
||||
Version: "v0.3.0",
|
||||
}
|
||||
var initCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
|
@ -57,20 +57,20 @@ func init() {
|
|||
|
||||
loadCmd.PersistentFlags().Bool("disable-files", false, "files will be ignored")
|
||||
loadCmd.PersistentFlags().Bool("disable-folders", false, "folders will be ignored")
|
||||
loadCmd.PersistentFlags().Bool("disable-pkgs", false, "package managers will be ignored")
|
||||
loadCmd.PersistentFlags().Bool("pkgs", false, "packages will be installed")
|
||||
viper.BindPFlag("load-disable-files", loadCmd.PersistentFlags().Lookup("disable-files"))
|
||||
viper.BindPFlag("load-disable-folders", loadCmd.PersistentFlags().Lookup("disable-folders"))
|
||||
viper.BindPFlag("load-disable-pkgs", loadCmd.PersistentFlags().Lookup("disable-pkgs"))
|
||||
viper.BindPFlag("load-enable-pkgs", loadCmd.PersistentFlags().Lookup("pkgs"))
|
||||
|
||||
saveCmd.PersistentFlags().Bool("disable-files", false, "files will be ignored")
|
||||
saveCmd.PersistentFlags().Bool("disable-folders", false, "folders will be ignored")
|
||||
saveCmd.PersistentFlags().Bool("disable-pkgs", false, "package managers will be ignored")
|
||||
saveCmd.PersistentFlags().Bool("pkgs", false, "packages will be saved")
|
||||
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().Bool("disable-index", false, "configuration index will not be updated")
|
||||
viper.BindPFlag("save-disable-files", saveCmd.PersistentFlags().Lookup("disable-files"))
|
||||
viper.BindPFlag("save-disable-folders", saveCmd.PersistentFlags().Lookup("disable-folders"))
|
||||
viper.BindPFlag("save-disable-pkgs", saveCmd.PersistentFlags().Lookup("disable-pkgs"))
|
||||
viper.BindPFlag("save-enable-pkgs", saveCmd.PersistentFlags().Lookup("pkgs"))
|
||||
viper.BindPFlag("push", saveCmd.Flags().Lookup("push"))
|
||||
viper.BindPFlag("disable-index-update", saveCmd.Flags().Lookup("disable-index"))
|
||||
viper.BindPFlag("message", saveCmd.Flags().Lookup("message"))
|
||||
|
@ -113,7 +113,7 @@ func save(cmd *cobra.Command, args []string) {
|
|||
|
||||
el.Action("save")
|
||||
|
||||
if !viper.GetBool("save-disable-pkgs") {
|
||||
if viper.GetBool("save-enable-pkgs") {
|
||||
if err := mapper.SavePkgs(c); err != nil {
|
||||
mapper.PrintError(err.Error())
|
||||
os.Exit(1)
|
||||
|
@ -167,7 +167,7 @@ func load(cmd *cobra.Command, args []string) {
|
|||
|
||||
el.Action("load")
|
||||
|
||||
if !viper.GetBool("load-disable-pkgs") {
|
||||
if viper.GetBool("load-enable-pkgs") {
|
||||
if err := mapper.LoadPkgs(c.PackageManagers); err != nil {
|
||||
mapper.PrintError(err.Error())
|
||||
os.Exit(1)
|
||||
|
|
|
@ -10,13 +10,13 @@ import (
|
|||
)
|
||||
|
||||
type Configuration struct {
|
||||
Storage Storage `mapstructure:"storage" yaml:"storage"`
|
||||
Files []ItemLocation `mapstructure:"files" yaml:"files"`
|
||||
Folders []ItemLocation `mapstructure:"folders" yaml:"folders"`
|
||||
PackageManagers PkgManagers `mapstructure:"package-managers" yaml:"package-managers"`
|
||||
Storage Storage `mapstructure:"storage" yaml:"storage"`
|
||||
Files []OSLocation `mapstructure:"files" yaml:"files"`
|
||||
Folders []OSLocation `mapstructure:"folders" yaml:"folders"`
|
||||
PackageManagers PkgManagers `mapstructure:"package-managers" yaml:"package-managers"`
|
||||
}
|
||||
|
||||
type ItemLocation struct {
|
||||
type OSLocation struct {
|
||||
Darwin string `mapstructure:"darwin" yaml:"darwin"`
|
||||
Linux string `mapstructure:"linux" yaml:"linux"`
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
type Items struct {
|
||||
locations []ItemLocation
|
||||
locations []OSLocation
|
||||
storage string
|
||||
repository RepositoryActions
|
||||
indexer Indexer
|
||||
|
@ -20,13 +20,13 @@ type Items struct {
|
|||
|
||||
type ItemsActions interface {
|
||||
Action(action string)
|
||||
AddItems(items []ItemLocation)
|
||||
AddItems(items []OSLocation)
|
||||
CleanUp(removedLines []string) error
|
||||
}
|
||||
|
||||
func NewItemsActions(items []ItemLocation, storage string, repository RepositoryActions, indexer Indexer) ItemsActions {
|
||||
func NewItemsActions(items []OSLocation, storage string, repository RepositoryActions, indexer Indexer) ItemsActions {
|
||||
if items == nil {
|
||||
items = []ItemLocation{}
|
||||
items = []OSLocation{}
|
||||
}
|
||||
|
||||
return &Items{
|
||||
|
@ -37,79 +37,35 @@ func NewItemsActions(items []ItemLocation, storage string, repository Repository
|
|||
}
|
||||
}
|
||||
|
||||
func (e *Items) Action(a string) {
|
||||
color.Blue("# %s", a)
|
||||
func (e *Items) Action(action string) {
|
||||
color.Blue("# %s", action)
|
||||
newLines := []string{}
|
||||
|
||||
for i, l := range e.locations {
|
||||
var src, dst string
|
||||
var src string
|
||||
storagePath, systemPath, err := configPaths(l, e.storage)
|
||||
if err != nil {
|
||||
PrintError("[%d] failed to resolve item paths \"%v\": %v", i, l, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if a == "save" {
|
||||
if action == "save" {
|
||||
src = systemPath
|
||||
dst = storagePath
|
||||
|
||||
if newItem := e.saveItem(systemPath, storagePath, i); newItem != "" {
|
||||
newLines = append(newLines, newItem)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
src = storagePath
|
||||
dst = systemPath
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path.Dir(dst), 0755); err != nil {
|
||||
PrintError("[%d] failed to create directory architecture for destination path \"%s\": %v", i, path.Dir(dst), err)
|
||||
continue
|
||||
}
|
||||
|
||||
s, err := os.Stat(src)
|
||||
if err != nil {
|
||||
PrintError("[%d] failed to check if source path is a folder \"%s\": %v", i, src, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if s.IsDir() {
|
||||
dstPerms := fs.FileMode(0755)
|
||||
s, err := os.Stat(dst)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
PrintError("[%d] failed to check if destination folder \"%s\" exists: %v", i, dst, err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
dstPerms = s.Mode()
|
||||
}
|
||||
|
||||
if err := os.Mkdir(dst, dstPerms); err != nil {
|
||||
if !os.IsExist(err) {
|
||||
PrintError("[%d] failed to create destination folder \"%s\": %v", i, dst, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := copyFolder(src, dst); err != nil {
|
||||
PrintError("[%d] failed to %s folder from \"%s\" to \"%s\": %v", i, a, src, dst, err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if err := copyFile(src, dst); err != nil {
|
||||
PrintError("[%d] failed to %s file from \"%s\" to \"%s\": %v", i, a, src, dst, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if a == "save" {
|
||||
p, err := absolutePath(e.storage)
|
||||
if err != nil {
|
||||
PrintError("[%d] failed resolve absolute path from configuration storage: %v", i, err)
|
||||
continue
|
||||
}
|
||||
newLines = append(newLines, strings.ReplaceAll(dst, p+"/", ""))
|
||||
e.loadItem(storagePath, systemPath, i)
|
||||
}
|
||||
|
||||
color.Green("[%d] %s copied", i, src)
|
||||
}
|
||||
|
||||
if a == "save" && !viper.GetBool("disable-index-update") {
|
||||
if action == "save" && !viper.GetBool("disable-index-update") {
|
||||
if err := e.indexer.Write(newLines); err != nil {
|
||||
PrintError(err.Error())
|
||||
os.Exit(1)
|
||||
|
@ -117,7 +73,99 @@ func (e *Items) Action(a string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (e *Items) AddItems(items []ItemLocation) {
|
||||
func (e *Items) saveItem(src, dst string, index int) string {
|
||||
if err := os.MkdirAll(path.Dir(dst), 0755); err != nil {
|
||||
PrintError("[%d] failed to create directory architecture for destination path \"%s\": %v", index, path.Dir(dst), err)
|
||||
return ""
|
||||
}
|
||||
|
||||
s, err := os.Stat(src)
|
||||
if err != nil {
|
||||
PrintError("[%d] failed to check if source path is a folder \"%s\": %v", index, src, err)
|
||||
return ""
|
||||
}
|
||||
|
||||
if s.IsDir() {
|
||||
dstPerms := fs.FileMode(0755)
|
||||
s, err := os.Stat(dst)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
PrintError("[%d] failed to check if destination folder \"%s\" exists: %v", index, dst, err)
|
||||
return ""
|
||||
}
|
||||
} else {
|
||||
dstPerms = s.Mode()
|
||||
}
|
||||
|
||||
if err := os.Mkdir(dst, dstPerms); err != nil {
|
||||
if !os.IsExist(err) {
|
||||
PrintError("[%d] failed to create destination folder \"%s\": %v", index, dst, err)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
if err := copyFolder(src, dst, true); err != nil {
|
||||
PrintError("[%d] failed to save folder from \"%s\" to \"%s\": %v", index, src, dst, err)
|
||||
return ""
|
||||
}
|
||||
} else {
|
||||
if err := copyFile(src, dst); err != nil {
|
||||
PrintError("[%d] failed to save file from \"%s\" to \"%s\": %v", index, src, dst, err)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
p, err := absolutePath(e.storage)
|
||||
if err != nil {
|
||||
PrintError("[%d] failed resolve absolute path from configuration storage: %v", index, err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.ReplaceAll(dst, p+"/", "")
|
||||
}
|
||||
|
||||
func (e *Items) loadItem(src, dst string, index int) {
|
||||
if err := os.MkdirAll(path.Dir(dst), 0755); err != nil {
|
||||
PrintError("[%d] failed to create directory architecture for destination path \"%s\": %v", index, path.Dir(dst), err)
|
||||
return
|
||||
}
|
||||
|
||||
s, err := os.Stat(src)
|
||||
if err != nil {
|
||||
PrintError("[%d] failed to check if source path is a folder \"%s\": %v", index, src, err)
|
||||
return
|
||||
}
|
||||
|
||||
if s.IsDir() {
|
||||
dstPerms := fs.FileMode(0755)
|
||||
s, err := os.Stat(dst)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
PrintError("[%d] failed to check if destination folder \"%s\" exists: %v", index, dst, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
dstPerms = s.Mode()
|
||||
}
|
||||
|
||||
if err := os.Mkdir(dst, dstPerms); err != nil {
|
||||
if !os.IsExist(err) {
|
||||
PrintError("[%d] failed to create destination folder \"%s\": %v", index, dst, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := copyFolder(src, dst, false); err != nil {
|
||||
PrintError("[%d] failed to load folder from \"%s\" to \"%s\": %v", index, src, dst, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err := copyFile(src, dst); err != nil {
|
||||
PrintError("[%d] failed to load file from \"%s\" to \"%s\": %v", index, src, dst, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Items) AddItems(items []OSLocation) {
|
||||
e.locations = append(e.locations, items...)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
|
@ -44,6 +45,10 @@ func absolutePath(p string) (string, error) {
|
|||
func getPaths(p string, l string) (string, string, error) {
|
||||
paths := strings.Split(p, ":")
|
||||
|
||||
if len(paths) < 2 {
|
||||
return "", "", errors.New("path incorrectly formatted. It requires \"source:destination\"")
|
||||
}
|
||||
|
||||
src, err := absolutePath(strings.Replace(paths[0], "$LOCATION", l, 1))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
|
@ -86,18 +91,18 @@ func copyFile(src, dst string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func configPaths(f ItemLocation, location string) (string, string, error) {
|
||||
func configPaths(os OSLocation, location string) (string, string, error) {
|
||||
var src, dst string
|
||||
var err error
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
src, dst, err = getPaths(f.Linux, location)
|
||||
src, dst, err = getPaths(os.Linux, location)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
case "darwin":
|
||||
src, dst, err = getPaths(f.Darwin, location)
|
||||
src, dst, err = getPaths(os.Darwin, location)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
@ -108,15 +113,40 @@ func configPaths(f ItemLocation, location string) (string, string, error) {
|
|||
return src, dst, nil
|
||||
}
|
||||
|
||||
func copyFolder(src, dst string) error {
|
||||
var ignored map[string]bool
|
||||
|
||||
func copyFolder(src, dst string, checkIgnore bool) error {
|
||||
items, err := os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if checkIgnore {
|
||||
f, err := ioutil.ReadFile(fmt.Sprintf("%s/.ignore", src))
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ignored = map[string]bool{}
|
||||
for _, l := range strings.Split(string(f), "\n") {
|
||||
if l != "" && !strings.Contains(l, "#") {
|
||||
ignored[fmt.Sprintf("%s/%s", src, l)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range items {
|
||||
itemName := i.Name()
|
||||
srcItem := fmt.Sprintf("%s/%s", src, itemName)
|
||||
// do not copy item if it's present in .ignore file
|
||||
if ignored != nil {
|
||||
if _, ok := ignored[srcItem]; ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
dstItem := fmt.Sprintf("%s/%s", dst, itemName)
|
||||
|
||||
if i.IsDir() {
|
||||
|
@ -128,7 +158,7 @@ func copyFolder(src, dst string) error {
|
|||
if err := os.MkdirAll(dstItem, info.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := copyFolder(srcItem, dstItem); err != nil {
|
||||
if err := copyFolder(srcItem, dstItem, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
Reference in New Issue