start rework

This commit is contained in:
DataHearth 2023-08-22 11:29:18 +02:00
parent 06d09e1be6
commit 89ead068e0
No known key found for this signature in database
GPG Key ID: E88FD356ACC5F3C4
16 changed files with 409 additions and 1060 deletions

View File

@ -1,32 +1,60 @@
# OPTIONAL: where your configuration is stored locally [default: $HOME/.config/config-mapper]
path: /path/to/folder
logging:
log-level: info # OPTIONAL: [default: info] available: debug, info, warn, error, fatal
time: true # OPTIONAL: [default: false] display time in logs
time-format: 02/01/2006 15:04:05 # OPTIONAL: [default: 02/01/2006 15:04:05] format of time displayed in logs
file: /path/to/logfile # OPTIONAL: [default: no file logging] path to log file
out: stdout # OPTIONAL: [default: stdout] where to log: stdout, stderr
storage:
# Where will be the repository folder located ? [DEFAULT: MacOS($TMPDIR/config-mapper) | Linux(/tmp/config-mapper)]
location: /path/to/folder
git:
# username used for commit author
# REQUIRED: username used for commit author
name: USERNAME
# email used for commit author
# REQUIRED: email used for commit author
email: EMAIL
# REQUIRED: git repository url
repository: git@github.com:DataHearth/my-config.git
# REQUIRED (if ssh-auth not set): basic authentication credentials for git repository
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 provider
password: TOKEN
# * by default, if ssh dict is set with its keys filled, I'll try to clone with SSH
# REQUIRED (if basic-auth not set): list of ssh keys used to authenticate to git repository
ssh:
# path can be relative and can contain environment variables
private-key: /path/to/private/key
passphrase: PASSPHRASE
- private-key: /path/to/private/key
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 where it should be located on your system
files:
- darwin: "$LOCATION/macos/.zshrc:~/.zshrc"
linux: "$LOCATION/linux/.zshrc:~/.zshrc"
folders:
- darwin: "$LOCATION/macos/.config:~/.config"
linux: "$LOCATION/macos/.config:~/.config"
# REQUIRED: list of items to save in configuration repository
items:
- name: .zshrc # OPTIONAL: [default: name of last path segment in "(universal|darwin|windows|linux).local" key]
# REQUIRED (if any of "darwin|windows|linux" keys aren't set): universal location for all operating systems
universal:
# REQUIRED: path to local file
local: /path/to/file
# REQUIRED: path to remote file (absolute path where "/" is the root of the repository)
remote: /remote/path
# REQUIRED (if "universal" key is not set): location for darwin operating system
darwin:
# REQUIRED: path to local file
local: /path/to/file
# REQUIRED: path to remote file (absolute path where "/" is the root of the repository)
remote: /remote/path
# REQUIRED (if "universal" key is not set): location for windows operating system
windows:
# REQUIRED: path to local file
local: /path/to/file
# REQUIRED: path to remote file (absolute path where "/" is the root of the repository)
remote: /remote/path
# REQUIRED (if "universal" key is not set): location for linux operating system
linux:
# REQUIRED: path to local file
local: /path/to/file
# REQUIRED: path to remote file (absolute path where "/" is the root of the repository)
remote: /remote/path
package-managers:
# available: brew, pip (pip check also for pip3), cargo, apt, npm, go

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"files.associations": {
".config-mapper.yml.template": "yaml"
}
}

View File

