Repository: helm/chart-testing
Pull request #460 (2022-08-17 08:37:20)

Upgrade go1.19, helm update and add golangci-lint job
**What this PR does / why we need it**:
- upgrade to go1.19
- update helm to v3.9.3
- add golangci-lint
- fix lints and Replace github.com/pkg/errors dependency with native error wrapping

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #

**Special notes for your reviewer**:



Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index c2c83b7..406ba7d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -12,7 +12,7 @@ jobs:
     steps:
 
       - name: checkout
-        uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3.0.1
+        uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3
         with:
           fetch-depth: 0
 
@@ -22,7 +22,7 @@ jobs:
       - name: Setup go
         uses: actions/setup-go@268d8c0ca0432bb2cf416faae41297df9d262d7f # v3.3.0
         with:
-          go-version: '1.18'
+          go-version-file: './go.mod'
           check-latest: true
 
       - uses: azure/setup-helm@b5b231a831f96336bbfeccc1329990f0005c5bb1 # v3.3
@@ -40,16 +40,17 @@ jobs:
       - name: Install syft
         uses: anchore/sbom-action/download-syft@b5042e9d19d8b32849779bfe17673ff84aec702d # v0.12.0
 
+      - name: Install k8s Kind
+        uses: helm/kind-action@d08cf6ff1575077dee99962540d77ce91c62387d # v1.3.0
+        with:
+          install_only: true
+
       - name: Install tools
         run: |
           curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
           chmod +x ./kubectl
           sudo mv ./kubectl /usr/local/bin/kubectl
 
-          curl -fsSLo kind "https://github.com/kubernetes-sigs/kind/releases/download/v0.14.0/kind-linux-amd64"
-          chmod +x kind
-          sudo mv kind /usr/local/bin/kind
-
           ./setup.sh
 
       - name: Test
@@ -64,10 +65,10 @@ jobs:
           git diff --exit-code
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@8b122486cedac8393e77aa9734c3528886e4a1a8 # v1.2.0
+        uses: docker/setup-qemu-action@8b122486cedac8393e77aa9734c3528886e4a1a8 # v2.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@dc7b9719a96d48369863986a06765841d7ea23f6 # v1.6.0
+        uses: docker/setup-buildx-action@dc7b9719a96d48369863986a06765841d7ea23f6 # v2.0.0
 
       - name: Build
         run: |
@@ -76,3 +77,17 @@ jobs:
 
           echo "Building snapshot..."
           ./build.sh
+
+  golangci:
+    name: lint
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # v3
+      - uses: actions/setup-go@84cbf8094393cdc5fe1fe1671ff2647332956b1a # v3
+        with:
+          go-version-file: './go.mod'
+          check-latest: true
+      - name: golangci-lint
+        uses: golangci/golangci-lint-action@537aa1903e5d359d0b27dbc19ddd22c5087f3fbc # v3
+        with:
+          version: v1.48.0
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index e1c12f6..f2e2e15 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -27,7 +27,7 @@ jobs:
       - name: Setup go
         uses: actions/setup-go@268d8c0ca0432bb2cf416faae41297df9d262d7f # v3.3.0
         with:
-          go-version: '1.18'
+          go-version-file: './go.mod'
           check-latest: true
 
       - name: Install GoReleaser
@@ -53,13 +53,13 @@ jobs:
           git diff --exit-code
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@8b122486cedac8393e77aa9734c3528886e4a1a8 # v1.2.0
+        uses: docker/setup-qemu-action@8b122486cedac8393e77aa9734c3528886e4a1a8 # v2.0.0
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@dc7b9719a96d48369863986a06765841d7ea23f6 # v1.6.0
+        uses: docker/setup-buildx-action@dc7b9719a96d48369863986a06765841d7ea23f6 # v2.0.0
 
       - name: Login to registry
-        uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b # v1.14.1
+        uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b # v2.0.0
         with:
           registry: quay.io
           username: ${{ secrets.DOCKER_USERNAME }}
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..866db00
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,31 @@
+linters:
+  enable:
+  - asciicheck
+  - deadcode
+  - depguard
+  - errcheck
+  - errorlint
+  - gofmt
+  - goimports
+  - gosec
+  - gocritic
+  - importas
+  - prealloc
+  - revive
+  - misspell
+  - stylecheck
+  - unconvert
+  - whitespace
+output:
+  uniq-by-line: false
+issues:
+  exclude-rules:
+  - path: _test\.go
+    linters:
+    - errcheck
+    - gosec
+  max-issues-per-linter: 0
+  max-same-issues: 0
+run:
+  issues-exit-code: 1
+  timeout: 10m
diff --git a/Dockerfile b/Dockerfile
index 59be87c..b076f53 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -30,7 +30,7 @@ RUN curl -LO "https://storage.googleapis.com/kubernetes-release/release/$kubectl
     mv kubectl /usr/local/bin/
 
 # Install Helm
-ARG helm_version=v3.9.2
+ARG helm_version=v3.9.3
 LABEL helm_version=$helm_version
 RUN targetArch=$(echo $TARGETPLATFORM | cut -f2 -d '/') \
     && if [ ${targetArch} = "amd64" ]; then \
diff --git a/ct/cmd/install.go b/ct/cmd/install.go
index 12e76b5..41b234d 100644
--- a/ct/cmd/install.go
+++ b/ct/cmd/install.go
@@ -91,7 +91,7 @@ func install(cmd *cobra.Command, args []string) error {
 	}
 	configuration, err := config.LoadConfiguration(cfgFile, cmd, printConfig)
 	if err != nil {
-		return fmt.Errorf("Error loading configuration: %s", err)
+		return fmt.Errorf("failed loading configuration: %w", err)
 	}
 
 	extraSetArgs, err := cmd.Flags().GetString("helm-extra-set-args")
@@ -106,7 +106,7 @@ func install(cmd *cobra.Command, args []string) error {
 	testing.PrintResults(results)
 
 	if err != nil {
-		return fmt.Errorf("Error installing charts: %s", err)
+		return fmt.Errorf("failed installing charts: %w", err)
 	}
 
 	fmt.Println("All charts installed successfully")
diff --git a/ct/cmd/lint.go b/ct/cmd/lint.go
index 2656a4a..dd36f0f 100644
--- a/ct/cmd/lint.go
+++ b/ct/cmd/lint.go
@@ -85,7 +85,7 @@ func lint(cmd *cobra.Command, args []string) error {
 	}
 	configuration, err := config.LoadConfiguration(cfgFile, cmd, printConfig)
 	if err != nil {
-		return fmt.Errorf("Error loading configuration: %s", err)
+		return fmt.Errorf("failed loading configuration: %w", err)
 	}
 
 	emptyExtraSetArgs := ""
@@ -97,7 +97,7 @@ func lint(cmd *cobra.Command, args []string) error {
 	testing.PrintResults(results)
 
 	if err != nil {
-		return fmt.Errorf("Error linting charts: %s", err)
+		return fmt.Errorf("failed linting charts: %w", err)
 	}
 
 	fmt.Println("All charts linted successfully")
diff --git a/ct/cmd/lintAndInstall.go b/ct/cmd/lintAndInstall.go
index 0ec6238..c42a649 100644
--- a/ct/cmd/lintAndInstall.go
+++ b/ct/cmd/lintAndInstall.go
@@ -48,7 +48,7 @@ func lintAndInstall(cmd *cobra.Command, args []string) error {
 	}
 	configuration, err := config.LoadConfiguration(cfgFile, cmd, printConfig)
 	if err != nil {
-		return fmt.Errorf("Error loading configuration: %s", err)
+		return fmt.Errorf("failed loading configuration: %w", err)
 	}
 
 	extraSetArgs, err := cmd.Flags().GetString("helm-extra-set-args")
@@ -63,7 +63,7 @@ func lintAndInstall(cmd *cobra.Command, args []string) error {
 	testing.PrintResults(results)
 
 	if err != nil {
-		return fmt.Errorf("Error linting and installing charts: %s", err)
+		return fmt.Errorf("failed linting and installing charts: %w", err)
 	}
 
 	fmt.Println("All charts linted and installed successfully")
diff --git a/ct/cmd/listChanged.go b/ct/cmd/listChanged.go
index 4c286e3..412f7c1 100644
--- a/ct/cmd/listChanged.go
+++ b/ct/cmd/listChanged.go
@@ -47,7 +47,7 @@ func listChanged(cmd *cobra.Command, args []string) error {
 	}
 	configuration, err := config.LoadConfiguration(cfgFile, cmd, printConfig)
 	if err != nil {
-		return fmt.Errorf("Error loading configuration: %s", err)
+		return fmt.Errorf("failed loading configuration: %w", err)
 	}
 
 	emptyExtraSetArgs := ""
@@ -63,5 +63,6 @@ func listChanged(cmd *cobra.Command, args []string) error {
 	for _, dir := range chartDirs {
 		fmt.Println(dir)
 	}
+
 	return nil
 }
diff --git a/go.mod b/go.mod
index 302a70b..35d2703 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/helm/chart-testing/v3
 
-go 1.18
+go 1.19
 
 require (
 	github.com/MakeNowJust/heredoc v1.0.0
@@ -9,7 +9,6 @@ require (
 	github.com/hashicorp/go-retryablehttp v0.7.1
 	github.com/mattn/go-shellwords v1.0.12
 	github.com/mitchellh/go-homedir v1.1.0
-	github.com/pkg/errors v0.9.1
 	github.com/spf13/cobra v1.5.0
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.13.0
diff --git a/go.sum b/go.sum
index 2b2d980..0f35e49 100644
--- a/go.sum
+++ b/go.sum
@@ -168,7 +168,6 @@ github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
 github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
 github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go
index c66c8e0..2c63f98 100644
--- a/pkg/chart/chart.go
+++ b/pkg/chart/chart.go
@@ -15,8 +15,8 @@
 package chart
 
 import (
+	"errors"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -27,7 +27,6 @@ import (
 	"github.com/helm/chart-testing/v3/pkg/exec"
 	"github.com/helm/chart-testing/v3/pkg/tool"
 	"github.com/helm/chart-testing/v3/pkg/util"
-	"github.com/pkg/errors"
 )
 
 const maxNameLength = 63
@@ -46,7 +45,7 @@ const maxNameLength = 63
 //
 // ListChangedFilesInDirs diffs commit against HEAD and returns changed files for the specified dirs.
 //
-// GetUrlForRemote returns the repo URL for the specified remote.
+// GetURLForRemote returns the repo URL for the specified remote.
 //
 // ValidateRepository checks that the current working directory is a valid git repository,
 // and returns nil if valid.
@@ -57,17 +56,17 @@ type Git interface {
 	RemoveWorktree(path string) error
 	MergeBase(commit1 string, commit2 string) (string, error)
 	ListChangedFilesInDirs(commit string, dirs ...string) ([]string, error)
-	GetUrlForRemote(remote string) (string, error)
+	GetURLForRemote(remote string) (string, error)
 	ValidateRepository() error
 }
 
 // Helm is the interface that wraps Helm operations
 //
-// AddRepo adds a chart repository to the local Helm configuration
+// # AddRepo adds a chart repository to the local Helm configuration
 //
-// BuildDependencies builds the chart's dependencies
+// # BuildDependencies builds the chart's dependencies
 //
-// BuildDependenciesWithArgs allows passing additional arguments to BuildDependencies
+// # BuildDependenciesWithArgs allows passing additional arguments to BuildDependencies
 //
 // LintWithValues runs `helm lint` for the given chart using the specified values file.
 // Pass a zero value for valuesFile in order to run lint without specifying a values file.
@@ -95,21 +94,21 @@ type Helm interface {
 
 // Kubectl is the interface that wraps kubectl operations
 //
-// DeleteNamespace deletes a namespace
+// # DeleteNamespace deletes a namespace
 //
-// WaitForDeployments waits for a deployment to become ready
+// # WaitForDeployments waits for a deployment to become ready
 //
-// GetPodsforDeployment gets all pods for a deployment
+// # GetPodsforDeployment gets all pods for a deployment
 //
-// GetPods gets pods for the given args
+// # GetPods gets pods for the given args
 //
-// GetEvents prints all events for namespace
+// # GetEvents prints all events for namespace
 //
-// DescribePod prints the pod's description
+// # DescribePod prints the pod's description
 //
-// Logs prints the logs of container
+// # Logs prints the logs of container
 //
-// GetInitContainers gets all init containers of pod
+// # GetInitContainers gets all init containers of pod
 //
 // GetContainers gets all containers of pod
 type Kubectl interface {
@@ -127,7 +126,7 @@ type Kubectl interface {
 
 // Linter is the interface that wrap linting operations
 //
-// YamlLint runs `yamllint` on the specified file with the specified configuration
+// # YamlLint runs `yamllint` on the specified file with the specified configuration
 //
 // Yamale runs `yamale` on the specified file with the specified schema file
 type Linter interface {
@@ -149,10 +148,10 @@ type DirectoryLister interface {
 	ListChildDirs(parentDir string, test func(string) bool) ([]string, error)
 }
 
-// ChartUtils is the interface that wraps chart-related methods
+// Utils is the interface that wraps chart-related methods
 //
 // LookupChartDir looks up the chart's root directory based on some chart file that has changed
-type ChartUtils interface {
+type Utils interface {
 	LookupChartDir(chartDirs []string, dir string) (string, error)
 }
 
@@ -238,7 +237,7 @@ type Testing struct {
 	cmdExecutor              CmdExecutor
 	accountValidator         AccountValidator
 	directoryLister          DirectoryLister
-	chartUtils               ChartUtils
+	utils                    Utils
 	previousRevisionWorktree string
 }
 
@@ -268,7 +267,7 @@ func NewTesting(config config.Configuration, extraSetArgs string) (Testing, erro
 		cmdExecutor:      tool.NewCmdTemplateExecutor(procExec),
 		accountValidator: tool.AccountValidator{},
 		directoryLister:  util.DirectoryLister{},
-		chartUtils:       util.ChartUtils{},
+		utils:            util.Utils{},
 	}
 
 	versionString, err := testing.helm.Version()
@@ -294,10 +293,10 @@ func (t *Testing) computePreviousRevisionPath(fileOrDirPath string) string {
 }
 
 func (t *Testing) processCharts(action func(chart *Chart) TestResult) ([]TestResult, error) {
-	var results []TestResult
+	var results []TestResult // nolint: prealloc
 	chartDirs, err := t.FindChartDirsToBeProcessed()
 	if err != nil {
-		return nil, errors.Wrap(err, "Error identifying charts to process")
+		return nil, fmt.Errorf("failed identifying charts to process: %w", err)
 	} else if len(chartDirs) == 0 {
 		return results, nil
 	}
@@ -310,20 +309,20 @@ func (t *Testing) processCharts(action func(chart *Chart) TestResult) ([]TestRes
 		}
 
 		if t.config.ExcludeDeprecated && chart.yaml.Deprecated {
-			fmt.Printf("Chart '%s' is deprecated and will be ignored because '--exclude-deprecated' is set\n", chart.String())
+			fmt.Printf("Chart %q is deprecated and will be ignored because '--exclude-deprecated' is set\n", chart.String())
 		} else {
 			charts = append(charts, chart)
 		}
 	}
 
 	fmt.Println()
-	util.PrintDelimiterLine("-")
+	util.PrintDelimiterLineToWriter(os.Stdout, "-")
 	fmt.Println(" Charts to be processed:")
-	util.PrintDelimiterLine("-")
+	util.PrintDelimiterLineToWriter(os.Stdout, "-")
 	for _, chart := range charts {
 		fmt.Printf(" %s\n", chart)
 	}
-	util.PrintDelimiterLine("-")
+	util.PrintDelimiterLineToWriter(os.Stdout, "-")
 	fmt.Println()
 
 	repoArgs := map[string][]string{}
@@ -342,7 +341,7 @@ func (t *Testing) processCharts(action func(chart *Chart) TestResult) ([]TestRes
 
 		repoExtraArgs := repoArgs[name]
 		if err := t.helm.AddRepo(name, url, repoExtraArgs); err != nil {
-			return nil, errors.Wrapf(err, "Error adding repo: %s=%s", name, url)
+			return nil, fmt.Errorf("failed adding repo: %s=%s: %w", name, url, err)
 		}
 	}
 
@@ -355,31 +354,31 @@ func (t *Testing) processCharts(action func(chart *Chart) TestResult) ([]TestRes
 	if t.config.Upgrade {
 		mergeBase, err := t.computeMergeBase()
 		if err != nil {
-			return results, errors.Wrap(err, "Error identifying merge base")
+			return results, fmt.Errorf("failed identifying merge base: %w", err)
 		}
 		// Add worktree for the target revision
-		worktreePath, err := ioutil.TempDir("./", "ct_previous_revision")
+		worktreePath, err := os.MkdirTemp("./", "ct_previous_revision")
 		if err != nil {
-			return results, errors.Wrap(err, "Could not create previous revision directory")
+			return results, fmt.Errorf("could not create previous revision directory: %w", err)
 		}
 		t.previousRevisionWorktree = worktreePath
 		err = t.git.AddWorktree(worktreePath, mergeBase)
 		if err != nil {
-			return results, errors.Wrap(err, "Could not create worktree for previous revision")
+			return results, fmt.Errorf("could not create worktree for previous revision: %w", err)
 		}
-		defer t.git.RemoveWorktree(worktreePath)
+		defer t.git.RemoveWorktree(worktreePath) // nolint: errcheck
 
 		for _, chart := range charts {
 			if err := t.helm.BuildDependenciesWithArgs(t.computePreviousRevisionPath(chart.Path()), t.config.HelmDependencyExtraArgs); err != nil {
 				// Only print error (don't exit) if building dependencies for previous revision fails.
-				fmt.Println(errors.Wrapf(err, "Error building dependencies for previous revision of chart '%s'\n", chart))
+				fmt.Printf("failed building dependencies for previous revision of chart %q: %v\n", chart, err.Error())
 			}
 		}
 	}
 
 	for _, chart := range charts {
 		if err := t.helm.BuildDependenciesWithArgs(chart.Path(), t.config.HelmDependencyExtraArgs); err != nil {
-			return nil, errors.Wrapf(err, "Error building dependencies for chart '%s'", chart)
+			return nil, fmt.Errorf("failed building dependencies for chart %q: %w", chart, err)
 		}
 
 		result := action(chart)
@@ -392,7 +391,7 @@ func (t *Testing) processCharts(action func(chart *Chart) TestResult) ([]TestRes
 		return results, nil
 	}
 
-	return results, errors.New("Error processing charts")
+	return results, fmt.Errorf("failed processing charts")
 }
 
 // LintCharts lints charts (changed, all, specific) depending on the configuration.
@@ -412,7 +411,7 @@ func (t *Testing) LintAndInstallCharts() ([]TestResult, error) {
 
 // PrintResults writes test results to stdout.
 func (t *Testing) PrintResults(results []TestResult) {
-	util.PrintDelimiterLine("-")
+	util.PrintDelimiterLineToWriter(os.Stdout, "-")
 	if results != nil {
 		for _, result := range results {
 			err := result.Error
@@ -425,12 +424,12 @@ func (t *Testing) PrintResults(results []TestResult) {
 	} else {
 		fmt.Println("No chart changes detected.")
 	}
-	util.PrintDelimiterLine("-")
+	util.PrintDelimiterLineToWriter(os.Stdout, "-")
 }
 
 // LintChart lints the specified chart.
 func (t *Testing) LintChart(chart *Chart) TestResult {
-	fmt.Printf("Linting chart '%s'\n", chart)
+	fmt.Printf("Linting chart %q\n", chart)
 
 	result := TestResult{Chart: chart}
 
@@ -483,7 +482,7 @@ func (t *Testing) LintChart(chart *Chart) TestResult {
 
 	for _, valuesFile := range valuesFiles {
 		if valuesFile != "" {
-			fmt.Printf("\nLinting chart with values file '%s'...\n\n", valuesFile)
+			fmt.Printf("\nLinting chart with values file %q...\n\n", valuesFile)
 		}
 		if err := t.helm.LintWithValues(chart.Path(), valuesFile); err != nil {
 			result.Error = err
@@ -531,11 +530,11 @@ func (t *Testing) UpgradeChart(chart *Chart) TestResult {
 
 	if breakingChangeAllowed {
 		if err != nil {
-			fmt.Println(errors.Wrap(err, fmt.Sprintf("Skipping upgrade test of '%s' because", chart)))
+			fmt.Printf("Skipping upgrade test of %q because: %v\n", chart, err.Error())
 		}
 		return result
 	} else if err != nil {
-		fmt.Printf("Error comparing chart versions for '%s'\n", chart)
+		fmt.Printf("Error comparing chart versions for %q\n", chart)
 		result.Error = err
 		return result
 	}
@@ -548,7 +547,7 @@ func (t *Testing) UpgradeChart(chart *Chart) TestResult {
 }
 
 func (t *Testing) doInstall(chart *Chart) error {
-	fmt.Printf("Installing chart '%s'...\n", chart)
+	fmt.Printf("Installing chart %q...\n", chart)
 	valuesFiles := chart.ValuesFilePathsForCI()
 
 	// Test with defaults if no values files are specified.
@@ -558,7 +557,7 @@ func (t *Testing) doInstall(chart *Chart) error {
 
 	for _, valuesFile := range valuesFiles {
 		if valuesFile != "" {
-			fmt.Printf("\nInstalling chart with values file '%s'...\n\n", valuesFile)
+			fmt.Printf("\nInstalling chart with values file %q...\n\n", valuesFile)
 		}
 
 		// Use anonymous function. Otherwise deferred calls would pile up
@@ -587,7 +586,7 @@ func (t *Testing) doInstall(chart *Chart) error {
 }
 
 func (t *Testing) doUpgrade(oldChart, newChart *Chart, oldChartMustPass bool) error {
-	fmt.Printf("Testing upgrades of chart '%s' relative to previous revision '%s'...\n", newChart, oldChart)
+	fmt.Printf("Testing upgrades of chart %q relative to previous revision %q...\n", newChart, oldChart)
 	valuesFiles := oldChart.ValuesFilePathsForCI()
 	if len(valuesFiles) == 0 {
 		valuesFiles = append(valuesFiles, "")
@@ -595,10 +594,10 @@ func (t *Testing) doUpgrade(oldChart, newChart *Chart, oldChartMustPass bool) er
 	for _, valuesFile := range valuesFiles {
 		if valuesFile != "" {
 			if t.config.SkipMissingValues && !newChart.HasCIValuesFile(valuesFile) {
-				fmt.Printf("Upgrade testing for values file '%s' skipped because a corresponding values file was not found in %s/ci", valuesFile, newChart.Path())
+				fmt.Printf("Upgrade testing for values file %q skipped because a corresponding values file was not found in %s/ci\n", valuesFile, newChart.Path())
 				continue
 			}
-			fmt.Printf("\nInstalling chart '%s' with values file '%s'...\n\n", oldChart, valuesFile)
+			fmt.Printf("\nInstalling chart %q with values file %q...\n\n", oldChart, valuesFile)
 		}
 
 		// Use anonymous function. Otherwise deferred calls would pile up
@@ -617,14 +616,14 @@ func (t *Testing) doUpgrade(oldChart, newChart *Chart, oldChartMustPass bool) er
 				if oldChartMustPass {
 					return err
 				}
-				fmt.Println(errors.Wrap(err, fmt.Sprintf("Upgrade testing for release '%s' skipped because of previous revision installation error", release)))
+				fmt.Printf("Upgrade testing for release %q skipped because of previous revision installation error: %v\n", release, err.Error())
 				return nil
 			}
 			if err := t.testRelease(namespace, release, releaseSelector); err != nil {
 				if oldChartMustPass {
 					return err
 				}
-				fmt.Println(errors.Wrap(err, fmt.Sprintf("Upgrade testing for release '%s' skipped because of previous revision testing error", release)))
+				fmt.Printf("Upgrade testing for release %q skipped because of previous revision testing error: %v\n", release, err.Error())
 				return nil
 			}
 
@@ -656,14 +655,14 @@ func (t *Testing) testRelease(namespace, release, releaseSelector string) error
 func (t *Testing) generateInstallConfig(chart *Chart) (namespace, release, releaseSelector string, cleanup func()) {
 	if t.config.Namespace != "" {
 		namespace = t.config.Namespace
-		release, _ = chart.CreateInstallParams(t.config.BuildId)
+		release, _ = chart.CreateInstallParams(t.config.BuildID)
 		releaseSelector = fmt.Sprintf("%s=%s", t.config.ReleaseLabel, release)
 		cleanup = func() {
 			t.PrintEventsPodDetailsAndLogs(namespace, releaseSelector)
 			t.helm.DeleteRelease(namespace, release)
 		}
 	} else {
-		release, namespace = chart.CreateInstallParams(t.config.BuildId)
+		release, namespace = chart.CreateInstallParams(t.config.BuildID)
 		cleanup = func() {
 			t.PrintEventsPodDetailsAndLogs(namespace, releaseSelector)
 			t.helm.DeleteRelease(namespace, release)
@@ -698,7 +697,7 @@ func (t *Testing) FindChartDirsToBeProcessed() ([]string, error) {
 func (t *Testing) computeMergeBase() (string, error) {
 	err := t.git.ValidateRepository()
 	if err != nil {
-		return "", errors.New("Must be in a git repository")
+		return "", errors.New("must be in a git repository")
 	}
 	return t.git.MergeBase(fmt.Sprintf("%s/%s", t.config.Remote, t.config.TargetBranch), t.config.Since)
 }
@@ -715,7 +714,7 @@ func (t *Testing) ComputeChangedChartDirectories() ([]string, error) {
 
 	allChangedChartFiles, err := t.git.ListChangedFilesInDirs(mergeBase, cfg.ChartDirs...)
 	if err != nil {
-		return nil, errors.Wrap(err, "Error creating diff")
+		return nil, fmt.Errorf("failed creating diff: %w", err)
 	}
 
 	var changedChartDirs []string
@@ -726,7 +725,7 @@ func (t *Testing) ComputeChangedChartDirectories() ([]string, error) {
 		}
 		dir := filepath.Dir(file)
 		// Make sure directory is really a chart directory
-		chartDir, err := t.chartUtils.LookupChartDir(cfg.ChartDirs, dir)
+		chartDir, err := t.utils.LookupChartDir(cfg.ChartDirs, dir)
 		chartDirElement := strings.Split(chartDir, "/")
 		if err == nil {
 			if len(chartDirElement) > 1 {
@@ -740,7 +739,7 @@ func (t *Testing) ComputeChangedChartDirectories() ([]string, error) {
 				changedChartDirs = append(changedChartDirs, chartDir)
 			}
 		} else {
-			fmt.Fprintf(os.Stderr, "Directory '%s' is not a valid chart directory. Skipping...\n", dir)
+			fmt.Fprintf(os.Stderr, "Directory %q is not a valid chart directory. Skipping...\n", dir)
 		}
 	}
 
@@ -756,11 +755,11 @@ func (t *Testing) ReadAllChartDirectories() ([]string, error) {
 	for _, chartParentDir := range cfg.ChartDirs {
 		dirs, err := t.directoryLister.ListChildDirs(chartParentDir,
 			func(dir string) bool {
-				_, err := t.chartUtils.LookupChartDir(cfg.ChartDirs, dir)
+				_, err := t.utils.LookupChartDir(cfg.ChartDirs, dir)
 				return err == nil && !util.StringSliceContains(cfg.ExcludedCharts, filepath.Base(dir))
 			})
 		if err != nil {
-			return nil, errors.Wrap(err, "Error reading chart directories")
+			return nil, fmt.Errorf("failed reading chart directories: %w", err)
 		}
 		chartDirs = append(chartDirs, dirs...)
 	}
@@ -770,7 +769,7 @@ func (t *Testing) ReadAllChartDirectories() ([]string, error) {
 
 // CheckVersionIncrement checks that the new chart version is greater than the old one using semantic version comparison.
 func (t *Testing) CheckVersionIncrement(chart *Chart) error {
-	fmt.Printf("Checking chart '%s' for a version bump...\n", chart)
+	fmt.Printf("Checking chart %q for a version bump...\n", chart)
 
 	oldVersion, err := t.GetOldChartVersion(chart.Path())
 	if err != nil {
@@ -793,7 +792,7 @@ func (t *Testing) CheckVersionIncrement(chart *Chart) error {
 	}
 
 	if result >= 0 {
-		return errors.New("Chart version not ok. Needs a version bump!")
+		return errors.New("chart version not ok. Needs a version bump! ")
 	}
 
 	fmt.Println("Chart version ok.")
@@ -827,12 +826,12 @@ func (t *Testing) GetOldChartVersion(chartPath string) (string, error) {
 
 	chartYamlContents, err := t.git.Show(chartYamlFile, cfg.Remote, cfg.TargetBranch)
 	if err != nil {
-		return "", errors.Wrap(err, "Error reading old Chart.yaml")
+		return "", fmt.Errorf("failed reading old Chart.yaml: %w", err)
 	}
 
 	chartYaml, err := util.UnmarshalChartYaml([]byte(chartYamlContents))
 	if err != nil {
-		return "", errors.Wrap(err, "Error reading old chart version")
+		return "", fmt.Errorf("failed reading old chart version: %w", err)
 	}
 
 	return chartYaml.Version, nil
@@ -847,22 +846,22 @@ func (t *Testing) ValidateMaintainers(chart *Chart) error {
 
 	if chartYaml.Deprecated {
 		if len(chartYaml.Maintainers) > 0 {
-			return errors.New("Deprecated chart must not have maintainers")
+			return errors.New("deprecated chart must not have maintainers")
 		}
 		return nil
 	}
 
 	if len(chartYaml.Maintainers) == 0 {
-		return errors.New("Chart doesn't have maintainers")
+		return errors.New("chart doesn't have maintainers")
 	}
 
-	repoUrl, err := t.git.GetUrlForRemote(t.config.Remote)
+	repoURL, err := t.git.GetURLForRemote(t.config.Remote)
 	if err != nil {
 		return err
 	}
 
 	for _, maintainer := range chartYaml.Maintainers {
-		if err := t.accountValidator.Validate(repoUrl, maintainer.Name); err != nil {
+		if err := t.accountValidator.Validate(repoURL, maintainer.Name); err != nil {
 			return err
 		}
 	}
@@ -871,7 +870,7 @@ func (t *Testing) ValidateMaintainers(chart *Chart) error {
 }
 
 func (t *Testing) PrintEventsPodDetailsAndLogs(namespace string, selector string) {
-	util.PrintDelimiterLine("=")
+	util.PrintDelimiterLineToWriter(os.Stdout, "=")
 
 	printDetails(namespace, "Events of namespace", ".", func(item string) error {
 		return t.kubectl.GetEvents(namespace)
@@ -909,7 +908,7 @@ func (t *Testing) PrintEventsPodDetailsAndLogs(namespace string, selector string
 
 		containers, err := t.kubectl.GetContainers(namespace, pod)
 		if err != nil {
-			fmt.Println("Error printing logs:", err)
+			fmt.Printf("failed printing logs: %v\n", err.Error())
 			return
 		}
 
@@ -920,24 +919,24 @@ func (t *Testing) PrintEventsPodDetailsAndLogs(namespace string, selector string
 			containers...)
 	}
 
-	util.PrintDelimiterLine("=")
+	util.PrintDelimiterLineToWriter(os.Stdout, "=")
 }
 
 func printDetails(resource string, text string, delimiterChar string, printFunc func(item string) error, items ...string) {
 	for _, item := range items {
 		item = strings.Trim(item, "'")
 
-		util.PrintDelimiterLine(delimiterChar)
+		util.PrintDelimiterLineToWriter(os.Stdout, delimiterChar)
 		fmt.Printf("==> %s %s\n", text, resource)
-		util.PrintDelimiterLine(delimiterChar)
+		util.PrintDelimiterLineToWriter(os.Stdout, delimiterChar)
 
 		if err := printFunc(item); err != nil {
 			fmt.Println("Error printing details:", err)
 			return
 		}
 
-		util.PrintDelimiterLine(delimiterChar)
+		util.PrintDelimiterLineToWriter(os.Stdout, delimiterChar)
 		fmt.Printf("<== %s %s\n", text, resource)
-		util.PrintDelimiterLine(delimiterChar)
+		util.PrintDelimiterLineToWriter(os.Stdout, delimiterChar)
 	}
 }
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 7113761..1c849c8 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -15,6 +15,7 @@
 package config
 
 import (
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -25,7 +26,6 @@ import (
 	"github.com/mitchellh/go-homedir"
 
 	"github.com/helm/chart-testing/v3/pkg/util"
-	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 	flag "github.com/spf13/pflag"
 	"github.com/spf13/viper"
@@ -45,7 +45,7 @@ type Configuration struct {
 	Remote                  string        `mapstructure:"remote"`
 	TargetBranch            string        `mapstructure:"target-branch"`
 	Since                   string        `mapstructure:"since"`
-	BuildId                 string        `mapstructure:"build-id"`
+	BuildID                 string        `mapstructure:"build-id"`
 	LintConf                string        `mapstructure:"lint-conf"`
 	ChartYamlSchema         string        `mapstructure:"chart-yaml-schema"`
 	ValidateMaintainers     bool          `mapstructure:"validate-maintainers"`
@@ -73,14 +73,14 @@ type Configuration struct {
 func LoadConfiguration(cfgFile string, cmd *cobra.Command, printConfig bool) (*Configuration, error) {
 	v := viper.New()
 
-	v.SetDefault("kubectl-timeout", time.Duration(30*time.Second))
+	v.SetDefault("kubectl-timeout", 30*time.Second)
 
 	cmd.Flags().VisitAll(func(flag *flag.Flag) {
 		flagName := flag.Name
 		if flagName != "config" && flagName != "help" {
 			if err := v.BindPFlag(flagName, flag); err != nil {
 				// can't really happen
-				panic(fmt.Sprintln(errors.Wrapf(err, "Error binding flag '%s'", flagName)))
+				panic(fmt.Sprintf("failed binding flag %q: %v\n", flagName, err.Error()))
 			}
 		}
 	})
@@ -105,7 +105,7 @@ func LoadConfiguration(cfgFile string, cmd *cobra.Command, printConfig bool) (*C
 	if err := v.ReadInConfig(); err != nil {
 		if cfgFile != "" {
 			// Only error out for specified config file. Ignore for default locations.
-			return nil, errors.Wrap(err, "Error loading config file")
+			return nil, fmt.Errorf("failed loading config file: %w", err)
 		}
 	} else {
 		if printConfig {
@@ -118,7 +118,7 @@ func LoadConfiguration(cfgFile string, cmd *cobra.Command, printConfig bool) (*C
 
 	cfg := &Configuration{}
 	if err := v.Unmarshal(cfg); err != nil {
-		return nil, errors.Wrap(err, "Error unmarshaling configuration")
+		return nil, fmt.Errorf("failed unmarshaling configuration: %w", err)
 	}
 
 	if cfg.ProcessAllCharts && len(cfg.Charts) > 0 {
@@ -202,5 +202,5 @@ func findConfigFile(fileName string) (string, error) {
 		}
 	}
 
-	return "", errors.New(fmt.Sprintf("Config file not found: %s", fileName))
+	return "", fmt.Errorf("config file not found: %s", fileName)
 }
diff --git a/pkg/exec/exec.go b/pkg/exec/exec.go
index 3ab4bec..b0869b3 100644
--- a/pkg/exec/exec.go
+++ b/pkg/exec/exec.go
@@ -23,7 +23,6 @@ import (
 	"strings"
 
 	"github.com/helm/chart-testing/v3/pkg/util"
-	"github.com/pkg/errors"
 )
 
 type ProcessExecutor struct {
@@ -54,7 +53,7 @@ func (p ProcessExecutor) RunProcessInDirAndCaptureOutput(workingDirectory string
 	bytes, err := cmd.CombinedOutput()
 
 	if err != nil {
-		return "", errors.Wrap(err, "Error running process")
+		return "", fmt.Errorf("failed running process: %w", err)
 	}
 	return strings.TrimSpace(string(bytes)), nil
 }
@@ -69,7 +68,7 @@ func (p ProcessExecutor) RunProcessInDirAndCaptureStdout(workingDirectory string
 	bytes, err := cmd.Output()
 
 	if err != nil {
-		return "", errors.Wrap(err, "Error running process")
+		return "", fmt.Errorf("failed running process: %w", err)
 	}
 	return strings.TrimSpace(string(bytes)), nil
 }
@@ -82,12 +81,12 @@ func (p ProcessExecutor) RunProcess(executable string, execArgs ...interface{})
 
 	outReader, err := cmd.StdoutPipe()
 	if err != nil {
-		return errors.Wrap(err, "Error getting StdoutPipe for command")
+		return fmt.Errorf("failed getting StdoutPipe for command: %w", err)
 	}
 
 	errReader, err := cmd.StderrPipe()
 	if err != nil {
-		return errors.Wrap(err, "Error getting StderrPipe for command")
+		return fmt.Errorf("failed getting StderrPipe for command: %w", err)
 	}
 
 	scanner := bufio.NewScanner(io.MultiReader(outReader, errReader))
@@ -99,12 +98,12 @@ func (p ProcessExecutor) RunProcess(executable string, execArgs ...interface{})
 
 	err = cmd.Start()
 	if err != nil {
-		return errors.Wrap(err, "Error running process")
+		return fmt.Errorf("failed running process: %w", err)
 	}
 
 	err = cmd.Wait()
 	if err != nil {
-		return errors.Wrap(err, "Error waiting for process")
+		return fmt.Errorf("failed waiting for process: %w", err)
 	}
 
 	return nil
@@ -116,7 +115,7 @@ func (p ProcessExecutor) CreateProcess(executable string, execArgs ...interface{
 		fmt.Println(">>>", executable, strings.Join(args, " "))
 	}
 	if err != nil {
-		return nil, errors.Wrap(err, "Invalid arguments supplied")
+		return nil, fmt.Errorf("invalid arguments supplied: %w", err)
 	}
 	cmd := exec.Command(executable, args...)
 
@@ -128,25 +127,25 @@ type fn func(port int) error
 func (p ProcessExecutor) RunWithProxy(withProxy fn) error {
 	randomPort, err := util.GetRandomPort()
 	if err != nil {
-		return errors.Wrap(err, "Could not find a free port for running 'kubectl proxy'")
+		return fmt.Errorf("could not find a free port for running 'kubectl proxy': %w", err)
 	}
 
 	fmt.Printf("Running 'kubectl proxy' on port %d\n", randomPort)
 	cmdProxy, err := p.CreateProcess("kubectl", "proxy", fmt.Sprintf("--port=%d", randomPort))
 	if err != nil {
-		return errors.Wrap(err, "Error creating the 'kubectl proxy' process")
+		return fmt.Errorf("failed creating the 'kubectl proxy' process: %w", err)
 	}
 	err = cmdProxy.Start()
 	if err != nil {
-		return errors.Wrap(err, "Error starting the 'kubectl proxy' process")
+		return fmt.Errorf("failed starting the 'kubectl proxy' process: %w", err)
 	}
 
 	err = withProxy(randomPort)
 
-	cmdProxy.Process.Signal(os.Kill)
+	_ = cmdProxy.Process.Signal(os.Kill)
 
 	if err != nil {
-		return errors.Wrap(err, "Error running command with proxy")
+		return fmt.Errorf("failed running command with proxy: %w", err)
 	}
 
 	return nil
diff --git a/pkg/tool/account.go b/pkg/tool/account.go
index c1433fa..26de6f1 100644
--- a/pkg/tool/account.go
+++ b/pkg/tool/account.go
@@ -18,8 +18,6 @@ import (
 	"fmt"
 	"net/http"
 	"regexp"
-
-	"github.com/pkg/errors"
 )
 
 type AccountValidator struct{}
@@ -32,12 +30,12 @@ func (v AccountValidator) Validate(repoURL string, account string) error {
 		return err
 	}
 	url := fmt.Sprintf("https://%s/%s", domain, account)
-	response, err := http.Head(url)
+	response, err := http.Head(url) // nolint: gosec
 	if err != nil {
-		return errors.Wrap(err, "Error validating maintainers")
+		return fmt.Errorf("failed validating maintainers: %w", err)
 	}
 	if response.StatusCode != 200 {
-		return fmt.Errorf("Error validating maintainer '%s': %s", account, response.Status)
+		return fmt.Errorf("failed validating maintainer %q: %s", account, response.Status)
 	}
 	return nil
 }
@@ -46,7 +44,7 @@ func parseOutGitRepoDomain(repoURL string) (string, error) {
 	// This works for GitHub, Bitbucket, and Gitlab
 	submatch := repoDomainPattern.FindStringSubmatch(repoURL)
 	if submatch == nil || len(submatch) < 2 {
-		return "", fmt.Errorf("Could not parse git repository domain for '%s'", repoURL)
+		return "", fmt.Errorf("could not parse git repository domain for %q", repoURL)
 	}
 	return submatch[1], nil
 }
diff --git a/pkg/tool/git.go b/pkg/tool/git.go
index 361a688..1fe5551 100644
--- a/pkg/tool/git.go
+++ b/pkg/tool/git.go
@@ -19,7 +19,6 @@ import (
 	"strings"
 
 	"github.com/helm/chart-testing/v3/pkg/exec"
-	"github.com/pkg/errors"
 )
 
 type Git struct {
@@ -59,7 +58,7 @@ func (g Git) ListChangedFilesInDirs(commit string, dirs ...string) ([]string, er
 	changedChartFilesString, err :=
 		g.exec.RunProcessAndCaptureOutput("git", "diff", "--find-renames", "--name-only", commit, "--", dirs)
 	if err != nil {
-		return nil, errors.Wrap(err, "Error creating diff")
+		return nil, fmt.Errorf("failed creating diff: %w", err)
 	}
 	if changedChartFilesString == "" {
 		return nil, nil
@@ -67,7 +66,7 @@ func (g Git) ListChangedFilesInDirs(commit string, dirs ...string) ([]string, er
 	return strings.Split(changedChartFilesString, "\n"), nil
 }
 
-func (g Git) GetUrlForRemote(remote string) (string, error) {
+func (g Git) GetURLForRemote(remote string) (string, error) {
 	return g.exec.RunProcessAndCaptureOutput("git", "ls-remote", "--get-url", remote)
 }
 
diff --git a/pkg/tool/helm.go b/pkg/tool/helm.go
index 44afd47..f7715d6 100644
--- a/pkg/tool/helm.go
+++ b/pkg/tool/helm.go
@@ -83,7 +83,7 @@ func (h Helm) Test(namespace string, release string) error {
 }
 
 func (h Helm) DeleteRelease(namespace string, release string) {
-	fmt.Printf("Deleting release '%s'...\n", release)
+	fmt.Printf("Deleting release %q...\n", release)
 	if err := h.exec.RunProcess("helm", "uninstall", release, "--namespace", namespace, h.extraArgs); err != nil {
 		fmt.Println("Error deleting Helm release:", err)
 	}
diff --git a/pkg/tool/kubectl.go b/pkg/tool/kubectl.go
index d2021e5..5ad72f0 100644
--- a/pkg/tool/kubectl.go
+++ b/pkg/tool/kubectl.go
@@ -3,6 +3,7 @@ package tool
 import (
 	"bytes"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"net/http"
 	"strings"
@@ -10,7 +11,6 @@ import (
 
 	"github.com/hashicorp/go-retryablehttp"
 	"github.com/helm/chart-testing/v3/pkg/exec"
-	"github.com/pkg/errors"
 )
 
 type Kubectl struct {
@@ -27,7 +27,7 @@ func NewKubectl(exec exec.ProcessExecutor, timeout time.Duration) Kubectl {
 
 // CreateNamespace creates a new namespace with the given name.
 func (k Kubectl) CreateNamespace(namespace string) error {
-	fmt.Printf("Creating namespace '%s'...\n", namespace)
+	fmt.Printf("Creating namespace %q...\n", namespace)
 	return k.exec.RunProcess("kubectl",
 		fmt.Sprintf("--request-timeout=%s", k.timeout),
 		"create", "namespace", namespace)
@@ -36,17 +36,17 @@ func (k Kubectl) CreateNamespace(namespace string) error {
 // DeleteNamespace deletes the specified namespace. If the namespace does not terminate within 120s, pods running in the
 // namespace and, eventually, the namespace itself are force-deleted.
 func (k Kubectl) DeleteNamespace(namespace string) {
-	fmt.Printf("Deleting namespace '%s'...\n", namespace)
+	fmt.Printf("Deleting namespace %q...\n", namespace)
 	timeoutSec := "180s"
 	err := k.exec.RunProcess("kubectl",
 		fmt.Sprintf("--request-timeout=%s", k.timeout),
 		"delete", "namespace", namespace, "--timeout", timeoutSec)
 	if err != nil {
-		fmt.Printf("Namespace '%s' did not terminate after %s.\n", namespace, timeoutSec)
+		fmt.Printf("Namespace %q did not terminate after %s.\n", namespace, timeoutSec)
 	}
 
 	if k.getNamespace(namespace) {
-		fmt.Printf("Namespace '%s' did not terminate after %s.\n", namespace, timeoutSec)
+		fmt.Printf("Namespace %q did not terminate after %s.\n", namespace, timeoutSec)
 
 		fmt.Println("Force-deleting everything...")
 		err = k.exec.RunProcess("kubectl",
@@ -93,7 +93,7 @@ func (k Kubectl) forceNamespaceDeletion(namespace string) error {
 
 	// Remove finalizer from the namespace
 	fun := func(port int) error {
-		fmt.Printf("Removing finalizers from namespace '%s'...\n", namespace)
+		fmt.Printf("Removing finalizers from namespace %q...\n", namespace)
 
 		k8sURL := fmt.Sprintf("http://127.0.0.1:%d/api/v1/namespaces/%s/finalize", port, namespace)
 		req, err := retryablehttp.NewRequest("PUT", k8sURL, bytes.NewReader(namespaceUpdateBytes))
@@ -107,7 +107,7 @@ func (k Kubectl) forceNamespaceDeletion(namespace string) error {
 		client := retryablehttp.NewClient()
 		client.Logger = nil
 		if resp, err := client.Do(req); err != nil {
-			return errors.Wrap(err, errMsg)
+			return fmt.Errorf("%s:%w", errMsg, err)
 		} else if resp.StatusCode != http.StatusOK {
 			return errors.New(errMsg)
 		}
@@ -117,7 +117,7 @@ func (k Kubectl) forceNamespaceDeletion(namespace string) error {
 
 	err = k.exec.RunWithProxy(fun)
 	if err != nil {
-		return errors.Wrapf(err, "Cannot force-delete namespace '%s'", namespace)
+		return fmt.Errorf("cannot force-delete namespace %q: %w", namespace, err)
 	}
 
 	// Give it some more time to be deleted by K8s
@@ -128,11 +128,11 @@ func (k Kubectl) forceNamespaceDeletion(namespace string) error {
 		fmt.Sprintf("--request-timeout=%s", k.timeout),
 		"get", "namespace", namespace)
 	if err != nil {
-		fmt.Printf("Namespace '%s' terminated.\n", namespace)
+		fmt.Printf("Namespace %q terminated.\n", namespace)
 		return nil
 	}
 
-	fmt.Printf("Force-deleting namespace '%s'...\n", namespace)
+	fmt.Printf("Force-deleting namespace %q...\n", namespace)
 	err = k.exec.RunProcess("kubectl",
 		fmt.Sprintf("--request-timeout=%s", k.timeout),
 		"delete", "namespace", namespace, "--force", "--grace-period=0",
@@ -250,7 +250,7 @@ func (k Kubectl) getNamespace(namespace string) bool {
 		fmt.Sprintf("--request-timeout=%s", k.timeout),
 		"get", "namespace", namespace)
 	if err != nil {
-		fmt.Printf("Namespace '%s' terminated.\n", namespace)
+		fmt.Printf("Namespace %q terminated.\n", namespace)
 		return false
 	}
 
diff --git a/pkg/util/util.go b/pkg/util/util.go
index c040521..d3f1974 100644
--- a/pkg/util/util.go
+++ b/pkg/util/util.go
@@ -15,9 +15,10 @@
 package util
 
 import (
+	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
+	"io/fs"
 	"math/rand"
 	"net"
 	"os"
@@ -28,7 +29,6 @@ import (
 
 	"github.com/Masterminds/semver"
 	"github.com/hashicorp/go-multierror"
-	"github.com/pkg/errors"
 	"gopkg.in/yaml.v2"
 )
 
@@ -70,7 +70,7 @@ func doFlatten(result []string, items interface{}) ([]string, error) {
 			}
 		}
 	default:
-		return nil, errors.New(fmt.Sprintf("Flatten does not support %T", v))
+		return nil, fmt.Errorf("flatten does not support %T", v)
 	}
 
 	return result, err
@@ -97,7 +97,7 @@ func RandomString(length int) string {
 	n := len(chars)
 	bytes := make([]byte, length)
 	for i := range bytes {
-		bytes[i] = chars[rand.Intn(n)]
+		bytes[i] = chars[rand.Intn(n)] // nolint: gosec
 	}
 	return string(bytes)
 }
@@ -106,10 +106,18 @@ type DirectoryLister struct{}
 
 // ListChildDirs lists subdirectories of parentDir matching the test function.
 func (l DirectoryLister) ListChildDirs(parentDir string, test func(dir string) bool) ([]string, error) {
-	fileInfos, err := ioutil.ReadDir(parentDir)
+	entries, err := os.ReadDir(parentDir)
 	if err != nil {
 		return nil, err
 	}
+	fileInfos := make([]fs.FileInfo, 0, len(entries))
+	for _, entry := range entries {
+		info, err := entry.Info()
+		if err != nil {
+			return nil, err
+		}
+		fileInfos = append(fileInfos, info)
+	}
 
 	var dirs []string
 	for _, dir := range fileInfos {
@@ -123,9 +131,9 @@ func (l DirectoryLister) ListChildDirs(parentDir string, test func(dir string) b
 	return dirs, nil
 }
 
-type ChartUtils struct{}
+type Utils struct{}
 
-func (u ChartUtils) LookupChartDir(chartDirs []string, dir string) (string, error) {
+func (u Utils) LookupChartDir(chartDirs []string, dir string) (string, error) {
 	for _, chartDir := range chartDirs {
 		currentDir := dir
 		for {
@@ -154,9 +162,9 @@ func (u ChartUtils) LookupChartDir(chartDirs []string, dir string) (string, erro
 // and return a newly allocated ChartYaml object. If no Chart.yaml is present
 // or there is an error unmarshaling the file contents, an error will be returned.
 func ReadChartYaml(dir string) (*ChartYaml, error) {
-	yamlBytes, err := ioutil.ReadFile(filepath.Join(dir, "Chart.yaml"))
+	yamlBytes, err := os.ReadFile(filepath.Join(dir, "Chart.yaml"))
 	if err != nil {
-		return nil, errors.Wrap(err, "Could not read 'Chart.yaml'")
+		return nil, fmt.Errorf("could not read 'Chart.yaml': %w", err)
 	}
 	return UnmarshalChartYaml(yamlBytes)
 }
@@ -166,7 +174,7 @@ func ReadChartYaml(dir string) (*ChartYaml, error) {
 func UnmarshalChartYaml(yamlBytes []byte) (*ChartYaml, error) {
 	chartYaml := &ChartYaml{}
 	if err := yaml.Unmarshal(yamlBytes, chartYaml); err != nil {
-		return nil, errors.Wrap(err, "Could not unmarshal 'Chart.yaml'")
+		return nil, fmt.Errorf("could not unmarshal 'Chart.yaml': %w", err)
 	}
 	return chartYaml, nil
 }
@@ -174,11 +182,11 @@ func UnmarshalChartYaml(yamlBytes []byte) (*ChartYaml, error) {
 func CompareVersions(left string, right string) (int, error) {
 	leftVersion, err := semver.NewVersion(left)
 	if err != nil {
-		return 0, errors.Wrap(err, "Error parsing semantic version")
+		return 0, fmt.Errorf("failed parsing semantic version: %w", err)
 	}
 	rightVersion, err := semver.NewVersion(right)
 	if err != nil {
-		return 0, errors.Wrap(err, "Error parsing semantic version")
+		return 0, fmt.Errorf("failed parsing semantic version: %w", err)
 	}
 	return leftVersion.Compare(rightVersion), nil
 }
@@ -186,11 +194,11 @@ func CompareVersions(left string, right string) (int, error) {
 func BreakingChangeAllowed(left string, right string) (bool, error) {
 	leftVersion, err := semver.NewVersion(left)
 	if err != nil {
-		return false, errors.Wrap(err, "Error parsing semantic version")
+		return false, fmt.Errorf("failed parsing semantic version: %w", err)
 	}
 	rightVersion, err := semver.NewVersion(right)
 	if err != nil {
-		return false, errors.Wrap(err, "Error parsing semantic version")
+		return false, fmt.Errorf("failed parsing semantic version: %w", err)
 	}
 
 	constraintOp := "^"
@@ -199,7 +207,7 @@ func BreakingChangeAllowed(left string, right string) (bool, error) {
 	}
 	c, err := semver.NewConstraint(fmt.Sprintf("%s %s", constraintOp, leftVersion.String()))
 	if err != nil {
-		return false, errors.Wrap(err, "Error parsing semantic version constraint")
+		return false, fmt.Errorf("failed parsing semantic version constraint: %w", err)
 	}
 
 	minor, reasons := c.Validate(rightVersion)
@@ -210,11 +218,6 @@ func BreakingChangeAllowed(left string, right string) (bool, error) {
 	return !minor, err
 }
 
-// Deprecated: To be removed in v4. Use PrintDelimiterLineToWriter instead.
-func PrintDelimiterLine(delimiterChar string) {
-	PrintDelimiterLineToWriter(os.Stdout, delimiterChar)
-}
-
 func PrintDelimiterLineToWriter(w io.Writer, delimiterChar string) {
 	delim := make([]string, 120)
 	for i := 0; i < 120; i++ {
@@ -235,8 +238,8 @@ func SanitizeName(s string, maxLength int) string {
 }
 
 func GetRandomPort() (int, error) {
-	listener, err := net.Listen("tcp", ":0")
-	defer listener.Close()
+	listener, err := net.Listen("tcp", ":0") // nolint: gosec
+	defer listener.Close()                   // nolint: staticcheck
 	if err != nil {
 		return 0, err
 	}


Test patch:
diff --git a/e2e-kind.sh b/e2e-kind.sh
index 796d656..c8cff13 100755
--- a/e2e-kind.sh
+++ b/e2e-kind.sh
@@ -7,7 +7,7 @@ set -o pipefail
 CLUSTER_NAME=chart-testing
 readonly CLUSTER_NAME
 
-K8S_VERSION=v1.21.2
+K8S_VERSION=v1.22.9
 readonly K8S_VERSION
 
 create_kind_cluster() {
diff --git a/pkg/chart/chart_test.go b/pkg/chart/chart_test.go
index 2f05a31..78cf8d1 100644
--- a/pkg/chart/chart_test.go
+++ b/pkg/chart/chart_test.go
@@ -21,7 +21,6 @@ import (
 
 	"github.com/helm/chart-testing/v3/pkg/config"
 	"github.com/helm/chart-testing/v3/pkg/util"
-	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 )
@@ -63,7 +62,7 @@ func (g fakeGit) RemoveWorktree(path string) error {
 	return nil
 }
 
-func (g fakeGit) GetUrlForRemote(remote string) (string, error) {
+func (g fakeGit) GetURLForRemote(remote string) (string, error) {
 	return "git@github.com/helm/chart-testing", nil
 }
 
@@ -77,7 +76,7 @@ func (v fakeAccountValidator) Validate(repoDomain string, account string) error
 	if strings.HasPrefix(account, "valid") {
 		return nil
 	}
-	return errors.New(fmt.Sprintf("Error validating account: %s", account))
+	return fmt.Errorf("failed validating account: %s", account)
 }
 
 type fakeLinter struct {
@@ -145,7 +144,7 @@ func newTestingMock(cfg config.Configuration) Testing {
 		config:           cfg,
 		directoryLister:  util.DirectoryLister{},
 		git:              fakeGit{},
-		chartUtils:       util.ChartUtils{},
+		utils:            util.Utils{},
 		accountValidator: fakeAccountValidator{},
 		linter:           fakeMockLinter,
 		helm:             new(fakeHelm),
@@ -299,7 +298,6 @@ func TestLintChartSchemaValidation(t *testing.T) {
 
 	runTests(true, 0, 1)
 	runTests(false, 0, 0)
-
 }
 
 func TestLintYamlValidation(t *testing.T) {
diff --git a/pkg/chart/integration_test.go b/pkg/chart/integration_test.go
index 9d5f881..5322d0f 100644
--- a/pkg/chart/integration_test.go
+++ b/pkg/chart/integration_test.go
@@ -38,7 +38,7 @@ func newTestingHelmIntegration(cfg config.Configuration, extraSetArgs string) Te
 		config:           cfg,
 		directoryLister:  util.DirectoryLister{},
 		git:              fakeGit{},
-		chartUtils:       util.ChartUtils{},
+		utils:            util.Utils{},
 		accountValidator: fakeAccountValidator{},
 		linter:           fakeMockLinter,
 		helm:             tool.NewHelm(procExec, extraArgs, strings.Fields(extraSetArgs)),
@@ -121,7 +121,7 @@ func TestUpgradeChart(t *testing.T) {
 		Upgrade: true,
 	}
 	ct := newTestingHelmIntegration(cfg, "")
-	processError := fmt.Errorf("Error waiting for process: exit status 1")
+	processError := fmt.Errorf("failed waiting for process: exit status 1")
 
 	cases := []testCase{
 		{
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index 0087192..4026f4e 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -40,7 +40,7 @@ func loadAndAssertConfigFromFile(t *testing.T, configFile string) {
 
 	require.Equal(t, "origin", cfg.Remote)
 	require.Equal(t, "main", cfg.TargetBranch)
-	require.Equal(t, "pr-42", cfg.BuildId)
+	require.Equal(t, "pr-42", cfg.BuildID)
 	require.Equal(t, "my-lint-conf.yaml", cfg.LintConf)
 	require.Equal(t, "my-chart-yaml-schema.yaml", cfg.ChartYamlSchema)
 	require.Equal(t, true, cfg.ValidateMaintainers)
diff --git a/pkg/tool/account_test.go b/pkg/tool/account_test.go
index 498d5e0..30a03f5 100644
--- a/pkg/tool/account_test.go
+++ b/pkg/tool/account_test.go
@@ -10,7 +10,7 @@ import (
 func TestParseOutGitDomain(t *testing.T) {
 	var testDataSlice = []struct {
 		name     string
-		repoUrl  string
+		repoURL  string
 		expected string
 		err      error
 	}{
@@ -23,12 +23,12 @@ func TestParseOutGitDomain(t *testing.T) {
 		{"Bitbucket SSH", "git@bitbucket.com:foo/bar", "bitbucket.com", nil},
 		{"Bitbucket HTTPS", "https://bitbucket.com/foo/bar", "bitbucket.com", nil},
 		{"Bitbucket HTTPS with username/password", "https://user:pass@bitbucket.com/foo/bar", "bitbucket.com", nil},
-		{"Invalid", "foo/bar", "", fmt.Errorf("Could not parse git repository domain for 'foo/bar'")},
+		{"Invalid", "foo/bar", "", fmt.Errorf("could not parse git repository domain for \"foo/bar\"")},
 	}
 
 	for _, testData := range testDataSlice {
 		t.Run(testData.name, func(t *testing.T) {
-			actual, err := parseOutGitRepoDomain(testData.repoUrl)
+			actual, err := parseOutGitRepoDomain(testData.repoURL)
 			assert.Equal(t, err, testData.err)
 			assert.Equal(t, testData.expected, actual)
 		})
diff --git a/pkg/tool/cmdexecutor_test.go b/pkg/tool/cmdexecutor_test.go
index 21cb6ea..67fb5f2 100644
--- a/pkg/tool/cmdexecutor_test.go
+++ b/pkg/tool/cmdexecutor_test.go
@@ -69,8 +69,8 @@ func TestCmdTemplateExecutor_RunCommand(t *testing.T) {
 			if err := templateExecutor.RunCommand(tt.args.cmdTemplate, tt.args.data); (err != nil) != tt.wantErr {
 				t.Errorf("RunCommand() error = %v, wantErr %v", err, tt.wantErr)
 			}
-			tt.validate(t, processExecutor)
 
+			tt.validate(t, processExecutor)
 		})
 	}
 }