From 03ca69407d42f8d89f9b1d316dddb49c3b9bc9ed Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Tue, 23 Sep 2014 21:16:54 +0200 Subject: [PATCH] Add method to create repository Also disables automatic creation on open --- cmd/khepri/cmd_init.go | 20 +++++++ cmd/khepri/main.go | 25 ++++---- repository.go | 132 ++++++++++++++++++++++++++++++++++------- repository_test.go | 2 +- test/test-backup.sh | 1 + 5 files changed, 145 insertions(+), 35 deletions(-) create mode 100644 cmd/khepri/cmd_init.go diff --git a/cmd/khepri/cmd_init.go b/cmd/khepri/cmd_init.go new file mode 100644 index 000000000..cd64d8dfd --- /dev/null +++ b/cmd/khepri/cmd_init.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "os" + + "github.com/fd0/khepri" +) + +func commandInit(path string) error { + repo, err := khepri.CreateRepository(path) + if err != nil { + fmt.Fprintf(os.Stderr, "creating repository at %s failed: %v\n", path, err) + os.Exit(1) + } + + fmt.Printf("created khepri repository at %s\n", repo.Path()) + + return nil +} diff --git a/cmd/khepri/main.go b/cmd/khepri/main.go index 5219456a8..0a2daadf7 100644 --- a/cmd/khepri/main.go +++ b/cmd/khepri/main.go @@ -4,15 +4,13 @@ import ( "fmt" "log" "os" - "sort" - "strings" "github.com/fd0/khepri" "github.com/jessevdk/go-flags" ) var Opts struct { - Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restor from"` + Repo string `short:"r" long:"repo" description:"Repository directory to backup to/restore from"` } func errx(code int, format string, data ...interface{}) { @@ -50,18 +48,17 @@ func main() { os.Exit(0) } - if len(args) == 0 { - cmds := []string{} - for k := range commands { - cmds = append(cmds, k) - } - sort.Strings(cmds) - fmt.Printf("nothing to do, available commands: [%v]\n", strings.Join(cmds, "|")) - os.Exit(0) - } - cmd := args[0] + if cmd == "init" { + err = commandInit(Opts.Repo) + if err != nil { + errx(1, "error executing command %q: %v", cmd, err) + } + + return + } + f, ok := commands[cmd] if !ok { errx(1, "unknown command: %q\n", cmd) @@ -70,7 +67,7 @@ func main() { repo, err := khepri.NewRepository(Opts.Repo) if err != nil { - errx(1, "unable to create/open repo: %v", err) + errx(1, "unable to open repo: %v", err) } err = f(repo, args[1:]) diff --git a/repository.go b/repository.go index 4309b7e07..61c977b69 100644 --- a/repository.go +++ b/repository.go @@ -1,7 +1,10 @@ package khepri import ( + "crypto/rand" "crypto/sha256" + "encoding/hex" + "encoding/json" "errors" "fmt" "hash" @@ -14,10 +17,11 @@ import ( ) const ( - dirMode = 0700 - blobPath = "blobs" - refPath = "refs" - tempPath = "tmp" + dirMode = 0700 + blobPath = "blobs" + refPath = "refs" + tempPath = "tmp" + configFileName = "config.json" ) var ( @@ -34,10 +38,27 @@ func (n Name) Encode() string { type HashFunc func() hash.Hash type Repository struct { - path string - hash HashFunc + path string + hash HashFunc + config *Config } +type Config struct { + Salt string + N uint + R uint `json:"r"` + P uint `json:"p"` +} + +// TODO: figure out scrypt values on the fly depending on the current +// hardware. +const ( + scrypt_N = 65536 + scrypt_r = 8 + scrypt_p = 1 + scrypt_saltsize = 64 +) + type Type int const ( @@ -67,15 +88,16 @@ func (t Type) String() string { panic(fmt.Sprintf("unknown type %d", t)) } -// NewDirRepository creates a new dir-baked repository at the given path. +// NewRepository opens a dir-baked repository at the given path. func NewRepository(path string) (*Repository, error) { + var err error + d := &Repository{ path: path, hash: sha256.New, } - err := d.create() - + d.config, err = d.read_config() if err != nil { return nil, err } @@ -83,22 +105,92 @@ func NewRepository(path string) (*Repository, error) { return d, nil } -func (r *Repository) create() error { - dirs := []string{ - r.path, - path.Join(r.path, blobPath), - path.Join(r.path, refPath), - path.Join(r.path, tempPath), +func (r *Repository) read_config() (*Config, error) { + // try to open config file + f, err := os.Open(path.Join(r.path, configFileName)) + if err != nil { + return nil, err } - for _, dir := range dirs { - err := os.MkdirAll(dir, dirMode) - if err != nil { - return err + cfg := new(Config) + buf, err := ioutil.ReadAll(f) + if err != nil { + return nil, err + } + + err = json.Unmarshal(buf, cfg) + if err != nil { + return nil, err + } + + return cfg, nil +} + +// CreateRepository creates all the necessary files and directories for the +// Repository. +func CreateRepository(p string) (*Repository, error) { + dirs := []string{ + p, + path.Join(p, blobPath), + path.Join(p, refPath), + path.Join(p, tempPath), + } + + var configfile = path.Join(p, configFileName) + + // test if repository directories or config file already exist + if _, err := os.Stat(configfile); err == nil { + return nil, fmt.Errorf("config file %s already exists", configfile) + } + + for _, d := range dirs[1:] { + if _, err := os.Stat(d); err == nil { + return nil, fmt.Errorf("dir %s already exists", d) } } - return nil + // create initial json configuration + cfg := &Config{ + N: scrypt_N, + R: scrypt_r, + P: scrypt_p, + } + + // generate salt + buf := make([]byte, scrypt_saltsize) + n, err := rand.Read(buf) + if n != scrypt_saltsize || err != nil { + panic("unable to read enough random bytes for salt") + } + cfg.Salt = hex.EncodeToString(buf) + + // create ps for blobs, refs and temp + for _, dir := range dirs { + err := os.MkdirAll(dir, dirMode) + if err != nil { + return nil, err + } + } + + // write config file + f, err := os.Create(configfile) + defer f.Close() + if err != nil { + return nil, err + } + + s, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + _, err = f.Write(s) + if err != nil { + return nil, err + } + + // open repository + return NewRepository(p) } // SetHash changes the hash function used for deriving IDs. Default is SHA256. diff --git a/repository_test.go b/repository_test.go index 27e1a2f6e..ee8337520 100644 --- a/repository_test.go +++ b/repository_test.go @@ -30,7 +30,7 @@ func setupRepo() (*khepri.Repository, error) { return nil, err } - repo, err := khepri.NewRepository(tempdir) + repo, err := khepri.CreateRepository(tempdir) if err != nil { return nil, err } diff --git a/test/test-backup.sh b/test/test-backup.sh index 510f5764e..c41f3670e 100755 --- a/test/test-backup.sh +++ b/test/test-backup.sh @@ -1,6 +1,7 @@ set -e prepare +run khepri init run khepri backup "${BASE}/fake-data" run khepri restore "$(khepri list ref)" "${BASE}/fake-data-restore" dirdiff "${BASE}/fake-data" "${BASE}/fake-data-restore"