sq fails on first config write (#387)

* Fixed bug with config write on fresh install

* Added test for config write on fresh install
This commit is contained in:
Neil O'Toole 2024-01-29 22:03:03 -07:00 committed by GitHub
parent fd070eefd3
commit 135318f542
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 62 additions and 14 deletions

View File

@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Breaking changes are annotated with ☢️, and alpha/beta features with 🐥. Breaking changes are annotated with ☢️, and alpha/beta features with 🐥.
## [v0.47.2] - 2024-01-29
Yet another morning-after-the-big-release issue, a nasty little one this time.
See the earlier [`v0.47.0`](https://github.com/neilotoole/sq/releases/tag/v0.47.0) release
for recent headline features.
### Fixed
- `sq` was failing to write config when there was no pre-existing config file. This was due to
a bug in the newly-introduced (as of `v0.47.0`) config locking mechanism. Fixed.
## [v0.47.1] - 2024-01-29 ## [v0.47.1] - 2024-01-29
This is a tiny bugfix release for a runtime issue on some Linux distros. See This is a tiny bugfix release for a runtime issue on some Linux distros. See
@ -1103,3 +1115,4 @@ make working with lots of sources much easier.
[v0.46.1]: https://github.com/neilotoole/sq/compare/v0.46.0...v0.46.1 [v0.46.1]: https://github.com/neilotoole/sq/compare/v0.46.0...v0.46.1
[v0.47.0]: https://github.com/neilotoole/sq/compare/v0.46.1...v0.47.0 [v0.47.0]: https://github.com/neilotoole/sq/compare/v0.46.1...v0.47.0
[v0.47.1]: https://github.com/neilotoole/sq/compare/v0.47.0...v0.47.1 [v0.47.1]: https://github.com/neilotoole/sq/compare/v0.47.0...v0.47.1
[v0.47.2]: https://github.com/neilotoole/sq/compare/v0.47.1...v0.47.2

View File

@ -10,6 +10,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/neilotoole/sq/cli/config"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -26,6 +28,27 @@ import (
"github.com/neilotoole/sq/testh/tu" "github.com/neilotoole/sq/testh/tu"
) )
// TestConfigWriteOnFreshInstall verifies that sq can write
// to the config store when there is no existing config.
func TestConfigWriteOnFreshInstall(t *testing.T) {
cfgDir := tu.TempDir(t)
t.Setenv(config.EnvarConfigDir, cfgDir)
ctx := context.Background()
// We want to write to the config, it doesn't matter what
// we write, just that we write something.
args := []string{"config", "set", cli.OptPingCmdTimeout.Key(), "3m33s"}
err := cli.Execute(ctx, os.Stdin, os.Stdout, os.Stderr, args)
require.NoError(t, err)
args = []string{"config", "get", cli.OptPingCmdTimeout.Key()}
buf := &bytes.Buffer{}
err = cli.Execute(ctx, os.Stdin, buf, os.Stderr, args)
require.NoError(t, err)
require.Equal(t, "3m33s\n", buf.String())
}
func TestSmoke(t *testing.T) { func TestSmoke(t *testing.T) {
t.Parallel() t.Parallel()
// Execute a bunch of smoke test cases. // Execute a bunch of smoke test cases.

View File

@ -13,6 +13,9 @@ import (
// Store saves and loads config. // Store saves and loads config.
type Store interface { type Store interface {
// Exists returns true if the config exists in the store.
Exists() bool
// Save writes config to the store. // Save writes config to the store.
Save(ctx context.Context, cfg *Config) error Save(ctx context.Context, cfg *Config) error
@ -34,6 +37,11 @@ type Store interface {
// and Load always returns a new empty Config. Useful for testing. // and Load always returns a new empty Config. Useful for testing.
type DiscardStore struct{} type DiscardStore struct{}
// Exists implements Store.Exists. It returns true.
func (s DiscardStore) Exists() bool {
return true
}
// Lockfile implements Store.Lockfile. // Lockfile implements Store.Lockfile.
func (DiscardStore) Lockfile() (lockfile.Lockfile, error) { func (DiscardStore) Lockfile() (lockfile.Lockfile, error) {
f, err := os.CreateTemp("", fmt.Sprintf("sq-%d.lock", os.Getpid())) f, err := os.CreateTemp("", fmt.Sprintf("sq-%d.lock", os.Getpid()))

View File

@ -48,7 +48,7 @@ func Load(ctx context.Context, osArgs []string, optsReg *options.Registry,
OptionsRegistry: optsReg, OptionsRegistry: optsReg,
} }
if !cfgStore.fileExists() { if !cfgStore.Exists() {
cfg := config.New() cfg := config.New()
return cfg, cfgStore, nil return cfg, cfgStore, nil
} }

View File

@ -209,9 +209,9 @@ func (fs *Store) write(ctx context.Context, data []byte) error {
return nil return nil
} }
// fileExists returns true if the backing file can be accessed, false if it doesn't // Exists returns true if the backing file can be accessed, false if it doesn't
// exist or on any error. // exist or on any error.
func (fs *Store) fileExists() bool { func (fs *Store) Exists() bool {
_, err := os.Stat(fs.Path) _, err := os.Stat(fs.Path)
return err == nil return err == nil
} }

View File

@ -348,7 +348,9 @@ func cmdRequiresConfigLock(cmd *cobra.Command) bool {
// lockReloadConfig acquires the lock for the config store, and updates the // lockReloadConfig acquires the lock for the config store, and updates the
// run (as found on cmd's context) with a fresh copy of the config, loaded // run (as found on cmd's context) with a fresh copy of the config, loaded
// after lock acquisition. // after lock acquisition. If there's no config persisted in the store,
// the run's config is left untouched. (That run config will later be
// saved to the store, if appropriate.)
// //
// The config lock should be acquired before making any changes to config. // The config lock should be acquired before making any changes to config.
// Timeout and progress options from ctx are honored. // Timeout and progress options from ctx are honored.
@ -387,18 +389,20 @@ func lockReloadConfig(cmd *cobra.Command) (unlock func(), err error) {
return nil, errz.Wrap(err, "acquire config lock") return nil, errz.Wrap(err, "acquire config lock")
} }
var cfg *config.Config if ru.ConfigStore.Exists() {
if cfg, err = ru.ConfigStore.Load(ctx); err != nil { var cfg *config.Config
// An error occurred reloading config; release the lock before returning. if cfg, err = ru.ConfigStore.Load(ctx); err != nil {
if unlockErr := lock.Unlock(); unlockErr != nil { // An error occurred reloading config; release the lock before returning.
lg.FromContext(ctx).Warn("Failed to release config lock", if unlockErr := lock.Unlock(); unlockErr != nil {
lga.Lock, lock, lga.Err, unlockErr) lg.FromContext(ctx).Warn("Failed to release config lock",
lga.Lock, lock, lga.Err, unlockErr)
}
return nil, err
} }
return nil, err
}
// Update the run with the fresh config. // Assign the newly-reloaded config to the run.
ru.Config = cfg ru.Config = cfg
} // Else, the config doesn't currently exist on disk; no reload required.
return func() { return func() {
if unlockErr := lock.Unlock(); unlockErr != nil { if unlockErr := lock.Unlock(); unlockErr != nil {