mirror of
https://github.com/kopia/kopia.git
synced 2025-12-23 22:57:50 -05:00
test(infra): improved support for in-process testing (#2169)
* feat(infra): improved support for in-process testing * support for killing of a running server using simulated Ctrl-C * support for overriding os.Stdin * migrated many tests from the exe runner to in-process runner * added required indirection when defining Envar() so we can later override it in tests * refactored CLI runners by moving environment overrides to CLITestEnv
This commit is contained in:
2
.github/workflows/race-detector.yml
vendored
2
.github/workflows/race-detector.yml
vendored
@@ -21,6 +21,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Unit Tests
|
||||
run: make -j2 test UNIT_TEST_RACE_FLAGS=-race
|
||||
run: make -j2 test UNIT_TEST_RACE_FLAGS=-race UNIT_TESTS_TIMEOUT=1200s
|
||||
- name: Integration Tests
|
||||
run: make -j2 ci-integration-tests INTEGRATION_TEST_RACE_FLAGS=-race
|
||||
|
||||
@@ -26,6 +26,7 @@ linters-settings:
|
||||
- time.Now # do not use outside of 'clock' and 'timetrack' packages use clock.Now or timetrack.StartTimer
|
||||
- time.Since # use timetrack.Timer.Elapsed()
|
||||
- time.Until # never use this
|
||||
- Envar\(\" # do not use envar literals, always wrap with EnvName()
|
||||
funlen:
|
||||
lines: 100
|
||||
statements: 60
|
||||
|
||||
8
Makefile
8
Makefile
@@ -42,7 +42,7 @@ GOTESTSUM_FLAGS=--format=$(GOTESTSUM_FORMAT) --no-summary=skipped
|
||||
GO_TEST?=$(gotestsum) $(GOTESTSUM_FLAGS) --
|
||||
|
||||
LINTER_DEADLINE=600s
|
||||
UNIT_TESTS_TIMEOUT=300s
|
||||
UNIT_TESTS_TIMEOUT=600s
|
||||
|
||||
ifeq ($(GOARCH),amd64)
|
||||
PARALLEL=8
|
||||
@@ -233,11 +233,13 @@ dev-deps:
|
||||
GO111MODULE=off go get -u github.com/sqs/goreturns
|
||||
|
||||
test-with-coverage: export KOPIA_COVERAGE_TEST=1
|
||||
test-with-coverage: $(gotestsum)
|
||||
test-with-coverage: export TESTING_ACTION_EXE ?= $(TESTING_ACTION_EXE)
|
||||
test-with-coverage: $(gotestsum) $(TESTING_ACTION_EXE)
|
||||
$(GO_TEST) $(UNIT_TEST_RACE_FLAGS) -tags testing -count=$(REPEAT_TEST) -short -covermode=atomic -coverprofile=coverage.txt --coverpkg $(COVERAGE_PACKAGES) -timeout 300s ./...
|
||||
|
||||
test: GOTESTSUM_FLAGS=--format=$(GOTESTSUM_FORMAT) --no-summary=skipped --jsonfile=.tmp.unit-tests.json
|
||||
test: $(gotestsum)
|
||||
test: export TESTING_ACTION_EXE ?= $(TESTING_ACTION_EXE)
|
||||
test: $(gotestsum) $(TESTING_ACTION_EXE)
|
||||
$(GO_TEST) $(UNIT_TEST_RACE_FLAGS) -tags testing -count=$(REPEAT_TEST) -timeout $(UNIT_TESTS_TIMEOUT) ./...
|
||||
-$(gotestsum) tool slowest --jsonfile .tmp.unit-tests.json --threshold 1000ms
|
||||
|
||||
|
||||
53
cli/app.go
53
cli/app.go
@@ -87,6 +87,9 @@ type appServices interface {
|
||||
getProgress() *cliProgress
|
||||
stdout() io.Writer
|
||||
Stderr() io.Writer
|
||||
stdin() io.Reader
|
||||
onCtrlC(callback func())
|
||||
EnvName(s string) string
|
||||
}
|
||||
|
||||
type advancedAppServices interface {
|
||||
@@ -150,17 +153,24 @@ type App struct {
|
||||
logs commandLogs
|
||||
|
||||
// testability hooks
|
||||
osExit func(int) // allows replacing os.Exit() with custom code
|
||||
stdoutWriter io.Writer
|
||||
stderrWriter io.Writer
|
||||
rootctx context.Context // nolint:containedctx
|
||||
loggerFactory logging.LoggerFactory
|
||||
osExit func(int) // allows replacing os.Exit() with custom code
|
||||
stdinReader io.Reader
|
||||
stdoutWriter io.Writer
|
||||
stderrWriter io.Writer
|
||||
rootctx context.Context // nolint:containedctx
|
||||
loggerFactory logging.LoggerFactory
|
||||
simulatedCtrlC chan bool
|
||||
envNamePrefix string
|
||||
}
|
||||
|
||||
func (c *App) getProgress() *cliProgress {
|
||||
return c.progress
|
||||
}
|
||||
|
||||
func (c *App) stdin() io.Reader {
|
||||
return c.stdinReader
|
||||
}
|
||||
|
||||
func (c *App) stdout() io.Writer {
|
||||
return c.stdoutWriter
|
||||
}
|
||||
@@ -222,21 +232,21 @@ func (c *App) setup(app *kingpin.Application) {
|
||||
app.Flag("auto-maintenance", "Automatic maintenance").Default("true").Hidden().BoolVar(&c.enableAutomaticMaintenance)
|
||||
|
||||
// hidden flags to control auto-update behavior.
|
||||
app.Flag("initial-update-check-delay", "Initial delay before first time update check").Default("24h").Hidden().Envar("KOPIA_INITIAL_UPDATE_CHECK_DELAY").DurationVar(&c.initialUpdateCheckDelay)
|
||||
app.Flag("update-check-interval", "Interval between update checks").Default("168h").Hidden().Envar("KOPIA_UPDATE_CHECK_INTERVAL").DurationVar(&c.updateCheckInterval)
|
||||
app.Flag("update-available-notify-interval", "Interval between update notifications").Default("1h").Hidden().Envar("KOPIA_UPDATE_NOTIFY_INTERVAL").DurationVar(&c.updateAvailableNotifyInterval)
|
||||
app.Flag("config-file", "Specify the config file to use").Default("repository.config").Envar("KOPIA_CONFIG_PATH").StringVar(&c.configPath)
|
||||
app.Flag("initial-update-check-delay", "Initial delay before first time update check").Default("24h").Hidden().Envar(c.EnvName("KOPIA_INITIAL_UPDATE_CHECK_DELAY")).DurationVar(&c.initialUpdateCheckDelay)
|
||||
app.Flag("update-check-interval", "Interval between update checks").Default("168h").Hidden().Envar(c.EnvName("KOPIA_UPDATE_CHECK_INTERVAL")).DurationVar(&c.updateCheckInterval)
|
||||
app.Flag("update-available-notify-interval", "Interval between update notifications").Default("1h").Hidden().Envar(c.EnvName("KOPIA_UPDATE_NOTIFY_INTERVAL")).DurationVar(&c.updateAvailableNotifyInterval)
|
||||
app.Flag("config-file", "Specify the config file to use").Default("repository.config").Envar(c.EnvName("KOPIA_CONFIG_PATH")).StringVar(&c.configPath)
|
||||
app.Flag("trace-storage", "Enables tracing of storage operations.").Default("true").Hidden().BoolVar(&c.traceStorage)
|
||||
app.Flag("timezone", "Format time according to specified time zone (local, utc, original or time zone name)").Hidden().StringVar(&timeZone)
|
||||
app.Flag("password", "Repository password.").Envar("KOPIA_PASSWORD").Short('p').StringVar(&c.password)
|
||||
app.Flag("persist-credentials", "Persist credentials").Default("true").Envar("KOPIA_PERSIST_CREDENTIALS_ON_CONNECT").BoolVar(&c.persistCredentials)
|
||||
app.Flag("disable-internal-log", "Disable internal log").Hidden().Envar("KOPIA_DISABLE_INTERNAL_LOG").BoolVar(&c.disableInternalLog)
|
||||
app.Flag("advanced-commands", "Enable advanced (and potentially dangerous) commands.").Hidden().Envar("KOPIA_ADVANCED_COMMANDS").StringVar(&c.AdvancedCommands)
|
||||
app.Flag("track-releasable", "Enable tracking of releasable resources.").Hidden().Envar("KOPIA_TRACK_RELEASABLE").StringsVar(&c.trackReleasable)
|
||||
app.Flag("password", "Repository password.").Envar(c.EnvName("KOPIA_PASSWORD")).Short('p').StringVar(&c.password)
|
||||
app.Flag("persist-credentials", "Persist credentials").Default("true").Envar(c.EnvName("KOPIA_PERSIST_CREDENTIALS_ON_CONNECT")).BoolVar(&c.persistCredentials)
|
||||
app.Flag("disable-internal-log", "Disable internal log").Hidden().Envar(c.EnvName("KOPIA_DISABLE_INTERNAL_LOG")).BoolVar(&c.disableInternalLog)
|
||||
app.Flag("advanced-commands", "Enable advanced (and potentially dangerous) commands.").Hidden().Envar(c.EnvName("KOPIA_ADVANCED_COMMANDS")).StringVar(&c.AdvancedCommands)
|
||||
app.Flag("track-releasable", "Enable tracking of releasable resources.").Hidden().Envar(c.EnvName("KOPIA_TRACK_RELEASABLE")).StringsVar(&c.trackReleasable)
|
||||
|
||||
c.observability.setup(app)
|
||||
c.observability.setup(c, app)
|
||||
|
||||
c.setupOSSpecificKeychainFlags(app)
|
||||
c.setupOSSpecificKeychainFlags(c, app)
|
||||
|
||||
_ = app.Flag("caching", "Enables caching of objects (disable with --no-caching)").Default("true").Hidden().Action(
|
||||
deprecatedFlag(c.stderrWriter, "The '--caching' flag is deprecated and has no effect, use 'kopia cache set' instead."),
|
||||
@@ -298,10 +308,21 @@ func NewApp() *App {
|
||||
osExit: os.Exit,
|
||||
stdoutWriter: colorable.NewColorableStdout(),
|
||||
stderrWriter: colorable.NewColorableStderr(),
|
||||
stdinReader: os.Stdin,
|
||||
rootctx: context.Background(),
|
||||
}
|
||||
}
|
||||
|
||||
// SetEnvNamePrefixForTesting sets the name prefix to be used for all environment variable names for testing.
|
||||
func (c *App) SetEnvNamePrefixForTesting(prefix string) {
|
||||
c.envNamePrefix = prefix
|
||||
}
|
||||
|
||||
// EnvName overrides the provided environment variable name for testability.
|
||||
func (c *App) EnvName(n string) string {
|
||||
return c.envNamePrefix + n
|
||||
}
|
||||
|
||||
// Attach attaches the CLI parser to the application.
|
||||
func (c *App) Attach(app *kingpin.Application) {
|
||||
c.setup(app)
|
||||
|
||||
@@ -26,7 +26,7 @@ func (c *commandDiff) setup(svc appServices, parent commandParent) {
|
||||
cmd.Arg("object-path1", "First object/path").Required().StringVar(&c.diffFirstObjectPath)
|
||||
cmd.Arg("object-path2", "Second object/path").Required().StringVar(&c.diffSecondObjectPath)
|
||||
cmd.Flag("files", "Compare files by launching diff command for all pairs of (old,new)").Short('f').BoolVar(&c.diffCompareFiles)
|
||||
cmd.Flag("diff-command", "Displays differences between two repository objects (files or directories)").Default(defaultDiffCommand()).Envar("KOPIA_DIFF").StringVar(&c.diffCommandCommand)
|
||||
cmd.Flag("diff-command", "Displays differences between two repository objects (files or directories)").Default(defaultDiffCommand()).Envar(svc.EnvName("KOPIA_DIFF")).StringVar(&c.diffCommandCommand)
|
||||
cmd.Action(svc.repositoryReaderAction(c.run))
|
||||
|
||||
c.out.setup(svc)
|
||||
|
||||
@@ -24,6 +24,8 @@ type commandMount struct {
|
||||
mountPreferWebDAV bool
|
||||
maxCachedEntries int
|
||||
maxCachedDirectories int
|
||||
|
||||
svc appServices
|
||||
}
|
||||
|
||||
func (c *commandMount) setup(svc appServices, parent commandParent) {
|
||||
@@ -41,6 +43,7 @@ func (c *commandMount) setup(svc appServices, parent commandParent) {
|
||||
cmd.Flag("max-cached-entries", "Limit the number of cached directory entries").Default("100000").IntVar(&c.maxCachedEntries)
|
||||
cmd.Flag("max-cached-dirs", "Limit the number of cached directories").Default("100").IntVar(&c.maxCachedDirectories)
|
||||
|
||||
c.svc = svc
|
||||
cmd.Action(svc.repositoryReaderAction(c.run))
|
||||
}
|
||||
|
||||
@@ -100,7 +103,7 @@ func (c *commandMount) run(ctx context.Context, rep repo.Repository) error {
|
||||
// Wait until ctrl-c pressed or until the directory is unmounted.
|
||||
ctrlCPressed := make(chan bool)
|
||||
|
||||
onCtrlC(func() {
|
||||
c.svc.onCtrlC(func() {
|
||||
close(ctrlCPressed)
|
||||
})
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ type commandRepositoryChangePassword struct {
|
||||
|
||||
func (c *commandRepositoryChangePassword) setup(svc advancedAppServices, parent commandParent) {
|
||||
cmd := parent.Command("change-password", "Change repository password")
|
||||
cmd.Flag("new-password", "New password").Envar("KOPIA_NEW_PASSWORD").StringVar(&c.newPassword)
|
||||
cmd.Flag("new-password", "New password").Envar(svc.EnvName("KOPIA_NEW_PASSWORD")).StringVar(&c.newPassword)
|
||||
|
||||
c.svc = svc
|
||||
cmd.Action(svc.directRepositoryWriteAction(c.run))
|
||||
|
||||
@@ -33,14 +33,12 @@ func (s *formatSpecificTestSuite) TestRepositoryChangePassword(t *testing.T) {
|
||||
// at this point env2 stops working
|
||||
env2.RunAndExpectFailure(t, "snapshot", "ls")
|
||||
|
||||
r3 := testenv.NewInProcRunner(t)
|
||||
|
||||
// new connections will fail when using old (default) password
|
||||
env3 := testenv.NewCLITest(t, s.formatFlags, r3)
|
||||
env3 := testenv.NewCLITest(t, s.formatFlags, testenv.NewInProcRunner(t))
|
||||
env3.RunAndExpectFailure(t, "repo", "connect", "filesystem", "--path", env1.RepoDir, "--disable-repository-format-cache")
|
||||
|
||||
// new connections will succeed when using new password
|
||||
r3.RepoPassword = "newPass"
|
||||
env3.Environment["KOPIA_PASSWORD"] = "newPass"
|
||||
|
||||
env3.RunAndExpectSuccess(t, "repo", "connect", "filesystem", "--path", env1.RepoDir, "--disable-repository-format-cache")
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ type commandRepositoryConnect struct {
|
||||
func (c *commandRepositoryConnect) setup(svc advancedAppServices, parent commandParent) {
|
||||
cmd := parent.Command("connect", "Connect to a repository.")
|
||||
|
||||
c.co.setup(cmd)
|
||||
c.co.setup(svc, cmd)
|
||||
c.server.setup(svc, cmd, &c.co)
|
||||
|
||||
for _, prov := range svc.storageProviders() {
|
||||
@@ -61,16 +61,16 @@ type connectOptions struct {
|
||||
disableFormatBlobCache bool
|
||||
}
|
||||
|
||||
func (c *connectOptions) setup(cmd *kingpin.CmdClause) {
|
||||
func (c *connectOptions) setup(svc appServices, cmd *kingpin.CmdClause) {
|
||||
// Set up flags shared between 'create' and 'connect'. Note that because those flags are used by both command
|
||||
// we must use *Var() methods, otherwise one of the commands would always get default flag values.
|
||||
cmd.Flag("cache-directory", "Cache directory").PlaceHolder("PATH").Envar("KOPIA_CACHE_DIRECTORY").StringVar(&c.connectCacheDirectory)
|
||||
cmd.Flag("cache-directory", "Cache directory").PlaceHolder("PATH").Envar(svc.EnvName("KOPIA_CACHE_DIRECTORY")).StringVar(&c.connectCacheDirectory)
|
||||
cmd.Flag("content-cache-size-mb", "Size of local content cache").PlaceHolder("MB").Default("5000").Int64Var(&c.connectMaxCacheSizeMB)
|
||||
cmd.Flag("metadata-cache-size-mb", "Size of local metadata cache").PlaceHolder("MB").Default("5000").Int64Var(&c.connectMaxMetadataCacheSizeMB)
|
||||
cmd.Flag("max-list-cache-duration", "Duration of index cache").Default("30s").Hidden().DurationVar(&c.connectMaxListCacheDuration)
|
||||
cmd.Flag("override-hostname", "Override hostname used by this repository connection").Hidden().StringVar(&c.connectHostname)
|
||||
cmd.Flag("override-username", "Override username used by this repository connection").Hidden().StringVar(&c.connectUsername)
|
||||
cmd.Flag("check-for-updates", "Periodically check for Kopia updates on GitHub").Default("true").Envar(checkForUpdatesEnvar).BoolVar(&c.connectCheckForUpdates)
|
||||
cmd.Flag("check-for-updates", "Periodically check for Kopia updates on GitHub").Default("true").Envar(svc.EnvName(checkForUpdatesEnvar)).BoolVar(&c.connectCheckForUpdates)
|
||||
cmd.Flag("readonly", "Make repository read-only to avoid accidental changes").BoolVar(&c.connectReadonly)
|
||||
cmd.Flag("description", "Human-readable description of the repository").StringVar(&c.connectDescription)
|
||||
cmd.Flag("enable-actions", "Allow snapshot actions").BoolVar(&c.connectEnableActions)
|
||||
|
||||
@@ -48,7 +48,7 @@ func (c *commandRepositoryCreate) setup(svc advancedAppServices, parent commandP
|
||||
cmd.Flag("retention-mode", "Set the blob retention-mode for supported storage backends.").EnumVar(&c.retentionMode, blob.Governance.String(), blob.Compliance.String())
|
||||
cmd.Flag("retention-period", "Set the blob retention-period for supported storage backends.").DurationVar(&c.retentionPeriod)
|
||||
|
||||
c.co.setup(cmd)
|
||||
c.co.setup(svc, cmd)
|
||||
c.svc = svc
|
||||
c.out.setup(svc)
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ func (c *commandRestore) setup(svc appServices, parent commandParent) {
|
||||
cmd.Flag("overwrite-files", "Specifies whether or not to overwrite already existing files").Default("true").BoolVar(&c.restoreOverwriteFiles)
|
||||
cmd.Flag("overwrite-symlinks", "Specifies whether or not to overwrite already existing symlinks").Default("true").BoolVar(&c.restoreOverwriteSymlinks)
|
||||
cmd.Flag("write-sparse-files", "When doing a restore, attempt to write files sparsely-allocating the minimum amount of disk space needed.").Default("false").BoolVar(&c.restoreWriteSparseFiles)
|
||||
cmd.Flag("consistent-attributes", "When multiple snapshots match, fail if they have inconsistent attributes").Envar("KOPIA_RESTORE_CONSISTENT_ATTRIBUTES").BoolVar(&c.restoreConsistentAttributes)
|
||||
cmd.Flag("consistent-attributes", "When multiple snapshots match, fail if they have inconsistent attributes").Envar(svc.EnvName("KOPIA_RESTORE_CONSISTENT_ATTRIBUTES")).BoolVar(&c.restoreConsistentAttributes)
|
||||
cmd.Flag("mode", "Override restore mode").Default(restoreModeAuto).EnumVar(&c.restoreMode, restoreModeAuto, restoreModeLocal, restoreModeZip, restoreModeZipNoCompress, restoreModeTar, restoreModeTgz)
|
||||
cmd.Flag("parallel", "Restore parallelism (1=disable)").Default("8").IntVar(&c.restoreParallel)
|
||||
cmd.Flag("skip-owners", "Skip owners during restore").BoolVar(&c.restoreSkipOwners)
|
||||
|
||||
@@ -28,10 +28,10 @@ type serverFlags struct {
|
||||
serverPassword string
|
||||
}
|
||||
|
||||
func (c *serverFlags) setup(cmd *kingpin.CmdClause) {
|
||||
func (c *serverFlags) setup(svc appServices, cmd *kingpin.CmdClause) {
|
||||
cmd.Flag("address", "Server address").Default("http://127.0.0.1:51515").StringVar(&c.serverAddress)
|
||||
cmd.Flag("server-username", "HTTP server username (basic auth)").Envar("KOPIA_SERVER_USERNAME").Default("kopia").StringVar(&c.serverUsername)
|
||||
cmd.Flag("server-password", "HTTP server password (basic auth)").Envar("KOPIA_SERVER_PASSWORD").StringVar(&c.serverPassword)
|
||||
cmd.Flag("server-username", "HTTP server username (basic auth)").Envar(svc.EnvName("KOPIA_SERVER_USERNAME")).Default("kopia").StringVar(&c.serverUsername)
|
||||
cmd.Flag("server-password", "HTTP server password (basic auth)").Envar(svc.EnvName("KOPIA_SERVER_PASSWORD")).StringVar(&c.serverPassword)
|
||||
}
|
||||
|
||||
type serverClientFlags struct {
|
||||
@@ -41,18 +41,18 @@ type serverClientFlags struct {
|
||||
serverCertFingerprint string
|
||||
}
|
||||
|
||||
func (c *serverClientFlags) setup(cmd *kingpin.CmdClause) {
|
||||
func (c *serverClientFlags) setup(svc appServices, cmd *kingpin.CmdClause) {
|
||||
c.serverUsername = "server-control"
|
||||
|
||||
cmd.Flag("address", "Address of the server to connect to").Envar("KOPIA_SERVER_ADDRESS").Default("http://127.0.0.1:51515").StringVar(&c.serverAddress)
|
||||
cmd.Flag("server-control-username", "Server control username").Envar("KOPIA_SERVER_USERNAME").StringVar(&c.serverUsername)
|
||||
cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar("KOPIA_SERVER_PASSWORD").StringVar(&c.serverPassword)
|
||||
cmd.Flag("address", "Address of the server to connect to").Envar(svc.EnvName("KOPIA_SERVER_ADDRESS")).Default("http://127.0.0.1:51515").StringVar(&c.serverAddress)
|
||||
cmd.Flag("server-control-username", "Server control username").Envar(svc.EnvName("KOPIA_SERVER_USERNAME")).StringVar(&c.serverUsername)
|
||||
cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar(svc.EnvName("KOPIA_SERVER_PASSWORD")).StringVar(&c.serverPassword)
|
||||
|
||||
// aliases for backwards compat
|
||||
cmd.Flag("server-username", "Server control username").Hidden().StringVar(&c.serverUsername)
|
||||
cmd.Flag("server-password", "Server control password").Hidden().StringVar(&c.serverPassword)
|
||||
|
||||
cmd.Flag("server-cert-fingerprint", "Server certificate fingerprint").PlaceHolder("SHA256-FINGERPRINT").Envar("KOPIA_SERVER_CERT_FINGERPRINT").StringVar(&c.serverCertFingerprint)
|
||||
cmd.Flag("server-cert-fingerprint", "Server certificate fingerprint").PlaceHolder("SHA256-FINGERPRINT").Envar(svc.EnvName("KOPIA_SERVER_CERT_FINGERPRINT")).StringVar(&c.serverCertFingerprint)
|
||||
}
|
||||
|
||||
func (c *commandServer) setup(svc advancedAppServices, parent commandParent) {
|
||||
|
||||
@@ -13,7 +13,7 @@ type commandServerFlush struct {
|
||||
|
||||
func (c *commandServerFlush) setup(svc appServices, parent commandParent) {
|
||||
cmd := parent.Command("flush", "Flush the state of Kopia server to persistent storage, etc.")
|
||||
c.sf.setup(cmd)
|
||||
c.sf.setup(svc, cmd)
|
||||
cmd.Action(svc.serverAction(&c.sf, c.run))
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ type commandServerRefresh struct {
|
||||
|
||||
func (c *commandServerRefresh) setup(svc appServices, parent commandParent) {
|
||||
cmd := parent.Command("refresh", "Refresh the cache in Kopia server to observe new sources, etc.")
|
||||
c.sf.setup(cmd)
|
||||
c.sf.setup(svc, cmd)
|
||||
cmd.Action(svc.serverAction(&c.sf, c.run))
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ type commandServerShutdown struct {
|
||||
|
||||
func (c *commandServerShutdown) setup(svc appServices, parent commandParent) {
|
||||
cmd := parent.Command("shutdown", "Gracefully shutdown the server")
|
||||
c.sf.setup(cmd)
|
||||
c.sf.setup(svc, cmd)
|
||||
c.out.setup(svc)
|
||||
cmd.Action(svc.serverAction(&c.sf, c.run))
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func (c *commandServerSourceManagerAction) setup(svc appServices, cmd *kingpin.C
|
||||
cmd.Flag("all", "All paths managed by server").BoolVar(&c.all)
|
||||
cmd.Arg("source", "Source path managed by server").StringVar(&c.source)
|
||||
|
||||
c.sf.setup(cmd)
|
||||
c.sf.setup(svc, cmd)
|
||||
c.out.setup(svc)
|
||||
}
|
||||
|
||||
|
||||
@@ -90,10 +90,10 @@ func (c *commandServerStart) setup(svc advancedAppServices, parent commandParent
|
||||
cmd.Flag("htpasswd-file", "Path to htpasswd file that contains allowed user@hostname entries").Hidden().ExistingFileVar(&c.serverStartHtpasswdFile)
|
||||
|
||||
cmd.Flag("random-server-control-password", "Generate random server control password and print to stderr").Hidden().BoolVar(&c.randomServerControlPassword)
|
||||
cmd.Flag("server-control-username", "Server control username").Default("server-control").Envar("KOPIA_SERVER_CONTROL_USER").StringVar(&c.serverControlUsername)
|
||||
cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar("KOPIA_SERVER_CONTROL_PASSWORD").StringVar(&c.serverControlPassword)
|
||||
cmd.Flag("server-control-username", "Server control username").Default("server-control").Envar(svc.EnvName("KOPIA_SERVER_CONTROL_USER")).StringVar(&c.serverControlUsername)
|
||||
cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar(svc.EnvName("KOPIA_SERVER_CONTROL_PASSWORD")).StringVar(&c.serverControlPassword)
|
||||
|
||||
cmd.Flag("auth-cookie-signing-key", "Force particular auth cookie signing key").Envar("KOPIA_AUTH_COOKIE_SIGNING_KEY").Hidden().StringVar(&c.serverAuthCookieSingingKey)
|
||||
cmd.Flag("auth-cookie-signing-key", "Force particular auth cookie signing key").Envar(svc.EnvName("KOPIA_AUTH_COOKIE_SIGNING_KEY")).Hidden().StringVar(&c.serverAuthCookieSingingKey)
|
||||
|
||||
cmd.Flag("shutdown-on-stdin", "Shut down the server when stdin handle has closed.").Hidden().BoolVar(&c.serverStartShutdownWhenStdinClosed)
|
||||
|
||||
@@ -106,14 +106,14 @@ func (c *commandServerStart) setup(svc advancedAppServices, parent commandParent
|
||||
cmd.Flag("tls-print-server-cert", "Print server certificate").Hidden().BoolVar(&c.serverStartTLSPrintFullServerCert)
|
||||
|
||||
cmd.Flag("async-repo-connect", "Connect to repository asynchronously").Hidden().BoolVar(&c.asyncRepoConnect)
|
||||
cmd.Flag("ui-title-prefix", "UI title prefix").Hidden().Envar("KOPIA_UI_TITLE_PREFIX").StringVar(&c.uiTitlePrefix)
|
||||
cmd.Flag("ui-title-prefix", "UI title prefix").Hidden().Envar(svc.EnvName("KOPIA_UI_TITLE_PREFIX")).StringVar(&c.uiTitlePrefix)
|
||||
cmd.Flag("ui-preferences-file", "Path to JSON file storing UI preferences").StringVar(&c.uiPreferencesFile)
|
||||
|
||||
cmd.Flag("log-server-requests", "Log server requests").Hidden().BoolVar(&c.logServerRequests)
|
||||
cmd.Flag("disable-csrf-token-checks", "Disable CSRF token").Hidden().BoolVar(&c.disableCSRFTokenChecks)
|
||||
|
||||
c.sf.setup(cmd)
|
||||
c.co.setup(cmd)
|
||||
c.sf.setup(svc, cmd)
|
||||
c.co.setup(svc, cmd)
|
||||
c.svc = svc
|
||||
c.out.setup(svc)
|
||||
|
||||
@@ -192,7 +192,7 @@ func (c *commandServerStart) run(ctx context.Context) error {
|
||||
|
||||
srv.OnShutdown = httpServer.Shutdown
|
||||
|
||||
onCtrlC(func() {
|
||||
c.svc.onCtrlC(func() {
|
||||
log(ctx).Infof("Shutting down...")
|
||||
|
||||
if err = httpServer.Shutdown(ctx); err != nil {
|
||||
|
||||
@@ -22,7 +22,7 @@ func (c *commandServerStatus) setup(svc appServices, parent commandParent) {
|
||||
|
||||
cmd.Flag("remote", "Show remote sources").BoolVar(&c.remote)
|
||||
|
||||
c.sf.setup(cmd)
|
||||
c.sf.setup(svc, cmd)
|
||||
c.out.setup(svc)
|
||||
|
||||
cmd.Action(svc.serverAction(&c.sf, c.runServerStatus))
|
||||
|
||||
@@ -17,7 +17,7 @@ type commandServerThrottleGet struct {
|
||||
|
||||
func (c *commandServerThrottleGet) setup(svc appServices, parent commandParent) {
|
||||
cmd := parent.Command("get", "Get throttling parameters for a running server")
|
||||
c.sf.setup(cmd)
|
||||
c.sf.setup(svc, cmd)
|
||||
c.ctg.setup(svc, cmd)
|
||||
cmd.Action(svc.serverAction(&c.sf, c.run))
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type commandServerThrottleSet struct {
|
||||
|
||||
func (c *commandServerThrottleSet) setup(svc appServices, parent commandParent) {
|
||||
cmd := parent.Command("set", "Set throttling parameters for a running server")
|
||||
c.sf.setup(cmd)
|
||||
c.sf.setup(svc, cmd)
|
||||
c.cts.setup(cmd)
|
||||
|
||||
cmd.Action(svc.serverAction(&c.sf, c.run))
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -58,7 +57,7 @@ func (c *commandSnapshotCreate) setup(svc appServices, parent commandParent) {
|
||||
cmd.Flag("upload-limit-mb", "Stop the backup process after the specified amount of data (in MB) has been uploaded.").PlaceHolder("MB").Default("0").Int64Var(&c.snapshotCreateCheckpointUploadLimitMB)
|
||||
cmd.Flag("checkpoint-interval", "Frequency for creating periodic checkpoint.").DurationVar(&c.snapshotCreateCheckpointInterval)
|
||||
cmd.Flag("description", "Free-form snapshot description.").StringVar(&c.snapshotCreateDescription)
|
||||
cmd.Flag("fail-fast", "Fail fast when creating snapshot.").Envar("KOPIA_SNAPSHOT_FAIL_FAST").BoolVar(&c.snapshotCreateFailFast)
|
||||
cmd.Flag("fail-fast", "Fail fast when creating snapshot.").Envar(svc.EnvName("KOPIA_SNAPSHOT_FAIL_FAST")).BoolVar(&c.snapshotCreateFailFast)
|
||||
cmd.Flag("force-hash", "Force hashing of source files for a given percentage of files [0.0 .. 100.0]").Default("0").Float64Var(&c.snapshotCreateForceHash)
|
||||
cmd.Flag("parallel", "Upload N files in parallel").PlaceHolder("N").Default("0").IntVar(&c.snapshotCreateParallelUploads)
|
||||
cmd.Flag("start-time", "Override snapshot start timestamp.").StringVar(&c.snapshotCreateStartTime)
|
||||
@@ -234,7 +233,7 @@ func (c *commandSnapshotCreate) setupUploader(rep repo.RepositoryWriter) *snapsh
|
||||
u.CheckpointInterval = interval
|
||||
}
|
||||
|
||||
onCtrlC(u.Cancel)
|
||||
c.svc.onCtrlC(u.Cancel)
|
||||
|
||||
u.ForceHashPercentage = c.snapshotCreateForceHash
|
||||
u.ParallelUploads = c.snapshotCreateParallelUploads
|
||||
@@ -274,7 +273,7 @@ func (c *commandSnapshotCreate) snapshotSingleSource(ctx context.Context, rep re
|
||||
// stdin source will be snapshotted using a virtual static root directory with a single streaming file entry
|
||||
// Create a new static directory with the given name and add a streaming file entry with os.Stdin reader
|
||||
fsEntry = virtualfs.NewStaticDirectory(sourceInfo.Path, []fs.Entry{
|
||||
virtualfs.StreamingFileFromReader(c.snapshotCreateStdinFileName, os.Stdin),
|
||||
virtualfs.StreamingFileFromReader(c.snapshotCreateStdinFileName, c.svc.stdin()),
|
||||
})
|
||||
setManual = true
|
||||
} else {
|
||||
|
||||
@@ -68,7 +68,7 @@ func (c *commandSnapshotMigrate) run(ctx context.Context, destRepo repo.Reposito
|
||||
|
||||
c.svc.getProgress().StartShared()
|
||||
|
||||
onCtrlC(func() {
|
||||
c.svc.onCtrlC(func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
|
||||
@@ -25,12 +25,20 @@ func deprecatedFlag(w io.Writer, help string) func(_ *kingpin.ParseContext) erro
|
||||
}
|
||||
}
|
||||
|
||||
func onCtrlC(f func()) {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
func (c *App) onCtrlC(f func()) {
|
||||
s := make(chan os.Signal, 1)
|
||||
signal.Notify(s, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
<-c
|
||||
// invoke the function when either real or simulated Ctrl-C signal is delivered
|
||||
select {
|
||||
case v := <-c.simulatedCtrlC:
|
||||
if !v {
|
||||
return
|
||||
}
|
||||
|
||||
case <-s:
|
||||
}
|
||||
f()
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -7,18 +7,23 @@
|
||||
"github.com/alecthomas/kingpin"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kopia/kopia/internal/releasable"
|
||||
"github.com/kopia/kopia/repo/logging"
|
||||
)
|
||||
|
||||
// RunSubcommand executes the subcommand asynchronously in current process
|
||||
// with flags in an isolated CLI environment and returns standard output and standard error.
|
||||
func (c *App) RunSubcommand(ctx context.Context, kpapp *kingpin.Application, argsAndFlags []string) (stdout, stderr io.Reader, wait func() error, kill func()) {
|
||||
func (c *App) RunSubcommand(ctx context.Context, kpapp *kingpin.Application, stdin io.Reader, argsAndFlags []string) (stdout, stderr io.Reader, wait func() error, kill func()) {
|
||||
stdoutReader, stdoutWriter := io.Pipe()
|
||||
stderrReader, stderrWriter := io.Pipe()
|
||||
|
||||
c.stdinReader = stdin
|
||||
c.stdoutWriter = stdoutWriter
|
||||
c.stderrWriter = stderrWriter
|
||||
c.rootctx = logging.WithLogger(ctx, logging.ToWriter(stderrWriter))
|
||||
c.simulatedCtrlC = make(chan bool, 1)
|
||||
|
||||
releasable.Created("simulated-ctrl-c", c.simulatedCtrlC)
|
||||
|
||||
c.Attach(kpapp)
|
||||
|
||||
@@ -31,6 +36,11 @@ func (c *App) RunSubcommand(ctx context.Context, kpapp *kingpin.Application, arg
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
close(c.simulatedCtrlC)
|
||||
releasable.Released("simulated-ctrl-c", c.simulatedCtrlC)
|
||||
}()
|
||||
|
||||
defer close(resultErr)
|
||||
defer stderrWriter.Close() //nolint:errcheck
|
||||
defer stdoutWriter.Close() //nolint:errcheck
|
||||
@@ -48,6 +58,9 @@ func (c *App) RunSubcommand(ctx context.Context, kpapp *kingpin.Application, arg
|
||||
}()
|
||||
|
||||
return stdoutReader, stderrReader, func() error {
|
||||
return <-resultErr
|
||||
}, func() {}
|
||||
return <-resultErr
|
||||
}, func() {
|
||||
// deliver simulated Ctrl-C to the app.
|
||||
c.simulatedCtrlC <- true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,19 +52,19 @@ type observabilityFlags struct {
|
||||
traceProvider *trace.TracerProvider
|
||||
}
|
||||
|
||||
func (c *observabilityFlags) setup(app *kingpin.Application) {
|
||||
func (c *observabilityFlags) setup(svc appServices, app *kingpin.Application) {
|
||||
app.Flag("metrics-listen-addr", "Expose Prometheus metrics on a given host:port").Hidden().StringVar(&c.metricsListenAddr)
|
||||
app.Flag("enable-pprof", "Expose pprof handlers").Hidden().BoolVar(&c.enablePProf)
|
||||
|
||||
// push gateway parameters
|
||||
app.Flag("metrics-push-addr", "Address of push gateway").Envar("KOPIA_METRICS_PUSH_ADDR").Hidden().StringVar(&c.metricsPushAddr)
|
||||
app.Flag("metrics-push-interval", "Frequency of metrics push").Envar("KOPIA_METRICS_PUSH_INTERVAL").Hidden().Default("5s").DurationVar(&c.metricsPushInterval)
|
||||
app.Flag("metrics-push-job", "Job ID for to push gateway").Envar("KOPIA_METRICS_JOB").Hidden().Default("kopia").StringVar(&c.metricsJob)
|
||||
app.Flag("metrics-push-grouping", "Grouping for push gateway").Envar("KOPIA_METRICS_PUSH_GROUPING").Hidden().StringsVar(&c.metricsGroupings)
|
||||
app.Flag("metrics-push-username", "Username for push gateway").Envar("KOPIA_METRICS_PUSH_USERNAME").Hidden().StringVar(&c.metricsPushUsername)
|
||||
app.Flag("metrics-push-password", "Password for push gateway").Envar("KOPIA_METRICS_PUSH_PASSWORD").Hidden().StringVar(&c.metricsPushPassword)
|
||||
app.Flag("metrics-push-addr", "Address of push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_ADDR")).Hidden().StringVar(&c.metricsPushAddr)
|
||||
app.Flag("metrics-push-interval", "Frequency of metrics push").Envar(svc.EnvName("KOPIA_METRICS_PUSH_INTERVAL")).Hidden().Default("5s").DurationVar(&c.metricsPushInterval)
|
||||
app.Flag("metrics-push-job", "Job ID for to push gateway").Envar(svc.EnvName("KOPIA_METRICS_JOB")).Hidden().Default("kopia").StringVar(&c.metricsJob)
|
||||
app.Flag("metrics-push-grouping", "Grouping for push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_GROUPING")).Hidden().StringsVar(&c.metricsGroupings)
|
||||
app.Flag("metrics-push-username", "Username for push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_USERNAME")).Hidden().StringVar(&c.metricsPushUsername)
|
||||
app.Flag("metrics-push-password", "Password for push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_PASSWORD")).Hidden().StringVar(&c.metricsPushPassword)
|
||||
|
||||
app.Flag("enable-jaeger-collector", "Emit OpenTelemetry traces to Jaeger collector").Hidden().Envar("KOPIA_ENABLE_JAEGER_COLLECTOR").BoolVar(&c.enableJaeger)
|
||||
app.Flag("enable-jaeger-collector", "Emit OpenTelemetry traces to Jaeger collector").Hidden().Envar(svc.EnvName("KOPIA_ENABLE_JAEGER_COLLECTOR")).BoolVar(&c.enableJaeger)
|
||||
|
||||
var formats []string
|
||||
|
||||
@@ -74,7 +74,7 @@ func (c *observabilityFlags) setup(app *kingpin.Application) {
|
||||
|
||||
sort.Strings(formats)
|
||||
|
||||
app.Flag("metrics-push-format", "Format to use for push gateway").Envar("KOPIA_METRICS_FORMAT").Hidden().EnumVar(&c.metricsPushFormat, formats...)
|
||||
app.Flag("metrics-push-format", "Format to use for push gateway").Envar(svc.EnvName("KOPIA_METRICS_FORMAT")).Hidden().EnumVar(&c.metricsPushFormat, formats...)
|
||||
}
|
||||
|
||||
func (c *observabilityFlags) startMetrics(ctx context.Context) error {
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"github.com/alecthomas/kingpin"
|
||||
)
|
||||
|
||||
func (c *App) setupOSSpecificKeychainFlags(app *kingpin.Application) {
|
||||
func (c *App) setupOSSpecificKeychainFlags(svc appServices, app *kingpin.Application) {
|
||||
app.Flag("use-keychain", "Use macOS Keychain for storing repository password.").Default("true").BoolVar(&c.keyRingEnabled)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"github.com/alecthomas/kingpin"
|
||||
)
|
||||
|
||||
func (c *App) setupOSSpecificKeychainFlags(app *kingpin.Application) {
|
||||
app.Flag("use-keyring", "Use Gnome Keyring for storing repository password.").Default("false").Envar("KOPIA_USE_KEYRING").BoolVar(&c.keyRingEnabled)
|
||||
func (c *App) setupOSSpecificKeychainFlags(svc appServices, app *kingpin.Application) {
|
||||
app.Flag("use-keyring", "Use Gnome Keyring for storing repository password.").Default("false").Envar(svc.EnvName("KOPIA_USE_KEYRING")).BoolVar(&c.keyRingEnabled)
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"github.com/alecthomas/kingpin"
|
||||
)
|
||||
|
||||
func (c *App) setupOSSpecificKeychainFlags(app *kingpin.Application) {
|
||||
func (c *App) setupOSSpecificKeychainFlags(svc appServices, app *kingpin.Application) {
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
"github.com/alecthomas/kingpin"
|
||||
)
|
||||
|
||||
func (c *App) setupOSSpecificKeychainFlags(app *kingpin.Application) {
|
||||
func (c *App) setupOSSpecificKeychainFlags(svc appServices, app *kingpin.Application) {
|
||||
app.Flag("use-credential-manager", "Use Windows Credential Manager for storing repository password.").Default("true").BoolVar(&c.keyRingEnabled)
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ type storageAzureFlags struct {
|
||||
azOptions azure.Options
|
||||
}
|
||||
|
||||
func (c *storageAzureFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
|
||||
func (c *storageAzureFlags) Setup(svc StorageProviderServices, cmd *kingpin.CmdClause) {
|
||||
cmd.Flag("container", "Name of the Azure blob container").Required().StringVar(&c.azOptions.Container)
|
||||
cmd.Flag("storage-account", "Azure storage account name (overrides AZURE_STORAGE_ACCOUNT environment variable)").Required().Envar("AZURE_STORAGE_ACCOUNT").StringVar(&c.azOptions.StorageAccount)
|
||||
cmd.Flag("storage-key", "Azure storage account key (overrides AZURE_STORAGE_KEY environment variable)").Envar("AZURE_STORAGE_KEY").StringVar(&c.azOptions.StorageKey)
|
||||
cmd.Flag("storage-domain", "Azure storage domain").Envar("AZURE_STORAGE_DOMAIN").StringVar(&c.azOptions.StorageDomain)
|
||||
cmd.Flag("sas-token", "Azure SAS Token").Envar("AZURE_STORAGE_SAS_TOKEN").StringVar(&c.azOptions.SASToken)
|
||||
cmd.Flag("storage-account", "Azure storage account name (overrides AZURE_STORAGE_ACCOUNT environment variable)").Required().Envar(svc.EnvName("AZURE_STORAGE_ACCOUNT")).StringVar(&c.azOptions.StorageAccount)
|
||||
cmd.Flag("storage-key", "Azure storage account key (overrides AZURE_STORAGE_KEY environment variable)").Envar(svc.EnvName("AZURE_STORAGE_KEY")).StringVar(&c.azOptions.StorageKey)
|
||||
cmd.Flag("storage-domain", "Azure storage domain").Envar(svc.EnvName("AZURE_STORAGE_DOMAIN")).StringVar(&c.azOptions.StorageDomain)
|
||||
cmd.Flag("sas-token", "Azure SAS Token").Envar(svc.EnvName("AZURE_STORAGE_SAS_TOKEN")).StringVar(&c.azOptions.SASToken)
|
||||
cmd.Flag("prefix", "Prefix to use for objects in the bucket").StringVar(&c.azOptions.Prefix)
|
||||
|
||||
commonThrottlingFlags(cmd, &c.azOptions.Limits)
|
||||
|
||||
@@ -13,10 +13,10 @@ type storageB2Flags struct {
|
||||
b2options b2.Options
|
||||
}
|
||||
|
||||
func (c *storageB2Flags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
|
||||
func (c *storageB2Flags) Setup(svc StorageProviderServices, cmd *kingpin.CmdClause) {
|
||||
cmd.Flag("bucket", "Name of the B2 bucket").Required().StringVar(&c.b2options.BucketName)
|
||||
cmd.Flag("key-id", "Key ID (overrides B2_KEY_ID environment variable)").Required().Envar("B2_KEY_ID").StringVar(&c.b2options.KeyID)
|
||||
cmd.Flag("key", "Secret key (overrides B2_KEY environment variable)").Required().Envar("B2_KEY").StringVar(&c.b2options.Key)
|
||||
cmd.Flag("key-id", "Key ID (overrides B2_KEY_ID environment variable)").Required().Envar(svc.EnvName("B2_KEY_ID")).StringVar(&c.b2options.KeyID)
|
||||
cmd.Flag("key", "Secret key (overrides B2_KEY environment variable)").Required().Envar(svc.EnvName("B2_KEY")).StringVar(&c.b2options.Key)
|
||||
cmd.Flag("prefix", "Prefix to use for objects in the bucket").StringVar(&c.b2options.Prefix)
|
||||
commonThrottlingFlags(cmd, &c.b2options.Limits)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// StorageProviderServices is implemented by the cli App that allows the cli
|
||||
// and tests to mutate the default storage providers.
|
||||
type StorageProviderServices interface {
|
||||
EnvName(s string) string
|
||||
setPasswordFromToken(pwd string)
|
||||
storageProviders() []StorageProvider
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ type storageS3Flags struct {
|
||||
s3options s3.Options
|
||||
}
|
||||
|
||||
func (c *storageS3Flags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
|
||||
func (c *storageS3Flags) Setup(svc StorageProviderServices, cmd *kingpin.CmdClause) {
|
||||
cmd.Flag("bucket", "Name of the S3 bucket").Required().StringVar(&c.s3options.BucketName)
|
||||
cmd.Flag("endpoint", "Endpoint to use").Default("s3.amazonaws.com").StringVar(&c.s3options.Endpoint)
|
||||
cmd.Flag("region", "S3 Region").Default("").StringVar(&c.s3options.Region)
|
||||
cmd.Flag("access-key", "Access key ID (overrides AWS_ACCESS_KEY_ID environment variable)").Required().Envar("AWS_ACCESS_KEY_ID").StringVar(&c.s3options.AccessKeyID)
|
||||
cmd.Flag("secret-access-key", "Secret access key (overrides AWS_SECRET_ACCESS_KEY environment variable)").Required().Envar("AWS_SECRET_ACCESS_KEY").StringVar(&c.s3options.SecretAccessKey)
|
||||
cmd.Flag("session-token", "Session token (overrides AWS_SESSION_TOKEN environment variable)").Envar("AWS_SESSION_TOKEN").StringVar(&c.s3options.SessionToken)
|
||||
cmd.Flag("access-key", "Access key ID (overrides AWS_ACCESS_KEY_ID environment variable)").Required().Envar(svc.EnvName("AWS_ACCESS_KEY_ID")).StringVar(&c.s3options.AccessKeyID)
|
||||
cmd.Flag("secret-access-key", "Secret access key (overrides AWS_SECRET_ACCESS_KEY environment variable)").Required().Envar(svc.EnvName("AWS_SECRET_ACCESS_KEY")).StringVar(&c.s3options.SecretAccessKey)
|
||||
cmd.Flag("session-token", "Session token (overrides AWS_SESSION_TOKEN environment variable)").Envar(svc.EnvName("AWS_SESSION_TOKEN")).StringVar(&c.s3options.SessionToken)
|
||||
cmd.Flag("prefix", "Prefix to use for objects in the bucket").StringVar(&c.s3options.Prefix)
|
||||
cmd.Flag("disable-tls", "Disable TLS security (HTTPS)").BoolVar(&c.s3options.DoNotUseTLS)
|
||||
cmd.Flag("disable-tls-verification", "Disable TLS (HTTPS) certificate verification").BoolVar(&c.s3options.DoNotVerifyTLS)
|
||||
|
||||
@@ -15,11 +15,11 @@ type storageWebDAVFlags struct {
|
||||
connectFlat bool
|
||||
}
|
||||
|
||||
func (c *storageWebDAVFlags) Setup(_ StorageProviderServices, cmd *kingpin.CmdClause) {
|
||||
func (c *storageWebDAVFlags) Setup(svc StorageProviderServices, cmd *kingpin.CmdClause) {
|
||||
cmd.Flag("url", "URL of WebDAV server").Required().StringVar(&c.options.URL)
|
||||
cmd.Flag("flat", "Use flat directory structure").BoolVar(&c.connectFlat)
|
||||
cmd.Flag("webdav-username", "WebDAV username").Envar("KOPIA_WEBDAV_USERNAME").StringVar(&c.options.Username)
|
||||
cmd.Flag("webdav-password", "WebDAV password").Envar("KOPIA_WEBDAV_PASSWORD").StringVar(&c.options.Password)
|
||||
cmd.Flag("webdav-username", "WebDAV username").Envar(svc.EnvName("KOPIA_WEBDAV_USERNAME")).StringVar(&c.options.Username)
|
||||
cmd.Flag("webdav-password", "WebDAV password").Envar(svc.EnvName("KOPIA_WEBDAV_PASSWORD")).StringVar(&c.options.Password)
|
||||
cmd.Flag("list-parallelism", "Set list parallelism").Hidden().IntVar(&c.options.ListParallelism)
|
||||
cmd.Flag("atomic-writes", "Assume WebDAV provider implements atomic writes").BoolVar(&c.options.AtomicWrites)
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ func verifyGitHubReleaseIsComplete(ctx context.Context, releaseName string) erro
|
||||
}
|
||||
|
||||
func (c *App) maybeCheckForUpdates(ctx context.Context) (string, error) {
|
||||
if v := os.Getenv(checkForUpdatesEnvar); v != "" {
|
||||
if v := os.Getenv(c.EnvName(checkForUpdatesEnvar)); v != "" {
|
||||
// see if environment variable is set to false.
|
||||
if b, err := strconv.ParseBool(v); err == nil && !b {
|
||||
return "", errors.Errorf("update check disabled")
|
||||
|
||||
@@ -59,23 +59,23 @@ func (c *loggingFlags) setup(cliApp *cli.App, app *kingpin.Application) {
|
||||
app.Flag("log-file", "Override log file.").StringVar(&c.logFile)
|
||||
app.Flag("content-log-file", "Override content log file.").Hidden().StringVar(&c.contentLogFile)
|
||||
|
||||
app.Flag("log-dir", "Directory where log files should be written.").Envar("KOPIA_LOG_DIR").Default(ospath.LogsDir()).StringVar(&c.logDir)
|
||||
app.Flag("log-dir-max-files", "Maximum number of log files to retain").Envar("KOPIA_LOG_DIR_MAX_FILES").Default("1000").Hidden().IntVar(&c.logDirMaxFiles)
|
||||
app.Flag("log-dir-max-age", "Maximum age of log files to retain").Envar("KOPIA_LOG_DIR_MAX_AGE").Hidden().Default("720h").DurationVar(&c.logDirMaxAge)
|
||||
app.Flag("log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar("KOPIA_LOG_DIR_MAX_SIZE_MB").Hidden().Default("1000").Float64Var(&c.logDirMaxTotalSizeMB)
|
||||
app.Flag("max-log-file-segment-size", "Maximum size of a single log file segment").Envar("KOPIA_LOG_FILE_MAX_SEGMENT_SIZE").Default("50000000").Hidden().IntVar(&c.logFileMaxSegmentSize)
|
||||
app.Flag("log-dir", "Directory where log files should be written.").Envar(cliApp.EnvName("KOPIA_LOG_DIR")).Default(ospath.LogsDir()).StringVar(&c.logDir)
|
||||
app.Flag("log-dir-max-files", "Maximum number of log files to retain").Envar(cliApp.EnvName("KOPIA_LOG_DIR_MAX_FILES")).Default("1000").Hidden().IntVar(&c.logDirMaxFiles)
|
||||
app.Flag("log-dir-max-age", "Maximum age of log files to retain").Envar(cliApp.EnvName("KOPIA_LOG_DIR_MAX_AGE")).Hidden().Default("720h").DurationVar(&c.logDirMaxAge)
|
||||
app.Flag("log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar(cliApp.EnvName("KOPIA_LOG_DIR_MAX_SIZE_MB")).Hidden().Default("1000").Float64Var(&c.logDirMaxTotalSizeMB)
|
||||
app.Flag("max-log-file-segment-size", "Maximum size of a single log file segment").Envar(cliApp.EnvName("KOPIA_LOG_FILE_MAX_SEGMENT_SIZE")).Default("50000000").Hidden().IntVar(&c.logFileMaxSegmentSize)
|
||||
app.Flag("wait-for-log-sweep", "Wait for log sweep before program exit").Default("true").Hidden().BoolVar(&c.waitForLogSweep)
|
||||
app.Flag("content-log-dir-max-files", "Maximum number of content log files to retain").Envar("KOPIA_CONTENT_LOG_DIR_MAX_FILES").Default("5000").Hidden().IntVar(&c.contentLogDirMaxFiles)
|
||||
app.Flag("content-log-dir-max-age", "Maximum age of content log files to retain").Envar("KOPIA_CONTENT_LOG_DIR_MAX_AGE").Default("720h").Hidden().DurationVar(&c.contentLogDirMaxAge)
|
||||
app.Flag("content-log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar("KOPIA_CONTENT_LOG_DIR_MAX_SIZE_MB").Hidden().Default("1000").Float64Var(&c.contentLogDirMaxTotalSizeMB)
|
||||
app.Flag("content-log-dir-max-files", "Maximum number of content log files to retain").Envar(cliApp.EnvName("KOPIA_CONTENT_LOG_DIR_MAX_FILES")).Default("5000").Hidden().IntVar(&c.contentLogDirMaxFiles)
|
||||
app.Flag("content-log-dir-max-age", "Maximum age of content log files to retain").Envar(cliApp.EnvName("KOPIA_CONTENT_LOG_DIR_MAX_AGE")).Default("720h").Hidden().DurationVar(&c.contentLogDirMaxAge)
|
||||
app.Flag("content-log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar(cliApp.EnvName("KOPIA_CONTENT_LOG_DIR_MAX_SIZE_MB")).Hidden().Default("1000").Float64Var(&c.contentLogDirMaxTotalSizeMB)
|
||||
app.Flag("log-level", "Console log level").Default("info").EnumVar(&c.logLevel, logLevels...)
|
||||
app.Flag("json-log-console", "JSON log file").Hidden().BoolVar(&c.jsonLogConsole)
|
||||
app.Flag("json-log-file", "JSON log file").Hidden().BoolVar(&c.jsonLogFile)
|
||||
app.Flag("file-log-level", "File log level").Default("debug").EnumVar(&c.fileLogLevel, logLevels...)
|
||||
app.Flag("file-log-local-tz", "When logging to a file, use local timezone").Hidden().Envar("KOPIA_FILE_LOG_LOCAL_TZ").BoolVar(&c.fileLogLocalTimezone)
|
||||
app.Flag("force-color", "Force color output").Hidden().Envar("KOPIA_FORCE_COLOR").BoolVar(&c.forceColor)
|
||||
app.Flag("disable-color", "Disable color output").Hidden().Envar("KOPIA_DISABLE_COLOR").BoolVar(&c.disableColor)
|
||||
app.Flag("console-timestamps", "Log timestamps to stderr.").Hidden().Default("false").Envar("KOPIA_CONSOLE_TIMESTAMPS").BoolVar(&c.consoleLogTimestamps)
|
||||
app.Flag("file-log-local-tz", "When logging to a file, use local timezone").Hidden().Envar(cliApp.EnvName("KOPIA_FILE_LOG_LOCAL_TZ")).BoolVar(&c.fileLogLocalTimezone)
|
||||
app.Flag("force-color", "Force color output").Hidden().Envar(cliApp.EnvName("KOPIA_FORCE_COLOR")).BoolVar(&c.forceColor)
|
||||
app.Flag("disable-color", "Disable color output").Hidden().Envar(cliApp.EnvName("KOPIA_DISABLE_COLOR")).BoolVar(&c.disableColor)
|
||||
app.Flag("console-timestamps", "Log timestamps to stderr.").Hidden().Default("false").Envar(cliApp.EnvName("KOPIA_CONSOLE_TIMESTAMPS")).BoolVar(&c.consoleLogTimestamps)
|
||||
|
||||
app.PreAction(c.initialize)
|
||||
c.cliApp = cliApp
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
func TestACL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
serverRunner := testenv.NewExeRunner(t)
|
||||
serverRunner := testenv.NewInProcRunner(t)
|
||||
serverEnvironment := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, serverRunner)
|
||||
|
||||
defer serverEnvironment.RunAndExpectSuccess(t, "repo", "disconnect")
|
||||
@@ -42,7 +42,7 @@ func TestACL(t *testing.T) {
|
||||
|
||||
var sp testutil.ServerParameters
|
||||
|
||||
_, kill := serverEnvironment.RunAndProcessStderr(t, sp.ProcessOutput,
|
||||
wait, kill := serverEnvironment.RunAndProcessStderr(t, sp.ProcessOutput,
|
||||
"server", "start",
|
||||
"--address=localhost:0",
|
||||
"--server-control-username=admin-user",
|
||||
@@ -53,14 +53,15 @@ func TestACL(t *testing.T) {
|
||||
|
||||
t.Logf("detected server parameters %#v", sp)
|
||||
|
||||
defer wait()
|
||||
defer kill()
|
||||
|
||||
fooBarRunner := testenv.NewExeRunner(t)
|
||||
fooBarRunner := testenv.NewInProcRunner(t)
|
||||
foobarClientEnvironment := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, fooBarRunner)
|
||||
|
||||
defer foobarClientEnvironment.RunAndExpectSuccess(t, "repo", "disconnect")
|
||||
|
||||
fooBarRunner.RemoveDefaultPassword()
|
||||
delete(foobarClientEnvironment.Environment, "KOPIA_PASSWORD")
|
||||
|
||||
// connect as foo@bar with password baz
|
||||
foobarClientEnvironment.RunAndExpectSuccess(t, "repo", "connect", "server",
|
||||
@@ -71,12 +72,12 @@ func TestACL(t *testing.T) {
|
||||
"--password", "baz",
|
||||
)
|
||||
|
||||
aliceInWonderlandRunner := testenv.NewExeRunner(t)
|
||||
aliceInWonderlandRunner := testenv.NewInProcRunner(t)
|
||||
aliceInWonderlandClientEnvironment := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, aliceInWonderlandRunner)
|
||||
|
||||
defer aliceInWonderlandClientEnvironment.RunAndExpectSuccess(t, "repo", "disconnect")
|
||||
|
||||
aliceInWonderlandRunner.RemoveDefaultPassword()
|
||||
delete(aliceInWonderlandClientEnvironment.Environment, "KOPIA_PASSWORD")
|
||||
|
||||
// connect as alice@wonderland with password baz
|
||||
aliceInWonderlandClientEnvironment.RunAndExpectSuccess(t, "repo", "connect", "server",
|
||||
|
||||
@@ -211,7 +211,7 @@ func testAPIServerRepository(t *testing.T, serverStartArgs []string, useGRPC, al
|
||||
// we are providing custom password to connect, make sure we won't be providing
|
||||
// (different) default password via environment variable, as command-line password
|
||||
// takes precedence over persisted password.
|
||||
runner2.RemoveDefaultPassword()
|
||||
delete(e2.Environment, "KOPIA_PASSWORD")
|
||||
|
||||
// should see one snapshot
|
||||
snapshots := clitestutil.ListSnapshotsAndExpectSuccess(t, e2)
|
||||
|
||||
@@ -15,21 +15,21 @@ func TestAutoUpdateEnableTest(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
extraArgs []string
|
||||
extraEnv []string
|
||||
extraEnv map[string]string
|
||||
wantEnabled bool
|
||||
wantInitialDelay time.Duration
|
||||
}{
|
||||
{desc: "Default", wantEnabled: true, wantInitialDelay: 24 * time.Hour},
|
||||
{desc: "DisabledByFlag", extraArgs: []string{"--no-check-for-updates"}, wantEnabled: false},
|
||||
{desc: "DisabledByEnvar-false", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=false"}, wantEnabled: false},
|
||||
{desc: "DisabledByEnvar-0", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=0"}, wantEnabled: false},
|
||||
{desc: "DisabledByEnvar-f", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=f"}, wantEnabled: false},
|
||||
{desc: "DisabledByEnvar-False", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=False"}, wantEnabled: false},
|
||||
{desc: "DisabledByEnvar-FALSE", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=FALSE"}, wantEnabled: false},
|
||||
{desc: "DisabledByEnvarOverriddenByFlag", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=false"}, extraArgs: []string{"--check-for-updates"}, wantEnabled: true, wantInitialDelay: 24 * time.Hour},
|
||||
{desc: "EnabledByEnvarOverriddenByFlag", extraEnv: []string{"KOPIA_CHECK_FOR_UPDATES=true"}, extraArgs: []string{"--no-check-for-updates"}, wantEnabled: false, wantInitialDelay: 24 * time.Hour},
|
||||
{desc: "DisabledByEnvar-false", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "false"}, wantEnabled: false},
|
||||
{desc: "DisabledByEnvar-0", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "0"}, wantEnabled: false},
|
||||
{desc: "DisabledByEnvar-f", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "f"}, wantEnabled: false},
|
||||
{desc: "DisabledByEnvar-False", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "False"}, wantEnabled: false},
|
||||
{desc: "DisabledByEnvar-FALSE", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "FALSE"}, wantEnabled: false},
|
||||
{desc: "DisabledByEnvarOverriddenByFlag", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "false"}, extraArgs: []string{"--check-for-updates"}, wantEnabled: true, wantInitialDelay: 24 * time.Hour},
|
||||
{desc: "EnabledByEnvarOverriddenByFlag", extraEnv: map[string]string{"KOPIA_CHECK_FOR_UPDATES": "true"}, extraArgs: []string{"--no-check-for-updates"}, wantEnabled: false, wantInitialDelay: 24 * time.Hour},
|
||||
|
||||
{desc: "InitialUpdateCheckIntervalFlag", extraEnv: []string{"KOPIA_INITIAL_UPDATE_CHECK_DELAY=1h"}, wantEnabled: true, wantInitialDelay: 1 * time.Hour},
|
||||
{desc: "InitialUpdateCheckIntervalFlag", extraEnv: map[string]string{"KOPIA_INITIAL_UPDATE_CHECK_DELAY": "1h"}, wantEnabled: true, wantInitialDelay: 1 * time.Hour},
|
||||
{desc: "InitialUpdateCheckIntervalEnvar", extraArgs: []string{"--initial-update-check-delay=3h"}, wantEnabled: true, wantInitialDelay: 3 * time.Hour},
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestAutoUpdateEnableTest(t *testing.T) {
|
||||
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runner := testenv.NewExeRunner(t)
|
||||
runner := testenv.NewInProcRunner(t)
|
||||
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
|
||||
|
||||
// create repo
|
||||
@@ -48,7 +48,10 @@ func TestAutoUpdateEnableTest(t *testing.T) {
|
||||
"repo", "create", "filesystem", "--path", e.RepoDir,
|
||||
}, tc.extraArgs...)
|
||||
|
||||
runner.Environment = append(runner.Environment, tc.extraEnv...)
|
||||
for k, v := range tc.extraEnv {
|
||||
e.Environment[k] = v
|
||||
}
|
||||
|
||||
e.RunAndExpectSuccess(t, args...)
|
||||
|
||||
updateInfoFile := filepath.Join(e.ConfigDir, ".kopia.config.update-info.json")
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
func TestRestoreFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runner := testenv.NewExeRunner(t)
|
||||
runner := testenv.NewInProcRunner(t)
|
||||
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
|
||||
|
||||
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
|
||||
|
||||
@@ -21,12 +21,11 @@
|
||||
func TestSnapshotActionsBeforeSnapshotRoot(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
th := os.Getenv("TESTING_ACTION_EXE")
|
||||
if th == "" {
|
||||
t.Skip("TESTING_ACTION_EXE must be set")
|
||||
}
|
||||
th := skipUnlessTestAction(t)
|
||||
|
||||
runner := testenv.NewExeRunner(t)
|
||||
logsDir := testutil.TempLogDirectory(t)
|
||||
|
||||
runner := testenv.NewInProcRunner(t)
|
||||
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
|
||||
|
||||
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
|
||||
@@ -34,7 +33,7 @@ func TestSnapshotActionsBeforeSnapshotRoot(t *testing.T) {
|
||||
e.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", e.RepoDir, "--override-hostname=foo", "--override-username=foo", "--enable-actions")
|
||||
e.RunAndExpectSuccess(t, "snapshot", "create", sharedTestDataDir2)
|
||||
|
||||
envFile1 := filepath.Join(runner.LogsDir, "env1.txt")
|
||||
envFile1 := filepath.Join(logsDir, "env1.txt")
|
||||
|
||||
// set a action before-snapshot-root that fails and which saves the environment to a file.
|
||||
e.RunAndExpectSuccess(t,
|
||||
@@ -45,7 +44,7 @@ func TestSnapshotActionsBeforeSnapshotRoot(t *testing.T) {
|
||||
// this prevents the snapshot from being created
|
||||
e.RunAndExpectFailure(t, "snapshot", "create", sharedTestDataDir1)
|
||||
|
||||
envFile2 := filepath.Join(runner.LogsDir, "env2.txt")
|
||||
envFile2 := filepath.Join(logsDir, "env2.txt")
|
||||
|
||||
// now set a action before-snapshot-root that succeeds and saves environment to a different file
|
||||
e.RunAndExpectSuccess(t,
|
||||
@@ -53,7 +52,7 @@ func TestSnapshotActionsBeforeSnapshotRoot(t *testing.T) {
|
||||
"--before-snapshot-root-action",
|
||||
th+" --save-env="+envFile2)
|
||||
|
||||
envFile3 := filepath.Join(runner.LogsDir, "env2.txt")
|
||||
envFile3 := filepath.Join(logsDir, "env2.txt")
|
||||
|
||||
// set a action after-snapshot-root that succeeds and saves environment to a different file
|
||||
e.RunAndExpectSuccess(t,
|
||||
@@ -167,12 +166,11 @@ func TestSnapshotActionsBeforeSnapshotRoot(t *testing.T) {
|
||||
func TestSnapshotActionsBeforeAfterFolder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
th := os.Getenv("TESTING_ACTION_EXE")
|
||||
if th == "" {
|
||||
t.Skip("TESTING_ACTION_EXE must be set")
|
||||
}
|
||||
th := skipUnlessTestAction(t)
|
||||
|
||||
runner := testenv.NewExeRunner(t)
|
||||
logsDir := testutil.TempLogDirectory(t)
|
||||
|
||||
runner := testenv.NewInProcRunner(t)
|
||||
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
|
||||
|
||||
e.RunAndExpectSuccess(t, "repo", "create", "filesystem", "--path", e.RepoDir, "--enable-actions")
|
||||
@@ -201,8 +199,8 @@ func TestSnapshotActionsBeforeAfterFolder(t *testing.T) {
|
||||
actionRanFileBeforeSD2 := filepath.Join(actionRanDir, "before-sd2")
|
||||
actionRanFileAfterSD2 := filepath.Join(actionRanDir, "before-sd2")
|
||||
|
||||
envFile1 := filepath.Join(runner.LogsDir, "env1.txt")
|
||||
envFile2 := filepath.Join(runner.LogsDir, "env2.txt")
|
||||
envFile1 := filepath.Join(logsDir, "env1.txt")
|
||||
envFile2 := filepath.Join(logsDir, "env2.txt")
|
||||
|
||||
// setup actions that will write a marker file when the action is executed.
|
||||
//
|
||||
@@ -300,10 +298,7 @@ func TestSnapshotActionsEmbeddedScript(t *testing.T) {
|
||||
func TestSnapshotActionsEnable(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
th := os.Getenv("TESTING_ACTION_EXE")
|
||||
if th == "" {
|
||||
t.Skip("TESTING_ACTION_EXE must be set")
|
||||
}
|
||||
th := skipUnlessTestAction(t)
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -326,14 +321,16 @@ func TestSnapshotActionsEnable(t *testing.T) {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runner := testenv.NewExeRunner(t)
|
||||
logsDir := testutil.TempLogDirectory(t)
|
||||
|
||||
runner := testenv.NewInProcRunner(t)
|
||||
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
|
||||
|
||||
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
|
||||
|
||||
e.RunAndExpectSuccess(t, append([]string{"repo", "create", "filesystem", "--path", e.RepoDir}, tc.connectFlags...)...)
|
||||
|
||||
envFile := filepath.Join(runner.LogsDir, "env1.txt")
|
||||
envFile := filepath.Join(logsDir, "env1.txt")
|
||||
|
||||
// set an action before-snapshot-root that fails and which saves the environment to a file.
|
||||
e.RunAndExpectSuccess(t,
|
||||
@@ -411,12 +408,9 @@ func mustReadEnvFile(t *testing.T, fname string) map[string]string {
|
||||
func TestSnapshotActionsHonorIgnoreRules(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
th := os.Getenv("TESTING_ACTION_EXE")
|
||||
if th == "" {
|
||||
t.Skip("TESTING_ACTION_EXE must be set")
|
||||
}
|
||||
th := skipUnlessTestAction(t)
|
||||
|
||||
runner := testenv.NewExeRunner(t)
|
||||
runner := testenv.NewInProcRunner(t)
|
||||
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
|
||||
|
||||
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
|
||||
@@ -450,3 +444,18 @@ func TestSnapshotActionsHonorIgnoreRules(t *testing.T) {
|
||||
// make sure .kopiaignore was honored
|
||||
require.NotContains(t, entries, "some-ignored-file")
|
||||
}
|
||||
|
||||
func skipUnlessTestAction(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
th := os.Getenv("TESTING_ACTION_EXE")
|
||||
if th == "" {
|
||||
t.Skip("TESTING_ACTION_EXE must be set")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(th); os.IsNotExist(err) {
|
||||
t.Fatal("TESTING_ACTION_EXE does not exist")
|
||||
}
|
||||
|
||||
return th
|
||||
}
|
||||
|
||||
@@ -561,7 +561,7 @@ func TestSnapshotCreateAllWithManualSnapshot(t *testing.T) {
|
||||
func TestSnapshotCreateWithStdinStream(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runner := testenv.NewExeRunner(t)
|
||||
runner := testenv.NewInProcRunner(t)
|
||||
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
|
||||
|
||||
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
|
||||
@@ -582,7 +582,8 @@ func TestSnapshotCreateWithStdinStream(t *testing.T) {
|
||||
w.Close()
|
||||
|
||||
streamFileName := "stream-file"
|
||||
runner.NextCommandStdin = r
|
||||
|
||||
runner.SetNextStdin(r)
|
||||
|
||||
e.RunAndExpectSuccess(t, "snapshot", "create", "rootdir", "--stdin-file", streamFileName)
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestSnapshotFail_Default(t *testing.T) {
|
||||
|
||||
func TestSnapshotFail_EnvOverride(t *testing.T) {
|
||||
t.Parallel()
|
||||
testSnapshotFail(t, true, nil, []string{"KOPIA_SNAPSHOT_FAIL_FAST=true"})
|
||||
testSnapshotFail(t, true, nil, map[string]string{"KOPIA_SNAPSHOT_FAIL_FAST": "true"})
|
||||
}
|
||||
|
||||
func TestSnapshotFail_NoFailFast(t *testing.T) {
|
||||
@@ -70,7 +70,7 @@ func cond(c bool, a, b int) int {
|
||||
}
|
||||
|
||||
// nolint:thelper,cyclop
|
||||
func testSnapshotFail(t *testing.T, isFailFast bool, snapshotCreateFlags, snapshotCreateEnv []string) {
|
||||
func testSnapshotFail(t *testing.T, isFailFast bool, snapshotCreateFlags []string, snapshotCreateEnv map[string]string) {
|
||||
if runtime.GOOS == windowsOSName {
|
||||
t.Skip("this test does not work on Windows")
|
||||
}
|
||||
@@ -240,7 +240,7 @@ func testSnapshotFail(t *testing.T, isFailFast bool, snapshotCreateFlags, snapsh
|
||||
t.Run(tname, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runner := testenv.NewExeRunner(t)
|
||||
runner := testenv.NewInProcRunner(t)
|
||||
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
|
||||
|
||||
defer e.RunAndExpectSuccess(t, "repo", "disconnect")
|
||||
@@ -260,7 +260,7 @@ func testSnapshotFail(t *testing.T, isFailFast bool, snapshotCreateFlags, snapsh
|
||||
|
||||
e.RunAndExpectSuccess(t, "policy", "set", snapSource, "--ignore-dir-errors", tcIgnoreDirErr, "--ignore-file-errors", tcIgnoreFileErr)
|
||||
restoreDir := fmt.Sprintf("%s%d_%v_%v", restoreDirPrefix, tcIdx, tcIgnoreDirErr, tcIgnoreFileErr)
|
||||
testPermissions(t, runner, e, snapSource, modifyEntry, restoreDir, tc.expectSuccess, snapshotCreateFlags, snapshotCreateEnv)
|
||||
testPermissions(t, e, snapSource, modifyEntry, restoreDir, tc.expectSuccess, snapshotCreateFlags, snapshotCreateEnv)
|
||||
|
||||
e.RunAndExpectSuccess(t, "policy", "remove", snapSource)
|
||||
})
|
||||
@@ -299,7 +299,7 @@ func createSimplestFileTree(t *testing.T, dirDepth, currDepth int, currPath stri
|
||||
// against "source" and will test permissions against all entries in "parentDir".
|
||||
// It returns the number of successful snapshot operations.
|
||||
// nolint:thelper
|
||||
func testPermissions(t *testing.T, runner *testenv.CLIExeRunner, e *testenv.CLITest, source, modifyEntry, restoreDir string, expect map[os.FileMode]expectedSnapshotResult, snapshotCreateFlags, snapshotCreateEnv []string) int {
|
||||
func testPermissions(t *testing.T, e *testenv.CLITest, source, modifyEntry, restoreDir string, expect map[os.FileMode]expectedSnapshotResult, snapshotCreateFlags []string, snapshotCreateEnv map[string]string) int {
|
||||
var numSuccessfulSnapshots int
|
||||
|
||||
changeFile, err := os.Stat(modifyEntry)
|
||||
@@ -323,10 +323,18 @@ func() {
|
||||
require.NoError(t, err)
|
||||
|
||||
// set up environment for the child process.
|
||||
oldEnv := runner.Environment
|
||||
runner.Environment = append(append([]string{}, runner.Environment...), snapshotCreateEnv...)
|
||||
oldEnv := e.Environment
|
||||
|
||||
defer func() { runner.Environment = oldEnv }()
|
||||
e.Environment = map[string]string{}
|
||||
for k, v := range oldEnv {
|
||||
e.Environment[k] = v
|
||||
}
|
||||
|
||||
for k, v := range snapshotCreateEnv {
|
||||
e.Environment[k] = v
|
||||
}
|
||||
|
||||
defer func() { e.Environment = oldEnv }()
|
||||
|
||||
snapshotCreateWithArgs := append([]string{"snapshot", "create", source}, snapshotCreateFlags...)
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestEndurance(t *testing.T) {
|
||||
ft := httptest.NewServer(fts)
|
||||
defer ft.Close()
|
||||
|
||||
runner.Environment = append(runner.Environment, "KOPIA_FAKE_CLOCK_ENDPOINT="+ft.URL)
|
||||
e.Environment["KOPIA_FAKE_CLOCK_ENDPOINT"] = ft.URL
|
||||
|
||||
sts := httptest.NewServer(&webdav.Handler{
|
||||
FileSystem: webdavDirWithFakeClock{webdav.Dir(tmpDir), fts},
|
||||
@@ -267,10 +267,8 @@ func enduranceRunner(t *testing.T, runnerID int, fakeTimeServer, webdavServer st
|
||||
runner := testenv.NewExeRunner(t)
|
||||
e := testenv.NewCLITest(t, testenv.RepoFormatNotImportant, runner)
|
||||
|
||||
runner.Environment = append(runner.Environment,
|
||||
"KOPIA_FAKE_CLOCK_ENDPOINT="+fakeTimeServer,
|
||||
"KOPIA_CHECK_FOR_UPDATES=false",
|
||||
)
|
||||
e.Environment["KOPIA_FAKE_CLOCK_ENDPOINT"] = fakeTimeServer
|
||||
e.Environment["KOPIA_CHECK_FOR_UPDATES"] = "false"
|
||||
|
||||
e.RunAndExpectSuccess(t, "repo", "connect", "webdav", "--url", webdavServer, "--override-username="+fmt.Sprintf("runner-%v", runnerID))
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kopia/kopia/internal/testutil"
|
||||
@@ -14,21 +13,24 @@
|
||||
// CLIExeRunner is a CLIExeRunner that invokes the commands via external executable.
|
||||
type CLIExeRunner struct {
|
||||
Exe string
|
||||
Environment []string
|
||||
PassthroughStderr bool // this is for debugging only
|
||||
NextCommandStdin io.Reader // this is used for stdin source tests
|
||||
LogsDir string
|
||||
}
|
||||
|
||||
// Start implements CLIRunner.
|
||||
func (e *CLIExeRunner) Start(t *testing.T, args []string) (stdout, stderr io.Reader, wait func() error, kill func()) {
|
||||
func (e *CLIExeRunner) Start(t *testing.T, args []string, env map[string]string) (stdout, stderr io.Reader, wait func() error, kill func()) {
|
||||
t.Helper()
|
||||
|
||||
c := exec.Command(e.Exe, append([]string{
|
||||
"--log-dir", e.LogsDir,
|
||||
}, args...)...)
|
||||
|
||||
c.Env = append(os.Environ(), e.Environment...)
|
||||
c.Env = append(c.Env, os.Environ()...)
|
||||
|
||||
for k, v := range env {
|
||||
c.Env = append(c.Env, k+"="+v)
|
||||
}
|
||||
|
||||
stdoutPipe, err := c.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -52,19 +54,6 @@ func (e *CLIExeRunner) Start(t *testing.T, args []string) (stdout, stderr io.Rea
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveDefaultPassword prevents KOPIA_PASSWORD from being passed to kopia.
|
||||
func (e *CLIExeRunner) RemoveDefaultPassword() {
|
||||
var newEnv []string
|
||||
|
||||
for _, s := range e.Environment {
|
||||
if !strings.HasPrefix(s, "KOPIA_PASSWORD=") {
|
||||
newEnv = append(newEnv, s)
|
||||
}
|
||||
}
|
||||
|
||||
e.Environment = newEnv
|
||||
}
|
||||
|
||||
// NewExeRunner returns a CLIRunner that will execute kopia commands by launching subprocesses
|
||||
// for each. The kopia executable must be passed via KOPIA_EXE environment variable. The test
|
||||
// will be skipped if it's not provided (unless running inside an IDE in which case system-wide
|
||||
@@ -96,11 +85,7 @@ func NewExeRunnerWithBinary(t *testing.T, exe string) *CLIExeRunner {
|
||||
logsDir := testutil.TempLogDirectory(t)
|
||||
|
||||
return &CLIExeRunner{
|
||||
Exe: filepath.FromSlash(exe),
|
||||
Environment: []string{
|
||||
"KOPIA_PASSWORD=" + TestRepoPassword,
|
||||
"KOPIA_ADVANCED_COMMANDS=enabled",
|
||||
},
|
||||
Exe: filepath.FromSlash(exe),
|
||||
LogsDir: logsDir,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package testenv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/alecthomas/kingpin"
|
||||
@@ -11,15 +14,20 @@
|
||||
"github.com/kopia/kopia/internal/testlogging"
|
||||
)
|
||||
|
||||
var envPrefixCounter = new(int32)
|
||||
|
||||
// CLIInProcRunner is a CLIRunner that invokes provided commands in the current process.
|
||||
type CLIInProcRunner struct {
|
||||
RepoPassword string
|
||||
mu sync.Mutex
|
||||
|
||||
// +checklocks:mu
|
||||
nextCommandStdin io.Reader // this is used for stdin source tests
|
||||
|
||||
CustomizeApp func(a *cli.App, kp *kingpin.Application)
|
||||
}
|
||||
|
||||
// Start implements CLIRunner.
|
||||
func (e *CLIInProcRunner) Start(t *testing.T, args []string) (stdout, stderr io.Reader, wait func() error, kill func()) {
|
||||
func (e *CLIInProcRunner) Start(t *testing.T, args []string, env map[string]string) (stdout, stderr io.Reader, wait func() error, kill func()) {
|
||||
t.Helper()
|
||||
|
||||
ctx := testlogging.Context(t)
|
||||
@@ -27,15 +35,33 @@ func (e *CLIInProcRunner) Start(t *testing.T, args []string) (stdout, stderr io.
|
||||
a := cli.NewApp()
|
||||
a.AdvancedCommands = "enabled"
|
||||
|
||||
envPrefix := fmt.Sprintf("T%v_", atomic.AddInt32(envPrefixCounter, 1))
|
||||
a.SetEnvNamePrefixForTesting(envPrefix)
|
||||
|
||||
kpapp := kingpin.New("test", "test")
|
||||
|
||||
if e.CustomizeApp != nil {
|
||||
e.CustomizeApp(a, kpapp)
|
||||
}
|
||||
|
||||
return a.RunSubcommand(ctx, kpapp, append([]string{
|
||||
"--password", e.RepoPassword,
|
||||
}, args...))
|
||||
e.mu.Lock()
|
||||
stdin := e.nextCommandStdin
|
||||
e.nextCommandStdin = nil
|
||||
e.mu.Unlock()
|
||||
|
||||
for k, v := range env {
|
||||
os.Setenv(envPrefix+k, v)
|
||||
}
|
||||
|
||||
return a.RunSubcommand(ctx, kpapp, stdin, args)
|
||||
}
|
||||
|
||||
// SetNextStdin sets the stdin to be used on next command execution.
|
||||
func (e *CLIInProcRunner) SetNextStdin(stdin io.Reader) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
e.nextCommandStdin = stdin
|
||||
}
|
||||
|
||||
// NewInProcRunner returns a runner that executes CLI subcommands in the current process using cli.RunSubcommand().
|
||||
@@ -47,7 +73,6 @@ func NewInProcRunner(t *testing.T) *CLIInProcRunner {
|
||||
}
|
||||
|
||||
return &CLIInProcRunner{
|
||||
RepoPassword: TestRepoPassword,
|
||||
CustomizeApp: func(a *cli.App, kp *kingpin.Application) {
|
||||
a.AddStorageProvider(cli.StorageProvider{
|
||||
Name: "in-memory",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
// CLIRunner encapsulates running kopia subcommands for testing purposes.
|
||||
// It supports implementations that use subprocesses or in-process invocations.
|
||||
type CLIRunner interface {
|
||||
Start(t *testing.T, args []string) (stdout, stderr io.Reader, wait func() error, kill func())
|
||||
Start(t *testing.T, args []string, env map[string]string) (stdout, stderr io.Reader, wait func() error, kill func())
|
||||
}
|
||||
|
||||
// CLITest encapsulates state for a CLI-based test.
|
||||
@@ -39,7 +39,8 @@ type CLITest struct {
|
||||
|
||||
Runner CLIRunner
|
||||
|
||||
fixedArgs []string
|
||||
fixedArgs []string
|
||||
Environment map[string]string
|
||||
|
||||
DefaultRepositoryCreateFlags []string
|
||||
}
|
||||
@@ -86,7 +87,10 @@ func NewCLITest(t *testing.T, repoCreateFlags []string, runner CLIRunner) *CLITe
|
||||
ConfigDir: configDir,
|
||||
fixedArgs: fixedArgs,
|
||||
DefaultRepositoryCreateFlags: formatFlags,
|
||||
Runner: runner,
|
||||
Environment: map[string]string{
|
||||
"KOPIA_PASSWORD": TestRepoPassword,
|
||||
},
|
||||
Runner: runner,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +110,7 @@ func (e *CLITest) RunAndExpectSuccess(t *testing.T, args ...string) []string {
|
||||
func (e *CLITest) RunAndProcessStderr(t *testing.T, callback func(line string) bool, args ...string) (wait func() error, kill func()) {
|
||||
t.Helper()
|
||||
|
||||
stdout, stderr, wait, kill := e.Runner.Start(t, e.cmdArgs(args))
|
||||
stdout, stderr, wait, kill := e.Runner.Start(t, e.cmdArgs(args), e.Environment)
|
||||
go io.Copy(io.Discard, stdout)
|
||||
|
||||
scanner := bufio.NewScanner(stderr)
|
||||
@@ -179,11 +183,11 @@ func (e *CLITest) Run(t *testing.T, expectedError bool, args ...string) (stdout,
|
||||
t.Helper()
|
||||
|
||||
args = e.cmdArgs(args)
|
||||
t.Logf("running 'kopia %v'", strings.Join(args, " "))
|
||||
t.Logf("running 'kopia %v' with %v", strings.Join(args, " "), e.Environment)
|
||||
|
||||
timer := timetrack.StartTimer()
|
||||
|
||||
stdoutReader, stderrReader, wait, _ := e.Runner.Start(t, args)
|
||||
stdoutReader, stderrReader, wait, _ := e.Runner.Start(t, args, e.Environment)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
|
||||
Reference in New Issue
Block a user