Skip to content

Commit

Permalink
sq fails on first config write (#387)
Browse files Browse the repository at this point in the history
* Fixed bug with config write on fresh install

* Added test for config write on fresh install
  • Loading branch information
neilotoole committed Jan 30, 2024
1 parent fd070ee commit 135318f
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 14 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 🐥.

## [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

This is a tiny bugfix release for a runtime issue on some Linux distros. See
Expand Down Expand Up @@ -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.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.2]: https://github.com/neilotoole/sq/compare/v0.47.1...v0.47.2
23 changes: 23 additions & 0 deletions cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"strings"
"testing"

"github.com/neilotoole/sq/cli/config"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand All @@ -26,6 +28,27 @@ import (
"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) {
t.Parallel()
// Execute a bunch of smoke test cases.
Expand Down
8 changes: 8 additions & 0 deletions cli/config/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import (

// Store saves and loads config.
type Store interface {
// Exists returns true if the config exists in the store.
Exists() bool

// Save writes config to the store.
Save(ctx context.Context, cfg *Config) error

Expand All @@ -34,6 +37,11 @@ type Store interface {
// and Load always returns a new empty Config. Useful for testing.
type DiscardStore struct{}

// Exists implements Store.Exists. It returns true.
func (s DiscardStore) Exists() bool {
return true
}

// Lockfile implements Store.Lockfile.
func (DiscardStore) Lockfile() (lockfile.Lockfile, error) {
f, err := os.CreateTemp("", fmt.Sprintf("sq-%d.lock", os.Getpid()))
Expand Down
2 changes: 1 addition & 1 deletion cli/config/yamlstore/defaultload.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func Load(ctx context.Context, osArgs []string, optsReg *options.Registry,
OptionsRegistry: optsReg,
}

if !cfgStore.fileExists() {
if !cfgStore.Exists() {
cfg := config.New()
return cfg, cfgStore, nil
}
Expand Down
4 changes: 2 additions & 2 deletions cli/config/yamlstore/yamlstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,9 @@ func (fs *Store) write(ctx context.Context, data []byte) error {
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.
func (fs *Store) fileExists() bool {
func (fs *Store) Exists() bool {
_, err := os.Stat(fs.Path)
return err == nil
}
Expand Down
26 changes: 15 additions & 11 deletions cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,9 @@ func cmdRequiresConfigLock(cmd *cobra.Command) bool {

// 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
// 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.
// Timeout and progress options from ctx are honored.
Expand Down Expand Up @@ -387,18 +389,20 @@ func lockReloadConfig(cmd *cobra.Command) (unlock func(), err error) {
return nil, errz.Wrap(err, "acquire config lock")
}

var cfg *config.Config
if cfg, err = ru.ConfigStore.Load(ctx); err != nil {
// An error occurred reloading config; release the lock before returning.
if unlockErr := lock.Unlock(); unlockErr != nil {
lg.FromContext(ctx).Warn("Failed to release config lock",
lga.Lock, lock, lga.Err, unlockErr)
if ru.ConfigStore.Exists() {
var cfg *config.Config
if cfg, err = ru.ConfigStore.Load(ctx); err != nil {
// An error occurred reloading config; release the lock before returning.
if unlockErr := lock.Unlock(); unlockErr != nil {
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.
ru.Config = cfg
// Assign the newly-reloaded config to the run.
ru.Config = cfg
} // Else, the config doesn't currently exist on disk; no reload required.

return func() {
if unlockErr := lock.Unlock(); unlockErr != nil {
Expand Down

0 comments on commit 135318f

Please sign in to comment.