diff --git a/.gitignore b/.gitignore index 1923301..875c2ea 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ /coverage.txt /bitcask +/bitcaskd /tmp /dist diff --git a/.goreleaser.yml b/.goreleaser.yml index 720596a..f261aca 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,10 +1,18 @@ builds: - + binary: bitcask main: ./cmd/bitcask flags: -tags "static_build" ldflags: -w -X .Version={{.Version}} -X .Commit={{.Commit}} env: - CGO_ENABLED=0 + - + binary: bitcaskd + main: ./cmd/bitcaskd + flags: -tags "static_build" + ldflags: -w -X .Version={{.Version}} -X .Commit={{.Commit}} + env: + - CGO_ENABLED=0 sign: artifacts: checksum archive: diff --git a/Makefile b/Makefile index e232a10..df3bc41 100644 --- a/Makefile +++ b/Makefile @@ -7,12 +7,17 @@ all: dev dev: build @./bitcask --version + @./bitcaskd --version build: clean generate @go build \ -tags "netgo static_build" -installsuffix netgo \ -ldflags "-w -X $(shell go list)/.Commit=$(COMMIT)" \ ./cmd/bitcask/... + @go build \ + -tags "netgo static_build" -installsuffix netgo \ + -ldflags "-w -X $(shell go list)/.Commit=$(COMMIT)" \ + ./cmd/bitcaskd/... generate: @go generate $(shell go list)/... diff --git a/bitcask.go b/bitcask.go index 17211d2..280ae87 100644 --- a/bitcask.go +++ b/bitcask.go @@ -135,7 +135,7 @@ func (b *Bitcask) setMaxDatafileSize(size int64) error { return nil } -func MaxDatafileSize(size int64) func(*Bitcask) error { +func WithMaxDatafileSize(size int64) func(*Bitcask) error { return func(b *Bitcask) error { return b.setMaxDatafileSize(size) } diff --git a/cmd/bitcaskd/main.go b/cmd/bitcaskd/main.go new file mode 100644 index 0000000..8daa987 --- /dev/null +++ b/cmd/bitcaskd/main.go @@ -0,0 +1,123 @@ +package main + +import ( + "fmt" + "os" + "strings" + + log "github.com/sirupsen/logrus" + flag "github.com/spf13/pflag" + "github.com/tidwall/redcon" + + "github.com/prologic/bitcask" +) + +var ( + bind string + debug bool + version bool + maxDatafileSize int64 +) + +func init() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage: %s [options] \n", os.Args[0]) + flag.PrintDefaults() + } + + flag.BoolVarP(&version, "version", "v", false, "display version information") + flag.BoolVarP(&debug, "debug", "d", false, "enable debug logging") + + flag.StringVarP(&bind, "bind", "b", ":6379", "interface and port to bind to") + + flag.Int64Var(&maxDatafileSize, "max-datafile-size", 1<<20, "maximum datafile size in bytes") +} + +func main() { + flag.Parse() + + if debug { + log.SetLevel(log.DebugLevel) + } else { + log.SetLevel(log.InfoLevel) + } + + if version { + fmt.Printf("bitcaskd version %s", bitcask.FullVersion()) + os.Exit(0) + } + + if len(flag.Args()) < 1 { + flag.PrintDefaults() + os.Exit(1) + } + + path := flag.Arg(0) + + db, err := bitcask.Open(path, bitcask.WithMaxDatafileSize(maxDatafileSize)) + if err != nil { + log.WithError(err).WithField("path", path).Error("error opening database") + os.Exit(1) + } + + log.WithField("bind", bind).WithField("path", path).Infof("starting bitcaskd v%s", bitcask.FullVersion()) + + err = redcon.ListenAndServe(bind, + func(conn redcon.Conn, cmd redcon.Command) { + switch strings.ToLower(string(cmd.Args[0])) { + case "ping": + conn.WriteString("PONG") + case "quit": + conn.WriteString("OK") + conn.Close() + case "set": + if len(cmd.Args) != 3 { + conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") + return + } + key := string(cmd.Args[0]) + value := cmd.Args[1] + err = db.Put(key, value) + if err != nil { + conn.WriteString(fmt.Sprintf("ERR: %s", err)) + } else { + conn.WriteString("OK") + } + case "get": + if len(cmd.Args) != 2 { + conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") + return + } + key := string(cmd.Args[0]) + value, err := db.Get(key) + if err != nil { + conn.WriteNull() + } else { + conn.WriteBulk(value) + } + case "del": + if len(cmd.Args) != 2 { + conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") + return + } + key := string(cmd.Args[0]) + err := db.Delete(key) + if err != nil { + conn.WriteInt(0) + } else { + conn.WriteInt(1) + } + default: + conn.WriteError("ERR unknown command '" + string(cmd.Args[0]) + "'") + } + }, + func(conn redcon.Conn) bool { + return true + }, + func(conn redcon.Conn, err error) { + }, + ) + if err != nil { + log.WithError(err).Fatal("oops") + } +} diff --git a/go.mod b/go.mod index 1360dc1..f57542d 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,9 @@ require ( github.com/prometheus/client_golang v0.9.2 // indirect github.com/sirupsen/logrus v1.3.0 github.com/spf13/cobra v0.0.3 + github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.3.1 github.com/stretchr/testify v1.3.0 + github.com/tidwall/redcon v0.9.0 gopkg.in/vmihailenco/msgpack.v2 v2.9.1 ) diff --git a/go.sum b/go.sum index e4127ac..a373297 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/tidwall/redcon v0.9.0 h1:tiT9DLAoohsdNaFg9Si5dRsv9+FjvZYnhMOEtSFwBqA= +github.com/tidwall/redcon v0.9.0/go.mod h1:bdYBm4rlcWpst2XMwKVzWDF9CoUxEbUmM7CQrKeOZas= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=