Skip to main content

3 posts tagged with "bash"

View All Tags

Validating for Terraform docs in Merge Requests

· 4 min read
Aurelian Shuttleworth
Aurelian Shuttleworth
Site Reliability Engineer

Terraform Docs is a fantastic helper when documenting Terraform code, but enforcing its use is not straight forward as it seems. In this post, I attempt to integrate it into a typical Gitlab CiCd pipeline as a validation step.

Problem

Developers must ensure the documentation for their Terraform changes is updated, so developers maintain quality docs.

Doing this separately to change requests results in sub-par documentation as the documents are no longer the responsibility of the user creating the change.

Enforcing this is harder than doing so with the terraform fmt and terraform validate commands. Enforcing docs are updated is difficult because terraforming docs are not part of Terraform but a separate tool that must be installed and is missing the ability to work with git diff natively.

Solutions

Terraform Docs Docker image

Terraform docs helpfully provides a maintained Docker image compatible as a part of any CI/CD Pipeline.

A validation step could be as simple as adding a pipeline step like the one below.

validate.docs:
image: quay.io/terraform-docs/terraform-docs:0.16.0
script:
- terraform-docs markdown --output-check --recursive .

There are some limitations with a simple application like the above example, especially if your Terraform code is that awkward Terrarmod phase of development where multiple terraform modules live in one repository. The recursive flag will not cut it.

You could use a simple looping function like the one below to ensure all docs are updated.

doc_paths=$(find . -type f -name '.terraform-docs.yml' -exec dirname {} + | sort -u)
for doc_path in ${doc_paths}; do
terraform-docs markdown --output-check --recursive -c "${doc_path}/.terraform-docs.yml" "${doc_path}"
done

The above works, but you now need to deal with the problem that the pipeline will fail if any doc is not updated, even if it is unrelated to the current terraform changes.

Git hooks

Git provides some handy functionality when it comes to Git Hooks. There are both server-side and client-side hooks we will be focusing on client-side.

note

A helpful stack overflow post helped me figure out how to add my hooks to a repository.

How can I commit Git hooks?

There are multiple hooks, such as pre-commit, prepare-commit-msg, commit-msg and pre-push; this is not all of them. You can learn about them here, but we will focus on the pre-push and post-commit hooks.

To create a git hook, create a script in the .git/hooks/<hook name> folder of your project and make it executable chmod +x .git/hooks/<hoook name>.

info

commit-msg works as it executes any time a user attempts to run git commit, but this could result in friction among users, so it is advisable only to use warnings and not to block behaviour.

info

pre-push is the perfect place to put blocking behaviour, such as ensuring changes never push to the main branch.

Due to the friction, if we interrupt a user every time they attempt to create a commit, we will only be using pre-push hooks.

Before we work on the code, it is essential to ensure the user is only interrupted by out-of-date docs that are part of the changes the user creates. Therefore, we will use git diff with the --name-only flag to get a list of directories that need to be checked.

default_branch="master"
git_root_path=$(git rev-parse --show-toplevel)
diff_dir_paths=()

getDiffPaths() {
diff_paths="$(git diff --name-only ${default_branch})"
for path in $diff_paths ; do
is_duplicate=false
dir_path=$(dirname "${path}")
diff_dir_path_string="${git_root_path}/${dir_path}"

for diff_dir_path in "${diff_dir_paths[@]}" ; do
if [ "${diff_dir_path_string}" = "${diff_dir_path}" ]; then
is_duplicate=true
fi
done

if [ ${is_duplicate} != true ]; then
diff_dir_paths+=("${diff_dir_path_string}")
fi
done
}

Using the code in the above example, we can use the array diff_dir_paths to identify what directories contain active changes.

terraformDocsCheck() {
if which terraform-docs 1>/dev/null ; then
for path in "${diff_dir_paths[@]}" ; do
config_path="${path}/.terraform-docs.yml"
if [ -f "${config_path}" ]; then
terraform-docs markdown --output-check -c "${config_path}" "${path}" 1>/dev/null
fi
done
else
echo "Terraform Docs not installed please install using brew install terraform-docs"
fi
}

Git push New Branch

· 2 min read
Aurelian Shuttleworth
Aurelian Shuttleworth
Site Reliability Engineer

Often I need to push a new branch to remote here is a helpful flag to know.

Git will not let you push changes if they exist on a local-only branch.

If you do, you will get an error.

fatal: The current branch <Branch Name> has no upstream branch.
To push the current branch and set the remote as upstream, use

git push --set-upstream origin <Branch Name>

To avoid copy-pasting the command from the error each time to push your changes, create you can create a function.

note

To get the current branch you can use the command git rev-parse --abbrev-ref HEAD

function gitpush() {
cur_branch="$(git rev-parse --abbrev-ref HEAD)"
remote=$(git remote)
git push --set-upstream "${remote}" "${cur_branch}"
}

Command

git-push - Update remote refs along with associated objects

SYNOPSIS

git push [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
[-u | --set-upstream] [-o <string> | --push-option=<string>]
[--[no-]signed|--signed=(true|false|if-asked)]
[--force-with-lease[=<refname>[:<expect>]] [--force-if-includes]]
[--no-verify] [<repository> [<refspec>...]]

-u, --set-upstream

For every branch that is up to date or successfully pushed, add upstream (tracking) reference, used by argument-less git-pull(1) and other commands. For more information, see branch.<name>.merge in git-config(1).

Rebasing is boring

· 2 min read
Aurelian Shuttleworth
Aurelian Shuttleworth
Site Reliability Engineer

The problem

When working in a team, you must ensure you sync code reviews with your default branch. Making sure you are up to date is definitely a chore and will take some time out of your day, but it will be worth it.

For me, the primary time sink is having to run the same commands and wait for the outputs each time.

  1. git checkout <default branch>
  2. git pull
  3. git pull again because I have not logged in today.
  4. git checkout <branch I was working on>
  5. git rebase <default branch> and hope there are no new merge conflicts.
  6. git push -f
  7. Check that my pipelines still work.

As you can imagine, this is a tedious process and can easily result in you messing up.

Solution

An easy fix is adding a new function to your cli. In my case, I use zsh, so I will add it to my ~/.zshrc file.

Code

tip

If you want to understand what is going on I recommend you use Explain Shell

update-branch() {
current_branch="$(git rev-parse --abbrev-ref HEAD)"
remote=$(git remote)
REPLY="Y"
if default_branch="$(git remote show "${remote}" | sed -n '/HEAD branch/s/.*: //p')"; then
vared -p "Rebase branch(${current_branch}) onto branch(${default_branch}) (Y/N)?: " -c REPLY
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
REPLY=""
git checkout "${default_branch}"
if git pull; then
git checkout "${current_branch}"
if git rebase "${default_branch}"; then
echo
else
vared -p "Unable to rebase without errors, abort (Y/N)?: " -c REPLY
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
git rebase --abort
fi
fi
fi
fi
else
echo "Unable to pull latest changes check your auth probably."
fi
}
note

Thanks to making this script I learnt that vared exists for editing variables and prompting users.

Bonus code

Here is a quick script to allow quick switching and creation of branches.

checkout-branch() {
branch="$1"
source_branch="${2:-main}"
REPLY="Y"
if git rev-parse --verify "${branch}" > /dev/null 2>&1; then
git checkout "${branch}"
else
vared -p "Can't find Branch(${branch}) new branch (Y/N)?: " -c REPLY
if [[ $REPLY =~ ^[Yy]$ ]]; then
vared -p "What source branch (Y/N)?: " -c source_branch
git checkout "${source_branch}" && git checkout -b "${branch}"
fi
fi
}