Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 10 additions & 31 deletions cmd/config-manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package main

import (
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -398,29 +399,18 @@ func updateSymlink(config string, f *Flags) (bool, error) {
src = filepath.Join(f.ConfigFileSrcdir, config)
}

exists, err := fileExists(f.ConfigFileDst)
if err != nil {
return false, fmt.Errorf("error checking if file '%s' exists: %v", f.ConfigFileDst, err)
}
if exists {
srcRealpath, err := filepath.EvalSymlinks(src)
if err != nil {
return false, fmt.Errorf("error evaluating realpath of '%v': %v", src, err)
}

dstRealpath, err := filepath.EvalSymlinks(f.ConfigFileDst)
if err != nil {
return false, fmt.Errorf("error evaluating realpath of '%v': %v", f.ConfigFileDst, err)
}

if srcRealpath == dstRealpath {
current, err := os.Readlink(f.ConfigFileDst)
if err == nil {
if current == src {
return false, nil
}
} else if !os.IsNotExist(err) && !errors.Is(err, syscall.EINVAL) {
return false, fmt.Errorf("error reading symlink '%s': %v", f.ConfigFileDst, err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to quote strings it's better to use %q instead of %s. In this case however, it's better to just unquote the string as the logs are emitted in JSON format.

Suggested change
return false, fmt.Errorf("error reading symlink '%s': %v", f.ConfigFileDst, err)
return false, fmt.Errorf("error reading symlink %s: %w", f.ConfigFileDst, err)

}

err = os.Remove(f.ConfigFileDst)
if err != nil {
return false, fmt.Errorf("error removing existing config: %v", err)
}
err = os.Remove(f.ConfigFileDst)
if err != nil && !os.IsNotExist(err) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this redundant?

!os.IsNotExist(err)

We already check for an IsNotExist error earlier.

return false, fmt.Errorf("error removing existing config: %v", err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return false, fmt.Errorf("error removing existing config: %v", err)
return false, fmt.Errorf("error removing existing config: %w", err)

}

err = os.Symlink(src, f.ConfigFileDst)
Expand Down Expand Up @@ -463,17 +453,6 @@ func findPidToSignal(f *Flags) (int, error) {
return -1, fmt.Errorf("no process found")
}

func fileExists(filename string) (bool, error) {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, err
}
return !info.IsDir(), nil
}

func getConfigFileNameMap(f *Flags) (map[string]bool, error) {
files, err := os.ReadDir(f.ConfigFileSrcdir)
if err != nil {
Expand Down
67 changes: 67 additions & 0 deletions cmd/config-manager/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
# Copyright (c) NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
**/

package main

import (
"os"
"path/filepath"
"testing"

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

func newTestFlags(srcdir, dst string) *Flags {
return &Flags{
ConfigFileSrcdir: srcdir,
ConfigFileDst: dst,
}
}

func TestUpdateSymlinkDanglingDestination(t *testing.T) {
srcdir := t.TempDir()
dst := filepath.Join(t.TempDir(), "config.yaml")
f := newTestFlags(srcdir, dst)

testCases := []struct {
description string
config string
wantChanged bool
}{
{
description: "create dangling symlink",
config: "missing-config",
wantChanged: true,
},
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is not clear to me. It seems like it depends on the previous test. If this is indeed the case, then it's an antipattern. The unit test should be independent of any other unit tests and it should pass when run in isolation.

description: "dangling symlink already pointing at config is a no operation",
config: "missing-config",
wantChanged: false,
},
}

for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
changed, err := updateSymlink(tc.config, f)
require.NoError(t, err)
require.Equal(t, tc.wantChanged, changed)
})
}

link, err := os.Readlink(dst)
require.NoError(t, err)
require.Equal(t, filepath.Join(srcdir, "missing-config"), link)
}
Loading