Compare commits

...

53 Commits
v0.2.0 ... main

Author SHA1 Message Date
DataHearth 06d09e1be6
chore(ci): use new runner image label
build / Build and test (push) Successful in 35s Details
2023-07-18 14:10:14 +02:00
DataHearth 9ab934eb4d
chore(process): update justfile
build / Build and test (push) Successful in 10m10s Details
2023-07-18 11:04:52 +02:00
DataHearth c61a8e4a4d
chore(process): force use of gitea token goreleaser
release / Build and test (push) Successful in 10m5s Details
build / Build and test (push) Successful in 10m12s Details
release / Release (push) Successful in 14m0s Details
2023-07-18 00:33:02 +02:00
DataHearth f8ef48e57a
chore(ci): update build workflow with latest go version
build / Build and test (push) Successful in 10m1s Details
release / Build and test (push) Successful in 9m46s Details
release / Release (push) Failing after 6m40s Details
2023-07-17 23:42:21 +02:00
DataHearth 4f66244e9a
chore(ci): update goreleaser and ci
build / build (push) Successful in 10m12s Details
2023-07-17 23:36:05 +02:00
DataHearth fac6dd5b69
chore(doc): remove build status README.md 2023-07-17 23:35:18 +02:00
DataHearth ef93dd8db7
chore(changelog): release v0.6.2
release / release (push) Failing after 5m44s Details
build / build (push) Successful in 6m10s Details
2023-07-15 23:25:25 +02:00
DataHearth 35c3c41ec7
chore(process): add dotenv-load instruction to justfile
build / build (push) Successful in 6m6s Details
2023-07-15 23:24:21 +02:00
Antoine Langlois d0b19e61f6 Update 'LICENSE'
build / build (push) Failing after 1m38s Details
2023-05-24 10:02:31 +02:00
DataHearth d444de44f1
chore(ci): use gitea actions instead of droneCI 2023-03-31 18:59:19 +02:00
DataHearth 8a4932aa72
chore(doc): update readme
continuous-integration/drone/push Build is passing Details
2023-03-02 18:52:07 +01:00
DataHearth a568ca9bb8
chore(logging): use charmbracelet/log instead of custom logging
continuous-integration/drone/push Build is passing Details
2023-03-02 18:35:26 +01:00
DataHearth f627f8e199
chore(ci): update secret name 2023-02-10 17:57:12 +01:00
DataHearth bcc4faae27
chore(ci): add DroneCI 2023-02-10 17:45:02 +01:00
DataHearth 7c1c71ad55
chore: update CHANGELOG v0.6.1 2022-10-04 22:47:06 +02:00
DataHearth 5682db8ac9
chore(process): add just and goreleaser 2022-10-02 21:15:10 +02:00
DataHearth 29e0be566c
chore(doc): update README.md 2022-09-12 18:47:10 +02:00
DataHearth b27c1f9e89
feat(ssh): add possibilty to use multiple SSH configurations 2022-09-12 18:40:40 +02:00
DataHearth dd6eeb3041
v0.6.0 2022-08-21 01:40:20 +02:00
DataHearth 68f263e245
chore(release): update release process using python script 2022-08-21 01:39:27 +02:00
DataHearth fe4ff05d5d
chore(release): update release process 2022-08-20 18:35:56 +02:00
DataHearth 8efdca8fe4
chore(changelog): update git repository url 2022-08-20 16:38:59 +02:00
DataHearth f85fc9d39c
fix(pkgs): update command building 2022-07-20 18:03:31 +02:00
DataHearth 8051912b35
feat(packages): add nala package manager 2022-07-20 13:33:04 +02:00
DataHearth 224754870d
fix(pkgs): add pkg manager validation check and parsing cli arguments 2022-07-20 11:35:19 +02:00
DataHearth 0f1019774d
fix(items): fix stdout when no path is available 2022-07-20 11:33:51 +02:00
DataHearth 42d2152f8e chore(template): update brew key 2022-07-17 23:17:37 +02:00
DataHearth 66f0fe3042 fix(configuration): remove installation-order default value 2022-07-17 23:15:38 +02:00
DataHearth 21c031d8fc chore(deps): update go.mod dependencies 2022-07-16 15:46:17 +02:00
DataHearth 6e5ede992a chore(release): fix release version creation 2022-07-10 12:44:53 +02:00
DataHearth 48fef365e8 v0.5.0 2022-07-10 12:39:35 +02:00
DataHearth f576eec3c3 feat(pkgs): add more package manager 2022-07-10 12:37:28 +02:00
DataHearth f00d7c0288 fix(git): use go-git for adding removed file (workaround) 2022-07-10 12:37:16 +02:00
DataHearth 7aa343fc2a
chore(release): update release creation with "gh" 2022-06-16 18:54:07 +02:00
DataHearth 0c8d52f32d
v0.4.0 2022-06-16 18:41:39 +02:00
DataHearth 6f24de2bd0
chore(release): change script permissions 2022-06-16 18:41:19 +02:00
DataHearth 5f52ce6658
chore(release): automate release creation 2022-06-15 12:04:15 +02:00
DataHearth e92265791e
chore(doc): update README.md 2022-06-14 13:23:04 +02:00
DataHearth ea25075c82
fix(save): remove folder before copy (avoid unwanted files) 2022-06-14 12:15:24 +02:00
DataHearth 56da676e15
chore(doc): update function documentation 2022-06-14 12:03:53 +02:00
DataHearth a0662e9066
fix(config): don't throw error when file not available on OS 2022-06-14 11:31:35 +02:00
DataHearth d3bab447e0
feat(cli): add verbose flag and a spinner for pkgs 2022-06-06 15:32:01 +02:00
DataHearth 588e3c1e66
chore(doc): update readme issues 2022-06-06 14:52:39 +02:00
DataHearth 555d05d640 chore(doc): update readme 2022-06-02 19:53:41 +02:00
DataHearth 46249857f3 feat(config): add SSH capability with user/pass or key/pass 2022-06-02 19:44:51 +02:00
DataHearth f4bf8070ac chore(git): add .env to .gitignore 2022-06-02 19:44:07 +02:00
DataHearth 3a7210bc69 chore(doc): update readme 2022-06-02 18:02:33 +02:00
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
26 changed files with 1085 additions and 456 deletions

View File

@ -0,0 +1,31 @@
<a name="{{ (index .Versions 0).Tag.Name }}"></a>
## {{ if (index .Versions 0).Tag.Previous }}[{{ (index .Versions 0).Tag.Name }}]{{ else }}{{ (index .Versions 0).Tag.Name }}{{ end }} - {{ datetime "2006-01-02" (index .Versions 0).Tag.Date }}
{{ range (index .Versions 0).CommitGroups -}}
### {{ .Title }}
{{ range .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
{{ end }}
{{ end -}}
{{- if (index .Versions 0).RevertCommits -}}
### Reverts
{{ range (index .Versions 0).RevertCommits -}}
- {{ .Revert.Header }}
{{ end }}
{{ end -}}
{{- if (index .Versions 0).MergeCommits -}}
### Pull Requests
{{ range (index .Versions 0).MergeCommits -}}
- {{ .Header }}
{{ end }}
{{ end -}}
{{- if (index .Versions 0).NoteGroups -}}
{{ range (index .Versions 0).NoteGroups -}}
### {{ .Title }}
{{ range .Notes }}
{{ .Body }}
{{ end }}
{{ end -}}
{{ end -}}

View File

@ -2,7 +2,7 @@ style: github
template: CHANGELOG.tpl.md
info:
title: CHANGELOG
repository_url: https://github.com/DataHearth/config-mapper
repository_url: https://gitea.antoine-langlois.net/DataHearth/config-mapper
options:
commits:
filters:

View File

@ -10,7 +10,7 @@ storage:
basic-auth:
username: USERNAME
# * NOTE: if you're having trouble with error "authentication required", you should maybe use a token access
# * In some cases, it's due to 2FA authentication enabled on the git hosting provided
# * In some cases, it's due to 2FA authentication enabled on the git hosting provider
password: TOKEN
# * by default, if ssh dict is set with its keys filled, I'll try to clone with SSH
ssh:
@ -19,7 +19,7 @@ storage:
passphrase: PASSPHRASE
# NOTE: the $LOCATION if refering to the "storage.location" path. It'll be replaced automatically
# The left part of ":" is your repository location and right part when it should be on your system
# The left part of ":" is your repository location and right part where it should be located on your system
files:
- darwin: "$LOCATION/macos/.zshrc:~/.zshrc"
linux: "$LOCATION/linux/.zshrc:~/.zshrc"
@ -29,8 +29,10 @@ folders:
linux: "$LOCATION/macos/.config:~/.config"
package-managers:
installation-order: ["homebrew"]
homebrew:
# available: brew, pip (pip check also for pip3), cargo, apt, npm, go
installation-order:
- brew
brew:
- bat
- hexyl
- fd
@ -46,5 +48,3 @@ package-managers:
- nmap
- pinentry
- zsh
apt-get: []

View File

@ -1,8 +0,0 @@
.config-mapper.yml
.config-mapper.yml.template
.gitignore
LICENSE
README.md
CHANGELOG.md
.chglog
build

View File

@ -0,0 +1,21 @@
name: build
run-name: Build and test
on:
push:
branches:
- "*"
jobs:
build:
name: Build and test
runs-on: debian-go
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Build
run: go build -v ./...

View File

@ -0,0 +1,45 @@
name: release
on:
push:
tags:
- "v*.*.*"
jobs:
build:
name: Build and test
runs-on: debian-go
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Build
run: go build -v ./...
release:
name: Release
needs: build
runs-on: debian-go
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Fetch tags
run: git fetch --force --tags
- name: Build
run: go build -v ./...
- name: Release
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
go install github.com/goreleaser/goreleaser@latest
go install github.com/git-chglog/git-chglog/cmd/git-chglog@latest
goreleaser release --clean --release-notes <(git-chglog -t .chglog/RELEASE_CHANGELOG.tpl.md)

6
.gitignore vendored
View File

@ -1,3 +1,5 @@
.config-mapper.yml
.env
.DS_STORE
build
.DS_Store
dist/
.config-mapper.yml

47
.goreleaser.yaml Normal file
View File

@ -0,0 +1,47 @@
before:
hooks:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
goarch:
- "386"
- amd64
- arm
- arm64
archives:
- format: tar.gz
files:
- LICENSE
- README.md
- .config-mapper.yml.template
- CHANGELOG.md
name_template: >-
{{ .ProjectName }}_
{{ .Version }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
gitea_urls:
api: https://gitea.antoine-langlois.net/api/v1
download: https://gitea.antoine-langlois.net
release:
gitea:
owner: DataHearth
name: config-mapper
mode: append
checksum:
name_template: "checksums.txt"
snapshot:
name_template: "{{ incpatch .Version }}-next"
force_token: gitea

View File

@ -3,11 +3,57 @@
## [Unreleased]
<a name="v0.2.0"></a>
## [v0.2.0] - 2022-05-23
<a name="v0.6.2"></a>
## [v0.6.2] - 2023-07-15
<a name="v0.6.1"></a>
## [v0.6.1] - 2022-10-04
### Features
- **ssh:** add possibilty to use multiple SSH configurations
<a name="v0.6.0"></a>
## [v0.6.0] - 2022-08-21
### Bug Fixes
- **configuration:** remove installation-order default value
- **items:** fix stdout when no path is available
- **pkgs:** update command building
- **pkgs:** add pkg manager validation check and parsing cli arguments
### Features
- **packages:** add nala package manager
<a name="v0.5.0"></a>
## [v0.5.0] - 2022-08-01
### Bug Fixes
- **git:** use go-git for adding removed file (workaround)
### Features
- **pkgs:** add more package manager
<a name="v0.4.0"></a>
## [v0.4.0] - 2022-08-01
### Bug Fixes
- **config:** don't throw error when file not available on OS
- **save:** remove folder before copy (avoid unwanted files)
### Features
- **cli:** add verbose flag and a spinner for pkgs
- **config:** add SSH capability with user/pass or key/pass
<a name="v0.3.0"></a>
## [v0.3.0] - 2022-08-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-08-01
### Bug Fixes
- **config:** fix config path check
- **copy:** use io.Copy instead of custom copy
- **git:** use git binary for "git add"
- **git:** deleted files are not pushed
- **git:** add error handling and repo URL from config
@ -17,24 +63,15 @@
### Code Refactoring
- **archi:** reduce base code to one struct
- **cli:** separate functions from CLI for lisibility
- **config:** unmarshal configuration instead of raw read
- **logging:** drop pterm
### Features
- **cli:** add configuration-file persistant flag
- **cli:** add git push option with message
- **cli:** add save and load features
- **cli:** add init sub-command
- **cli:** add copy folder
- **cli:** add save command
- **cli:** implement pkgs installation
- **config:** update git configuration
- **config:** add yaml tags for yaml.v3
- **index:** add indexing system
<a name="v0.1.0"></a>
## v0.1.0 - 2022-02-27
## v0.1.0 - 2022-07-31
### Bug Fixes
- **config:** fix config path check
- **copy:** use io.Copy instead of custom copy
@ -44,14 +81,20 @@
### Features
- **cli:** add save and load features
- **cli:** add init sub-command
- **cli:** add copy folder
- **cli:** add save command
- **cli:** add configuration-file persistant flag
- **cli:** implement pkgs installation
- **config:** update git configuration
- **cli:** add init sub-command
- **config:** add yaml tags for yaml.v3
- **config:** update git configuration
[Unreleased]: https://github.com/DataHearth/config-mapper/compare/v0.2.0...HEAD
[v0.2.0]: https://github.com/DataHearth/config-mapper/compare/v0.1.0...v0.2.0
[Unreleased]: https://gitea.antoine-langlois.net/DataHearth/config-mapper/compare/v0.6.2...HEAD
[v0.6.2]: https://gitea.antoine-langlois.net/DataHearth/config-mapper/compare/v0.6.1...v0.6.2
[v0.6.1]: https://gitea.antoine-langlois.net/DataHearth/config-mapper/compare/v0.6.0...v0.6.1
[v0.6.0]: https://gitea.antoine-langlois.net/DataHearth/config-mapper/compare/v0.5.0...v0.6.0
[v0.5.0]: https://gitea.antoine-langlois.net/DataHearth/config-mapper/compare/v0.4.0...v0.5.0
[v0.4.0]: https://gitea.antoine-langlois.net/DataHearth/config-mapper/compare/v0.3.0...v0.4.0
[v0.3.0]: https://gitea.antoine-langlois.net/DataHearth/config-mapper/compare/v0.2.0...v0.3.0
[v0.2.0]: https://gitea.antoine-langlois.net/DataHearth/config-mapper/compare/v0.1.0...v0.2.0

View File

@ -1,14 +0,0 @@
VERSION 0.6
FROM golang:1.17-alpine3.15
WORKDIR /config-mapper
build-macos:
COPY . .
RUN GOOS=darwin go build -o build/config-mapper main.go
SAVE ARTIFACT build/config-mapper /config-mapper AS LOCAL build/x86-x64_darwin_config-mapper
build-linux:
COPY . .
RUN GOOS=linux go build -o build/config-mapper main.go
SAVE ARTIFACT build/config-mapper /config-mapper AS LOCAL build/x86-x64_linux_config-mapper

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Antoine Langlois
Copyright (c) 2022 Antoine Langlois "antoine.l@antoine-langlois.net"
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,7 +1,10 @@
# config-mapper
`config-mapper` is CLI utility tool to help you manage your configuration between systems.
It provides a set of tools to load your configuration from a system, save it on a git repository and then save it to a new system. This configuration can be a set of files, folders or even dependencies.
[![License](https://img.shields.io/badge/license-MIT-blue)](https://gitea.antoine-langlois.net/DataHearth/config-mapper/src/branch/main/LICENSE)
[![Version](https://img.shields.io/badge/version-v0.6.2-blue)](https://gitea.antoine-langlois.net/DataHearth/config-mapper/tags)
`config-mapper` is CLI utility tool to help you manage your configuration between UNIX systems.
It provides a set of tools to load your configuration from a system, save it into a git repository and then save it to a new system. This configuration can be a set of files, folders or even dependencies.
## Usage
@ -11,14 +14,34 @@ When copying a file from your configuration repository to your system, it's perf
The system is detected automatically. You just need to specify whether the related field in case of `files` or folders `sections` (fields: `darwin` | `linux`).
You can get a configuration template [here](https://raw.githubusercontent.com/DataHearth/config-mapper/main/.config-mapper.yml.template).
You can get a configuration template [here](https://gitea.antoine-langlois.net/DataHearth/config-mapper/raw/branch/main/.config-mapper.yml.template).
### Installation
- Using a pre-build binary
Binaries are available in the `release` section at [https://gitea.antoine-langlois.net/DataHearth/config-mapper/releases](https://gitea.antoine-langlois.net/DataHearth/config-mapper/releases).
- Building from source:
```bash
git clone https://gitea.antoine-langlois.net/DataHearth/config-mapper.git
cd config-mapper
go build -o $HOME/.local/bin/config-mapper
```
- With Golang cli
```bash
go install gitea.antoine-langlois.net/datahearth/config-mapper@latest
```
### Setup
Create a file called `.config-mapper.yml` in your `home` directory (it is the default search path for config-mapper).
If you wish to move it to another directory, you can have to choice to inform the tool. By either set an environment like this one: `CONFIG_MAPPER_CFG=/path/to/config/.config-mapper.yml`. Or by providing the `-c /path/to/config/.config-mapper.yml` flag to the tool.
If you wish to move it to another directory, you can choose by either setting an environment: `CONFIG_MAPPER_CFG=/path/to/config/.config-mapper.yml` or by using the `-c /path/to/config/.config-mapper.yml` flag.
Once the configuratio file created, run this command to initialize the repository localy:
Once the configuration file created, run this command to initialize the repository locally:
```bash
config-mapper init
@ -56,15 +79,37 @@ 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`.
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`.
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:
```text
# bar file will be ignored
foo/bar
# egg folder will be ignore
egg
```
template for your configuration:
```yaml
# NOTE: the $LOCATION if refering to the "storage.location" path. It'll be replaced automatically
# The left part of ":" is your repository location and right part when it should be on your system
# The left part of ":" is your repository location and right part is on your system
files:
- darwin: "$LOCATION/macos/.zshrc:~/.zshrc"
linux: "$LOCATION/linux/.zshrc:~/.zshrc"
@ -74,8 +119,9 @@ folders:
linux: "$LOCATION/macos/.config:~/.config"
package-managers:
installation-order: ["homebrew"]
homebrew:
installation-order:
- brew
brew:
- bat
- hexyl
- fd
@ -93,6 +139,10 @@ package-managers:
- zsh
apt-get: []
pip: []
cargo: []
pip: []
go: []
```
### Load your configuration onto the system
@ -107,8 +157,9 @@ The same ignore flags are used in the `save` command.
## TO-DO
- [ ] load configuration though SSH
- [ ] save configuration though SSH
- [x] add `.ignore` file to ignore content inside directory
- [x] use remote configuration: SSH
- [ ] optimisation over speed and memory
- add more storage options
- [ ] smb storage
- [ ] nfs storage
@ -120,3 +171,7 @@ The same ignore flags are used in the `save` command.
Resolved by create a new primary key based on GitHub new GIT SSH standards ([issue](https://github.com/go-git/go-git/issues/411))
- Cloning from GitHub with `https BasicAuth` and 2FA activated: `authentication required`
Resolved by creating an access token and set it as password in configuration
- WSL might have a rough time with opened files by `homebrew` and throwing `Error: too many open files`.
[This thread](https://github.com/Homebrew/linuxbrew-core/issues/21139) discuss about this issue.
The workaround seems to be increase the filesystem limit (`ulimit -Hn && ulimit -Sn`). Another way is to launch again your command as homebrew already installed
some the packages.

View File

@ -1,28 +1,23 @@
package cmd
import (
"log"
"os"
"strconv"
"time"
mapper "github.com/datahearth/config-mapper/internal"
"github.com/fatih/color"
mapper "gitea.antoine-langlois.net/datahearth/config-mapper/internal"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/configuration"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/git"
"github.com/charmbracelet/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
errLogger = log.New(os.Stderr, "", 0)
logger = log.New(os.Stderr, "", 0)
)
var rootCmd = &cobra.Command{
Use: "config-mapper",
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.6.2",
}
var initCmd = &cobra.Command{
Use: "init",
@ -45,32 +40,50 @@ var saveCmd = &cobra.Command{
saved location based on your configuration file`,
Run: save,
}
var installCmd = &cobra.Command{
Use: "install",
Short: "install additional tools",
Long: `install additional tools like package managers, programming languages, etc.`,
Run: func(cmd *cobra.Command, args []string) {
log.Fatal("install command not implemented yet")
},
}
func init() {
cobra.OnInitialize(mapper.InitConfig)
cobra.OnInitialize(configuration.InitConfig)
rootCmd.AddCommand(initCmd)
rootCmd.AddCommand(loadCmd)
rootCmd.AddCommand(saveCmd)
rootCmd.AddCommand(installCmd)
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "STDOUT will be more verbose")
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("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
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.PersistentFlags().Bool("disable-folders", false, "folders will be ignored")
loadCmd.PersistentFlags().Bool("disable-pkgs", false, "package managers will be ignored")
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"))
loadCmd.Flags().Bool("disable-files", false, "files will be ignored")
loadCmd.Flags().Bool("disable-folders", false, "folders will be ignored")
loadCmd.Flags().Bool("pkgs", false, "packages will be installed")
loadCmd.Flags().StringSlice("exclude-pkg-managers", []string{}, "package managers to exclude (comma separated)")
viper.BindPFlag("load-disable-files", loadCmd.Flags().Lookup("disable-files"))
viper.BindPFlag("load-disable-folders", loadCmd.Flags().Lookup("disable-folders"))
viper.BindPFlag("load-enable-pkgs", loadCmd.Flags().Lookup("pkgs"))
viper.BindPFlag("exclude-pkg-managers", loadCmd.Flags().Lookup("exclude-pkg-managers"))
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.Flags().Bool("disable-files", false, "files will be ignored")
saveCmd.Flags().Bool("disable-folders", false, "folders will be ignored")
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-disable-files", saveCmd.Flags().Lookup("disable-files"))
viper.BindPFlag("save-disable-folders", saveCmd.Flags().Lookup("disable-folders"))
viper.BindPFlag("push", saveCmd.Flags().Lookup("push"))
viper.BindPFlag("disable-index-update", saveCmd.Flags().Lookup("disable-index"))
viper.BindPFlag("message", saveCmd.Flags().Lookup("message"))
@ -78,28 +91,24 @@ func init() {
func Execute() {
if err := rootCmd.Execute(); err != nil {
errLogger.Printf("an error occured while running command: %v\n", err)
os.Exit(1)
log.Fatal("an error occured while running command", "err", err)
}
}
func save(cmd *cobra.Command, args []string) {
var c mapper.Configuration
var c configuration.Configuration
if err := viper.Unmarshal(&c); err != nil {
mapper.PrintError("failed to decode configuration: %v\n", err)
os.Exit(1)
log.Fatal("failed to decode configuration", "err", err)
}
indexer, err := mapper.NewIndexer(c.Storage.Path)
if err != nil {
mapper.PrintError("failed to open the indexer: %v\n", err)
os.Exit(1)
log.Fatal("failed to open the indexer", "err", err)
}
r, err := mapper.NewRepository(c.Storage.Git, c.Storage.Path)
r, err := git.NewRepository(c.Storage.Git, c.Storage.Path)
if err != nil {
mapper.PrintError("failed to open repository at %s: %v\n", c.Storage.Path, err)
os.Exit(1)
log.Fatal("failed to open repository", "path", c.Storage.Path, "err", err)
}
el := mapper.NewItemsActions(nil, c.Storage.Path, r, indexer)
@ -113,47 +122,33 @@ func save(cmd *cobra.Command, args []string) {
el.Action("save")
if !viper.GetBool("save-disable-pkgs") {
if err := mapper.SavePkgs(c); err != nil {
mapper.PrintError(err.Error())
os.Exit(1)
}
}
if err := el.CleanUp(indexer.RemovedLines()); err != nil {
mapper.PrintError("failed to clean repository: %v\n", err)
os.Exit(1)
log.Fatal("failed to clean repository", "err", err)
}
if viper.GetBool("push") {
color.Blue("# Pushing items")
log.Info("pushing changes...")
if err := r.PushChanges(viper.GetString("message"), indexer.Lines(), indexer.RemovedLines()); err != nil {
mapper.PrintError("failed to push changes to repository: %v\n", err)
os.Exit(1)
log.Fatal("failed to push changes to repository", "err", err)
}
color.Green("Items pushed")
}
}
func load(cmd *cobra.Command, args []string) {
var c mapper.Configuration
var c configuration.Configuration
if err := viper.Unmarshal(&c); err != nil {
mapper.PrintError("failed to decode configuration: %v\n", err)
os.Exit(1)
log.Fatal("failed to decode configuration", "err", err)
}
i, err := mapper.NewIndexer(c.Storage.Path)
if err != nil {
mapper.PrintError("failed to open the indexer: %v\n", err)
os.Exit(1)
log.Fatal("failed to open the indexer", "err", err)
}
r, err := mapper.NewRepository(c.Storage.Git, c.Storage.Path)
r, err := git.NewRepository(c.Storage.Git, c.Storage.Path)
if err != nil {
mapper.PrintError("failed to open repository at %s: %v\n", c.Storage.Path, err)
os.Exit(1)
log.Fatal("failed to open repository", "path", c.Storage.Path, "err", err)
}
el := mapper.NewItemsActions(nil, c.Storage.Path, r, i)
@ -167,28 +162,24 @@ func load(cmd *cobra.Command, args []string) {
el.Action("load")
if !viper.GetBool("load-disable-pkgs") {
if err := mapper.LoadPkgs(c.PackageManagers); err != nil {
mapper.PrintError(err.Error())
os.Exit(1)
if viper.GetBool("load-enable-pkgs") {
if err := mapper.InstallPackages(c.PackageManagers); err != nil {
log.Fatal(err)
}
}
}
func initCommand(cmd *cobra.Command, args []string) {
var config mapper.Configuration
if err := viper.Unmarshal(&config); err != nil {
mapper.PrintError("failed to decode configuration: %v\n", err)
os.Exit(1)
var c configuration.Configuration
if err := viper.Unmarshal(&c); err != nil {
log.Fatal("failed to decode configuration", "err", err)
}
logger.Println("initializing config-mapper folder from configuration...")
log.Info("initializing config-mapper folder from configuration...")
if _, err := mapper.NewRepository(config.Storage.Git, config.Storage.Path); err != nil {
mapper.PrintError("failed to initialize folder: %v\n", err)
os.Exit(1)
if _, err := git.NewRepository(c.Storage.Git, c.Storage.Path); err != nil {
log.Fatal("failed to initialize folder", "err", err)
}
logger.Printf("repository initialized at \"%v\"\n", viper.GetString("storage.location"))
log.Info("repository initialized", "path", viper.GetString("storage.location"))
}

22
go.mod
View File

@ -1,34 +1,41 @@
module github.com/datahearth/config-mapper
module gitea.antoine-langlois.net/datahearth/config-mapper
go 1.17
require (
github.com/gernest/wow v0.1.0
github.com/go-git/go-git/v5 v5.4.2
github.com/mitchellh/mapstructure v1.5.0
github.com/spf13/cobra v1.3.0
github.com/spf13/viper v1.10.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
github.com/charmbracelet/log v0.1.2
)
require (
github.com/Microsoft/go-winio v0.4.16 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/charmbracelet/lipgloss v0.6.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/fatih/color v1.13.0
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 // indirect
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
@ -36,9 +43,8 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/ini.v1 v1.66.2 // indirect

43
go.sum
View File

@ -80,6 +80,10 @@ github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
github.com/charmbracelet/log v0.1.2 h1:xmKMxo0T/lcftgggQOhUkS32exku2/ID55FGYbr4nKQ=
github.com/charmbracelet/log v0.1.2/go.mod h1:86XdIdmrubqtL/6u0z+jGFol1bQejBGG/qPSTwGZuQQ=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -118,11 +122,12 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/gernest/wow v0.1.0 h1:g9xdwCwP0+xgVYlA2sopI0gZHqXe7HjI/7/LykG4fks=
github.com/gernest/wow v0.1.0/go.mod h1:dEPabJRi5BneI1Nev1VWo0ZlcTWibHWp43qxKms4elY=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
@ -142,6 +147,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -283,6 +290,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
@ -292,15 +301,18 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
@ -311,13 +323,18 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@ -345,6 +362,9 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -374,13 +394,18 @@ github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@ -407,6 +432,7 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -531,6 +557,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -594,8 +621,9 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -837,8 +865,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -1,93 +0,0 @@
package mapper
import (
"fmt"
"os"
"path"
"strings"
"github.com/spf13/viper"
)
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"`
}
type ItemLocation struct {
Darwin string `mapstructure:"darwin" yaml:"darwin"`
Linux string `mapstructure:"linux" yaml:"linux"`
}
type Storage struct {
Path string `mapstructure:"location" yaml:"location"`
Git Git `mapstructure:"git" yaml:"git"`
}
type Git struct {
URL string `mapstructure:"repository" yaml:"repository"`
Name string `mapstructure:"name" yaml:"name"`
Email string `mapstructure:"email" yaml:"email"`
BasicAuth BasicAuth `mapstructure:"basic-auth" yaml:"basic-auth"`
SSH Ssh `mapstructure:"ssh" yaml:"ssh"`
}
type BasicAuth struct {
Username string `mapstructure:"username" yaml:"username"`
Password string `mapstructure:"password" yaml:"password"`
}
type Ssh struct {
PrivateKey string `mapstructure:"private-key" yaml:"private-key"`
Passphrase string `mapstructure:"passphrase" yaml:"passphrase"`
}
type PkgManagers struct {
InstallationOrder []string `mapstructure:"installation-order" yaml:"installation-order"`
Homebrew []string `mapstructure:"homebrew" yaml:"homebrew"`
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,64 @@
package configuration
import (
"fmt"
"os"
"path"
"strings"
"github.com/charmbracelet/log"
"github.com/spf13/viper"
)
func InitConfig() {
h, err := os.UserHomeDir()
if err != nil {
log.Fatal(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 {
log.Fatal(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()))
if err := viper.ReadInConfig(); err != nil {
var errMsg string
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
errMsg = "no configuration file found"
} else {
errMsg = "failed to read config"
}
log.Fatal(errMsg, "err", err)
}
viper.Set("configuration-file", viper.ConfigFileUsed())
}

View File

@ -0,0 +1,47 @@
package configuration
type Configuration struct {
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 OSLocation struct {
Darwin string `mapstructure:"darwin" yaml:"darwin"`
Linux string `mapstructure:"linux" yaml:"linux"`
}
type Storage struct {
Path string `mapstructure:"location" yaml:"location"`
Git Git `mapstructure:"git" yaml:"git"`
}
type Git struct {
URL string `mapstructure:"repository" yaml:"repository"`
Name string `mapstructure:"name" yaml:"name"`
Email string `mapstructure:"email" yaml:"email"`
BasicAuth BasicAuth `mapstructure:"basic-auth" yaml:"basic-auth"`
SSH interface{} `mapstructure:"ssh" yaml:"ssh"`
}
type BasicAuth struct {
Username string `mapstructure:"username" yaml:"username"`
Password string `mapstructure:"password" yaml:"password"`
}
type Ssh struct {
PrivateKey string `mapstructure:"private-key" yaml:"private-key"`
Passphrase string `mapstructure:"passphrase" yaml:"passphrase"`
}
type PkgManagers struct {
InstallationOrder []string `mapstructure:"installation-order" yaml:"installation-order"`
Brew []string `mapstructure:"brew" yaml:"brew"`
Apt []string `mapstructure:"apt" yaml:"apt"`
Cargo []string `mapstructure:"cargo" yaml:"cargo"`
Pip []string `mapstructure:"pip" yaml:"pip"`
Npm []string `mapstructure:"npm" yaml:"npm"`
Go []string `mapstructure:"go" yaml:"go"`
Nala []string `mapstructure:"nala" yaml:"nala"`
}

View File

@ -0,0 +1,174 @@
package configuration
import (
"bytes"
"errors"
"fmt"
"os"
osUser "os/user"
"strings"
"github.com/charmbracelet/log"
"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 == "" {
log.Warn("no user was found in either the URI and flags. Current user will be used")
var currentUser *osUser.User
currentUser, err = osUser.Current()
if err != nil {
return nil, "", "", err
}
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 := os.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,21 +1,23 @@
package mapper
package git
import (
"errors"
"fmt"
"os"
"os/exec"
"time"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/configuration"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/misc"
"github.com/go-git/go-git/v5"
"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/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/mitchellh/mapstructure"
)
var (
ErrDirIsFile = errors.New("path is a file")
ErrInvalidEnv = errors.New("found invalid environment variable in path")
ErrDirIsFile = errors.New("path is a file")
)
type RepositoryActions interface {
@ -38,35 +40,55 @@ type author struct {
email string
}
func NewRepository(config Git, repoPath string) (RepositoryActions, error) {
func NewRepository(config configuration.Git, repoPath string) (RepositoryActions, error) {
var auth transport.AuthMethod
if config.URL == "" {
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 {
return nil, err
}
if config.SSH.Passphrase != "" && config.SSH.PrivateKey != "" {
privateKey, err := absolutePath(config.SSH.PrivateKey)
if err != nil {
switch sshConfig := config.SSH.(type) {
case map[string]interface{}:
var outConfig configuration.Ssh
if err := mapstructure.Decode(sshConfig, &outConfig); err != nil {
return nil, err
}
if _, err := os.Stat(privateKey); err != nil {
return nil, err
}
auth, err = ssh.NewPublicKeysFromFile("git", privateKey, config.SSH.Passphrase)
auth, err = getSSHAuthMethod(outConfig)
if err != nil {
return nil, err
}
} else {
auth = &http.BasicAuth{
Username: config.BasicAuth.Username,
Password: config.BasicAuth.Password,
case []interface{}:
for i, c := range sshConfig {
if _, ok := c.(map[interface{}]interface{}); !ok {
fmt.Printf("invalid format for configuration n°%d", i)
continue
}
var outConfig configuration.Ssh
if err := mapstructure.Decode(c, &outConfig); err != nil {
fmt.Printf("failed to decode ssh configuration n°%d: %v\n", i, err)
continue
}
auth, err = getSSHAuthMethod(outConfig)
if err != nil {
fmt.Printf("failed to create SSH authentication method for configuration n°%d: %v\n", i, err)
continue
}
}
if auth == nil {
auth = &http.BasicAuth{
Username: config.BasicAuth.Username,
Password: config.BasicAuth.Password,
}
}
default:
return nil, errors.New("git ssh configuration canno't be unmarshaled. Please, pass a valid configuration")
}
repo := &Repository{
@ -138,13 +160,18 @@ func (r *Repository) PushChanges(msg string, newLines, removedLines []string) er
return err
}
// TODO: investigated why w.AddWithOptions doesn't add removed files and sometimes .index
cmd := exec.Command("git", "add", ".")
cmd.Dir = r.repoPath
if err := cmd.Run(); err != nil {
status, err := w.Status()
if err != nil {
return err
}
for file := range status {
_, err = w.Add(file)
if err != nil {
return err
}
}
if _, err := w.Commit(msg, &git.CommitOptions{
Author: r.GetAuthor(),
}); err != nil {
@ -165,3 +192,25 @@ func (r *Repository) GetAuthor() *object.Signature {
When: time.Now(),
}
}
func getSSHAuthMethod(config configuration.Ssh) (transport.AuthMethod, error) {
if config.Passphrase == "" && config.PrivateKey == "" {
return nil, errors.New("passphrase and private are empty")
}
privateKey, err := misc.AbsolutePath(config.PrivateKey)
if err != nil {
return nil, err
}
if _, err := os.Stat(privateKey); err != nil {
return nil, err
}
auth, err := ssh.NewPublicKeysFromFile("git", privateKey, config.Passphrase)
if err != nil {
return nil, err
}
return auth, nil
}

View File

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

View File

@ -7,26 +7,29 @@ import (
"path"
"strings"
"github.com/fatih/color"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/configuration"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/git"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/misc"
"github.com/charmbracelet/log"
"github.com/spf13/viper"
)
type Items struct {
locations []ItemLocation
locations []configuration.OSLocation
storage string
repository RepositoryActions
repository git.RepositoryActions
indexer Indexer
}
type ItemsActions interface {
Action(action string)
AddItems(items []ItemLocation)
AddItems(items []configuration.OSLocation)
CleanUp(removedLines []string) error
}
func NewItemsActions(items []ItemLocation, storage string, repository RepositoryActions, indexer Indexer) ItemsActions {
func NewItemsActions(items []configuration.OSLocation, storage string, repository git.RepositoryActions, indexer Indexer) ItemsActions {
if items == nil {
items = []ItemLocation{}
items = []configuration.OSLocation{}
}
return &Items{
@ -37,93 +40,161 @@ func NewItemsActions(items []ItemLocation, storage string, repository Repository
}
}
func (e *Items) Action(a string) {
color.Blue("# %s", a)
// Action performs a "save" or "load" action on all given items.
//
// Any error is printed to STDERR and item is skipped.
//
// If the performed action is "save", it'll also write the `.index` file with all new items.
func (e *Items) Action(action string) {
log.Info("performing action", "action", action)
newLines := []string{}
for i, l := range e.locations {
var src, dst string
storagePath, systemPath, err := configPaths(l, e.storage)
storagePath, systemPath, err := misc.ConfigPaths(l, e.storage)
if err != nil {
PrintError("[%d] failed to resolve item paths \"%v\": %v", i, l, err)
log.Error("failed to resolve item paths", "item", i, "location", l, "err", err)
continue
}
if storagePath == "" && systemPath == "" {
log.Info("item is empty", "item", i, "location", l)
continue
}
if a == "save" {
src = systemPath
dst = storagePath
} 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
}
if action == "save" {
if newItem := e.saveItem(systemPath, storagePath, i); newItem != "" {
newLines = append(newLines, newItem)
} 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
}
e.loadItem(storagePath, systemPath, i)
}
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+"/", ""))
}
color.Green("[%d] %s copied", i, src)
log.Info("item processed", "action", action, "item", i, "location", l)
}
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)
log.Fatal(err)
}
}
}
func (e *Items) AddItems(items []ItemLocation) {
// saveItem saves a given item inside the configured saved location.
//
// If an error is given during the process, the function returns an empty string
// (meaning the item hasn't been saved) and prints the error in STDERR.
//
// Else, returns the relative item location from the saved location to write the index
// (E.g: /home/user/.config => .config)
func (e *Items) saveItem(src, dst string, index int) string {
if err := os.MkdirAll(path.Dir(dst), 0755); err != nil {
log.Error("failed to create directory architecture for destination path", "path", path.Dir(dst), "err", err)
return ""
}
s, err := os.Stat(src)
if err != nil {
log.Error("failed to check if source path is a folder", "path", src, "err", err)
return ""
}
if s.IsDir() {
dstPerms := fs.FileMode(0755)
s, err := os.Stat(dst)
if err != nil {
if !os.IsNotExist(err) {
log.Error("failed to check if destination folder exists", "path", dst, "err", err)
return ""
}
} else {
dstPerms = s.Mode()
}
// remove the destination if it exists. It cleans up the saved location from unused files
if err := os.RemoveAll(dst); err != nil {
log.Error("failed to truncate destination folder", "path", dst, "err", err)
}
if err := os.Mkdir(dst, dstPerms); err != nil {
if !os.IsExist(err) {
log.Error("failed to create destination folder", "path", dst, "err", err)
return ""
}
}
if err := misc.CopyFolder(src, dst, true); err != nil {
log.Error("failed to save folder from source to destination", "source", src, "destination", dst, "err", err)
return ""
}
} else {
if err := misc.CopyFile(src, dst); err != nil {
log.Error("failed to save file from source to destination", "source", src, "destination", dst, "err", err)
return ""
}
}
p, err := misc.AbsolutePath(e.storage)
if err != nil {
log.Error("failed resolve absolute path from configuration storage", "err", err)
return ""
}
return strings.ReplaceAll(dst, p+"/", "")
}
// loadItem loads a given item onto the system.
//
// If an error is given during the process, the function returns an empty string
// (meaning the item hasn't been saved) and prints the error in STDERR.
func (e *Items) loadItem(src, dst string, index int) {
if err := os.MkdirAll(path.Dir(dst), 0755); err != nil {
log.Error("failed to create directory architecture for destination path", "path", path.Dir(dst), "err", err)
return
}
s, err := os.Stat(src)
if err != nil {
log.Error("failed to check if source path is a folder", "path", src, "err", err)
return
}
if s.IsDir() {
dstPerms := fs.FileMode(0755)
s, err := os.Stat(dst)
if err != nil {
if !os.IsNotExist(err) {
log.Error("failed to check if destination folder exists", "path", dst, "err", err)
return
}
} else {
dstPerms = s.Mode()
}
if err := os.Mkdir(dst, dstPerms); err != nil {
if !os.IsExist(err) {
log.Error("failed to create destination folder", "path", dst, "err", err)
return
}
}
if err := misc.CopyFolder(src, dst, false); err != nil {
log.Error("failed to load folder from source to destination", "source", src, "destination", dst, "err", err)
return
}
} else {
if err := misc.CopyFile(src, dst); err != nil {
log.Error("failed to load file from source to destination", "source", src, "destination", dst, "err", err)
return
}
}
}
func (e *Items) AddItems(items []configuration.OSLocation) {
e.locations = append(e.locations, items...)
}
func (e *Items) CleanUp(removedLines []string) error {
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 {
return err
}

View File

@ -1,4 +1,4 @@
package mapper
package misc
import (
"errors"
@ -9,10 +9,10 @@ import (
"runtime"
"strings"
"github.com/fatih/color"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/configuration"
)
func absolutePath(p string) (string, error) {
func AbsolutePath(p string) (string, error) {
finalPath := p
if strings.Contains(finalPath, "~") {
h, err := os.UserHomeDir()
@ -30,7 +30,8 @@ func absolutePath(p string) (string, error) {
if strings.Contains(s, "$") {
env := os.Getenv(s)
if env == "" {
return "", ErrInvalidEnv
return "", errors.New("found invalid environment variable in path")
}
pathPart = env
}
@ -44,12 +45,16 @@ func absolutePath(p string) (string, error) {
func getPaths(p string, l string) (string, string, error) {
paths := strings.Split(p, ":")
src, err := absolutePath(strings.Replace(paths[0], "$LOCATION", l, 1))
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
}
dst, err := absolutePath(paths[1])
dst, err := AbsolutePath(paths[1])
if err != nil {
return "", "", err
}
@ -57,7 +62,7 @@ func getPaths(p string, l string) (string, string, error) {
return src, dst, nil
}
func copyFile(src, dst string) error {
func CopyFile(src, dst string) error {
s, err := os.Stat(src)
if err != nil {
return err
@ -86,18 +91,24 @@ func copyFile(src, dst string) error {
return nil
}
func configPaths(f ItemLocation, location string) (string, string, error) {
func ConfigPaths(os configuration.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)
if os.Linux == "" {
return "", "", nil
}
src, dst, err = getPaths(os.Linux, location)
if err != nil {
return "", "", err
}
case "darwin":
src, dst, err = getPaths(f.Darwin, location)
if os.Darwin == "" {
return "", "", nil
}
src, dst, err = getPaths(os.Darwin, location)
if err != nil {
return "", "", err
}
@ -108,15 +119,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 := os.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,21 +164,17 @@ 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
}
continue
}
if err := copyFile(srcItem, dstItem); err != nil {
if err := CopyFile(srcItem, dstItem); err != nil {
return err
}
}
return nil
}
func PrintError(err string, values ...interface{}) {
color.Error.Write([]byte(color.RedString(err+"\n", values...)))
}

View File

@ -1,40 +1,114 @@
package mapper
import (
"errors"
"fmt"
"log"
"os"
"os/exec"
"runtime"
"strings"
"github.com/fatih/color"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/configuration"
"github.com/charmbracelet/log"
"github.com/gernest/wow"
"github.com/gernest/wow/spin"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
var (
errLogger = log.New(os.Stderr, "", 0)
ErrFailedInstallation = errors.New("failed to install some packages. Please, checkout STDERR for more information")
ErrFailedSaving = errors.New("failed to save some packages. Please, checkout STDERR for more information")
ErrBrewNotAvailable = errors.New("homebrew is not available on your system")
ErrAptNotAvailable = errors.New("aptitude is not available on your system")
)
// InstallPackages install all packages from the configuration file by installation order
func InstallPackages(c configuration.PkgManagers) error {
pkgManagers := map[string]bool{}
for _, pkgManager := range viper.GetStringSlice("exclude-pkg-managers") {
pkgManagers[pkgManager] = true
}
func LoadPkgs(c PkgManagers) error {
color.Blue("# Load folders into saved location")
for _, pkgManager := range c.InstallationOrder {
log.Info("installing packages", "package-manager", pkgManager)
if _, ok := pkgManagers[pkgManager]; ok {
log.Info("skipping package manager", "package-manager", pkgManager)
continue
}
for _, pkg := range c.InstallationOrder {
switch pkg {
case "homebrew":
if err := installBrewPkgs(c.Homebrew); err != nil {
PrintError(err.Error())
return ErrFailedInstallation
}
var pkgs []string
switch pkgManager {
case "brew":
pkgs = c.Brew
case "apt":
if err := installAptPkgs(c.Aptitude); err != nil {
PrintError(err.Error())
return ErrFailedInstallation
pkgs = c.Apt
case "cargo":
pkgs = c.Cargo
case "npm":
pkgs = c.Npm
case "pip":
pkgs = c.Pip
case "go":
pkgs = c.Go
case "nala":
pkgs = c.Nala
default:
log.Error("package manager not supported", "package-manager", pkgManager)
continue
}
if _, err := exec.LookPath(pkgManager); err != nil {
// * pip might not be available on the system but pip3 is
if pkgManager == "pip" {
if _, err := exec.LookPath("pip3"); err != nil {
log.Error("pip and pip3 are not available on your system", "package-manager", pkgManager)
continue
}
pkgManager = "pip3"
} else {
log.Error("package manager not available on your system", "package-manager", pkgManager)
continue
}
}
// * for some reason, apt binary is available on darwin. Exclude it to avoid errors
if pkgManager == "apt" && runtime.GOOS == "darwin" {
log.Error("package manager not available on your system", "package-manager", pkgManager)
continue
}
if len(pkgs) == 0 {
fmt.Printf("✔️ nothing to do\n\n")
continue
}
v := viper.GetBool("verbose")
commands := []*exec.Cmd{}
// * package managers requiring sudo permission
if pkgManager == "apt" || pkgManager == "nala" {
commands = append(commands, buildDefaultCommand([]string{"sudo", pkgManager, "install", "-y"}, pkgs, v))
} else if pkgManager == "cargo" {
commands = buildCargoCommand(pkgs, v)
} else {
commands = append(commands, buildDefaultCommand([]string{pkgManager, "install"}, pkgs, v))
}
for i, cmd := range commands {
spinner := wow.New(os.Stdout, spin.Get(spin.Dots3), " Installing...")
if !v {
spinner.Start()
}
if err := cmd.Run(); err != nil {
if v {
log.Error(err)
} else {
msg := fmt.Sprintf(" %s", cmd.Args)
if i == len(commands)-1 {
msg = fmt.Sprintf("%s\n", msg)
}
spinner.PersistWith(spin.Spinner{Frames: []string{"❌"}}, msg)
}
continue
}
if !v {
// msg := fmt.Sprintf(" %s %s", color.GreenString("Success\t"), cmd.Args)
msg := fmt.Sprintf(" %s", cmd.Args)
if i == len(commands)-1 {
msg = fmt.Sprintf("%s\n", msg)
}
spinner.PersistWith(spin.Spinner{Frames: []string{"✔️"}}, msg)
}
}
}
@ -42,98 +116,42 @@ func LoadPkgs(c PkgManagers) error {
return nil
}
func SavePkgs(cfg Configuration) error {
color.Blue("# Save user installed packages")
func buildCargoCommand(packages []string, verbose bool) []*exec.Cmd {
commands := []*exec.Cmd{}
for _, pkg := range cfg.PackageManagers.InstallationOrder {
switch pkg {
case "homebrew":
if err := SaveBrewPkgs(cfg); err != nil {
PrintError(err.Error())
return ErrFailedSaving
cmd := exec.Command("cargo", "install")
for _, pkg := range packages {
if strings.Contains(pkg, " ") {
customCmd := exec.Command("cargo", "install")
customCmd.Args = append(cmd.Args, strings.Split(pkg, " ")...)
if verbose {
customCmd.Stderr = os.Stderr
customCmd.Stdout = os.Stdout
}
case "apt":
fmt.Println("implemented soon!")
commands = append(commands, customCmd)
} else {
cmd.Args = append(cmd.Args, pkg)
}
}
return nil
if verbose {
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
}
if len(cmd.Args) > 2 {
commands = append(commands, cmd)
}
return commands
}
func SaveBrewPkgs(cfg Configuration) error {
if _, err := exec.LookPath("brew"); err != nil {
return err
func buildDefaultCommand(command, packages []string, verbose bool) *exec.Cmd {
cmd := exec.Command(command[0], command[1:]...)
cmd.Args = append(cmd.Args, packages...)
if verbose {
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
}
color.Blue("## Saving Homebrew packages")
o, err := exec.Command("brew", "leaves", "--installed-on-request").Output()
if err != nil {
return err
}
pkgs := strings.Split(string(o), "\n")
cfg.PackageManagers.Homebrew = append([]string{}, pkgs[:len(pkgs)-1]...)
b, err := yaml.Marshal(cfg)
if err != nil {
return err
}
if err := os.WriteFile(viper.GetString("configuration-file"), b, 0755); err != nil {
return err
}
color.Green("Packages saved succesfully !")
return nil
}
func installBrewPkgs(pkgs []string) error {
if _, err := exec.LookPath("brew"); err != nil {
return ErrBrewNotAvailable
}
if len(pkgs) == 0 {
fmt.Println("homebrew: nothing to do")
return nil
}
cmd := exec.Command("brew", "install")
cmd.Args = append(cmd.Args, pkgs...)
color.Blue("## Installing Homebrew packages")
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
PrintError("brew command failed: %v", err)
return err
}
color.Green("Packages intalled succesfully !")
return nil
}
func installAptPkgs(pkgs []string) error {
if _, err := exec.LookPath("apt-get"); err != nil {
return ErrAptNotAvailable
}
if len(pkgs) == 0 {
fmt.Println("aptitude: nothing to do")
return nil
}
cmd := exec.Command("sudo", "apt-get", "install")
cmd.Args = append(cmd.Args, pkgs...)
color.Blue("## Installing aptitude packages")
if err := cmd.Run(); err != nil {
PrintError("aptitude command failed: %v", err)
return err
}
color.Green("Packages intalled succesfully !")
return nil
return cmd
}

17
justfile Normal file
View File

@ -0,0 +1,17 @@
set dotenv-load
set shell := ["zsh", "-uc"]
latest-tag := `git describe --tags --abbrev=0`
default:
@just --list
publish version: (bump-files-version version)
git-chglog --next-tag {{version}} --output CHANGELOG.md
git add CHANGELOG.md cmd/cli.go && git commit -m "chore(changelog): release {{version}}"
git tag -a {{version}} -m "{{version}}"
git push --follow-tags
bump-files-version version:
sd {{latest-tag}} {{version}} cmd/cli.go
sd {{latest-tag}} "version-{{version}}-blue" CHANGELOG.md

View File

@ -1,6 +1,6 @@
package main
import "github.com/datahearth/config-mapper/cmd"
import "gitea.antoine-langlois.net/datahearth/config-mapper/cmd"
func main() {
cmd.Execute()