Remediate all lint findings under the canonical golangci-lint config
Fix every finding surfaced by the canonical .golangci.yml with golangci-lint v2.12.2 (refs #61), behavior-preserving throughout: - err113: dynamic errors replaced with package-level sentinels and %w wrapping; direct comparisons converted to errors.Is - goprintffuncname: printf-style helpers renamed with an f suffix (ui.Writer message methods, cli.ReportErrorf, database.Fatalf, vaultik stdoutf) and all call sites updated - revive: stuttering type names renamed (blob.Handler, blob.WithReader, blob.ChunkPosition, storage.URL, storage.Info), doc comments added, unused parameters blanked, package comments added - contextcheck/noctx: ctx threaded through blob.Packer (AddChunk/Flush/FinalizeBlob/PackChunks) and scanner call sites; context-aware exec and sql variants used - funlen/cyclop/gocognit/nestif/dupl: oversized or duplicated functions split into focused helpers across production and test code - paralleltest/tparallel/thelper/usetesting/testpackage: tests parallelized where safe (global log.Initialize kept in the serial phase), helpers marked, t.TempDir adopted, external test packages where only exported API is used - gosec: integer conversions clamped or justified, header timeouts added, remaining findings suppressed with per-site justifications - mnd/goconst/lll/wsl_v5/nlreturn/noinlineerr/errcheck and other mechanical findings fixed directly Remove the deprecated log.LogOptions alias (callers migrated to log.Options). make check is green.
This commit is contained in:
@@ -13,6 +13,26 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// configFileMode is the permission set for freshly written config files;
|
||||
// configs may hold S3 credentials, so keep them owner-only.
|
||||
const configFileMode = 0o600
|
||||
|
||||
// configSetArgs is the argument count of `config set <key> <value>`.
|
||||
const configSetArgs = 2
|
||||
|
||||
// configDirMode is the permission set for created config directories;
|
||||
// parent config dirs (e.g. ~/.config) are conventionally traversable.
|
||||
const configDirMode = 0o755
|
||||
|
||||
var (
|
||||
errConfigExists = errors.New("config file already exists")
|
||||
errEmptyConfig = errors.New("empty config file")
|
||||
errKeyNotFound = errors.New("key not found")
|
||||
errNeedNumericIndex = errors.New("key is a list; use a numeric index")
|
||||
errIndexOutOfRange = errors.New("index out of range")
|
||||
errNotMapOrList = errors.New("key is not a map or list")
|
||||
)
|
||||
|
||||
const defaultConfigTemplate = `# vaultik configuration
|
||||
# Documentation: https://sneak.berlin/go/vaultik
|
||||
|
||||
@@ -233,27 +253,29 @@ The config is written to the path from --config, $VAULTIK_CONFIG, or
|
||||
the platform default config directory (e.g. ~/Library/Application Support/
|
||||
on macOS, ~/.config/ on Linux, /etc/vaultik/ as root).`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
path := configPathForInit()
|
||||
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return fmt.Errorf("config file already exists: %s", path)
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return fmt.Errorf("%w: %s", errConfigExists, path)
|
||||
}
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
err := os.MkdirAll(dir, 0o755)
|
||||
err = os.MkdirAll(dir, configDirMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating config directory %s: %w", dir, err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(path, []byte(defaultConfigTemplate), 0o600)
|
||||
err = os.WriteFile(path, []byte(defaultConfigTemplate), configFileMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing config file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Config written to %s\n", path)
|
||||
fmt.Println("Edit it to set your age_recipients, snapshots, and storage_url.")
|
||||
_, _ = fmt.Fprintf(os.Stdout, "Config written to %s\n", path)
|
||||
_, _ = fmt.Fprintln(os.Stdout,
|
||||
"Edit it to set your age_recipients, snapshots, and storage_url.")
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -266,7 +288,7 @@ func newConfigEditCommand() *cobra.Command {
|
||||
Use: "edit",
|
||||
Short: "Open the config file in $EDITOR",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
path, err := ResolveConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -277,7 +299,8 @@ func newConfigEditCommand() *cobra.Command {
|
||||
editor = "vi"
|
||||
}
|
||||
|
||||
ed := exec.Command(editor, path)
|
||||
//nolint:gosec // G204: launching the operator's own $EDITOR is the point
|
||||
ed := exec.CommandContext(cmd.Context(), editor, path)
|
||||
ed.Stdin = os.Stdin
|
||||
ed.Stdout = os.Stdout
|
||||
ed.Stderr = os.Stderr
|
||||
@@ -293,7 +316,7 @@ func newConfigGetCommand() *cobra.Command {
|
||||
Use: "get <key>",
|
||||
Short: "Print a config value by dotted path (e.g. storage_url, compression_level)",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
path, err := ResolveConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -310,7 +333,7 @@ func newConfigGetCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
if node.Kind == yaml.ScalarNode {
|
||||
fmt.Println(node.Value)
|
||||
_, _ = fmt.Fprintln(os.Stdout, node.Value)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -320,7 +343,7 @@ func newConfigGetCommand() *cobra.Command {
|
||||
return fmt.Errorf("marshaling value: %w", err)
|
||||
}
|
||||
|
||||
fmt.Print(string(out))
|
||||
_, _ = fmt.Fprint(os.Stdout, string(out))
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -341,8 +364,8 @@ Examples:
|
||||
vaultik config set storage_url "s3://bucket/prefix?endpoint=host®ion=us-east-1"
|
||||
vaultik config set compression_level 9
|
||||
vaultik config set s3.bucket mybucket # legacy S3 fields still supported`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
Args: cobra.ExactArgs(configSetArgs),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
path, err := ResolveConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -353,7 +376,8 @@ Examples:
|
||||
return err
|
||||
}
|
||||
|
||||
if err := yamlPathSet(root, strings.Split(args[0], "."), args[1]); err != nil {
|
||||
err = yamlPathSet(root, strings.Split(args[0], "."), args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -362,16 +386,19 @@ Examples:
|
||||
return fmt.Errorf("marshaling config: %w", err)
|
||||
}
|
||||
|
||||
mode := os.FileMode(0o600)
|
||||
if info, err := os.Stat(path); err == nil {
|
||||
mode := os.FileMode(configFileMode)
|
||||
|
||||
info, statErr := os.Stat(path)
|
||||
if statErr == nil {
|
||||
mode = info.Mode().Perm()
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, out, mode); err != nil {
|
||||
err = os.WriteFile(path, out, mode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing config file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s = %s\n", args[0], args[1])
|
||||
_, _ = fmt.Fprintf(os.Stdout, "%s = %s\n", args[0], args[1])
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -381,13 +408,15 @@ Examples:
|
||||
// loadYAMLFile parses a YAML file into a yaml.Node document tree,
|
||||
// which preserves comments and ordering for round-tripping.
|
||||
func loadYAMLFile(path string) (*yaml.Node, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
data, err := os.ReadFile(path) //nolint:gosec // G304: config path is operator-supplied
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading config file: %w", err)
|
||||
}
|
||||
|
||||
var root yaml.Node
|
||||
if err := yaml.Unmarshal(data, &root); err != nil {
|
||||
|
||||
err = yaml.Unmarshal(data, &root)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing config file: %w", err)
|
||||
}
|
||||
|
||||
@@ -409,7 +438,7 @@ func yamlPathGet(root *yaml.Node, keys []string) (*yaml.Node, error) {
|
||||
node := root
|
||||
if node.Kind == yaml.DocumentNode {
|
||||
if len(node.Content) == 0 {
|
||||
return nil, errors.New("empty config file")
|
||||
return nil, errEmptyConfig
|
||||
}
|
||||
|
||||
node = node.Content[0]
|
||||
@@ -430,21 +459,29 @@ func yamlPathGet(root *yaml.Node, keys []string) (*yaml.Node, error) {
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, fmt.Errorf("key not found: %s", strings.Join(keys[:i+1], "."))
|
||||
return nil, fmt.Errorf("%w: %s",
|
||||
errKeyNotFound, strings.Join(keys[:i+1], "."))
|
||||
}
|
||||
case yaml.SequenceNode:
|
||||
idx, err := strconv.Atoi(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("key %q is a list; use a numeric index", strings.Join(keys[:i], "."))
|
||||
return nil, fmt.Errorf("%w: %s",
|
||||
errNeedNumericIndex, strings.Join(keys[:i], "."))
|
||||
}
|
||||
|
||||
if idx < 0 || idx >= len(node.Content) {
|
||||
return nil, fmt.Errorf("index %d out of range for %s (len %d)", idx, strings.Join(keys[:i], "."), len(node.Content))
|
||||
return nil, fmt.Errorf("%w: index %d for %s (len %d)",
|
||||
errIndexOutOfRange, idx, strings.Join(keys[:i], "."),
|
||||
len(node.Content))
|
||||
}
|
||||
|
||||
node = node.Content[idx]
|
||||
case yaml.DocumentNode, yaml.ScalarNode, yaml.AliasNode:
|
||||
return nil, fmt.Errorf("%w: %s",
|
||||
errNotMapOrList, strings.Join(keys[:i], "."))
|
||||
default:
|
||||
return nil, fmt.Errorf("key %q is not a map or list", strings.Join(keys[:i], "."))
|
||||
return nil, fmt.Errorf("%w: %s",
|
||||
errNotMapOrList, strings.Join(keys[:i], "."))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,62 +507,88 @@ func yamlPathSet(root *yaml.Node, keys []string, value string) error {
|
||||
|
||||
switch node.Kind {
|
||||
case yaml.MappingNode:
|
||||
var valueNode *yaml.Node
|
||||
|
||||
for j := 0; j+1 < len(node.Content); j += 2 {
|
||||
if node.Content[j].Value == key {
|
||||
valueNode = node.Content[j+1]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if valueNode == nil {
|
||||
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Value: key}
|
||||
|
||||
valueNode = &yaml.Node{Kind: yaml.MappingNode}
|
||||
if last {
|
||||
valueNode = &yaml.Node{Kind: yaml.ScalarNode, Value: value}
|
||||
}
|
||||
|
||||
node.Content = append(node.Content, keyNode, valueNode)
|
||||
} else if last {
|
||||
setScalar(valueNode, value)
|
||||
}
|
||||
|
||||
node = valueNode
|
||||
|
||||
node = yamlSetInMapping(node, key, value, last)
|
||||
case yaml.SequenceNode:
|
||||
idx, err := strconv.Atoi(key)
|
||||
next, err := yamlSetInSequence(node, keys, i, value, last)
|
||||
if err != nil {
|
||||
return fmt.Errorf("key %q is a list; use a numeric index", strings.Join(keys[:i], "."))
|
||||
return err
|
||||
}
|
||||
|
||||
if idx < 0 || idx > len(node.Content) {
|
||||
return fmt.Errorf("index %d out of range for %s (len %d)", idx, strings.Join(keys[:i], "."), len(node.Content))
|
||||
}
|
||||
|
||||
if idx == len(node.Content) {
|
||||
newNode := &yaml.Node{Kind: yaml.MappingNode}
|
||||
if last {
|
||||
newNode = &yaml.Node{Kind: yaml.ScalarNode, Value: value}
|
||||
}
|
||||
|
||||
node.Content = append(node.Content, newNode)
|
||||
} else if last {
|
||||
setScalar(node.Content[idx], value)
|
||||
}
|
||||
|
||||
node = node.Content[idx]
|
||||
|
||||
node = next
|
||||
case yaml.DocumentNode, yaml.ScalarNode, yaml.AliasNode:
|
||||
return fmt.Errorf("%w: %s",
|
||||
errNotMapOrList, strings.Join(keys[:i], "."))
|
||||
default:
|
||||
return fmt.Errorf("key %q is not a map or list", strings.Join(keys[:i], "."))
|
||||
return fmt.Errorf("%w: %s",
|
||||
errNotMapOrList, strings.Join(keys[:i], "."))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// yamlSetInMapping resolves (creating if needed) the value node for key
|
||||
// within a mapping node, setting it to value when it is the final path
|
||||
// element, and returns the node to descend into.
|
||||
func yamlSetInMapping(node *yaml.Node, key, value string, last bool) *yaml.Node {
|
||||
var valueNode *yaml.Node
|
||||
|
||||
for j := 0; j+1 < len(node.Content); j += 2 {
|
||||
if node.Content[j].Value == key {
|
||||
valueNode = node.Content[j+1]
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if valueNode == nil {
|
||||
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Value: key}
|
||||
|
||||
valueNode = &yaml.Node{Kind: yaml.MappingNode}
|
||||
if last {
|
||||
valueNode = &yaml.Node{Kind: yaml.ScalarNode, Value: value}
|
||||
}
|
||||
|
||||
node.Content = append(node.Content, keyNode, valueNode)
|
||||
} else if last {
|
||||
setScalar(valueNode, value)
|
||||
}
|
||||
|
||||
return valueNode
|
||||
}
|
||||
|
||||
// yamlSetInSequence indexes (or appends to) a sequence node using the
|
||||
// numeric path element keys[i], setting the element to value when it is
|
||||
// the final path element, and returns the node to descend into.
|
||||
func yamlSetInSequence(
|
||||
node *yaml.Node, keys []string, i int, value string, last bool,
|
||||
) (*yaml.Node, error) {
|
||||
idx, err := strconv.Atoi(keys[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s",
|
||||
errNeedNumericIndex, strings.Join(keys[:i], "."))
|
||||
}
|
||||
|
||||
if idx < 0 || idx > len(node.Content) {
|
||||
return nil, fmt.Errorf("%w: index %d for %s (len %d)",
|
||||
errIndexOutOfRange, idx, strings.Join(keys[:i], "."),
|
||||
len(node.Content))
|
||||
}
|
||||
|
||||
if idx == len(node.Content) {
|
||||
newNode := &yaml.Node{Kind: yaml.MappingNode}
|
||||
if last {
|
||||
newNode = &yaml.Node{Kind: yaml.ScalarNode, Value: value}
|
||||
}
|
||||
|
||||
node.Content = append(node.Content, newNode)
|
||||
} else if last {
|
||||
setScalar(node.Content[idx], value)
|
||||
}
|
||||
|
||||
return node.Content[idx], nil
|
||||
}
|
||||
|
||||
// setScalar overwrites a node in place with a plain scalar value.
|
||||
func setScalar(n *yaml.Node, value string) {
|
||||
n.Kind = yaml.ScalarNode
|
||||
|
||||
Reference in New Issue
Block a user