diff --git a/Makefile b/Makefile index 5b14a63..28bc59b 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,23 @@ _TESTPKGS := $(ROOT) $(foreach p,$(SUBPKGS),$(ROOT)/$(p)) ARTIFACTDIR := artifacts +ifndef ZREPL_VERSION + ZREPL_VERSION := $(shell git describe --dirty 2>/dev/null || echo "ZREPL_BUILD_INVALID_VERSION" ) + ifeq ($(ZREPL_VERSION),ZREPL_BUILD_INVALID_VERSION) # can't use .SHELLSTATUS because Debian Stretch is still on gmake 4.1 + $(error cannot infer variable ZREPL_VERSION using git and variable is not overriden by make invocation) + endif +endif +GO_LDFLAGS := "-X github.com/zrepl/zrepl/cmd.zreplVersion=$(ZREPL_VERSION)" + +GO_BUILD := go build -ldflags $(GO_LDFLAGS) + generate: #not part of the build, must do that manually @for pkg in $(_TESTPKGS); do\ go generate "$$pkg" || exit 1; \ done; build: - go build -o $(ARTIFACTDIR)/zrepl + $(GO_BUILD) -o "$(ARTIFACTDIR)/zrepl" test: @for pkg in $(_TESTPKGS); do \ @@ -54,10 +64,11 @@ docs-clean: BUILDDIR=../artifacts/docs release-bins: $(ARTIFACTDIR) vet test - GOOS=linux GOARCH=amd64 go build -o "$(ARTIFACTDIR)/zrepl-linux-amd64" - GOOS=freebsd GOARCH=amd64 go build -o "$(ARTIFACTDIR)/zrepl-freebsd-amd64" + GOOS=linux GOARCH=amd64 $(GO_BUILD) -o "$(ARTIFACTDIR)/zrepl-linux-amd64" + GOOS=freebsd GOARCH=amd64 $(GO_BUILD) -o "$(ARTIFACTDIR)/zrepl-freebsd-amd64" release: release-bins docs + clean: docs-clean rm -rf "$(ARTIFACTDIR)" diff --git a/README.md b/README.md index f80949e..5c73709 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,16 @@ zrepl is a ZFS filesystem backup & replication solution written in Go. The above does not apply if you already implemented everything. Check out the *Coding Workflow* section below for details. +## Package Maintainer Information + +* Follow the steps in `docs/installation.rst -> Compiling from Source` and read the Makefile / shell scripts used in this process. +* Make sure your distro is compatible with the paths in `docs/installation.rst`. +* Ship a default config that adheres to your distro's `hier` and logging system. +* Ship a service manager file and _please_ try to upstream it to this repository. +* Use `make release ZREPL_VERSION='mydistro-1.2.3_1'` + * Your distro's name and any versioning supplemental to zrepl's (e.g. package revesion) should be in this string +* Make sure you are informed about new zrepl versions, e.g. by subscribing to GitHub's release RSS feed. + ## Developer Documentation First, use `./lazy.sh devesetup` to install build dependencies and read `docs/installation.rst -> Compiling from Source`. diff --git a/cmd/config_job_control.go b/cmd/config_job_control.go index 0c636f0..7328cfb 100644 --- a/cmd/config_job_control.go +++ b/cmd/config_job_control.go @@ -1,8 +1,11 @@ package cmd import ( + "bytes" "context" + "encoding/json" "github.com/pkg/errors" + "io" "net" "net/http" "net/http/pprof" @@ -29,8 +32,20 @@ func (j *ControlJob) JobName() string { return j.Name } +func (j *ControlJob) EndpointVersion(w http.ResponseWriter, r *http.Request) { + var buf bytes.Buffer + err := json.NewEncoder(&buf).Encode(NewZreplVersionInformation()) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + io.WriteString(w, err.Error()) + } else { + io.Copy(w, &buf) + } +} + const ( ControlJobEndpointProfile string = "/debug/pprof/profile" + ControlJobEndpointVersion string = "/version" ) func (j *ControlJob) JobStart(ctx context.Context) { @@ -46,6 +61,7 @@ func (j *ControlJob) JobStart(ctx context.Context) { mux := http.NewServeMux() mux.Handle(ControlJobEndpointProfile, requestLogger{log, pprof.Profile}) + mux.Handle(ControlJobEndpointVersion, requestLogger{log, j.EndpointVersion}) server := http.Server{Handler: mux} outer: diff --git a/cmd/control.go b/cmd/control.go index b95379c..b0e0d92 100644 --- a/cmd/control.go +++ b/cmd/control.go @@ -1,7 +1,9 @@ package cmd import ( + "bytes" "context" + "encoding/json" "fmt" "github.com/spf13/cobra" "io" @@ -26,10 +28,33 @@ var pprofCmdArgs struct { seconds int64 } +var controlVersionCmd = &cobra.Command{ + Use: "version", + Short: "print version of running zrepl daemon", + Run: doControLVersionCmd, +} + func init() { RootCmd.AddCommand(controlCmd) controlCmd.AddCommand(pprofCmd) pprofCmd.Flags().Int64Var(&pprofCmdArgs.seconds, "seconds", 30, "seconds to profile") + controlCmd.AddCommand(controlVersionCmd) +} + +func controlHttpClient() (client http.Client, err error) { + + conf, err := ParseConfig(rootArgs.configFile) + if err != nil { + return http.Client{}, err + } + + return http.Client{ + Transport: &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", conf.Global.Control.Sockpath) + }, + }, + }, nil } func doControlPProf(cmd *cobra.Command, args []string) { @@ -41,12 +66,6 @@ func doControlPProf(cmd *cobra.Command, args []string) { os.Exit(1) } - conf, err := ParseConfig(rootArgs.configFile) - if err != nil { - log.Printf("error parsing config: %s", err) - die() - } - if cmd.Flags().Arg(0) != "cpu" { log.Printf("only CPU profiles are supported") log.Printf("%s", cmd.UsageString()) @@ -60,6 +79,7 @@ func doControlPProf(cmd *cobra.Command, args []string) { die() } var out io.Writer + var err error if outfn == "-" { out = os.Stdout } else { @@ -71,12 +91,10 @@ func doControlPProf(cmd *cobra.Command, args []string) { } log.Printf("connecting to daemon") - httpc := http.Client{ - Transport: &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", conf.Global.Control.Sockpath) - }, - }, + httpc, err := controlHttpClient() + if err != nil { + log.Printf("error parsing config: %s", err) + die() } log.Printf("profiling...") @@ -98,3 +116,40 @@ func doControlPProf(cmd *cobra.Command, args []string) { log.Printf("finished") } + +func doControLVersionCmd(cmd *cobra.Command, args []string) { + + log := golog.New(os.Stderr, "", 0) + + die := func() { + log.Printf("exiting after error") + os.Exit(1) + } + + httpc, err := controlHttpClient() + if err != nil { + log.Printf("could not connect to daemon: %s", err) + die() + } + + resp, err := httpc.Get("http://unix" + ControlJobEndpointVersion) + if err != nil { + log.Printf("error: %s", err) + die() + } else if resp.StatusCode != http.StatusOK { + var msg bytes.Buffer + io.CopyN(&msg, resp.Body, 4096) + log.Printf("error: %s", msg.String()) + die() + } + + var info ZreplVersionInformation + err = json.NewDecoder(resp.Body).Decode(&info) + if err != nil { + log.Printf("error unmarshaling response: %s", err) + die() + } + + fmt.Println(info.String()) + +} diff --git a/cmd/daemon.go b/cmd/daemon.go index 572f53a..c91e689 100644 --- a/cmd/daemon.go +++ b/cmd/daemon.go @@ -37,6 +37,7 @@ func doDaemon(cmd *cobra.Command, args []string) { log := logger.NewLogger(conf.Global.logging.Outlets, 1*time.Second) + log.Info(NewZreplVersionInformation().String()) log.Debug("starting daemon") ctx := context.WithValue(context.Background(), contextKeyLog, log) ctx = context.WithValue(ctx, contextKeyLog, log) diff --git a/cmd/main.go b/cmd/main.go index 19719a0..4a5c626 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -20,6 +20,10 @@ import ( // Printf(format string, v ...interface{}) //} +var ( + zreplVersion string // set by build infrastructure +) + type Logger = *logger.Logger var RootCmd = &cobra.Command{ diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..4597d2d --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,42 @@ +package cmd + +import ( + "fmt" + "github.com/spf13/cobra" + "runtime" +) + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "print version of zrepl binary (for running daemon 'zrepl control version' command)", + Run: doVersion, +} + +func init() { + RootCmd.AddCommand(versionCmd) +} + +func doVersion(cmd *cobra.Command, args []string) { + fmt.Println(NewZreplVersionInformation().String()) +} + +type ZreplVersionInformation struct { + Version string + RuntimeGOOS string + RuntimeGOARCH string + RUNTIMECompiler string +} + +func NewZreplVersionInformation() *ZreplVersionInformation { + return &ZreplVersionInformation{ + Version: zreplVersion, + RuntimeGOOS: runtime.GOOS, + RuntimeGOARCH: runtime.GOARCH, + RUNTIMECompiler: runtime.Compiler, + } +} + +func (i *ZreplVersionInformation) String() string { + return fmt.Sprintf("zrepl version=%s GOOS=%s GOARCH=%s Compiler=%s", + i.Version, i.RuntimeGOOS, i.RuntimeGOARCH, i.RUNTIMECompiler) +}