Compare commits

...

6 Commits

Author SHA1 Message Date
Antoine Langlois 07e187e528
Update README.md 2022-06-01 20:32:46 +02:00
Antoine Langlois 02c82bc9d1
chore(doc): update readme 2022-06-01 20:30:27 +02:00
DataHearth ec82828509
chore(doc): update changelog 2022-06-01 20:13:19 +02:00
DataHearth 29d91e0e73
chore(ci): use golang:1.18-alpine3.15 for earthly build 2022-06-01 20:13:19 +02:00
DataHearth 3b3a4d59e0
feat(sync): add .ignore file to filter folder's content 2022-06-01 20:12:29 +02:00
DataHearth 81713eb583
feat(cli): packages are disabled by default 2022-06-01 11:47:33 +02:00
7 changed files with 192 additions and 81 deletions

View File

@ -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

View File

@ -1,6 +1,6 @@
VERSION 0.6
FROM golang:1.17-alpine3.15
FROM golang:1.18-alpine3.15
WORKDIR /config-mapper
build-macos:

View File

@ -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

View File

@ -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)

View File

@ -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"`
}

View File

@ -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...)
}

View File

@ -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
}