From b3fa92763b3a18b52cae5c427df9100b0f1bf8af Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Tue, 9 Jul 2024 12:52:55 +0100 Subject: [PATCH] fix: Use 0666 file mode for writing configuration and lock files (#47) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, files were written with the 0777 file mode. On most systems with 022 umask they would be written with the execution bits enabled, which is unnecessary for text files. ```console ❯ speakeasy quickstart ❯ ls -la OUTDIR/.speakeasy total 40 drwxr-xr-x@ 6 bflad staff 192 Jul 5 14:25 . drwxr-xr-x@ 17 bflad staff 544 Jul 5 14:32 .. -rwxr-xr-x@ 1 bflad staff 4801 Jul 5 14:25 gen.lock -rwxr-xr-x@ 1 bflad staff 584 Jul 5 14:25 gen.yaml -rw-r--r--@ 1 bflad staff 1062 Jul 5 14:25 workflow.lock -rw-r--r--@ 1 bflad staff 301 Jul 5 14:26 workflow.yaml ``` This change sets the file mode to 0666 and adds verification unit testing for the final saved file permissions. If the testing ever becomes platform dependently flakey, the umask can be explicitly set in the unit testing. --- io.go | 2 +- io_test.go | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/io.go b/io.go index 5b1a1eb..4c11039 100644 --- a/io.go +++ b/io.go @@ -435,7 +435,7 @@ func write(path string, cfg any, o *options) ([]byte, error) { writeFileFunc = o.FS.WriteFile } - if err := writeFileFunc(path, data, os.ModePerm); err != nil { + if err := writeFileFunc(path, data, 0666); err != nil { return nil, fmt.Errorf("could not write gen.yaml: %w", err) } diff --git a/io_test.go b/io_test.go index fdce3a0..0464719 100644 --- a/io_test.go +++ b/io_test.go @@ -1,6 +1,7 @@ package config import ( + "io/fs" "os" "path/filepath" "testing" @@ -630,6 +631,123 @@ func TestLoad_BackwardsCompatibility_Success(t *testing.T) { assert.NoError(t, err) } +func TestSaveConfig(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + cfg *Configuration + opts []Option + expected []byte + }{ + "no-options": { + cfg: &Configuration{ + ConfigVersion: "0.0.0", + }, + expected: []byte("configVersion: 0.0.0\ngeneration: {}\n"), + }, + "option-dontwrite": { + cfg: &Configuration{ + ConfigVersion: Version, + }, + opts: []Option{WithDontWrite()}, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + tempDir := t.TempDir() + speakeasyPath := filepath.Join(tempDir, ".speakeasy") + configPath := filepath.Join(speakeasyPath, "gen.yaml") + + err := os.Mkdir(speakeasyPath, 0755) + assert.NoError(t, err) + + err = SaveConfig(tempDir, testCase.cfg, testCase.opts...) + assert.NoError(t, err) + + fileInfo, err := os.Stat(configPath) + + if len(testCase.expected) == 0 { + assert.ErrorIs(t, err, fs.ErrNotExist) + + return + } + + assert.NoError(t, err) + assert.Equal(t, false, fileInfo.IsDir()) + assert.Equal(t, fs.FileMode(0644), fileInfo.Mode()) + + contents, err := os.ReadFile(configPath) + assert.NoError(t, err) + assert.Equal(t, testCase.expected, contents) + }) + } +} + +func TestSaveLockFile(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + lf *LockFile + opts []Option + expected []byte + }{ + "no-options": { + lf: &LockFile{ + LockVersion: "0.0.0", + }, + expected: []byte(`lockVersion: 0.0.0 +id: "" +management: {} +`), + }, + "option-dontwrite": { + lf: &LockFile{ + LockVersion: v2, + }, + opts: []Option{WithDontWrite()}, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + tempDir := t.TempDir() + speakeasyPath := filepath.Join(tempDir, ".speakeasy") + configPath := filepath.Join(speakeasyPath, "gen.lock") + + err := os.Mkdir(speakeasyPath, 0755) + assert.NoError(t, err) + + err = SaveLockFile(tempDir, testCase.lf, testCase.opts...) + assert.NoError(t, err) + + fileInfo, err := os.Stat(configPath) + + if len(testCase.expected) == 0 { + assert.ErrorIs(t, err, fs.ErrNotExist) + + return + } + + assert.NoError(t, err) + assert.Equal(t, false, fileInfo.IsDir()) + assert.Equal(t, fs.FileMode(0644), fileInfo.Mode()) + + contents, err := os.ReadFile(configPath) + assert.NoError(t, err) + assert.Equal(t, testCase.expected, contents) + }) + } +} + func createTempFile(dir string, fileName, contents string) error { if err := os.MkdirAll(dir, os.ModePerm); err != nil { return err