Prevent accidental deployments on Friday

It is awesome when everything you push to the master branch gets automatically deployed. Usually it works flawlessly. It runs your tests, builds a docker image, and deploys it. The life is great ;)

One Friday afternoon someone wants to go home earlier. That person pushes the code to master. There are some tests and all of them pass. The code can be deployed even though not everything is tested. The code gets deployed. Its author leaves the office. Boom! Something breaks.

The solution is simple. You can quickly revert the changes and carry on. What if this situation happens again? What if it is not just one person but a few people make such mistakes?

One day you decide that the best solution is to avoid deployments on Friday. Can you enforce this rule? Obviously, you can block deployments on Jenkins. What if you need to quickly fix a production bug on Friday? That is allowed, isn’t it?

Maybe we should not push to master on Fridays? We probably should not push to master also when the majority of developers already left the office, so 4 pm deployments are also banned. Looks like a lot of rules to remember.

It will not work. You or someone else will type git push in console without checking the active branch. Even if nothing breaks you can become The One Weirdo Who Deploys On Fridays.

Pre push hook

Fortunately, all you need is a pre-push hook.

#!/usr/bin/env python

import sys
import datetime


def remote_branch(stdin_first_line):
    """
    Reads the name of the remote git branch from runtime parameters.
    In the pre-push.py hook the name of the remote branch is passed as the $1 parameter.
    :param stdin_first_line the first line of the standard input
    >>> remote_branch("refs/heads/master a9d45baccd631601087a75a6605909c16bbfdbca refs/heads/master 67b6dc7a5e256ae590d305a766e627258b164899")
    'master'
    >>> remote_branch("refs/heads/master a9d45baccd631601087a75a6605909c16bbfdbca refs/heads/hot-fix 67b6dc7a5e256ae590d305a766e627258b164899")
    'hot-fix'
    """
    stdin_parts = stdin_first_line.split(" ")
    remote = stdin_parts[2]
    remote_parts = remote.split("/")
    return remote_parts[-1]


def day_of_week():
    """
    Returns the integer indicating the day of week.
    Uses the datetime package so, Monday is 0 and Sunday is 6.
    """
    return datetime.datetime.today().weekday()


def hour_of_day():
    """
    Returns the current hour as an integer.
    :return:
    """
    return datetime.datetime.today().hour


def should_deploy(branch_name, day_of_week, hour_of_day):
    """
    Returns true if the code can be deployed.
    :param branch_name: the name of the remote git branch
    :param day_of_week: the day of the week as an integer
    :param hour_of_day: the current hour
    :return: true if the deployment should continue
    >>> should_deploy("hot-fix", 0, 10)
    True
    >>> should_deploy("hot-fix", 4, 10) #this branch can be deployed on Friday
    True
    >>> should_deploy("hot-fix", 5, 10) #this branch can be deployed on Saturday
    True
    >>> should_deploy("hot-fix", 6, 10) #this branch can be deployed on Sunday
    True
    >>> should_deploy("hot-fix", 0, 7) #this branch can be deployed before 8am
    True
    >>> should_deploy("hot-fix", 0, 16) #this branch can be deployed after 4pm
    True
    >>> should_deploy("master", 0, 10)
    True
    >>> should_deploy("master", 4, 10) #master cannot be deployed on Friday
    False
    >>> should_deploy("master", 5, 10) #master cannot be deployed on Saturday
    False
    >>> should_deploy("master", 6, 10) #master cannot be deployed on Sunday
    False
    >>> should_deploy("master", 0, 7) #master cannot be deployed before 8am
    False
    >>> should_deploy("master", 0, 16) #master cannot be deployed after 4pm
    False
    """
    if branch_name == "master" and day_of_week >= 4:
        return False
    elif branch_name == "master" and (hour_of_day < 8 or hour_of_day >= 16):
        return False
    else:
        return True


if __name__ == "__main__":
    print "Verifying deployment hours..."
    deploy = should_deploy(remote_branch(sys.stdin.readline()), day_of_week(), hour_of_day())

    if deploy:
        sys.exit(0)
    else:
        print "[ABORTED] Push stopped by the \"deployment hours\" pre-push hook!"
        print "If you know what you are doing and still want to push your code, use the --no-verify parameter"
        sys.exit(1)

To install the hook: save the code in pre-push file and make it executable. The file may be copied to repository hooks and run only in one repository. It can also be added to global hooks and run in every repository:

mkdir ~/git-global-hooks
cp pre-push.py ~/git-global-hooks/pre-push
chmod u+x ~/git-global-hooks/pre-push
git config --global core.hooksPath /Users/username/git-global-hooks/

Now you are safe. Every time you try to push to master when you should not do that, you will see this friendly message:

[ABORTED] Push stopped by the "deployment hours" pre-push hook!
If you know what you are doing and still want to push your code, use the --no-verify parameter

In those rare occasions when you need to deploy on Friday or after 4 pm, just add --no-verify to your git command.

Github repository: https://github.com/mikulskibartosz/pre-push-hook

Older post

Developers just wanna have fun

Software maintenance is painful because of hype driven development.

Newer post

Discipline Equals Freedom — Jocko Willink

A review of Jocko Willink’s book: “Discipline Equals Freedom.” Should you read it even if you don’t want to run a marathon?