@ -1,185 +0,0 @@
package cmd
import (
"strconv"
"time"
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 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.6.2",
}
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize your configuration folder",
Long: `Initialize will retrieve your configuration folder from the source location and
copy it into the destination field`,
Run: initCommand,
}
var loadCmd = &cobra.Command{
Use: "load",
Short: "Load your configurations onto your system",
Long: `Load your files, folders and package managers deps configurations onto your new
onto your new system based on your configuration file`,
Run: load,
}
var saveCmd = &cobra.Command{
Use: "save",
Short: "save your configurations onto your saved location",
Long: `Save your files, folders and package managers deps configurations onto your
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(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.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.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.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"))
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
log.Fatal("an error occured while running command", "err", err)
}
}
func save(cmd *cobra.Command, args []string) {
var c configuration.Configuration
if err := viper.Unmarshal(&c); err != nil {
log.Fatal("failed to decode configuration", "err", err)
}
indexer, err := mapper.NewIndexer(c.Storage.Path)
if err != nil {
log.Fatal("failed to open the indexer", "err", err)
}
r, err := git.NewRepository(c.Storage.Git, c.Storage.Path)
if err != nil {
log.Fatal("failed to open repository", "path", c.Storage.Path, "err", err)
}
el := mapper.NewItemsActions(nil, c.Storage.Path, r, indexer)
if !viper.GetBool("save-disable-files") {
el.AddItems(c.Files)
}
if !viper.GetBool("save-disable-folders") {
el.AddItems(c.Folders)
}
el.Action("save")
if err := el.CleanUp(indexer.RemovedLines()); err != nil {
log.Fatal("failed to clean repository", "err", err)
}
if viper.GetBool("push") {
log.Info("pushing changes...")
if err := r.PushChanges(viper.GetString("message"), indexer.Lines(), indexer.RemovedLines()); err != nil {
log.Fatal("failed to push changes to repository", "err", err)
}
}
}
func load(cmd *cobra.Command, args []string) {
var c configuration.Configuration
if err := viper.Unmarshal(&c); err != nil {
log.Fatal("failed to decode configuration", "err", err)
}
i, err := mapper.NewIndexer(c.Storage.Path)
if err != nil {
log.Fatal("failed to open the indexer", "err", err)
}
r, err := git.NewRepository(c.Storage.Git, c.Storage.Path)
if err != nil {
log.Fatal("failed to open repository", "path", c.Storage.Path, "err", err)
}
el := mapper.NewItemsActions(nil, c.Storage.Path, r, i)
if !viper.GetBool("load-disable-files") {
el.AddItems(c.Files)
}
if !viper.GetBool("load-disable-folders") {
el.AddItems(c.Folders)
}
el.Action("load")
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 c configuration.Configuration
if err := viper.Unmarshal(&c); err != nil {
log.Fatal("failed to decode configuration", "err", err)
}
log.Info("initializing config-mapper folder from configuration...")
if _, err := git.NewRepository(c.Storage.Git, c.Storage.Path); err != nil {
log.Fatal("failed to initialize folder", "err", err)
}
log.Info("repository initialized", "path", viper.GetString("storage.location"))
}

24
go.mod
View File

@ -1,22 +1,22 @@
module gitea.antoine-langlois.net/datahearth/config-mapper
go 1.17
go 1.21
require (
github.com/charmbracelet/log v0.2.3
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
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
github.com/charmbracelet/log v0.1.2
github.com/spf13/viper v1.10.0
github.com/sirupsen/logrus v1.9.3
)
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/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v0.7.1 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
@ -29,11 +29,12 @@ require (
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-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mitchellh/go-homedir v1.1.0 // 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/mitchellh/mapstructure v1.4.3 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // 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
@ -43,8 +44,9 @@ 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-20220811171246-fbc7d0a398ab // indirect
golang.org/x/sys v0.7.0 // 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

55
go.sum
View File

@ -71,6 +71,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@ -80,10 +82,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/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/charmbracelet/log v0.2.3 h1:YVmBhJtpGL7nW/nlf5u+SEloU8XYljxozGzZpgwIvhs=
github.com/charmbracelet/log v0.2.3/go.mod h1:ZApwwzDbbETVTIRTk7724yQRJAXIktt98yGVMMaa3y8=
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=
@ -227,7 +229,6 @@ github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pf
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
@ -308,11 +309,11 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
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/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/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/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=
@ -323,18 +324,17 @@ 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/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
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=
@ -370,13 +370,14 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
@ -389,23 +390,18 @@ github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmq
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.10.0 h1:mXH0UwHS4D2HwWZa75im4xIQynLfblmWV7qcWpfv0yk=
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
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/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/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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=
@ -621,9 +617,10 @@ 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/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/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/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=
@ -730,7 +727,6 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -828,7 +824,6 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=

View File

@ -1,64 +0,0 @@
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

@ -1,47 +1,54 @@
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"`
Path string `yaml:"path"`
Storage Storage `yaml:"storage"`
Items []Item `yaml:"items"`
PackageManagers PkgManagers `yaml:"package-managers"`
}
type OSLocation struct {
Darwin string `mapstructure:"darwin" yaml:"darwin"`
Linux string `mapstructure:"linux" yaml:"linux"`
type Item struct {
Name string `yaml:"name"`
Universal ItemLocation `yaml:"universal"`
Darwin ItemLocation `yaml:"darwin"`
Linux ItemLocation `yaml:"linux"`
Windows ItemLocation `yaml:"windows"`
}
type ItemLocation struct {
Local string `yaml:"local"`
Remote string `yaml:"remote"`
}
type Storage struct {
Path string `mapstructure:"location" yaml:"location"`
Git Git `mapstructure:"git" yaml:"git"`
Git 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"`
Repository string `yaml:"repository"`
Name string `yaml:"name"`
Email string `yaml:"email"`
BasicAuth BasicAuth `yaml:"basic-auth"`
SSH []SshAuth `yaml:"ssh-auth"`
}
type BasicAuth struct {
Username string `mapstructure:"username" yaml:"username"`
Password string `mapstructure:"password" yaml:"password"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}
type Ssh struct {
PrivateKey string `mapstructure:"private-key" yaml:"private-key"`
Passphrase string `mapstructure:"passphrase" yaml:"passphrase"`
type SshAuth struct {
PrivateKey string `yaml:"private-key"`
Passphrase string `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"`
InstallationOrder []string `yaml:"installation-order"`
Brew []string `yaml:"brew"`
Apt []string `yaml:"apt"`
Cargo []string `yaml:"cargo"`
Pip []string `yaml:"pip"`
Npm []string `yaml:"npm"`
Go []string `yaml:"go"`
Nala []string `yaml:"nala"`
}

View File

@ -1,174 +0,0 @@
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

@ -6,14 +6,13 @@ import (
"os"
"time"
"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/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 (
@ -42,60 +41,35 @@ type author struct {
func NewRepository(config configuration.Git, repoPath string) (RepositoryActions, error) {
var auth transport.AuthMethod
if config.URL == "" {
if config.Repository == "" {
return nil, errors.New("a repository URI is needed (either using GIT protocol or HTTPS)")
}
repoPath, err := misc.AbsolutePath(repoPath)
repoPath, err := internal.ResolvePath(repoPath)
if err != nil {
return nil, err
}
switch sshConfig := config.SSH.(type) {
case map[string]interface{}:
var outConfig configuration.Ssh
if err := mapstructure.Decode(sshConfig, &outConfig); err != nil {
return nil, err
}
auth, err = getSSHAuthMethod(outConfig)
for i, c := range config.SSH {
auth, err = getSSHAuthMethod(c)
if err != nil {
return nil, err
fmt.Printf("failed to create SSH authentication method for configuration n°%d: %v\n", i, err)
continue
}
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,
}
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{
auth: auth,
repository: nil,
repoPath: repoPath,
url: config.URL,
url: config.Repository,
author: author{
name: config.Name,
email: config.Email,
@ -143,10 +117,9 @@ func (r *Repository) openRepository() error {
return err
}
err = w.Pull(&git.PullOptions{
if err := w.Pull(&git.PullOptions{
Auth: r.auth,
})
if err != nil && err != git.NoErrAlreadyUpToDate {
}); err != nil && err != git.NoErrAlreadyUpToDate {
return err
}
@ -166,8 +139,7 @@ func (r *Repository) PushChanges(msg string, newLines, removedLines []string) er
}
for file := range status {
_, err = w.Add(file)
if err != nil {
if _, err := w.Add(file); err != nil {
return err
}
}
@ -178,7 +150,9 @@ func (r *Repository) PushChanges(msg string, newLines, removedLines []string) er
return err
}
return r.repository.Push(&git.PushOptions{})
return r.repository.Push(&git.PushOptions{
Auth: r.auth,
})
}
func (r *Repository) GetWorktree() (*git.Worktree, error) {
@ -193,19 +167,23 @@ func (r *Repository) GetAuthor() *object.Signature {
}
}
func getSSHAuthMethod(config configuration.Ssh) (transport.AuthMethod, error) {
if config.Passphrase == "" && config.PrivateKey == "" {
return nil, errors.New("passphrase and private are empty")
func getSSHAuthMethod(config configuration.SshAuth) (transport.AuthMethod, error) {
if config.PrivateKey == "" {
return nil, errors.New("\"private-key\" field is empty")
}
privateKey, err := misc.AbsolutePath(config.PrivateKey)
privateKey, err := internal.ResolvePath(config.PrivateKey)
if err != nil {
return nil, err
}
if _, err := os.Stat(privateKey); err != nil {
s, err := os.Stat(privateKey)
if err != nil {
return nil, err
}
if s.IsDir() {
return nil, errors.New("private key is a directory")
}
auth, err := ssh.NewPublicKeysFromFile("git", privateKey, config.Passphrase)
if err != nil {

View File

@ -1,109 +0,0 @@
package mapper
import (
"fmt"
"io/fs"
"os"
"strings"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/misc"
)
type Index struct {
lines []string
path string
perms fs.FileMode
repoPath string
removedLines []string
}
type Indexer interface {
Write(newLines []string) error
filter(configLines []string) map[string]bool
RemovedLines() []string
Lines() []string
}
func NewIndexer(repoPath string) (Indexer, error) {
perms := fs.FileMode(0755)
indexPath, err := misc.AbsolutePath(fmt.Sprintf("%s/.index", repoPath))
if err != nil {
return nil, err
}
var l []string
s, err := os.Stat(indexPath)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
l = []string{}
} else {
perms = s.Mode()
b, err := os.ReadFile(indexPath)
if err != nil {
return nil, err
}
l = strings.Split(string(b), "\n")
}
return &Index{
lines: l,
path: indexPath,
perms: perms,
repoPath: repoPath,
removedLines: []string{},
}, nil
}
func (i *Index) RemovedLines() []string {
return i.removedLines
}
func (i *Index) Lines() []string {
return i.lines
}
// filter removes lines that are no more used in configuration from the index
func (i *Index) filter(newLines []string) map[string]bool {
removedLines := []string{}
foundLines := map[string]bool{}
for _, nl := range newLines {
foundLines[nl] = true
}
for _, ml := range i.lines {
if _, ok := foundLines[ml]; !ok {
removedLines = append(removedLines, ml)
}
}
i.removedLines = removedLines
return foundLines
}
// Write add lines stored in memory the .index file
func (i *Index) Write(newLines []string) error {
lines := i.filter(newLines)
linesNumber := len(lines)
var data []byte
index := 0
i.lines = []string{}
for path := range lines {
if index+1 == linesNumber {
data = append(data, []byte(fmt.Sprint(path))...)
} else {
data = append(data, []byte(fmt.Sprintln(path))...)
}
index += 1
i.lines = append(i.lines, path)
}
os.WriteFile(i.path, data, i.perms)
return nil
}

View File

@ -1,208 +0,0 @@
package mapper
import (
"fmt"
"io/fs"
"os"
"path"
"strings"
"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 []configuration.OSLocation
storage string
repository git.RepositoryActions
indexer Indexer
}
type ItemsActions interface {
Action(action string)
AddItems(items []configuration.OSLocation)
CleanUp(removedLines []string) error
}
func NewItemsActions(items []configuration.OSLocation, storage string, repository git.RepositoryActions, indexer Indexer) ItemsActions {
if items == nil {
items = []configuration.OSLocation{}
}
return &Items{
locations: items,
storage: storage,
repository: repository,
indexer: indexer,
}
}
// 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 {
storagePath, systemPath, err := misc.ConfigPaths(l, e.storage)
if err != nil {
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 action == "save" {
if newItem := e.saveItem(systemPath, storagePath, i); newItem != "" {
newLines = append(newLines, newItem)
} else {
continue
}
} else {
e.loadItem(storagePath, systemPath, i)
}
log.Info("item processed", "action", action, "item", i, "location", l)
}
if action == "save" && !viper.GetBool("disable-index-update") {
if err := e.indexer.Write(newLines); err != nil {
log.Fatal(err)
}
}
}
// 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 := misc.AbsolutePath(fmt.Sprintf("%s/%s", e.storage, l))
if err != nil {
return err
}
if err := os.RemoveAll(path); err != nil {
return fmt.Errorf("failed to remove item %s: %v", l, err)
}
}
return nil
}

View File

@ -0,0 +1,76 @@
package logging
import (
"fmt"
"github.com/sirupsen/logrus"
)
const (
red = 31
yellow = 33
blue = 36
gray = 37
)
const (
PanicLevel logrus.Level = iota
FatalLevel
ErrorLevel
WarnLevel
InfoLevel
DebugLevel
TraceLevel
)
type LoggerFormatter struct{}
// Format gather all the data from the log entry and format it to return the final log message
func (f *LoggerFormatter) Format(e *logrus.Entry) ([]byte, error) {
msg := f.printColored(e)
if len(e.Data) != 0 {
for k, v := range e.Data {
msg = fmt.Sprintf("%s %s=%v", msg, k, v)
}
msg = fmt.Sprintf("%s message='%s'\n", msg, e.Message)
} else {
msg = fmt.Sprintf("%s %s\n", msg, e.Message)
}
return []byte(msg), nil
}
// printColored returns the prefix for log message with correct log level color and name
func (f *LoggerFormatter) printColored(e *logrus.Entry) string {
var levelColor int
var levelText string
switch e.Level {
case TraceLevel:
levelText = "trace:"
levelColor = gray
case DebugLevel:
levelText = "debug:"
levelColor = gray
case InfoLevel:
levelText = "info:"
levelColor = blue
case WarnLevel:
levelText = "warn:"
levelColor = yellow
case ErrorLevel:
levelText = "error:"
levelColor = red
case FatalLevel:
levelText = "fatal:"
levelColor = red
case PanicLevel:
levelText = "panic:"
levelColor = red
default:
levelColor = blue
}
return fmt.Sprintf("\x1b[%dm%s\x1b[0m", levelColor, levelText)
}

View File

@ -1,180 +0,0 @@
package misc
import (
"errors"
"fmt"
"io"
"os"
"path"
"runtime"
"strings"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/configuration"
)
func AbsolutePath(p string) (string, error) {
finalPath := p
if strings.Contains(finalPath, "~") {
h, err := os.UserHomeDir()
if err != nil {
return "", err
}
finalPath = strings.Replace(p, "~", h, 1)
}
splitted := strings.Split(finalPath, "/")
finalPath = ""
for _, s := range splitted {
pathPart := s
if strings.Contains(s, "$") {
env := os.Getenv(s)
if env == "" {
return "", errors.New("found invalid environment variable in path")
}
pathPart = env
}
finalPath += fmt.Sprintf("/%s", pathPart)
}
return path.Clean(finalPath), nil
}
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
}
dst, err := AbsolutePath(paths[1])
if err != nil {
return "", "", err
}
return src, dst, nil
}
func CopyFile(src, dst string) error {
s, err := os.Stat(src)
if err != nil {
return err
}
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
if _, err := io.Copy(out, in); err != nil {
return err
}
if err := os.Chmod(dst, s.Mode()); err != nil {
return err
}
return nil
}
func ConfigPaths(os configuration.OSLocation, location string) (string, string, error) {
var src, dst string
var err error
switch runtime.GOOS {
case "linux":
if os.Linux == "" {
return "", "", nil
}
src, dst, err = getPaths(os.Linux, location)
if err != nil {
return "", "", err
}
case "darwin":
if os.Darwin == "" {
return "", "", nil
}
src, dst, err = getPaths(os.Darwin, location)
if err != nil {
return "", "", err
}
default:
return "", "", errors.New("unsupported OS. Please, contact the maintainer")
}
return src, dst, nil
}
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() {
info, err := i.Info()
if err != nil {
return err
}
if err := os.MkdirAll(dstItem, info.Mode()); err != nil {
return err
}
if err := CopyFolder(srcItem, dstItem, false); err != nil {
return err
}
continue
}
if err := CopyFile(srcItem, dstItem); err != nil {
return err
}
}
return nil
}

View File

@ -1,4 +1,4 @@
package mapper
package internal
import (
"fmt"

23
internal/tools.go Normal file
View File

@ -0,0 +1,23 @@
package internal
import (
"os"
"os/user"
"path/filepath"
"strings"
)
// ResolvePath resolves the path using environment variables and "~"
func ResolvePath(path string) (string, error) {
path = os.ExpandEnv(path)
if strings.Contains(path, "~") {
usr, err := user.Current()
if err != nil {
return "", err
}
path = filepath.Join(usr.HomeDir, strings.Replace(path, "~", "", 1))
}
return path, nil
}

161
main.go
View File

@ -1,7 +1,162 @@
package main
import "gitea.antoine-langlois.net/datahearth/config-mapper/cmd"
import (
"strconv"
"time"
"gitea.antoine-langlois.net/datahearth/config-mapper/internal/logging"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
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.6.2",
}
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize your configuration folder",
Long: `Initialize will retrieve your configuration folder from the source location and
copy it into the destination field`,
Run: initCommand,
}
var loadCmd = &cobra.Command{
Use: "load",
Short: "Load your configurations onto your system",
Long: `Load your files, folders and package managers deps configurations onto your new
onto your new system based on your configuration file`,
Run: load,
}
var saveCmd = &cobra.Command{
Use: "save",
Short: "save your configurations onto your saved location",
Long: `Save your files, folders and package managers deps configurations onto your
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) {
logrus.Fatal("install command not implemented yet")
},
}
func init() {
logrus.SetFormatter(new(logging.LoggerFormatter))
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")
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)")
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")
}
func main() {
cmd.Execute()
}
if err := rootCmd.Execute(); err != nil {
logrus.Fatal("an error occured while running command", "err", err)
}
}
func save(cmd *cobra.Command, args []string) {
}
func load(cmd *cobra.Command, args []string) {
}
// func save(cmd *cobra.Command, args []string) {
// var c configuration.Configuration
// if err := viper.Unmarshal(&c); err != nil {
// logrus.Fatal("failed to decode configuration", "err", err)
// }
// indexer, err := mapper.NewIndexer(c.Storage.Path)
// if err != nil {
// logrus.Fatal("failed to open the indexer", "err", err)
// }
// r, err := git.NewRepository(c.Storage.Git, c.Storage.Path)
// if err != nil {
// logrus.Fatal("failed to open repository", "path", c.Storage.Path, "err", err)
// }
// el := mapper.NewItemsActions(nil, c.Storage.Path, r, indexer)
// if !viper.GetBool("save-disable-files") {
// el.AddItems(c.Files)
// }
// if !viper.GetBool("save-disable-folders") {
// el.AddItems(c.Folders)
// }
// el.Action("save")
// if err := el.CleanUp(indexer.RemovedLines()); err != nil {
// logrus.Fatal("failed to clean repository", "err", err)
// }
// if viper.GetBool("push") {
// logrus.Info("pushing changes...")
// if err := r.PushChanges(viper.GetString("message"), indexer.Lines(), indexer.RemovedLines()); err != nil {
// logrus.Fatal("failed to push changes to repository", "err", err)
// }
// }
// }
// func load(cmd *cobra.Command, args []string) {
// var c configuration.Configuration
// if err := viper.Unmarshal(&c); err != nil {
// logrus.Fatal("failed to decode configuration", "err", err)
// }
// i, err := mapper.NewIndexer(c.Storage.Path)
// if err != nil {
// logrus.Fatal("failed to open the indexer", "err", err)
// }
// r, err := git.NewRepository(c.Storage.Git, c.Storage.Path)
// if err != nil {
// logrus.Fatal("failed to open repository", "path", c.Storage.Path, "err", err)
// }
// el := mapper.NewItemsActions(nil, c.Storage.Path, r, i)
// if !viper.GetBool("load-disable-files") {
// el.AddItems(c.Files)
// }
// if !viper.GetBool("load-disable-folders") {
// el.AddItems(c.Folders)
// }
// el.Action("load")
// if viper.GetBool("load-enable-pkgs") {
// if err := mapper.InstallPackages(c.PackageManagers); err != nil {
// logrus.Fatal(err)
// }
// }
// }
func initCommand(cmd *cobra.Command, args []string) {
}