Add method to create repository
Also disables automatic creation on open
This commit is contained in:
parent
f0287b2c9a
commit
03ca69407d
5 changed files with 145 additions and 35 deletions
20
cmd/khepri/cmd_init.go
Normal file
20
cmd/khepri/cmd_init.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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:])
|
||||
|
|
132
repository.go
132
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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue