Repository: terraform-docs/terraform-docs (go)

Issue:
terraform_docs runtime failure  "panic: runtime error: invalid memory address or nil pointer dereference"
<!-- Please note, this template is for bugs report, not feature requests -->
<!-- For more information, see the Contributing Guidelines at -->
<!-- https://github.com/terraform-docs/terraform-docs/tree/master/CONTRIBUTING.md -->

**Describe the bug**

terraform_docs runtime failure  "panic: runtime error: invalid memory address or nil pointer dereference". 
running via precommit hooks. its being invoked from microsoft azure devops pipeline. 

Starting: Run pre-commit-terraform on /home/vsts/work/1/s/terraform/hhck-webapp
==============================================================================
Task         : Command line
Description  : Run a command line script using Bash on Linux and macOS and cmd.exe on Windows
Version      : 2.164.2
Author       : Microsoft Corporation
Help         : https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/command-line
==============================================================================
Generating script.
========================== Starting Command Output ===========================
/bin/bash --noprofile --norc /home/vsts/work/_temp/158cd010-1a06-4dea-a6d6-50c0f7ed0314.sh
pre-commit run --files *
[INFO] Initializing environment for git://github.com/antonbabenko/pre-commit-terraform.
Terraform docs...........................................................Failed
hookid: terraform_docs

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x7de744]

goroutine 1 [running]:
github.com/terraform-docs/terraform-docs/internal/cli.(*cfgreader).exist(0xc0000b41c0, 0xc0000b41c0, 0x2, 0xc00001cf40)
	/home/runner/work/terraform-docs/terraform-docs/internal/cli/reader.go:22 +0xe4
github.com/terraform-docs/terraform-docs/internal/cli.PreRunEFunc.func1(0xc00008fb80, 0xc000112240, 0x1, 0x1, 0x0, 0x0)
	/home/runner/work/terraform-docs/terraform-docs/internal/cli/run.go:46 +0x1b9
github.com/spf13/cobra.(*Command).execute(0xc00008fb80, 0xc000112220, 0x1, 0x1, 0xc00008fb80, 0xc000112220)

**To Reproduce**

Steps to reproduce the behavior:

1. download curl -Lo ./terraform-docs https://github.com/terraform-docs/terraform-docs/releases/download/v0.10.0-rc.1/terraform-docs-v0.10.0-rc.1-$(uname | tr '[:upper:]' '[:lower:]')-amd64
2. configure terraform_docs from precommit
3. execute precommit.


**Expected behavior**

terraform_docs should run successfully and return passed status without throwing runtime errors.
**Version information:**

- terraform-docs version (use `terraform-docs --version`): **v0.10.0-rc.1**
- Go version (if you manually built. use `go version`): **1.14.4**
- OS (e.g. Windows, MacOS): **Ubuntu** 


Thanks in advance

Cheers
Omkar


Pull request:
Prevent segfault error if input arg is a file
### Prerequisites

Put an `x` into the box(es) that apply:

- [ ] This pull request fixes a bug.
- [ ] This pull request adds a feature.
- [ ] This pull request enhances existing functionality.
- [ ] This pull request introduces breaking change.

For more information, see the [Contributing Guide](https://github.com/terraform-docs/terraform-docs/tree/master/CONTRIBUTING.md).

### Description

This PR fixes a regression added in `v0.10.0` in which if the input arg is a file instead of a directory the terraform-docs crashes with `panic: runtime error: invalid memory address or nil pointer dereference` error instead of gracefully handle it.

### Issues Resolved

Fixes #297 
Related to #325 

### Checklist

Put an `x` into all boxes that apply:

- [ ] I have read the [Contributing Guidelines](https://github.com/terraform-docs/terraform-docs/tree/master/CONTRIBUTING.md).

#### Tests

- [x] I have added tests to cover my changes.
- [x] All tests pass when I run `make test`.

#### Documentation

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.

#### Code Style

- [ ] My code follows the code style of this project.


Patch:
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index ff6be80..ca606c2 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -56,7 +56,7 @@ jobs:
       run: |
         echo ${REGISTRY_PASSWORD} | docker login -u ${REGISTRY_USERNAME} --password-stdin quay.io
         DOCKER_TAG=${{ steps.vars.outputs.tag }} make docker push
-        DOCKER_TAG=latest make push
+        DOCKER_TAG=latest make docker push
       env:
         REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
         REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
diff --git a/internal/cli/reader.go b/internal/cli/reader.go
index 89c0373..f03e8e8 100644
--- a/internal/cli/reader.go
+++ b/internal/cli/reader.go
@@ -19,7 +19,7 @@ func (c *cfgreader) exist() (bool, error) {
 	if c.file == "" {
 		return false, fmt.Errorf("config file name is missing")
 	}
-	if info, err := os.Stat(c.file); os.IsNotExist(err) || info.IsDir() {
+	if info, err := os.Stat(c.file); os.IsNotExist(err) || info == nil || info.IsDir() {
 		return false, err
 	}
 	return true, nil
diff --git a/scripts/build/build-all-osarch.sh b/scripts/build/build-all-osarch.sh
index 89225b8..20241b7 100755
--- a/scripts/build/build-all-osarch.sh
+++ b/scripts/build/build-all-osarch.sh
@@ -18,48 +18,46 @@ if [ -z "${GOLDFLAGS}" ]; then
     exit 1
 fi
 
-PWD=$(cd $(dirname "$0") && pwd -P)
+PWD=$(cd "$(dirname "$0")" && pwd -P)
 BUILD_DIR="${PWD}/../../${BUILD_DIR}"
 
 CGO_ENABLED=0 gox \
     -verbose \
     -ldflags "${GOLDFLAGS}" \
-    -gcflags=-trimpath=`go env GOPATH` \
+    -gcflags=-trimpath="$(go env GOPATH)" \
     -os="${GOOS}" \
     -arch="${GOARCH}" \
     -osarch="!darwin/arm" \
-    -output="${BUILD_DIR}/{{.OS}}-{{.Arch}}/{{.Dir}}" ${PWD}/../../
+    -output="${BUILD_DIR}/{{.OS}}-{{.Arch}}/{{.Dir}}" "${PWD}"/../../
 
 printf "\033[36m==> Finalize binary\033[0m\n"
 
-for platform in $(find ${BUILD_DIR} -mindepth 1 -maxdepth 1 -type d); do
-    OSARCH=$(basename ${platform})
+for platform in $(find "${BUILD_DIR}" -mindepth 1 -maxdepth 1 -type d | sort -u); do
+    OSARCH=$(basename "${platform}")
     FULLNAME="${NAME}-${VERSION}-${OSARCH}"
 
     case "${OSARCH}" in
     "windows"*)
-        mv ${platform}/${NAME}.exe ${BUILD_DIR}/${FULLNAME}.exe
+        mv "${platform}/${NAME}.exe" "${BUILD_DIR}/${FULLNAME}.exe"
         printf -- "--> %15s: bin/%s\n" "${OSARCH}" "${FULLNAME}.exe"
-
         ;;
     *)
-        mv ${platform}/${NAME} ${BUILD_DIR}/${FULLNAME}
+        mv "${platform}/${NAME}" "${BUILD_DIR}/${FULLNAME}"
         printf -- "--> %15s: bin/%s\n" "${OSARCH}" "${FULLNAME}"
-
         ;;
     esac
 done
 
-cd ${BUILD_DIR}
-touch ${NAME}-${VERSION}.sha256sum
+cd "${BUILD_DIR}"
+touch "${NAME}-${VERSION}.sha256sum"
 
 for binary in $(find . -mindepth 1 -maxdepth 1 -type f | grep -v "${NAME}-${VERSION}.sha256sum" | sort); do
-    binary=$(basename ${binary})
+    binary=$(basename "${binary}")
 
     if command -v sha256sum >/dev/null; then
-        sha256sum ${binary} >>${NAME}-${VERSION}.sha256sum
+        sha256sum "${binary}" >>"${NAME}-${VERSION}.sha256sum"
     elif command -v shasum >/dev/null; then
-        shasum -a256 ${binary} >>${NAME}-${VERSION}.sha256sum
+        shasum -a256 "${binary}" >>"${NAME}-${VERSION}.sha256sum"
     fi
 done
 
diff --git a/scripts/release/release-note.sh b/scripts/release/release-note.sh
index 86b804f..c8314c6 100755
--- a/scripts/release/release-note.sh
+++ b/scripts/release/release-note.sh
@@ -4,7 +4,7 @@ set -o errexit
 set -o nounset
 set -o pipefail
 
-PWD=$(cd $(dirname "$0") && pwd -P)
+PWD=$(cd "$(dirname "$0")" && pwd -P)
 
 # Find closest tag
 CLOSEST_VERSION=$(git describe --tags --abbrev=0)
@@ -15,6 +15,6 @@ if ! command -v git-chglog >/dev/null ; then
 fi
 
 # Generate Changelog
-git-chglog --config ${PWD}/../../scripts/chglog/config-release-note.yml --tag-filter-pattern v[0-9]+.[0-9]+.[0-9]+$ --output ${PWD}/../../CURRENT-RELEASE-CHANGELOG.md ${CLOSEST_VERSION}
+git-chglog --config "${PWD}"/../../scripts/chglog/config-release-note.yml --tag-filter-pattern v[0-9]+.[0-9]+.[0-9]+$ --output "${PWD}"/../../CURRENT-RELEASE-CHANGELOG.md "${CLOSEST_VERSION}"
 
-cat ${PWD}/../../CURRENT-RELEASE-CHANGELOG.md
+cat "${PWD}"/../../CURRENT-RELEASE-CHANGELOG.md
diff --git a/scripts/release/release.sh b/scripts/release/release.sh
index 1ce9d94..18dfb71 100755
--- a/scripts/release/release.sh
+++ b/scripts/release/release.sh
@@ -4,7 +4,7 @@ set -o errexit
 set -o pipefail
 
 CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
-if [ -z "${CURRENT_BRANCH}" -o "${CURRENT_BRANCH}" != "master" ]; then
+if [ -z "${CURRENT_BRANCH}" ] && [ "${CURRENT_BRANCH}" != "master" ]; then
     echo "Error: The current branch is '${CURRENT_BRANCH}', switch to 'master' to do the release."
     exit 1
 fi
@@ -31,61 +31,52 @@ if [ -z "${CURRENT_VERSION}" ]; then
     CURRENT_VERSION=$(git describe --tags --exact-match 2>/dev/null || git describe --tags 2>/dev/null || echo "v0.0.1-$(COMMIT_HASH)")
 fi
 
-if [ "v${RELEASE_VERSION}" = "${CURRENT_VERSION}" ]; then
+if [ "v${RELEASE_VERSION}" == "${CURRENT_VERSION}" ]; then
     echo "Error: provided version (v${RELEASE_VERSION}) already exists."
     exit 1
 fi
 
-if [ $(git describe --tags "v${RELEASE_VERSION}" 2>/dev/null) ]; then
+if [ "$(git describe --tags "v${RELEASE_VERSION}" 2>/dev/null)" ]; then
     echo "Error: provided version (v${RELEASE_VERSION}) already exists."
     exit 1
 fi
 
-PWD=$(cd $(dirname "$0") && pwd -P)
+PWD=$(cd "$(dirname "$0")" && pwd -P)
 
 # get closest GA tag, ignore alpha, beta and rc tags
 function getClosestVersion() {
     for t in $(git tag --sort=-creatordate); do
         tag="$t"
-        if [[ $tag == *"-alpha"* || $tag == *"-beta"* || $tag == *"-rc"* ]]; then
+        if [[ "$tag" == *"-alpha"* ]] || [[ "$tag" == *"-beta"* ]] || [[ "$tag" == *"-rc"* ]]; then
             continue
         fi
         break
     done
-    echo "$tag" | sed 's/^v//'
+    echo "${tag//^v/}"
 }
 CLOSEST_VERSION=$(getClosestVersion)
 
 # Bump the released version in README and version.go
-sed -i -E 's|'${CLOSEST_VERSION}'|'${RELEASE_VERSION}'|g' README.md
-sed -i -E 's|v'${RELEASE_VERSION}'-alpha|v'${RELEASE_VERSION}'|g' internal/version/version.go
+if [[ $RELEASE_VERSION != *"-alpha"* && $RELEASE_VERSION != *"-beta"* && $RELEASE_VERSION != *"-rc"* ]]; then
+    sed -i -E "s|${CLOSEST_VERSION}|${RELEASE_VERSION}|g" README.md
+    git add README.md
+fi
+sed -i -E "s|v${RELEASE_VERSION}-alpha|v${RELEASE_VERSION}|g" internal/version/version.go
 
 # Commit changes
 printf "\033[36m==> %s\033[0m\n" "Commit changes for release version v${RELEASE_VERSION}"
-git add README.md internal/version/version.go
+git add internal/version/version.go
 git commit -m "Release version v${RELEASE_VERSION}"
 
 printf "\033[36m==> %s\033[0m\n" "Push commits for v${RELEASE_VERSION}"
 git push origin master
 
 # Generate Changelog
-make --no-print-directory -f ${PWD}/../../Makefile changelog NEXT="--next-tag v${RELEASE_VERSION}"
+make --no-print-directory -f "${PWD}"/../../Makefile changelog NEXT="--next-tag v${RELEASE_VERSION}"
 
 # Tag the release
 printf "\033[36m==> %s\033[0m\n" "Tag release v${RELEASE_VERSION}"
 git tag --annotate --message "v${RELEASE_VERSION} Release" "v${RELEASE_VERSION}"
 
 printf "\033[36m==> %s\033[0m\n" "Push tag release v${RELEASE_VERSION}"
-git push origin v${RELEASE_VERSION}
-
-# Bump the next version in version.go
-NEXT_VERSION=$(echo "${RELEASE_VERSION}" | sed 's/^v//' | awk -F'[ .]' '{print $1"."$2+1".0"}')
-sed -i -E 's|v'${RELEASE_VERSION}'|v'${NEXT_VERSION}'-alpha|g' internal/version/version.go
-
-# Commit changes
-printf "\033[36m==> %s\033[0m\n" "Bump version to v${NEXT_VERSION}-alpha"
-git add internal/version/version.go
-git commit -m "Bump version to v${NEXT_VERSION}-alpha"
-
-printf "\033[36m==> %s\033[0m\n" "Push commits for v${NEXT_VERSION}-alpha"
-git push origin master
+git push origin "v${RELEASE_VERSION}"


Test patch:
diff --git a/internal/cli/reader_test.go b/internal/cli/reader_test.go
index 4a88638..5e139c9 100644
--- a/internal/cli/reader_test.go
+++ b/internal/cli/reader_test.go
@@ -36,6 +36,13 @@ func TestExists(t *testing.T) {
 			wantErr:  true,
 			errMsg:   "config file name is missing",
 		},
+		{
+			name:     "main argument is a file",
+			config:   cfgreader{file: "testdata/sample-config.yaml/some-config.yaml"},
+			expected: false,
+			wantErr:  true,
+			errMsg:   "stat testdata/sample-config.yaml/some-config.yaml: not a directory",
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {