Compare commits

..

2 Commits

Author SHA1 Message Date
Christian Schwarz bdd0c9a199 build: use go 1.19 for testing & release builds
New docker image since the old one was deprecated, according
to https://discuss.circleci.com/t/go-lang-docker-image-circleci-golang-1-19-is-missing/44961
2022-10-24 22:37:35 +02:00
Christian Schwarz 498036194d build: update golangci-lint
The previous commits were done in response to updating to
the version that we now pin in this commit.
We do the update after the fixes so that each commit builds.
2022-10-24 22:22:41 +02:00
423 changed files with 5217 additions and 6159 deletions
+215 -136
View File
@@ -1,8 +1,5 @@
version: 2.1 version: 2.1
orbs:
# NB: this is not the Go version, but the Orb version
# https://circleci.com/developer/orbs/orb/circleci/go#usage-go-modules-cache
go: circleci/go@1.11.0
commands: commands:
setup-home-local-bin: setup-home-local-bin:
steps: steps:
@@ -15,64 +12,132 @@ commands:
echo "$line" >> $BASH_ENV echo "$line" >> $BASH_ENV
fi fi
invoke-lazy-sh:
parameters:
subcommand:
type: string
steps:
- run:
environment:
TERM: xterm
command: ./lazy.sh <<parameters.subcommand>>
apt-update-and-install-common-deps: apt-update-and-install-common-deps:
steps: steps:
- run: sudo apt-get update - run: sudo apt update && sudo apt install gawk make
- run: sudo apt-get install -y gawk make
# CircleCI doesn't update its cimg/go images.
# So, need to update manually to get up-to-date trust chains.
# The need for this was required for cimg/go:1.12, but let's future proof this here and now.
- run: sudo apt-get install -y git ca-certificates
restore-cache-gomod:
steps:
- restore_cache:
key: go-mod-v4-{{ checksum "go.sum" }}
save-cache-gomod:
steps:
- save_cache:
key: go-mod-v4-{{ checksum "go.sum" }}
paths:
- "/go/pkg/mod"
install-godep:
steps:
- apt-update-and-install-common-deps
- invoke-lazy-sh:
subcommand: godep
install-docdep: install-docdep:
steps: steps:
- apt-update-and-install-common-deps - apt-update-and-install-common-deps
- run: sudo apt install python3 python3-pip libgirepository1.0-dev - run: sudo apt install python3 python3-pip libgirepository1.0-dev
- run: pip3 install -r docs/requirements.txt - invoke-lazy-sh:
subcommand: docdep
docs-publish-sh: download-and-install-minio-client:
parameters:
push:
type: boolean
steps: steps:
- checkout - setup-home-local-bin
- restore_cache:
key: minio-client-v2
- run: - run:
shell: /bin/bash -eo pipefail
command: | command: |
git config --global user.email "zreplbot@cschwarz.com" if which mc; then exit 0; fi
git config --global user.name "zrepl-github-io-ci" sudo curl -sSL https://dl.min.io/client/mc/release/linux-amd64/archive/mc.RELEASE.2020-08-20T00-23-01Z \
-o "$HOME/.local/bin/mc"
sudo chmod +x "$HOME/.local/bin/mc"
- save_cache:
key: minio-client-v2
paths:
- "$HOME/.local/bin/mc"
# if we're pushing, we need to add the deploy key upload-minio:
# which is stored as "Additional SSH Keys" in the CircleCI project settings. parameters:
# We can't use the CircleCI-manage deploy key because we're pushing src:
# to a different repo than the one we're building. type: string
- when: dst:
condition: << parameters.push >> type: string
steps: steps:
# https://circleci.com/docs/2.0/add-ssh-key/#adding-multiple-keys-with-blank-hostnames - run:
- run: ssh-add -D shell: /bin/bash -eo pipefail
# the default circleci ssh config only additional ssh keys for Host !github.com when: always
- run: command: |
command: | if [ -n "$CIRCLE_PR_NUMBER" ]; then # CIRCLE_PR_NUMBER is guaranteed to be only present in forked PRs (external)
cat > ~/.ssh/config \<<EOF echo "Forked PR detected. Sry, can't trust you with credentials to external artifact store, use CircleCI's instead."
Host * exit 0
IdentityFile /home/circleci/.ssh/id_rsa_458e62c517f6c480e40452126ce47421 fi
EOF set -u # from now on
- add_ssh_keys:
fingerprints:
# deploy key for zrepl.github.io
- "45:8e:62:c5:17:f6:c4:80:e4:04:52:12:6c:e4:74:21"
# caller must install-docdep mc config host add --api s3v4 zrepl-minio https://minio.cschwarz.com ${MINIO_ACCESS_KEY} ${MINIO_SECRET_KEY}
- when:
condition: << parameters.push >> # keep in sync with set-github-minio-status
steps: jobprefix=zrepl-ci-artifacts/${CIRCLE_SHA1}-pipeline-<<pipeline.number>>/${CIRCLE_JOB}
- run: bash -x docs/publish.sh -c -a -P
- when: # Upload artifacts
condition: mkdir -p ./artifacts
not: << parameters.push >> mc cp -r <<parameters.src>> "zrepl-minio/$jobprefix/<<parameters.dst>>"
steps:
- run: bash -x docs/publish.sh -c -a set-github-minio-status:
parameters:
context:
type: string
description:
type: string
minio-dst:
type: string
steps:
- run:
shell: /bin/bash -eo pipefail
command: |
if [ -n "$CIRCLE_PR_NUMBER" ]; then # CIRCLE_PR_NUMBER is guaranteed to be only present in forked PRs (external)
echo "Forked PR detected. Sry, can't trust you with credentials to external artifact store, use CircleCI's instead."
exit 0
fi
set -u # from now on
# keep in sync with with upload-minio command
jobprefix=zrepl-ci-artifacts/${CIRCLE_SHA1}-pipeline-<<pipeline.number>>/${CIRCLE_JOB}
# Push Artifact Link to GitHub
REPO="zrepl/zrepl"
COMMIT="${CIRCLE_SHA1}"
JOB_NAME="${CIRCLE_JOB}"
CONTEXT="<<parameters.context>>"
DESCRIPTION="<<parameters.description>>"
TARGETURL=https://minio.cschwarz.com/minio/"$jobprefix"/"<<parameters.minio-dst>>"
curl "https://api.github.com/repos/$REPO/statuses/$COMMIT" \
-H "Content-Type: application/json" \
-H "Authorization: token $GITHUB_COMMIT_STATUS_TOKEN" \
-X POST \
-d '{"context":"'"$CONTEXT"'", "state": "success", "description":"'"$DESCRIPTION"'", "target_url":"'"$TARGETURL"'"}'
trigger-pipeline:
parameters:
body_no_shell_subst:
type: string
steps:
- run: |
curl -X POST https://circleci.com/api/v2/project/github/zrepl/zrepl/pipeline \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Circle-Token: $ZREPL_BOT_CIRCLE_TOKEN" \
--data '<<parameters.body_no_shell_subst>>'
parameters: parameters:
do_ci: do_ci:
@@ -83,6 +148,10 @@ parameters:
type: boolean type: boolean
default: false default: false
release_docker_baseimage_tag:
type: string
default: "1.19"
workflows: workflows:
version: 2 version: 2
@@ -90,20 +159,20 @@ workflows:
when: << pipeline.parameters.do_ci >> when: << pipeline.parameters.do_ci >>
jobs: jobs:
- quickcheck-docs - quickcheck-docs
- quickcheck-go: - quickcheck-go: &quickcheck-go-smoketest
name: quickcheck-go-amd64-linux-1.23.1 name: quickcheck-go-amd64-linux-1.19
goversion: &latest-go-release "1.23.1" goversion: &latest-go-release "1.19"
goos: linux goos: linux
goarch: amd64 goarch: amd64
- test-go: - test-go-on-latest-go-release:
goversion: *latest-go-release goversion: *latest-go-release
- quickcheck-go: - quickcheck-go:
requires: requires:
- quickcheck-go-amd64-linux-1.23.1 #quickcheck-go-smoketest.name - quickcheck-go-amd64-linux-1.19 #quickcheck-go-smoketest.name
matrix: matrix: &quickcheck-go-matrix
alias: quickcheck-go-matrix alias: quickcheck-go-matrix
parameters: parameters:
goversion: [*latest-go-release, "1.22.7"] goversion: [*latest-go-release, "1.12"]
goos: ["linux", "freebsd"] goos: ["linux", "freebsd"]
goarch: ["amd64", "arm64"] goarch: ["amd64", "arm64"]
exclude: exclude:
@@ -111,15 +180,10 @@ workflows:
- goversion: *latest-go-release - goversion: *latest-go-release
goos: linux goos: linux
goarch: amd64 goarch: amd64
- platformtest: # not supported by Go 1.12
matrix: - goversion: "1.12"
parameters: goos: freebsd
goversion: [*latest-go-release] goarch: arm64
goos: ["linux"]
goarch: ["amd64"]
requires:
- test-go
- quickcheck-go-<< matrix.goarch >>-<< matrix.goos >>-<< matrix.goversion >>
release: release:
when: << pipeline.parameters.do_release >> when: << pipeline.parameters.do_release >>
@@ -137,7 +201,20 @@ workflows:
- release-deb - release-deb
- release-rpm - release-rpm
publish-zrepl.github.io: periodic:
triggers:
- schedule:
cron: "00 17 * * *"
filters:
branches:
only:
- master
- stable
- problame/circleci-build
jobs:
- periodic-full-pipeline-run
zrepl.github.io:
jobs: jobs:
- publish-zrepl-github-io: - publish-zrepl-github-io:
filters: filters:
@@ -148,15 +225,20 @@ workflows:
jobs: jobs:
quickcheck-docs: quickcheck-docs:
docker: docker:
- image: cimg/base:2024.09 - image: cimg/base:2020.08
steps: steps:
- checkout - checkout
- install-docdep - install-docdep
# do the current docs build
- run: make docs - run: make docs
# does the publish.sh script still work?
- docs-publish-sh: - download-and-install-minio-client
push: false - upload-minio:
src: artifacts
dst: ""
- set-github-minio-status:
context: artifacts/${CIRCLE_JOB}
description: artifacts of CI job ${CIRCLE_JOB}
minio-dst: ""
quickcheck-go: quickcheck-go:
parameters: parameters:
@@ -167,98 +249,62 @@ jobs:
goarch: goarch:
type: string type: string
docker: docker:
# any toolchain >= 1.22 will auto-download GOTOOLCHAIN - image: cimg/go:<<parameters.goversion>>
- image: &cimg_with_modern_go cimg/go:1.22
environment: environment:
GOOS: <<parameters.goos>> GOOS: <<parameters.goos>>
GOARCH: <<parameters.goarch>> GOARCH: <<parameters.goarch>>
GOTOOLCHAIN: "go<<parameters.goversion>>"
steps: steps:
- checkout - checkout
- go/load-cache: - restore-cache-gomod
key: quickcheck-<<parameters.goversion>>
- run: make build/install
- run: go mod download - run: go mod download
- run: cd build && go mod download - run: cd build && go mod download
- go/save-cache: - save-cache-gomod
key: quickcheck-<<parameters.goversion>>
# ensure all code has been generated - install-godep
- run: make generate - run: make formatcheck
- run: | - run: make generate-platform-test-list
if output=$(git status --porcelain) && [ -z "$output" ]; then
echo "Working directory clean"
else
echo "Uncommitted changes"
echo ""
echo "$output"
exit 1
fi
# other checks
- run: make zrepl-bin test-platform-bin - run: make zrepl-bin test-platform-bin
- run: make vet - run: make vet
- run: make lint - run: make lint
- download-and-install-minio-client
- run: rm -f artifacts/generate-platform-test-list
- store_artifacts: - store_artifacts:
path: artifacts path: artifacts
- persist_to_workspace: - upload-minio:
root: . src: artifacts
paths: [.] dst: ""
- set-github-minio-status:
context: artifacts/${CIRCLE_JOB}
description: artifacts of CI job ${CIRCLE_JOB}
minio-dst: ""
platformtest: test-go-on-latest-go-release:
parameters:
goversion:
type: string
goos:
type: string
goarch:
type: string
machine:
image: ubuntu-2204:current
resource_class: medium
environment:
GOOS: <<parameters.goos>>
GOARCH: <<parameters.goarch>>
steps:
- attach_workspace:
at: .
- run: sudo apt-get update
- run: sudo apt-get install -y zfsutils-linux
- run: sudo zfs version
- run: sudo make test-platform GOOS="$GOOS" GOARCH="$GOARCH"
test-go:
parameters: parameters:
goversion: goversion:
type: string type: string
docker: docker:
- image: *cimg_with_modern_go - image: cimg/go:<<parameters.goversion>>
environment:
GOTOOLCHAIN: "go<<parameters.goversion>>"
steps: steps:
- checkout - checkout
- go/load-cache: - restore-cache-gomod
key: make-test-go
- run: make test-go - run: make test-go
- go/save-cache: # don't save-cache-gomod here, test-go doesn't pull all the dependencies
key: make-test-go
release-build: release-build:
machine: machine:
image: &release-vm-image "ubuntu-2404:current" image: ubuntu-2004:202201-02
resource_class: large
steps: steps:
- checkout - checkout
- run: make release-docker - run: make release-docker RELEASE_DOCKER_BASEIMAGE_TAG=<<pipeline.parameters.release_docker_baseimage_tag>>
- persist_to_workspace: - persist_to_workspace:
root: . root: .
paths: [.] paths: [.]
release-deb: release-deb:
machine: machine:
image: *release-vm-image image: ubuntu-2004:202201-02
steps: steps:
- attach_workspace: - attach_workspace:
at: . at: .
@@ -270,7 +316,7 @@ jobs:
release-rpm: release-rpm:
machine: machine:
image: *release-vm-image image: ubuntu-2004:202201-02
steps: steps:
- attach_workspace: - attach_workspace:
at: . at: .
@@ -282,19 +328,52 @@ jobs:
release-upload: release-upload:
docker: docker:
- image: cimg/base:2024.09 - image: cimg/base:2020.08
steps: steps:
- attach_workspace: - attach_workspace:
at: . at: .
- run: make wrapup-and-checksum
- store_artifacts: - store_artifacts:
path: artifacts/release path: artifacts
- download-and-install-minio-client
- upload-minio:
src: artifacts
dst: ""
- set-github-minio-status:
context: artifacts/release
description: CI-generated release artifacts
minio-dst: ""
periodic-full-pipeline-run:
docker:
- image: cimg/base:2020.08
steps:
- trigger-pipeline:
body_no_shell_subst: '{"branch":"<<pipeline.git.branch>>", "parameters": { "do_ci": true, "do_release": true }}'
publish-zrepl-github-io: publish-zrepl-github-io:
docker: docker:
- image: cimg/base:2024.09 - image: cimg/python:3.7
steps: steps:
- checkout - checkout
- install-docdep - invoke-lazy-sh:
- docs-publish-sh: subcommand: docdep
push: true - run:
command: |
git config --global user.email "zreplbot@cschwarz.com"
git config --global user.name "zrepl-github-io-ci"
# https://circleci.com/docs/2.0/add-ssh-key/#adding-multiple-keys-with-blank-hostnames
- run: ssh-add -D
# the default circleci ssh config only additional ssh keys for Host !github.com
- run:
command: |
cat > ~/.ssh/config \<<EOF
Host *
IdentityFile /home/circleci/.ssh/id_rsa_458e62c517f6c480e40452126ce47421
EOF
- add_ssh_keys:
fingerprints:
# deploy key for zrepl.github.io
- "45:8e:62:c5:17:f6:c4:80:e4:04:52:12:6c:e4:74:21"
- run: bash -x docs/publish.sh -c -a
-84
View File
@@ -1,84 +0,0 @@
import argparse
from pathlib import Path
import re
import requests
import time
import os
import argparse
from pathlib import Path
circle_token = os.environ.get('CIRCLE_TOKEN')
if not circle_token:
raise ValueError('CIRCLE_TOKEN environment variable not set')
parser = argparse.ArgumentParser(description='Download artifacts from CircleCI')
parser.add_argument('build_num', type=str, help='Build number')
parser.add_argument('dst', type=Path, help='Destination directory')
parser.add_argument('--prefix', type=str, default='', help='Filter for prefix')
parser.add_argument('--match', type=str, default='.*', help='Only include paths matching the given regex')
args = parser.parse_args()
res = requests.get(
f"https://circleci.com/api/v1.1/project/github/zrepl/zrepl/{args.build_num}/artifacts",
headers={
"Circle-Token": circle_token,
},
)
res.raise_for_status()
# https://circleci.com/docs/api/v1/index.html#artifacts-of-a-job
# [ {
# "path" : "raw-test-output/go-test-report.xml",
# "pretty_path" : "raw-test-output/go-test-report.xml",
# "node_index" : 0,
# "url" : "https://24-88881093-gh.circle-artifacts.com/0/raw-test-output/go-test-report.xml"
# }, {
# "path" : "raw-test-output/go-test.out",
# "pretty_path" : "raw-test-output/go-test.out",
# "node_index" : 0,
# "url" : "https://24-88881093-gh.circle-artifacts.com/0/raw-test-output/go-test.out"
# } ]
res = res.json()
for artifact in res:
if not artifact["pretty_path"].startswith(args.prefix):
continue
if not re.match(args.match, artifact["pretty_path"]):
continue
stripped = artifact["pretty_path"][len(args.prefix):]
print(f"Downloading {artifact['pretty_path']} to {args.dst / stripped}")
artifact_rel = Path(stripped)
artifact_dst = args.dst / artifact_rel
artifact_dst.parent.mkdir(parents=True, exist_ok=True)
res = requests.get(
artifact["url"],
headers={
"Circle-Token": circle_token,
},
stream=True,
)
res.raise_for_status()
total_size = int(res.headers.get("Content-Length", 0))
block_size = 128 * 1024
with open(artifact_dst, "wb") as f:
progress = 0
start_time = time.time()
for chunk in res.iter_content(chunk_size=block_size):
f.write(chunk)
progress += len(chunk)
percent = progress / total_size * 100
elapsed_time = time.time() - start_time
if elapsed_time >= 5:
print(f"Downloaded {progress}/{total_size} bytes ({percent:.2f}%)", end="\r")
start_time = time.time()
print(f"Downloaded {progress}/{total_size} bytes ({percent:.2f}%)")
print("Download complete!")
print("All files downloaded")
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
COMMIT="$1"
GO_VERSION="$2"
curl -v -X POST https://api.github.com/repos/zrepl/debian-binary-packaging/dispatches \
-H 'Accept: application/vnd.github.v3+json' \
-H "Authorization: token $GITHUB_ACCESS_TOKEN" \
--data '{"event_type": "push", "client_payload": { "zrepl_main_repo_commit": "'"$COMMIT"'", "go_version": "'"$GO_VERSION"'" }}'
-9
View File
@@ -1,9 +0,0 @@
version: 2
updates:
# Docs use Python only for static site generation.
- package-ecosystem: "pip"
directory: /docs
schedule:
interval: "weekly"
ignore:
- dependency-name: "*"
-1
View File
@@ -32,4 +32,3 @@ _testmain.go
*.exe *.exe
*.test *.test
*.prof *.prof
.venv/
-8
View File
@@ -1,14 +1,6 @@
linters: linters:
enable: enable:
- goimports - goimports
- revive
linters-settings:
goimports:
local-prefixes: github.com/zrepl/zrepl
revive:
rules:
- name: time-equal
issues: issues:
exclude-rules: exclude-rules:
+79 -125
View File
@@ -1,5 +1,5 @@
.PHONY: generate build test vet cover release docs docs-clean clean format lint platformtest .PHONY: generate build test vet cover release docs docs-clean clean format lint platformtest
.PHONY: release release-noarch .PHONY: release bins-all release-noarch
.DEFAULT_GOAL := zrepl-bin .DEFAULT_GOAL := zrepl-bin
ARTIFACTDIR := artifacts ARTIFACTDIR := artifacts
@@ -14,25 +14,22 @@ ifndef _ZREPL_VERSION
endif endif
endif endif
ZREPL_PACKAGE_RELEASE := 1
GO := go GO := go
GOOS ?= $(shell bash -c 'source <($(GO) env) && echo "$$GOOS"') GOOS ?= $(shell bash -c 'source <($(GO) env) && echo "$$GOOS"')
GOARCH ?= $(shell bash -c 'source <($(GO) env) && echo "$$GOARCH"') GOARCH ?= $(shell bash -c 'source <($(GO) env) && echo "$$GOARCH"')
GOARM ?= $(shell bash -c 'source <($(GO) env) && echo "$$GOARM"') GOARM ?= $(shell bash -c 'source <($(GO) env) && echo "$$GOARM"')
GOHOSTOS ?= $(shell bash -c 'source <($(GO) env) && echo "$$GOHOSTOS"') GOHOSTOS ?= $(shell bash -c 'source <($(GO) env) && echo "$$GOHOSTOS"')
GOHOSTARCH ?= $(shell bash -c 'source <($(GO) env) && echo "$$GOHOSTARCH"') GOHOSTARCH ?= $(shell bash -c 'source <($(GO) env) && echo "$$GOHOSTARCH"')
GO_ENV_VARS := CGO_ENABLED=0 GO_ENV_VARS := GO111MODULE=on
GO_LDFLAGS := "-X github.com/zrepl/zrepl/version.zreplVersion=$(_ZREPL_VERSION)" GO_LDFLAGS := "-X github.com/zrepl/zrepl/version.zreplVersion=$(_ZREPL_VERSION)"
GO_MOD_READONLY := -mod=readonly GO_MOD_READONLY := -mod=readonly
GO_EXTRA_BUILDFLAGS := GO_EXTRA_BUILDFLAGS :=
GO_BUILDFLAGS := $(GO_MOD_READONLY) $(GO_EXTRA_BUILDFLAGS) GO_BUILDFLAGS := $(GO_MOD_READONLY) $(GO_EXTRA_BUILDFLAGS)
GO_BUILD := $(GO_ENV_VARS) $(GO) build $(GO_BUILDFLAGS) -ldflags $(GO_LDFLAGS) GO_BUILD := $(GO_ENV_VARS) $(GO) build $(GO_BUILDFLAGS) -ldflags $(GO_LDFLAGS)
GOCOVMERGE := $(GO) tool gocovmerge GOLANGCI_LINT := golangci-lint
RELEASE_GOVERSION ?= go1.24.0 GOCOVMERGE := gocovmerge
STRIPPED_GOVERSION := $(subst go,,$(RELEASE_GOVERSION)) RELEASE_DOCKER_BASEIMAGE_TAG ?= 1.19
RELEASE_DOCKER_BASEIMAGE ?= golang:$(STRIPPED_GOVERSION) RELEASE_DOCKER_BASEIMAGE ?= golang:$(RELEASE_DOCKER_BASEIMAGE_TAG)
RELEASE_DOCKER_CACHEMOUNT :=
ifneq ($(GOARM),) ifneq ($(GOARM),)
ZREPL_TARGET_TUPLE := $(GOOS)-$(GOARCH)v$(GOARM) ZREPL_TARGET_TUPLE := $(GOOS)-$(GOARCH)v$(GOARM)
@@ -40,46 +37,34 @@ else
ZREPL_TARGET_TUPLE := $(GOOS)-$(GOARCH) ZREPL_TARGET_TUPLE := $(GOOS)-$(GOARCH)
endif endif
.PHONY: printvars
printvars:
@echo GOOS=$(GOOS)
@echo GOARCH=$(GOARCH)
@echo GOARM=$(GOARM)
ifneq ($(RELEASE_DOCKER_CACHEMOUNT),)
_RELEASE_DOCKER_CACHEMOUNT := -v $(RELEASE_DOCKER_CACHEMOUNT)/mod:/go/pkg/mod -v $(RELEASE_DOCKER_CACHEMOUNT)/xdg-cache:/root/.cache/go-build
.PHONY: release-docker-mkcachemount
release-docker-mkcachemount:
mkdir -p $(RELEASE_DOCKER_CACHEMOUNT)
mkdir -p $(RELEASE_DOCKER_CACHEMOUNT)/mod
mkdir -p $(RELEASE_DOCKER_CACHEMOUNT)/xdg-cache
else
_RELEASE_DOCKER_CACHEMOUNT :=
.PHONY: release-docker-mkcachemount
release-docker-mkcachemount:
# nothing to do
endif
##################### PRODUCING A RELEASE ############# ##################### PRODUCING A RELEASE #############
.PHONY: release wrapup-and-checksum check-git-clean sign clean ensure-release-toolchain .PHONY: release wrapup-and-checksum check-git-clean sign clean
ensure-release-toolchain: release: clean
# ensure the toolchain is actually the one we expect # no cross-platform support for target test
test $(RELEASE_GOVERSION) = "$$($(GO_ENV_VARS) $(GO) env GOVERSION)" $(MAKE) test-go
$(MAKE) bins-all
release: ensure-release-toolchain
$(MAKE) _run_make_foreach_target_tuple RUN_MAKE_FOREACH_TARGET_TUPLE_ARG="vet"
$(MAKE) _run_make_foreach_target_tuple RUN_MAKE_FOREACH_TARGET_TUPLE_ARG="lint"
$(MAKE) _run_make_foreach_target_tuple RUN_MAKE_FOREACH_TARGET_TUPLE_ARG="zrepl-bin"
$(MAKE) _run_make_foreach_target_tuple RUN_MAKE_FOREACH_TARGET_TUPLE_ARG="test-platform-bin"
$(MAKE) noarch $(MAKE) noarch
$(MAKE) wrapup-and-checksum
$(MAKE) check-git-clean
ifeq (SIGN, 1)
$(MAKE) sign
endif
@echo "ZREPL RELEASE ARTIFACTS AVAILABLE IN artifacts/release"
release-docker: $(ARTIFACTDIR) release-docker-mkcachemount release-docker: $(ARTIFACTDIR)
sed 's/FROM.*!SUBSTITUTED_BY_MAKEFILE/FROM $(RELEASE_DOCKER_BASEIMAGE)/' build/build.Dockerfile > $(ARTIFACTDIR)/build.Dockerfile sed 's/FROM.*!SUBSTITUTED_BY_MAKEFILE/FROM $(RELEASE_DOCKER_BASEIMAGE)/' build.Dockerfile > artifacts/release-docker.Dockerfile
docker build -t zrepl_release --pull -f $(ARTIFACTDIR)/build.Dockerfile . docker build -t zrepl_release --pull -f artifacts/release-docker.Dockerfile .
docker run --rm -i \ docker run --rm -i -v $(CURDIR):/src -u $$(id -u):$$(id -g) \
$(_RELEASE_DOCKER_CACHEMOUNT) \
-v $(CURDIR):/src -u $$(id -u):$$(id -g) \
zrepl_release \ zrepl_release \
make release \ make release GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM)
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \
ZREPL_VERSION=$(ZREPL_VERSION) ZREPL_PACKAGE_RELEASE=$(ZREPL_PACKAGE_RELEASE) \
RELEASE_GOVERSION=$(RELEASE_GOVERSION)
debs-docker: debs-docker:
$(MAKE) _debs_or_rpms_docker _DEB_OR_RPM=deb $(MAKE) _debs_or_rpms_docker _DEB_OR_RPM=deb
@@ -96,14 +81,9 @@ rpm: $(ARTIFACTDIR) # artifacts/_zrepl.zsh_completion artifacts/bash_completion
$(eval _ZREPL_RPM_TOPDIR_ABS := $(CURDIR)/$(ARTIFACTDIR)/rpmbuild) $(eval _ZREPL_RPM_TOPDIR_ABS := $(CURDIR)/$(ARTIFACTDIR)/rpmbuild)
rm -rf "$(_ZREPL_RPM_TOPDIR_ABS)" rm -rf "$(_ZREPL_RPM_TOPDIR_ABS)"
mkdir "$(_ZREPL_RPM_TOPDIR_ABS)" mkdir "$(_ZREPL_RPM_TOPDIR_ABS)"
for d in BUILD BUILDROOT RPMS SOURCES SPECS SRPMS; do \ mkdir -p "$(_ZREPL_RPM_TOPDIR_ABS)"/{SPECS,RPMS,BUILD,BUILDROOT}
mkdir -p "$(_ZREPL_RPM_TOPDIR_ABS)/$$d"; \ sed "s/^Version:.*/Version: $(_ZREPL_RPM_VERSION)/g" \
done packaging/rpm/zrepl.spec > $(_ZREPL_RPM_TOPDIR_ABS)/SPECS/zrepl.spec
sed \
-e "s/^Version:.*/Version: $(_ZREPL_RPM_VERSION)/g" \
-e "s/^Release:.*/Release: $(ZREPL_PACKAGE_RELEASE)/g" \
packaging/rpm/zrepl.spec \
> $(_ZREPL_RPM_TOPDIR_ABS)/SPECS/zrepl.spec
# see /usr/lib/rpm/platform # see /usr/lib/rpm/platform
ifeq ($(GOARCH),amd64) ifeq ($(GOARCH),amd64)
@@ -130,16 +110,13 @@ rpm-docker:
docker build -t zrepl_rpm_pkg --pull -f packaging/rpm/Dockerfile . docker build -t zrepl_rpm_pkg --pull -f packaging/rpm/Dockerfile .
docker run --rm -i -v $(CURDIR):/build/src -u $$(id -u):$$(id -g) \ docker run --rm -i -v $(CURDIR):/build/src -u $$(id -u):$$(id -g) \
zrepl_rpm_pkg \ zrepl_rpm_pkg \
make rpm \ make rpm GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM)
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \
ZREPL_VERSION=$(ZREPL_VERSION) ZREPL_PACKAGE_RELEASE=$(ZREPL_PACKAGE_RELEASE)
deb: $(ARTIFACTDIR) # artifacts/_zrepl.zsh_completion artifacts/bash_completion docs zrepl-bin deb: $(ARTIFACTDIR) # artifacts/_zrepl.zsh_completion artifacts/bash_completion docs zrepl-bin
cp packaging/deb/debian/changelog.template packaging/deb/debian/changelog cp packaging/deb/debian/changelog.template packaging/deb/debian/changelog
sed -i 's/DATE_DASH_R_OUTPUT/$(shell date -R)/' packaging/deb/debian/changelog sed -i 's/DATE_DASH_R_OUTPUT/$(shell date -R)/' packaging/deb/debian/changelog
VERSION="$(subst -,.,$(_ZREPL_VERSION))-$(ZREPL_PACKAGE_RELEASE)"; \ VERSION="$(subst -,.,$(_ZREPL_VERSION))"; \
export VERSION="$${VERSION#v}"; \ export VERSION="$${VERSION#v}"; \
sed -i 's/VERSION/'"$$VERSION"'/' packaging/deb/debian/changelog sed -i 's/VERSION/'"$$VERSION"'/' packaging/deb/debian/changelog
@@ -157,36 +134,28 @@ endif
deb-docker: deb-docker:
docker build -t zrepl_debian_pkg --pull -f packaging/deb/Dockerfile . docker build -t zrepl_debian_pkg --pull -f packaging/deb/Dockerfile .
# Use a small open file limit to make fakeroot work. If we don't
# specify it, docker daemon will use its file limit. I don't know
# what changed (Docker, its systemd service, its Go version). But I
# observed fakeroot iterating close(i) up to i > 1000000, which costs
# a good amount of CPU time and makes the build slow.
docker run --rm -i -v $(CURDIR):/build/src -u $$(id -u):$$(id -g) \ docker run --rm -i -v $(CURDIR):/build/src -u $$(id -u):$$(id -g) \
--ulimit nofile=1024:1024 \
zrepl_debian_pkg \ zrepl_debian_pkg \
make deb \ make deb GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM)
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) \
ZREPL_VERSION=$(ZREPL_VERSION) ZREPL_PACKAGE_RELEASE=$(ZREPL_PACKAGE_RELEASE)
# expects `release`, `deb` & `rpm` targets to have run before # expects `release` target to have run before
NOARCH_TARBALL := $(ARTIFACTDIR)/zrepl-noarch.tar NOARCH_TARBALL := $(ARTIFACTDIR)/zrepl-noarch.tar
wrapup-and-checksum: wrapup-and-checksum:
rm -f $(NOARCH_TARBALL) rm -f $(NOARCH_TARBALL)
tar --mtime='1970-01-01' --sort=name \ tar --mtime='1970-01-01' --sort=name \
--transform 's/$(ARTIFACTDIR)/zrepl-$(_ZREPL_VERSION)-noarch/' \ --transform 's/$(ARTIFACTDIR)/zrepl-$(_ZREPL_VERSION)-noarch/' \
--transform 's#dist#zrepl-$(_ZREPL_VERSION)-noarch/dist#' \ --transform 's#dist#zrepl-$(_ZREPL_VERSION)-noarch/dist#' \
--transform 's#internal/config/samples#zrepl-$(_ZREPL_VERSION)-noarch/config#' \ --transform 's#config/samples#zrepl-$(_ZREPL_VERSION)-noarch/config#' \
-acf $(NOARCH_TARBALL) \ -acf $(NOARCH_TARBALL) \
$(ARTIFACTDIR)/docs/html \ $(ARTIFACTDIR)/docs/html \
$(ARTIFACTDIR)/bash_completion \ $(ARTIFACTDIR)/bash_completion \
$(ARTIFACTDIR)/_zrepl.zsh_completion \ $(ARTIFACTDIR)/_zrepl.zsh_completion \
$(ARTIFACTDIR)/go_env.txt \ $(ARTIFACTDIR)/go_env.txt \
dist \ dist \
internal/config/samples config/samples
rm -rf "$(ARTIFACTDIR)/release" rm -rf "$(ARTIFACTDIR)/release"
mkdir -p "$(ARTIFACTDIR)/release" mkdir -p "$(ARTIFACTDIR)/release"
cp -l $(ARTIFACTDIR)/zrepl* \ cp -l $(ARTIFACTDIR)/zrepl-* \
$(ARTIFACTDIR)/platformtest-* \ $(ARTIFACTDIR)/platformtest-* \
"$(ARTIFACTDIR)/release" "$(ARTIFACTDIR)/release"
cd "$(ARTIFACTDIR)/release" && sha512sum $$(ls | sort) > sha512sum.txt cd "$(ARTIFACTDIR)/release" && sha512sum $$(ls | sort) > sha512sum.txt
@@ -206,45 +175,42 @@ check-git-clean:
tag-release: tag-release:
test -n "$(ZREPL_TAG_VERSION)" || exit 1 test -n "$(ZREPL_TAG_VERSION)" || exit 1
git tag -u '328A6627FA98061D!' -m "$(ZREPL_TAG_VERSION)" "$(ZREPL_TAG_VERSION)" git tag -u E27CA5FC -m "$(ZREPL_TAG_VERSION)" "$(ZREPL_TAG_VERSION)"
sign: sign:
gpg -u '328A6627FA98061D!' \ gpg -u "89BC 5D89 C845 568B F578 B306 CDBD 8EC8 E27C A5FC" \
--armor \ --armor \
--detach-sign $(ARTIFACTDIR)/release/sha512sum.txt --detach-sign $(ARTIFACTDIR)/release/sha512sum.txt
clean: docs-clean clean: docs-clean
rm -rf "$(ARTIFACTDIR)" rm -rf "$(ARTIFACTDIR)"
download-circleci-release: ##################### BINARIES #####################
rm -rf "$(ARTIFACTDIR)" .PHONY: bins-all lint test-go test-platform cover-merge cover-html vet zrepl-bin test-platform-bin generate-platform-test-list
mkdir -p "$(ARTIFACTDIR)/release"
python3 .circleci/download_artifacts.py --prefix 'artifacts/release/' "$(BUILD_NUM)" "$(ARTIFACTDIR)/release"
##################### MULTI-ARCH HELPERS ##################### BINS_ALL_TARGETS := zrepl-bin test-platform-bin vet lint
GO_SUPPORTS_ILLUMOS := $(shell $(GO) version | gawk -F '.' '/^go version /{split($$0, comps, " "); split(comps[3], v, "."); if (v[1] == "go1" && v[2] >= 13) { print "illumos"; } else { print "noillumos"; }}')
_run_make_foreach_target_tuple: bins-all:
if [ "$(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG)" = "" ]; then \ $(MAKE) $(BINS_ALL_TARGETS) GOOS=freebsd GOARCH=amd64
echo "RUN_MAKE_FOREACH_TARGET_TUPLE_ARG must be set"; \ $(MAKE) $(BINS_ALL_TARGETS) GOOS=freebsd GOARCH=386
exit 1; \ $(MAKE) $(BINS_ALL_TARGETS) GOOS=freebsd GOARCH=arm GOARM=7
fi $(MAKE) $(BINS_ALL_TARGETS) GOOS=freebsd GOARCH=arm64
$(MAKE) $(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG) GOOS=freebsd GOARCH=amd64 $(MAKE) $(BINS_ALL_TARGETS) GOOS=linux GOARCH=amd64
$(MAKE) $(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG) GOOS=freebsd GOARCH=386 $(MAKE) $(BINS_ALL_TARGETS) GOOS=linux GOARCH=arm64
$(MAKE) $(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG) GOOS=freebsd GOARCH=arm GOARM=7 $(MAKE) $(BINS_ALL_TARGETS) GOOS=linux GOARCH=arm GOARM=7
$(MAKE) $(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG) GOOS=freebsd GOARCH=arm64 $(MAKE) $(BINS_ALL_TARGETS) GOOS=linux GOARCH=386
$(MAKE) $(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG) GOOS=linux GOARCH=amd64 $(MAKE) $(BINS_ALL_TARGETS) GOOS=darwin GOARCH=amd64
$(MAKE) $(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG) GOOS=linux GOARCH=arm64 $(MAKE) $(BINS_ALL_TARGETS) GOOS=solaris GOARCH=amd64
$(MAKE) $(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG) GOOS=linux GOARCH=arm GOARM=7 ifeq ($(GO_SUPPORTS_ILLUMOS), illumos)
$(MAKE) $(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG) GOOS=linux GOARCH=386 $(MAKE) $(BINS_ALL_TARGETS) GOOS=illumos GOARCH=amd64
$(MAKE) $(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG) GOOS=darwin GOARCH=amd64 else ifeq ($(GO_SUPPORTS_ILLUMOS), noillumos)
$(MAKE) $(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG) GOOS=solaris GOARCH=amd64 @echo "SKIPPING ILLUMOS BUILD BECAUSE GO VERSION DOESN'T SUPPORT IT"
$(MAKE) $(RUN_MAKE_FOREACH_TARGET_TUPLE_ARG) GOOS=illumos GOARCH=amd64 else
@echo "CANNOT DETERMINE WHETHER GO VERSION SUPPORTS GOOS=illumos"; exit 1
##################### REGULAR TARGETS ##################### endif
.PHONY: lint test-go test-platform cover-merge cover-html vet zrepl-bin test-platform-bin
lint: lint:
$(GO_ENV_VARS) $(GO) tool golangci-lint run ./... $(GO_ENV_VARS) $(GOLANGCI_LINT) run ./...
vet: vet:
$(GO_ENV_VARS) $(GO) vet $(GO_BUILDFLAGS) ./... $(GO_ENV_VARS) $(GO) vet $(GO_BUILDFLAGS) ./...
@@ -265,6 +231,9 @@ endif
zrepl-bin: zrepl-bin:
$(GO_BUILD) -o "$(ARTIFACTDIR)/zrepl-$(ZREPL_TARGET_TUPLE)" $(GO_BUILD) -o "$(ARTIFACTDIR)/zrepl-$(ZREPL_TARGET_TUPLE)"
generate-platform-test-list:
$(GO_BUILD) -o $(ARTIFACTDIR)/generate-platform-test-list ./platformtest/tests/gen
COVER_PLATFORM_BIN_PATH := $(ARTIFACTDIR)/platformtest-cover-$(ZREPL_TARGET_TUPLE) COVER_PLATFORM_BIN_PATH := $(ARTIFACTDIR)/platformtest-cover-$(ZREPL_TARGET_TUPLE)
cover-platform-bin: cover-platform-bin:
$(GO_ENV_VARS) $(GO) test $(GO_BUILDFLAGS) \ $(GO_ENV_VARS) $(GO) test $(GO_BUILDFLAGS) \
@@ -281,7 +250,7 @@ cover-platform:
TEST_PLATFORM_BIN_PATH := $(ARTIFACTDIR)/platformtest-$(ZREPL_TARGET_TUPLE) TEST_PLATFORM_BIN_PATH := $(ARTIFACTDIR)/platformtest-$(ZREPL_TARGET_TUPLE)
test-platform-bin: test-platform-bin:
$(GO_BUILD) -o "$(TEST_PLATFORM_BIN_PATH)" ./internal/platformtest/harness $(GO_BUILD) -o "$(TEST_PLATFORM_BIN_PATH)" ./platformtest/harness
test-platform: test-platform:
export _TEST_PLATFORM_CMD="\"$(TEST_PLATFORM_BIN_PATH)\""; \ export _TEST_PLATFORM_CMD="\"$(TEST_PLATFORM_BIN_PATH)\""; \
$(MAKE) _test-or-cover-platform-impl $(MAKE) _test-or-cover-platform-impl
@@ -298,7 +267,7 @@ ifndef _TEST_PLATFORM_CMD
endif endif
rm -f "$(ZREPL_PLATFORMTEST_ZFS_LOG)" rm -f "$(ZREPL_PLATFORMTEST_ZFS_LOG)"
rm -f "$(ARTIFACTDIR)/platformtest.cover" rm -f "$(ARTIFACTDIR)/platformtest.cover"
internal/platformtest/logmockzfs/logzfsenv "$(ZREPL_PLATFORMTEST_ZFS_LOG)" `which zfs` \ platformtest/logmockzfs/logzfsenv "$(ZREPL_PLATFORMTEST_ZFS_LOG)" `which zfs` \
$(_TEST_PLATFORM_CMD) \ $(_TEST_PLATFORM_CMD) \
-poolname "$(ZREPL_PLATFORMTEST_POOLNAME)" \ -poolname "$(ZREPL_PLATFORMTEST_POOLNAME)" \
-imagepath "$(ZREPL_PLATFORMTEST_IMAGEPATH)" \ -imagepath "$(ZREPL_PLATFORMTEST_IMAGEPATH)" \
@@ -320,38 +289,23 @@ cover-full:
##################### DEV TARGETS ##################### ##################### DEV TARGETS #####################
# not part of the build, must do that manually # not part of the build, must do that manually
.PHONY: generate format .PHONY: generate formatcheck format
build/install: generate: generate-platform-test-list
rm -rf build/install.tmp protoc -I=replication/logic/pdu --go_out=replication/logic/pdu --go-grpc_out=replication/logic/pdu replication/logic/pdu/pdu.proto
mkdir build/install.tmp protoc -I=rpc/grpcclientidentity/example --go_out=rpc/grpcclientidentity/example/pdu --go-grpc_out=rpc/grpcclientidentity/example/pdu rpc/grpcclientidentity/example/grpcauth.proto
$(GO_ENV_VARS) $(GO) generate $(GO_BUILDFLAGS) -x ./...
-echo "installing protoc" GOIMPORTS := goimports -srcdir . -local 'github.com/zrepl/zrepl'
mkdir build/install.tmp/protoc FINDSRCFILES := find . -type f -name '*.go' -not -path "./vendor/*" -not -name '*.pb.go' -not -name '*_enumer.go'
bash -x build/get_protoc.bash build/install.tmp/protoc
-echo "installing protoc plugins to GOBIN" formatcheck:
$(GO) install tool @# goimports doesn't have a knob to exit with non-zero status code if formatting is needed
@# see https://go-review.googlesource.com/c/tools/+/237378
mv build/install.tmp build/install @ affectedfiles=$$($(GOIMPORTS) -l $(shell $(FINDSRCFILES)) | tee /dev/stderr | wc -l); test "$$affectedfiles" = 0
generate: build/install
# protoc plugins are installed via 'go install tool' in build/install target
build/install/protoc/bin/protoc \
-I=internal/replication/logic/pdu \
--go_out=internal/replication/logic/pdu --go-grpc_out=internal/replication/logic/pdu \
internal/replication/logic/pdu/pdu.proto
build/install/protoc/bin/protoc \
-I=internal/rpc/grpcclientidentity/example \
--go_out=internal/rpc/grpcclientidentity/example/pdu --go-grpc_out=internal/rpc/grpcclientidentity/example/pdu \
internal/rpc/grpcclientidentity/example/grpcauth.proto
$(GO) generate $(GO_BUILDFLAGS) -x ./...
GOIMPORTS := $(GO) tool goimports -srcdir . -local 'github.com/zrepl/zrepl'
format: format:
@ $(GO) tool goimports -w -local 'github.com/zrepl/zrepl' $$(find . -type f -name '*.go' -not -path "./vendor/*" -not -name '*.pb.go' -not -name '*_enumer.go') @ $(GOIMPORTS) -w -d $(shell $(FINDSRCFILES))
##################### NOARCH ##################### ##################### NOARCH #####################
.PHONY: noarch $(ARTIFACTDIR)/bash_completion $(ARTIFACTDIR)/_zrepl.zsh_completion $(ARTIFACTDIR)/go_env.txt docs docs-clean .PHONY: noarch $(ARTIFACTDIR)/bash_completion $(ARTIFACTDIR)/_zrepl.zsh_completion $(ARTIFACTDIR)/go_env.txt docs docs-clean
+58 -61
View File
@@ -26,40 +26,24 @@ zrepl is a one-stop ZFS backup & replication solution.
If so, think of an expressive configuration example. If so, think of an expressive configuration example.
2. Think of at least one use case that generalizes from your concrete application. 2. Think of at least one use case that generalizes from your concrete application.
3. Open an issue on GitHub with example conf & use case attached. 3. Open an issue on GitHub with example conf & use case attached.
4. **Optional**: [Contact Christian Schwarz](https://cschwarz.com) for contract work. 4. **Optional**: [Post a bounty](https://www.bountysource.com/teams/zrepl) on the issue, or [contact Christian Schwarz](https://cschwarz.com) for contract work.
The above does not apply if you already implemented everything. The above does not apply if you already implemented everything.
Check out the *Coding Workflow* section below for details. Check out the *Coding Workflow* section below for details.
## Development ## Building, Releasing, Downstream-Packaging
This section provides an overview of the zrepl build & release process.
Check out `docs/installation/compile-from-source.rst` for build-from-source instructions.
### Overview
zrepl is written in [Go](https://golang.org) and uses [Go modules](https://github.com/golang/go/wiki/Modules) to manage dependencies. zrepl is written in [Go](https://golang.org) and uses [Go modules](https://github.com/golang/go/wiki/Modules) to manage dependencies.
The documentation is written in [ReStructured Text](http://docutils.sourceforge.net/rst.html) using the [Sphinx](https://www.sphinx-doc.org) framework. The documentation is written in [ReStructured Text](http://docutils.sourceforge.net/rst.html) using the [Sphinx](https://www.sphinx-doc.org) framework.
### Building Install **build dependencies** using `./lazy.sh devsetup`.
`lazy.sh` uses `python3-pip` to fetch the build dependencies for the docs - you might want to use a [venv](https://docs.python.org/3/library/venv.html).
#### Go Code If you just want to install the Go dependencies, run `./lazy.sh godep`.
Dependencies:
* Go 1.22 or newer
* GNU Make
* Git
* wget (`make generate`)
* unzip (`make generate`)
Some Go code is **generated**, and generated code is committed to the source tree.
Therefore, building does not require having code generation tools set up.
When making changes that require code to be (re-)generated, run `make generate`.
I downloads and installs pinned versions of the code generation tools into `./build/install`.
There is a CI check that ensures Git state is clean, i.e., code generation has been done by a PR and is deterministic.
#### Docs
Set up a Python environment that has `docs/requirements.txt` installed via `pip`.
Use a [venv](https://docs.python.org/3/library/venv.html) to avoid global state.
### Testing
The **test suite** is split into pure **Go tests** (`make test-go`) and **platform tests** that interact with ZFS and thus generally **require root privileges** (`sudo make test-platform`). The **test suite** is split into pure **Go tests** (`make test-go`) and **platform tests** that interact with ZFS and thus generally **require root privileges** (`sudo make test-platform`).
Platform tests run on their own pool with the name `zreplplatformtest`, which is created using the file vdev in `/tmp`. Platform tests run on their own pool with the name `zreplplatformtest`, which is created using the file vdev in `/tmp`.
@@ -67,28 +51,33 @@ Platform tests run on their own pool with the name `zreplplatformtest`, which is
For a full **code coverage** profile, run `make test-go COVER=1 && sudo make test-platform && make cover-merge`. For a full **code coverage** profile, run `make test-go COVER=1 && sudo make test-platform && make cover-merge`.
An HTML report can be generated using `make cover-html`. An HTML report can be generated using `make cover-html`.
### Circle CI **Code generation** is triggered by `make generate`. Generated code is committed to the source tree.
We use CircleCI for automated build & test pre- and post-merge. ### Build & Release Process
**The `Makefile` is catering to the needs of developers & CI, not distro packagers**.
It provides phony targets for
* local development (building, running tests, etc)
* building a release in Docker (used by the CI & release management)
* building .deb and .rpm packages out of the release artifacts.
**Build tooling & dependencies** are documented as code in `lazy.sh`.
Go dependencies are then fetched by the go command and pip dependencies are pinned through a `requirements.txt`.
**We use CircleCI for continuous integration**.
There are two workflows: There are two workflows:
* `ci` runs for every commit / branch / tag pushed to GitHub. * `ci` runs for every commit / branch / tag pushed to GitHub.
It is supposed to run very fast (<5min and provides quick feedback to developers). It is supposed to run very fast (<5min and provides quick feedback to developers).
It runs formatting checks, lints and tests on the most important OSes / architectures. It runs formatting checks, lints and tests on the most important OSes / architectures.
Artifacts are published to minio.cschwarz.com (see GitHub Commit Status).
* `release` runs * `release` runs
* on manual triggers through the CircleCI API (in order to produce a release) * on manual triggers through the CircleCI API (in order to produce a release)
* periodically on `master` * periodically on `master`
Artifacts are published to minio.cschwarz.com (see GitHub Commit Status).
Artifacts are stored in CircleCI. **Releases** are issued via Git tags + GitHub Releases feature.
### Releasing
All zrepl releases are git-tagged and then published as a GitHub Release.
There is a git tag for each zrepl release, usually `vMAJOR.MINOR.0`.
We don't move git tags once the release has been published.
The procedure to issue a release is as follows: The procedure to issue a release is as follows:
* Issue the source release: * Issue the source release:
* Git tag the release on the `master` branch. * Git tag the release on the `master` branch.
@@ -99,34 +88,14 @@ The procedure to issue a release is as follows:
* Download the artifacts to the release manager's machine. * Download the artifacts to the release manager's machine.
* Create a GitHub release, edit the changelog, upload all the release artifacts, including .rpm and .deb files. * Create a GitHub release, edit the changelog, upload all the release artifacts, including .rpm and .deb files.
* Issue the GitHub release. * Issue the GitHub release.
* Add the .rpm and .deb files to the official zrepl repos. * Add the .rpm and .deb files to the official zrepl repos, publish those.
* Code for management of these repos: https://github.com/zrepl/package-repo-ops (private repo at this time)
#### Patch releases, Go toolchain updates, APT/RPM Package rebuilds **Official binary releases are not re-built when Go receives an update. If the Go update is critical to zrepl (e.g. a Go security update that affects zrepl), we'd issue a new source release**.
The rationale for this is that whereas distros provide a mechanism for this (`$zrepl_source_release-$distro_package_revision`), GitHub Releases doesn't which means we'd need to update the existing GitHub release's assets, which nobody would notice (no RSS feed updates, etc.).
Downstream packagers can read the changelog to determine whether they want to push that minor release into their distro or simply skip it.
`vMAJOR.MINOR.0` is typically a tagged commit on `master`, because development velocity isn't high ### Additional Notes to Distro Package Maintainers
and thus release branches for stabilization aren't necessary.
Occasionally though there is a need for patch changes to a release, e.g.
- security issue in a dependency
- Go toolchain update (e.g. security issue in standard library)
The procedure for this is the following
- create a branch off the release tag we need to patch, named `releases/MAJOR.MINOR.X`
- that branch will never be merged into `master`, it'll be a dead-end for this specific patch
- make changes in that branch
- make the final commit that bumps version numbers
- create the git tag
- follow general procedure for publishing the release (previous sectino
For Go toolchain updates and package rebuilds with no source code changes, leverage the APT/RPM package revision field.
Control via the `ZREPL_PACKAGE_RELEASE` Makefile variable, see `origin/releases/0.6.1-2` for an example.
## Notes to Distro Package Maintainers
* The `Makefile` in this project is not suitable for builds in distros.
* Run the platform tests (Docs -> Usage -> Platform Tests) **on a test system** to validate that zrepl's abstractions on top of ZFS work with the system ZFS. * Run the platform tests (Docs -> Usage -> Platform Tests) **on a test system** to validate that zrepl's abstractions on top of ZFS work with the system ZFS.
* Ship a default config that adheres to your distro's `hier` and logging system. * 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. * Ship a service manager file and _please_ try to upstream it to this repository.
@@ -136,3 +105,31 @@ Control via the `ZREPL_PACKAGE_RELEASE` Makefile variable, see `origin/releases/
This is how `zrepl version` knows what version number to show. This is how `zrepl version` knows what version number to show.
Your build system should set the `ldFlags` flags appropriately and add a prefix or suffix that indicates that the given zrepl binary is a distro build, not an official one. Your build system should set the `ldFlags` flags appropriately and add a prefix or suffix that indicates that the given zrepl binary is a distro build, not an official one.
* Make sure you are informed about new zrepl versions, e.g. by subscribing to GitHub's release RSS feed. * Make sure you are informed about new zrepl versions, e.g. by subscribing to GitHub's release RSS feed.
## Contributing Code
* Open an issue when starting to hack on a new feature
* Commits should reference the issue they are related to
* Docs improvements not documenting new features do not require an issue.
### Breaking Changes
Backward-incompatible changes must be documented in the git commit message and are listed in `docs/changelog.rst`.
### Glossary & Naming Inconsistencies
In ZFS, *dataset* refers to the objects *filesystem*, *ZVOL* and *snapshot*. <br />
However, we need a word for *filesystem* & *ZVOL* but not a snapshot, bookmark, etc.
Toward the user, the following terminology is used:
* **filesystem**: a ZFS filesystem or a ZVOL
* **filesystem version**: a ZFS snapshot or a bookmark
Sadly, the zrepl implementation is inconsistent in its use of these words:
variables and types are often named *dataset* when they in fact refer to a *filesystem*.
There will not be a big refactoring (an attempt was made, but it's destroying too much history without much gain).
However, new contributions & patches should fix naming without further notice in the commit message.
+30
View File
@@ -0,0 +1,30 @@
FROM !SUBSTITUTED_BY_MAKEFILE
RUN apt-get update && apt-get install -y \
python3-pip \
unzip \
gawk
ADD build.installprotoc.bash ./
RUN bash build.installprotoc.bash
ADD lazy.sh /tmp/lazy.sh
ADD docs/requirements.txt /tmp/requirements.txt
ENV ZREPL_LAZY_DOCS_REQPATH=/tmp/requirements.txt
RUN /tmp/lazy.sh docdep
# prepare volume mount of git checkout to /zrepl
RUN mkdir -p /src/github.com/zrepl/zrepl
RUN mkdir -p /.cache && chmod -R 0777 /.cache
# $GOPATH is /go
# Go 1.12 doesn't use modules within GOPATH, but 1.13 and later do
# => store source outside of GOPATH
WORKDIR /src
# Install build tools (e.g. protobuf generator, stringer) into $GOPATH/bin
ADD build/ /tmp/build
RUN /tmp/lazy.sh godep
RUN chmod -R 0777 /go
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
set -x
MACH=$(uname -m)
MACH="${MACH/aarch64/aarch_64}"
VERSION=3.6.1
FILENAME=protoc-"$VERSION"-linux-"$MACH".zip
if [ -e "$FILENAME" ]; then
echo "$FILENAME" already exists 1>&2
exit 1
fi
wget https://github.com/protocolbuffers/protobuf/releases/download/v"$VERSION"/"$FILENAME"
stat "$FILENAME"
sha256sum -c --ignore-missing <<EOF
6003de742ea3fcf703cfec1cd4a3380fd143081a2eb0e559065563496af27807 protoc-3.6.1-linux-x86_64.zip
af8e5aaaf39ddec62ec8dd2be1b8d9602c6da66564883a16393ade5f71170922 protoc-3.6.1-linux-aarch_64.zip
EOF
unzip -d /usr "$FILENAME"
-1
View File
@@ -1 +0,0 @@
install/
-22
View File
@@ -1,22 +0,0 @@
FROM !SUBSTITUTED_BY_MAKEFILE
RUN apt-get update && apt-get install -y \
python3-pip \
python3-venv \
unzip \
gawk
# setup venv for docs
ENV VIRTUAL_ENV=/opt/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
ADD docs/requirements.txt /tmp/requirements.txt
RUN pip3 install -r /tmp/requirements.txt
# Go toolchain uses xdg-cache
RUN mkdir -p /.cache && chmod -R 0777 /.cache
# Go devtools are managed by Makefile
WORKDIR /src
-27
View File
@@ -1,27 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
set -x
cd "$1"
MACH=$(uname -m)
MACH="${MACH/aarch64/aarch_64}"
VERSION=28.0
FILENAME=protoc-"$VERSION"-linux-"$MACH".zip
if [ -e "$FILENAME" ]; then
echo "$FILENAME" already exists 1>&2
exit 1
fi
wget --continue https://github.com/protocolbuffers/protobuf/releases/download/v"$VERSION"/"$FILENAME"
stat "$FILENAME"
sha256sum -c --ignore-missing <<EOF
d622619dcbfb5ecb281cfb92c1a74d6a0f42e752d9a2774b197f475f7ab1c8c4 protoc-28.0-linux-aarch_64.zip
b2e187c8b9f2d97cd3ecae4926d1bb2cbebe3ab768e7c987cbc86bb17f319358 protoc-28.0-linux-x86_64.zip
EOF
unzip -d . "$FILENAME"
+13
View File
@@ -0,0 +1,13 @@
module github.com/zrepl/zrepl/build
go 1.12
require (
github.com/alvaroloes/enumer v1.1.1
github.com/golangci/golangci-lint v1.50.1
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad
golang.org/x/tools v0.2.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 // indirect
google.golang.org/protobuf v1.28.0
)
+1622 -376
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
//go:build tools
// +build tools
package main
// the lines are parsed by lazy.sh, do not edit
import (
_ "github.com/alvaroloes/enumer"
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "github.com/wadey/gocovmerge"
_ "golang.org/x/tools/cmd/goimports"
_ "golang.org/x/tools/cmd/stringer"
_ "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
_ "google.golang.org/protobuf/cmd/protoc-gen-go"
)
+2 -2
View File
@@ -8,9 +8,9 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/zrepl/zrepl/internal/daemon/logging/trace" "github.com/zrepl/zrepl/daemon/logging/trace"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
) )
var rootArgs struct { var rootArgs struct {
@@ -11,11 +11,11 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/zrepl/yaml-config" "github.com/zrepl/yaml-config"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon/job" "github.com/zrepl/zrepl/daemon/job"
"github.com/zrepl/zrepl/internal/daemon/logging" "github.com/zrepl/zrepl/daemon/logging"
"github.com/zrepl/zrepl/internal/logger" "github.com/zrepl/zrepl/logger"
) )
var configcheckArgs struct { var configcheckArgs struct {
@@ -9,12 +9,12 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/zrepl/zrepl/internal/daemon/job" "github.com/zrepl/zrepl/daemon/job"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
) )
var ( var (
+1 -1
View File
@@ -1,6 +1,6 @@
package client package client
import "github.com/zrepl/zrepl/internal/cli" import "github.com/zrepl/zrepl/cli"
var PprofCmd = &cli.Subcommand{ var PprofCmd = &cli.Subcommand{
Use: "pprof", Use: "pprof",
@@ -8,7 +8,7 @@ import (
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
) )
var pprofActivityTraceCmd = &cli.Subcommand{ var pprofActivityTraceCmd = &cli.Subcommand{
@@ -6,9 +6,9 @@ import (
"log" "log"
"os" "os"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon" "github.com/zrepl/zrepl/daemon"
) )
var pprofListenCmd struct { var pprofListenCmd struct {
@@ -5,9 +5,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon" "github.com/zrepl/zrepl/daemon"
) )
var SignalCmd = &cli.Subcommand{ var SignalCmd = &cli.Subcommand{
@@ -10,7 +10,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zrepl/zrepl/internal/daemon" "github.com/zrepl/zrepl/daemon"
) )
type Client struct { type Client struct {
@@ -9,11 +9,11 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/client/status/client" "github.com/zrepl/zrepl/client/status/client"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon" "github.com/zrepl/zrepl/daemon"
"github.com/zrepl/zrepl/internal/util/choices" "github.com/zrepl/zrepl/util/choices"
) )
type Client interface { type Client interface {
@@ -5,11 +5,11 @@ import (
"os" "os"
"strings" "strings"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zrepl/zrepl/internal/client/status/viewmodel" "github.com/zrepl/zrepl/client/status/viewmodel"
) )
func dump(c Client, job string) error { func dump(c Client, job string) error {
@@ -8,9 +8,9 @@ import (
"time" "time"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/rivo/tview" tview "gitlab.com/tslocum/cview"
"github.com/zrepl/zrepl/internal/client/status/viewmodel" "github.com/zrepl/zrepl/client/status/viewmodel"
) )
func interactive(c Client, flag statusFlags) error { func interactive(c Client, flag statusFlags) error {
@@ -29,6 +29,7 @@ func interactive(c Client, flag statusFlags) error {
jobMenuRoot.SetSelectable(true) jobMenuRoot.SetSelectable(true)
jobMenu.SetRoot(jobMenuRoot) jobMenu.SetRoot(jobMenuRoot)
jobMenu.SetCurrentNode(jobMenuRoot) jobMenu.SetCurrentNode(jobMenuRoot)
jobMenu.SetSelectedTextColor(tcell.ColorGreen)
jobTextDetail := tview.NewTextView() jobTextDetail := tview.NewTextView()
jobTextDetail.SetWrap(false) jobTextDetail.SetWrap(false)
@@ -109,8 +110,10 @@ func interactive(c Client, flag statusFlags) error {
} }
app.SetRoot(toolbarSplit, true) app.SetRoot(toolbarSplit, true)
app.SetFocus(preModalFocus) app.SetFocus(preModalFocus)
app.Draw()
}) })
app.SetRoot(m, true) app.SetRoot(m, true)
app.Draw()
} }
app.SetRoot(toolbarSplit, true) app.SetRoot(toolbarSplit, true)
@@ -167,14 +170,12 @@ func interactive(c Client, flag statusFlags) error {
redrawJobsList = true redrawJobsList = true
} }
if redrawJobsList { if redrawJobsList {
selectedTextStyle := tcell.StyleDefault.Bold(true)
selectedJobN = nil selectedJobN = nil
children := make([]*tview.TreeNode, len(jobs)) children := make([]*tview.TreeNode, len(jobs))
for i := range jobs { for i := range jobs {
jobN := tview.NewTreeNode(jobs[i].JobTreeTitle()) jobN := tview.NewTreeNode(jobs[i].JobTreeTitle())
jobN.SetReference(jobs[i]) jobN.SetReference(jobs[i])
jobN.SetSelectable(true) jobN.SetSelectable(true)
jobN.SetSelectedTextStyle(selectedTextStyle)
children[i] = jobN children[i] = jobN
jobN.SetSelectedFunc(func() { jobN.SetSelectedFunc(func() {
viewmodelupdate(func(p *viewmodel.Params) { viewmodelupdate(func(p *viewmodel.Params) {
@@ -186,7 +187,6 @@ func interactive(c Client, flag statusFlags) error {
} }
} }
jobMenuRoot.SetChildren(children) jobMenuRoot.SetChildren(children)
jobMenuRoot.SetSelectedTextStyle(selectedTextStyle)
} }
if selectedJobN != nil && jobMenu.GetCurrentNode() != selectedJobN { if selectedJobN != nil && jobMenu.GetCurrentNode() != selectedJobN {
@@ -207,6 +207,9 @@ func interactive(c Client, flag statusFlags) error {
bottombar.ResizeItem(bottombarDateView, len(bottombardatestring), 0) bottombar.ResizeItem(bottombarDateView, len(bottombardatestring), 0)
bottomBarStatus.SetText(m.BottomBarStatus()) bottomBarStatus.SetText(m.BottomBarStatus())
app.Draw()
} }
go func() { go func() {
@@ -249,7 +252,6 @@ func interactive(c Client, flag statusFlags) error {
app.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey { app.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
if e.Key() == tcell.KeyTab { if e.Key() == tcell.KeyTab {
// TODO: only if there's no modal showing (long-time bug in zrepl status)
tabbableCycle() tabbableCycle()
return nil return nil
} }
@@ -282,8 +284,9 @@ func interactive(c Client, flag statusFlags) error {
signals := []string{"wakeup", "reset"} signals := []string{"wakeup", "reset"}
clientFuncs := []func(job string) error{c.SignalWakeup, c.SignalReset} clientFuncs := []func(job string) error{c.SignalWakeup, c.SignalReset}
sigMod := tview.NewModal() sigMod := tview.NewModal()
sigMod.SetBackgroundColor(tcell.ColorDefault)
sigMod.SetBorder(true) sigMod.SetBorder(true)
sigMod.SetButtonActivatedStyle(tcell.StyleDefault.Bold(true).Reverse(true)) sigMod.GetForm().SetButtonTextColorFocused(tcell.ColorGreen)
sigMod.AddButtons(signals) sigMod.AddButtons(signals)
sigMod.SetText(fmt.Sprintf("Send a signal to job %q", job.Name())) sigMod.SetText(fmt.Sprintf("Send a signal to job %q", job.Name()))
showModal(sigMod, func(idx int, _ string) { showModal(sigMod, func(idx int, _ string) {
@@ -10,9 +10,9 @@ import (
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rivo/tview" tview "gitlab.com/tslocum/cview"
"github.com/zrepl/zrepl/internal/client/status/viewmodel" "github.com/zrepl/zrepl/client/status/viewmodel"
) )
func legacy(c Client, flag statusFlags) error { func legacy(c Client, flag statusFlags) error {
@@ -28,7 +28,7 @@ func legacy(c Client, flag statusFlags) error {
textView := tview.NewTextView() textView := tview.NewTextView()
textView.SetWrap(true) textView.SetWrap(true)
textView.SetScrollable(true) // so that it allows us to set scroll position textView.SetScrollable(true) // so that it allows us to set scroll position
// textView.SetScrollBarVisibility(tview.ScrollBarNever) textView.SetScrollBarVisibility(tview.ScrollBarNever)
app.SetRoot(textView, true) app.SetRoot(textView, true)
@@ -10,12 +10,12 @@ import (
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
yaml "github.com/zrepl/yaml-config" yaml "github.com/zrepl/yaml-config"
"github.com/zrepl/zrepl/internal/client/status/viewmodel/stringbuilder" "github.com/zrepl/zrepl/client/status/viewmodel/stringbuilder"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/daemon"
"github.com/zrepl/zrepl/internal/daemon/job" "github.com/zrepl/zrepl/daemon/job"
"github.com/zrepl/zrepl/internal/daemon/pruner" "github.com/zrepl/zrepl/daemon/pruner"
"github.com/zrepl/zrepl/internal/daemon/snapper" "github.com/zrepl/zrepl/daemon/snapper"
"github.com/zrepl/zrepl/internal/replication/report" "github.com/zrepl/zrepl/replication/report"
) )
type M struct { type M struct {
@@ -85,7 +85,7 @@ func (m *M) Update(p Params) {
// filter out internal jobs // filter out internal jobs
var jobsList []*Job var jobsList []*Job
for _, j := range m.jobsList { for _, j := range m.jobsList {
if config.IsInternalJobName(j.name) { if daemon.IsInternalJobName(j.name) {
continue continue
} }
jobsList = append(jobsList, j) jobsList = append(jobsList, j)
@@ -374,15 +374,13 @@ func renderReplicationReport(t *stringbuilder.B, rep *report.Report, history *by
eta = time.Duration((float64(expected)-float64(replicated))/float64(rate)) * time.Second eta = time.Duration((float64(expected)-float64(replicated))/float64(rate)) * time.Second
} }
if !latest.State.IsTerminal() { t.Write("Progress: ")
t.Write("Progress: ") t.DrawBar(50, replicated, expected, changeCount)
t.DrawBar(50, replicated, expected, changeCount) t.Write(fmt.Sprintf(" %s / %s @ %s/s", ByteCountBinaryUint(replicated), ByteCountBinaryUint(expected), ByteCountBinary(rate)))
t.Write(fmt.Sprintf(" %s / %s @ %s/s", ByteCountBinaryUint(replicated), ByteCountBinaryUint(expected), ByteCountBinary(rate))) if eta != 0 {
if eta != 0 { t.Write(fmt.Sprintf(" (%s remaining)", humanizeDuration(eta)))
t.Write(fmt.Sprintf(" (%s remaining)", humanizeDuration(eta)))
}
t.Newline()
} }
t.Newline()
if containsInvalidSizeEstimates { if containsInvalidSizeEstimates {
t.Write("NOTE: not all steps could be size-estimated, total estimate is likely imprecise!") t.Write("NOTE: not all steps could be size-estimated, total estimate is likely imprecise!")
t.Newline() t.Newline()
@@ -495,17 +493,15 @@ func renderPrunerReport(t *stringbuilder.B, r *pruner.Report, fsfilter FilterFun
} }
// global progress bar // global progress bar
if !state.IsTerminal() { progress := int(math.Round(80 * float64(completedDestroyCount) / float64(totalDestroyCount)))
progress := int(math.Round(80 * float64(completedDestroyCount) / float64(totalDestroyCount))) t.Write("Progress: ")
t.Write("Progress: ") t.Write("[")
t.Write("[") t.Write(stringbuilder.Times("=", progress))
t.Write(stringbuilder.Times("=", progress)) t.Write(">")
t.Write(">") t.Write(stringbuilder.Times("-", 80-progress))
t.Write(stringbuilder.Times("-", 80-progress)) t.Write("]")
t.Write("]") t.Printf(" %d/%d snapshots", completedDestroyCount, totalDestroyCount)
t.Printf(" %d/%d snapshots", completedDestroyCount, totalDestroyCount) t.Newline()
t.Newline()
}
sort.SliceStable(all, func(i, j int) bool { sort.SliceStable(all, func(i, j int) bool {
return strings.Compare(all[i].Filesystem, all[j].Filesystem) == -1 return strings.Compare(all[i].Filesystem, all[j].Filesystem) == -1
@@ -5,8 +5,8 @@ import (
"github.com/problame/go-netssh" "github.com/problame/go-netssh"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"context" "context"
"errors" "errors"
@@ -9,10 +9,10 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon/filters" "github.com/zrepl/zrepl/daemon/filters"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
var TestCmd = &cli.Subcommand{ var TestCmd = &cli.Subcommand{
@@ -7,10 +7,10 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon" "github.com/zrepl/zrepl/daemon"
"github.com/zrepl/zrepl/internal/version" "github.com/zrepl/zrepl/version"
) )
var versionArgs struct { var versionArgs struct {
@@ -7,10 +7,10 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/daemon/filters" "github.com/zrepl/zrepl/daemon/filters"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
var ( var (
@@ -1,6 +1,6 @@
package client package client
import "github.com/zrepl/zrepl/internal/cli" import "github.com/zrepl/zrepl/cli"
var zabsCmdCreate = &cli.Subcommand{ var zabsCmdCreate = &cli.Subcommand{
Use: "create", Use: "create",
@@ -7,9 +7,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
var zabsCreateStepHoldFlags struct { var zabsCreateStepHoldFlags struct {
@@ -11,9 +11,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/util/chainlock" "github.com/zrepl/zrepl/util/chainlock"
) )
var zabsListFlags struct { var zabsListFlags struct {
@@ -10,8 +10,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/zrepl/zrepl/internal/cli" "github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
) )
// shared between release-all and release-step // shared between release-all and release-step
+88 -123
View File
@@ -2,19 +2,20 @@ package config
import ( import (
"fmt" "fmt"
"io/ioutil"
"log/syslog" "log/syslog"
"os" "os"
pathpkg "path" "reflect"
"path/filepath" "regexp"
"strings" "strconv"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"github.com/zrepl/yaml-config" "github.com/zrepl/yaml-config"
"github.com/zrepl/zrepl/internal/util/datasizeunit" "github.com/zrepl/zrepl/util/datasizeunit"
zfsprop "github.com/zrepl/zrepl/internal/zfs/property" zfsprop "github.com/zrepl/zrepl/zfs/property"
) )
type ParseFlags uint type ParseFlags uint
@@ -25,9 +26,8 @@ const (
) )
type Config struct { type Config struct {
Jobs []JobEnum `yaml:"jobs,optional"` Jobs []JobEnum `yaml:"jobs"`
Global *Global `yaml:"global,optional,fromdefaults"` Global *Global `yaml:"global,optional,fromdefaults"`
Include []string `yaml:"include,optional"`
} }
func (c *Config) Job(name string) (*JobEnum, error) { func (c *Config) Job(name string) (*JobEnum, error) {
@@ -67,6 +67,7 @@ type ActiveJob struct {
Name string `yaml:"name"` Name string `yaml:"name"`
Connect ConnectEnum `yaml:"connect"` Connect ConnectEnum `yaml:"connect"`
Pruning PruningSenderReceiver `yaml:"pruning"` Pruning PruningSenderReceiver `yaml:"pruning"`
Debug JobDebugSettings `yaml:"debug,optional"`
Replication *Replication `yaml:"replication,optional,fromdefaults"` Replication *Replication `yaml:"replication,optional,fromdefaults"`
ConflictResolution *ConflictResolution `yaml:"conflict_resolution,optional,fromdefaults"` ConflictResolution *ConflictResolution `yaml:"conflict_resolution,optional,fromdefaults"`
} }
@@ -76,15 +77,17 @@ type ConflictResolution struct {
} }
type PassiveJob struct { type PassiveJob struct {
Type string `yaml:"type"` Type string `yaml:"type"`
Name string `yaml:"name"` Name string `yaml:"name"`
Serve ServeEnum `yaml:"serve"` Serve ServeEnum `yaml:"serve"`
Debug JobDebugSettings `yaml:"debug,optional"`
} }
type SnapJob struct { type SnapJob struct {
Type string `yaml:"type"` Type string `yaml:"type"`
Name string `yaml:"name"` Name string `yaml:"name"`
Pruning PruningLocal `yaml:"pruning"` Pruning PruningLocal `yaml:"pruning"`
Debug JobDebugSettings `yaml:"debug,optional"`
Snapshotting SnapshottingEnum `yaml:"snapshotting"` Snapshotting SnapshottingEnum `yaml:"snapshotting"`
Filesystems FilesystemsFilter `yaml:"filesystems"` Filesystems FilesystemsFilter `yaml:"filesystems"`
} }
@@ -187,10 +190,13 @@ func (i *PositiveDurationOrManual) UnmarshalYAML(u func(interface{}, bool) error
return fmt.Errorf("value must not be empty") return fmt.Errorf("value must not be empty")
default: default:
i.Manual = false i.Manual = false
i.Interval, err = parsePositiveDuration(s) i.Interval, err = time.ParseDuration(s)
if err != nil { if err != nil {
return err return err
} }
if i.Interval <= 0 {
return fmt.Errorf("value must be a positive duration, got %q", s)
}
} }
return nil return nil
} }
@@ -222,16 +228,10 @@ type SnapshottingEnum struct {
} }
type SnapshottingPeriodic struct { type SnapshottingPeriodic struct {
Type string `yaml:"type"` Type string `yaml:"type"`
Prefix string `yaml:"prefix"` Prefix string `yaml:"prefix"`
Interval *PositiveDuration `yaml:"interval"` Interval time.Duration `yaml:"interval,positive"`
Hooks HookList `yaml:"hooks,optional"` Hooks HookList `yaml:"hooks,optional"`
TimestampFormattingSpec `yaml:",inline"`
}
type TimestampFormattingSpec struct {
TimestampFormat string `yaml:"timestamp_format,optional,default=dense"`
TimestampLocation string `yaml:"timestamp_location,optional,default=UTC"`
} }
type CronSpec struct { type CronSpec struct {
@@ -260,11 +260,10 @@ func (s *CronSpec) UnmarshalYAML(unmarshal func(v interface{}, not_strict bool)
} }
type SnapshottingCron struct { type SnapshottingCron struct {
Type string `yaml:"type"` Type string `yaml:"type"`
Prefix string `yaml:"prefix"` Prefix string `yaml:"prefix"`
Cron CronSpec `yaml:"cron"` Cron CronSpec `yaml:"cron"`
Hooks HookList `yaml:"hooks,optional"` Hooks HookList `yaml:"hooks,optional"`
TimestampFormattingSpec `yaml:",inline"`
} }
type SnapshottingManual struct { type SnapshottingManual struct {
@@ -306,6 +305,18 @@ type Global struct {
Serve *GlobalServe `yaml:"serve,optional,fromdefaults"` Serve *GlobalServe `yaml:"serve,optional,fromdefaults"`
} }
func Default(i interface{}) {
v := reflect.ValueOf(i)
if v.Kind() != reflect.Ptr {
panic(v)
}
y := `{}`
err := yaml.Unmarshal([]byte(y), v.Interface())
if err != nil {
panic(err)
}
}
type ConnectEnum struct { type ConnectEnum struct {
Ret interface{} Ret interface{}
} }
@@ -472,6 +483,14 @@ type GlobalStdinServer struct {
SockDir string `yaml:"sockdir,default=/var/run/zrepl/stdinserver"` SockDir string `yaml:"sockdir,default=/var/run/zrepl/stdinserver"`
} }
type JobDebugSettings struct {
Conn *struct {
ReadDump string `yaml:"read_dump"`
WriteDump string `yaml:"write_dump"`
} `yaml:"conn,optional"`
RPCLog bool `yaml:"rpc_log,optional,default=false"`
}
type HookList []HookEnum type HookList []HookEnum
type HookEnum struct { type HookEnum struct {
@@ -659,9 +678,8 @@ var ConfigFileDefaultLocations = []string{
"/usr/local/etc/zrepl/zrepl.yml", "/usr/local/etc/zrepl/zrepl.yml",
} }
func ParseConfig(path string) (rootConfig *Config, err error) { func ParseConfig(path string) (i *Config, err error) {
// Parse main configuration file
if path == "" { if path == "" {
// Try default locations // Try default locations
for _, l := range ConfigFileDefaultLocations { for _, l := range ConfigFileDefaultLocations {
@@ -680,94 +698,11 @@ func ParseConfig(path string) (rootConfig *Config, err error) {
var bytes []byte var bytes []byte
if bytes, err = os.ReadFile(path); err != nil { if bytes, err = ioutil.ReadFile(path); err != nil {
return return
} }
rootConfig, err = ParseConfigBytes(bytes) return ParseConfigBytes(bytes)
if err != nil {
return nil, err
}
err = expandConfigInclude(path, rootConfig)
if err != nil {
return nil, err
}
if err = validateJobNames(rootConfig); err != nil {
return nil, err
}
return rootConfig, err
}
func IsInternalJobName(s string) bool {
return strings.HasPrefix(s, "_")
}
func validateJobNames(config *Config) error {
seen := make(map[string]struct{})
for _, job := range config.Jobs {
name := job.Name()
if IsInternalJobName(name) {
return errors.Errorf("job name %q is reserved for internal use (starts with _)", name)
}
if _, ok := seen[name]; ok {
return errors.Errorf("duplicate job name %q", name)
}
seen[name] = struct{}{}
}
return nil
}
func expandConfigInclude(configPath string, config *Config) (err error) {
var includeConfigPaths []string
for _, path := range config.Include {
if !pathpkg.IsAbs(configPath) {
path = pathpkg.Join(pathpkg.Dir(configPath), path)
}
stat, statErr := os.Stat(path)
if statErr != nil {
return errors.Wrapf(statErr, "stat path %q", path)
}
if stat.Mode().IsDir() {
directoryPaths, err := filepath.Glob(path + "/*.yml")
if err != nil {
return err
}
includeConfigPaths = append(includeConfigPaths, directoryPaths...)
} else if stat.Mode().IsRegular() {
if extention := filepath.Ext(path); extention != ".yml" {
return fmt.Errorf("include config files must end with `.yml`: %s", path)
}
includeConfigPaths = append(includeConfigPaths, path)
} else {
return fmt.Errorf("not a file or directory: %s", path)
}
}
for _, path := range includeConfigPaths {
var bytes []byte
if bytes, err = os.ReadFile(path); err != nil {
return errors.Wrapf(err, "read file: %q", path)
}
includedConfig, err := ParseConfigBytes(bytes)
if err != nil {
return err
}
if len(includedConfig.Include) > 0 {
return errors.Errorf("included configuration files must not include other files: %s", path)
}
config.Jobs = append(config.Jobs, includedConfig.Jobs...)
}
return nil
} }
func ParseConfigBytes(bytes []byte) (*Config, error) { func ParseConfigBytes(bytes []byte) (*Config, error) {
@@ -775,16 +710,46 @@ func ParseConfigBytes(bytes []byte) (*Config, error) {
if err := yaml.UnmarshalStrict(bytes, &c); err != nil { if err := yaml.UnmarshalStrict(bytes, &c); err != nil {
return nil, err return nil, err
} }
if c != nil {
return c, nil
}
// There was no yaml document in the file, deserialize from default.
// => See TestFromdefaultsEmptyDoc in yaml-config package.
if err := yaml.UnmarshalStrict([]byte("{}"), &c); err != nil {
return nil, err
}
if c == nil { if c == nil {
panic("the fallback to deserialize from `{}` should work") return nil, fmt.Errorf("config is empty or only consists of comments")
} }
return c, nil return c, nil
} }
var durationStringRegex *regexp.Regexp = regexp.MustCompile(`^\s*(\d+)\s*(s|m|h|d|w)\s*$`)
func parsePositiveDuration(e string) (d time.Duration, err error) {
comps := durationStringRegex.FindStringSubmatch(e)
if len(comps) != 3 {
err = fmt.Errorf("does not match regex: %s %#v", e, comps)
return
}
durationFactor, err := strconv.ParseInt(comps[1], 10, 64)
if err != nil {
return 0, err
}
if durationFactor <= 0 {
return 0, errors.New("duration must be positive integer")
}
var durationUnit time.Duration
switch comps[2] {
case "s":
durationUnit = time.Second
case "m":
durationUnit = time.Minute
case "h":
durationUnit = time.Hour
case "d":
durationUnit = 24 * time.Hour
case "w":
durationUnit = 24 * 7 * time.Hour
default:
err = fmt.Errorf("contains unknown time unit '%s'", comps[2])
return
}
d = time.Duration(durationFactor) * durationUnit
return
}
@@ -2,8 +2,16 @@ package config
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestConfigEmptyFails(t *testing.T) {
conf, err := testConfig(t, "\n")
assert.Nil(t, conf)
assert.Error(t, err)
}
func TestJobsOnlyWorks(t *testing.T) { func TestJobsOnlyWorks(t *testing.T) {
testValidConfig(t, ` testValidConfig(t, `
jobs: jobs:
@@ -26,7 +34,7 @@ jobs:
keep_sender: keep_sender:
- type: not_replicated - type: not_replicated
keep_receiver: keep_receiver:
- type: last_n - type: last_n
count: 1 count: 1
`) `)
} }
@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
zfsprop "github.com/zrepl/zrepl/internal/zfs/property" zfsprop "github.com/zrepl/zrepl/zfs/property"
) )
func TestRecvOptions(t *testing.T) { func TestRecvOptions(t *testing.T) {
+90
View File
@@ -0,0 +1,90 @@
package config
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestSnapshotting(t *testing.T) {
tmpl := `
jobs:
- name: foo
type: push
connect:
type: local
listener_name: foo
client_identity: bar
filesystems: {"<": true}
%s
pruning:
keep_sender:
- type: last_n
count: 10
keep_receiver:
- type: last_n
count: 10
`
manual := `
snapshotting:
type: manual
`
periodic := `
snapshotting:
type: periodic
prefix: zrepl_
interval: 10m
`
hooks := `
snapshotting:
type: periodic
prefix: zrepl_
interval: 10m
hooks:
- type: command
path: /tmp/path/to/command
- type: command
path: /tmp/path/to/command
filesystems: { "zroot<": true, "<": false }
- type: postgres-checkpoint
dsn: "host=localhost port=5432 user=postgres sslmode=disable"
filesystems: {
"tank/postgres/data11": true
}
- type: mysql-lock-tables
dsn: "root@tcp(localhost)/"
filesystems: {
"tank/mysql": true
}
`
fillSnapshotting := func(s string) string { return fmt.Sprintf(tmpl, s) }
var c *Config
t.Run("manual", func(t *testing.T) {
c = testValidConfig(t, fillSnapshotting(manual))
snm := c.Jobs[0].Ret.(*PushJob).Snapshotting.Ret.(*SnapshottingManual)
assert.Equal(t, "manual", snm.Type)
})
t.Run("periodic", func(t *testing.T) {
c = testValidConfig(t, fillSnapshotting(periodic))
snp := c.Jobs[0].Ret.(*PushJob).Snapshotting.Ret.(*SnapshottingPeriodic)
assert.Equal(t, "periodic", snp.Type)
assert.Equal(t, 10*time.Minute, snp.Interval)
assert.Equal(t, "zrepl_", snp.Prefix)
})
t.Run("hooks", func(t *testing.T) {
c = testValidConfig(t, fillSnapshotting(hooks))
hs := c.Jobs[0].Ret.(*PushJob).Snapshotting.Ret.(*SnapshottingPeriodic).Hooks
assert.Equal(t, hs[0].Ret.(*HookCommand).Filesystems["<"], true)
assert.Equal(t, hs[1].Ret.(*HookCommand).Filesystems["zroot<"], true)
assert.Equal(t, hs[2].Ret.(*HookPostgresCheckpoint).Filesystems["tank/postgres/data11"], true)
assert.Equal(t, hs[3].Ret.(*HookMySQLLockTables).Filesystems["tank/mysql"], true)
})
}
@@ -22,8 +22,6 @@ func TestSampleConfigsAreParsedWithoutErrors(t *testing.T) {
t.Errorf("glob failed: %+v", err) t.Errorf("glob failed: %+v", err)
} }
paths = append(paths, "../../packaging/systemd-default-zrepl.yml")
for _, p := range paths { for _, p := range paths {
if path.Ext(p) != ".yml" { if path.Ext(p) != ".yml" {
@@ -45,20 +43,6 @@ func TestSampleConfigsAreParsedWithoutErrors(t *testing.T) {
} }
func TestInvalidSampleConfigsFailToParse(t *testing.T) {
paths, err := filepath.Glob("./samples/invalid/*/zrepl.yml")
require.NoError(t, err, "glob failed")
require.NotEmpty(t, paths, "no invalid sample configs found")
for _, p := range paths {
t.Run(p, func(t *testing.T) {
_, err := ParseConfig(p)
require.Error(t, err, "expected config %s to fail parsing", p)
t.Logf("config %s failed as expected: %v", p, err)
})
}
}
// template must be a template/text template with a single '{{ . }}' as placeholder for val // template must be a template/text template with a single '{{ . }}' as placeholder for val
// //
//nolint:deadcode,unused //nolint:deadcode,unused
@@ -100,7 +84,7 @@ func trimSpaceEachLineAndPad(s, pad string) string {
func TestTrimSpaceEachLineAndPad(t *testing.T) { func TestTrimSpaceEachLineAndPad(t *testing.T) {
foo := ` foo := `
foo foo
bar baz bar baz
` `
assert.Equal(t, " \n foo\n bar baz\n \n", trimSpaceEachLineAndPad(foo, " ")) assert.Equal(t, " \n foo\n bar baz\n \n", trimSpaceEachLineAndPad(foo, " "))
} }
@@ -154,18 +138,3 @@ func TestCronSpec(t *testing.T) {
} }
} }
func TestEmptyConfig(t *testing.T) {
cases := []string{
"",
"\n",
"---",
"---\n",
}
for _, input := range cases {
config := testValidConfig(t, input)
require.NotNil(t, config)
require.NotNil(t, config.Global)
require.Empty(t, config.Jobs)
}
}
@@ -14,14 +14,14 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/zrepl/zrepl/internal/daemon/job" "github.com/zrepl/zrepl/daemon/job"
"github.com/zrepl/zrepl/internal/daemon/nethelpers" "github.com/zrepl/zrepl/daemon/nethelpers"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/logger" "github.com/zrepl/zrepl/logger"
"github.com/zrepl/zrepl/internal/util/envconst" "github.com/zrepl/zrepl/util/envconst"
"github.com/zrepl/zrepl/internal/version" "github.com/zrepl/zrepl/version"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
"github.com/zrepl/zrepl/internal/zfs/zfscmd" "github.com/zrepl/zrepl/zfs/zfscmd"
) )
type controlJob struct { type controlJob struct {
+26 -16
View File
@@ -3,8 +3,10 @@ package daemon
import ( import (
"context" "context"
"fmt" "fmt"
"math/rand"
"os" "os"
"os/signal" "os/signal"
"strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
@@ -12,18 +14,18 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/zrepl/zrepl/internal/daemon/logging/trace" "github.com/zrepl/zrepl/daemon/logging/trace"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/util/envconst" "github.com/zrepl/zrepl/util/envconst"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon/job" "github.com/zrepl/zrepl/daemon/job"
"github.com/zrepl/zrepl/internal/daemon/job/reset" "github.com/zrepl/zrepl/daemon/job/reset"
"github.com/zrepl/zrepl/internal/daemon/job/wakeup" "github.com/zrepl/zrepl/daemon/job/wakeup"
"github.com/zrepl/zrepl/internal/daemon/logging" "github.com/zrepl/zrepl/daemon/logging"
"github.com/zrepl/zrepl/internal/logger" "github.com/zrepl/zrepl/logger"
"github.com/zrepl/zrepl/internal/version" "github.com/zrepl/zrepl/version"
"github.com/zrepl/zrepl/internal/zfs/zfscmd" "github.com/zrepl/zrepl/zfs/zfscmd"
) )
func Run(ctx context.Context, conf *config.Config) error { func Run(ctx context.Context, conf *config.Config) error {
@@ -37,6 +39,12 @@ func Run(ctx context.Context, conf *config.Config) error {
cancel() cancel()
}() }()
// The math/rand package is used presently for generating trace IDs, we
// seed it with the current time and pid so that the IDs are mostly
// unique.
rand.Seed(time.Now().UnixNano())
rand.Seed(int64(os.Getpid()))
outlets, err := logging.OutletsFromConfig(*conf.Global.Logging) outlets, err := logging.OutletsFromConfig(*conf.Global.Logging)
if err != nil { if err != nil {
return errors.Wrap(err, "cannot build logging from config") return errors.Wrap(err, "cannot build logging from config")
@@ -63,7 +71,7 @@ func Run(ctx context.Context, conf *config.Config) error {
}) })
for _, job := range confJobs { for _, job := range confJobs {
if config.IsInternalJobName(job.Name()) { if IsInternalJobName(job.Name()) {
panic(fmt.Sprintf("internal job name used for config job '%s'", job.Name())) //FIXME panic(fmt.Sprintf("internal job name used for config job '%s'", job.Name())) //FIXME
} }
} }
@@ -210,6 +218,10 @@ const (
jobNameControl = "_control" jobNameControl = "_control"
) )
func IsInternalJobName(s string) bool {
return strings.HasPrefix(s, "_")
}
func (s *jobs) start(ctx context.Context, j job.Job, internal bool) { func (s *jobs) start(ctx context.Context, j job.Job, internal bool) {
s.m.Lock() s.m.Lock()
defer s.m.Unlock() defer s.m.Unlock()
@@ -217,12 +229,10 @@ func (s *jobs) start(ctx context.Context, j job.Job, internal bool) {
ctx = logging.WithInjectedField(ctx, logging.JobField, j.Name()) ctx = logging.WithInjectedField(ctx, logging.JobField, j.Name())
jobName := j.Name() jobName := j.Name()
if !internal && IsInternalJobName(jobName) {
// package `config` enforces these with clean errors, these are just assertions
if !internal && config.IsInternalJobName(jobName) {
panic(fmt.Sprintf("internal job name used for non-internal job %s", jobName)) panic(fmt.Sprintf("internal job name used for non-internal job %s", jobName))
} }
if internal && !config.IsInternalJobName(jobName) { if internal && !IsInternalJobName(jobName) {
panic(fmt.Sprintf("internal job does not use internal job name %s", jobName)) panic(fmt.Sprintf("internal job does not use internal job name %s", jobName))
} }
if _, ok := s.jobs[jobName]; ok { if _, ok := s.jobs[jobName]; ok {
@@ -6,8 +6,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
type DatasetMapFilter struct { type DatasetMapFilter struct {
@@ -160,14 +160,6 @@ func (m DatasetMapFilter) Filter(p *zfs.DatasetPath) (pass bool, err error) {
return return
} }
func (m DatasetMapFilter) UserSpecifiedDatasets() (datasets zfs.UserSpecifiedDatasetsSet) {
datasets = make(zfs.UserSpecifiedDatasetsSet)
for i := range m.entries {
datasets[m.entries[i].path.ToString()] = true
}
return
}
// Construct a new filter-only DatasetMapFilter from a mapping // Construct a new filter-only DatasetMapFilter from a mapping
// The new filter allows exactly those paths that were not forbidden by the mapping. // The new filter allows exactly those paths that were not forbidden by the mapping.
func (m DatasetMapFilter) InvertedFilter() (inv *DatasetMapFilter, err error) { func (m DatasetMapFilter) InvertedFilter() (inv *DatasetMapFilter, err error) {
@@ -3,7 +3,7 @@ package filters
import ( import (
"testing" "testing"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
func TestDatasetMapFilter(t *testing.T) { func TestDatasetMapFilter(t *testing.T) {
@@ -3,8 +3,8 @@ package hooks
import ( import (
"fmt" "fmt"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
type List []Hook type List []Hook
@@ -7,7 +7,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
// Re-export type here so that // Re-export type here so that
@@ -6,9 +6,9 @@ import (
"context" "context"
"sync" "sync"
"github.com/zrepl/zrepl/internal/daemon/logging" "github.com/zrepl/zrepl/daemon/logging"
"github.com/zrepl/zrepl/internal/logger" "github.com/zrepl/zrepl/logger"
"github.com/zrepl/zrepl/internal/util/envconst" "github.com/zrepl/zrepl/util/envconst"
) )
type Logger = logger.Logger type Logger = logger.Logger
@@ -4,8 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/zrepl/zrepl/internal/daemon/filters" "github.com/zrepl/zrepl/daemon/filters"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
type HookJobCallback func(ctx context.Context) error type HookJobCallback func(ctx context.Context) error
@@ -12,11 +12,11 @@ import (
"sync" "sync"
"time" "time"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon/filters" "github.com/zrepl/zrepl/daemon/filters"
"github.com/zrepl/zrepl/internal/logger" "github.com/zrepl/zrepl/logger"
"github.com/zrepl/zrepl/internal/util/circlog" "github.com/zrepl/zrepl/util/circlog"
"github.com/zrepl/zrepl/internal/util/envconst" "github.com/zrepl/zrepl/util/envconst"
) )
type HookEnvVar string type HookEnvVar string
@@ -12,9 +12,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon/filters" "github.com/zrepl/zrepl/daemon/filters"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
// Hook to implement the following recommmendation from MySQL docs // Hook to implement the following recommmendation from MySQL docs
@@ -10,9 +10,9 @@ import (
"github.com/lib/pq" "github.com/lib/pq"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon/filters" "github.com/zrepl/zrepl/daemon/filters"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
type PgChkptHook struct { type PgChkptHook struct {
@@ -11,13 +11,13 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zrepl/zrepl/internal/daemon/logging/trace" "github.com/zrepl/zrepl/daemon/logging/trace"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon/hooks" "github.com/zrepl/zrepl/daemon/hooks"
"github.com/zrepl/zrepl/internal/daemon/logging" "github.com/zrepl/zrepl/daemon/logging"
"github.com/zrepl/zrepl/internal/logger" "github.com/zrepl/zrepl/logger"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
type comparisonAssertionFunc func(require.TestingT, interface{}, interface{}, ...interface{}) type comparisonAssertionFunc func(require.TestingT, interface{}, interface{}, ...interface{})
@@ -1,5 +1,6 @@
// Code generated by "enumer -type=StepStatus -trimprefix=Step"; DO NOT EDIT. // Code generated by "enumer -type=StepStatus -trimprefix=Step"; DO NOT EDIT.
//
package hooks package hooks
import ( import (
@@ -8,24 +8,25 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log"
"github.com/zrepl/zrepl/internal/daemon/logging/trace" "github.com/zrepl/zrepl/daemon/logging/trace"
"github.com/zrepl/zrepl/internal/util/envconst" "github.com/zrepl/zrepl/util/envconst"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon/job/reset" "github.com/zrepl/zrepl/daemon/job/reset"
"github.com/zrepl/zrepl/internal/daemon/job/wakeup" "github.com/zrepl/zrepl/daemon/job/wakeup"
"github.com/zrepl/zrepl/internal/daemon/pruner" "github.com/zrepl/zrepl/daemon/pruner"
"github.com/zrepl/zrepl/internal/daemon/snapper" "github.com/zrepl/zrepl/daemon/snapper"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/replication" "github.com/zrepl/zrepl/replication"
"github.com/zrepl/zrepl/internal/replication/driver" "github.com/zrepl/zrepl/replication/driver"
"github.com/zrepl/zrepl/internal/replication/logic" "github.com/zrepl/zrepl/replication/logic"
"github.com/zrepl/zrepl/internal/replication/report" "github.com/zrepl/zrepl/replication/report"
"github.com/zrepl/zrepl/internal/rpc" "github.com/zrepl/zrepl/rpc"
"github.com/zrepl/zrepl/internal/transport" "github.com/zrepl/zrepl/transport"
"github.com/zrepl/zrepl/internal/transport/fromconfig" "github.com/zrepl/zrepl/transport/fromconfig"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
type ActiveSide struct { type ActiveSide struct {
@@ -481,7 +482,7 @@ func (j *ActiveSide) do(ctx context.Context) {
go func() { go func() {
select { select {
case <-reset.Wait(ctx): case <-reset.Wait(ctx):
GetLogger(ctx).Info("reset received, cancelling current invocation") log.Info("reset received, cancelling current invocation")
cancelThisRun() cancelThisRun()
case <-ctx.Done(): case <-ctx.Done():
} }
@@ -6,8 +6,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/transport" "github.com/zrepl/zrepl/transport"
) )
func TestFakeActiveSideDirectMethodInvocationClientIdentityDoesNotPassValidityTest(t *testing.T) { func TestFakeActiveSideDirectMethodInvocationClientIdentityDoesNotPassValidityTest(t *testing.T) {
@@ -1,5 +1,6 @@
// Code generated by "enumer -type=ActiveSideState"; DO NOT EDIT. // Code generated by "enumer -type=ActiveSideState"; DO NOT EDIT.
//
package job package job
import ( import (
@@ -7,8 +7,8 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/util/bandwidthlimit" "github.com/zrepl/zrepl/util/bandwidthlimit"
) )
func JobsFromConfig(c *config.Config, parseFlags config.ParseFlags) ([]Job, error) { func JobsFromConfig(c *config.Config, parseFlags config.ParseFlags) ([]Job, error) {
@@ -24,6 +24,21 @@ func JobsFromConfig(c *config.Config, parseFlags config.ParseFlags) ([]Job, erro
js[i] = j js[i] = j
} }
// receiving-side root filesystems must not overlap
{
rfss := make([]string, 0, len(js))
for _, j := range js {
jrfs, ok := j.OwnedDatasetSubtreeRoot()
if !ok {
continue
}
rfss = append(rfss, jrfs.ToString())
}
if err := validateReceivingSidesDoNotOverlap(rfss); err != nil {
return nil, err
}
}
return js, nil return js, nil
} }
@@ -3,11 +3,11 @@ package job
import ( import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon/filters" "github.com/zrepl/zrepl/daemon/filters"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/util/nodefault" "github.com/zrepl/zrepl/util/nodefault"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
type SendingJobConfig interface { type SendingJobConfig interface {
@@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
) )
func TestValidateReceivingSidesDoNotOverlap(t *testing.T) { func TestValidateReceivingSidesDoNotOverlap(t *testing.T) {
@@ -113,7 +113,7 @@ jobs:
} }
func TestSampleConfigsAreBuiltWithoutErrors(t *testing.T) { func TestSampleConfigsAreBuiltWithoutErrors(t *testing.T) {
paths, err := filepath.Glob("../../../internal/config/samples/*") paths, err := filepath.Glob("../../config/samples/*")
if err != nil { if err != nil {
t.Errorf("glob failed: %+v", err) t.Errorf("glob failed: %+v", err)
} }
@@ -7,10 +7,10 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/zrepl/zrepl/internal/daemon/logging" "github.com/zrepl/zrepl/daemon/logging"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/logger" "github.com/zrepl/zrepl/logger"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
type Logger = logger.Logger type Logger = logger.Logger
@@ -7,16 +7,16 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/zrepl/zrepl/internal/daemon/logging/trace" "github.com/zrepl/zrepl/daemon/logging/trace"
"github.com/zrepl/zrepl/internal/config" "github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/internal/daemon/logging" "github.com/zrepl/zrepl/daemon/logging"
"github.com/zrepl/zrepl/internal/daemon/snapper" "github.com/zrepl/zrepl/daemon/snapper"
"github.com/zrepl/zrepl/internal/endpoint" "github.com/zrepl/zrepl/endpoint"
"github.com/zrepl/zrepl/internal/rpc" "github.com/zrepl/zrepl/rpc"
"github.com/zrepl/zrepl/internal/transport" "github.com/zrepl/zrepl/transport"
"github.com/zrepl/zrepl/internal/transport/fromconfig" "github.com/zrepl/zrepl/transport/fromconfig"
"github.com/zrepl/zrepl/internal/zfs" "github.com/zrepl/zrepl/zfs"
) )
type PassiveSide struct { type PassiveSide struct {

Some files were not shown because too many files have changed in this diff Show More