The article is a free translation of
this material . Fans of long and abstruse original sources can immediately read the original.
When we are given the task of changing the codebase, for example, in the Github repository, to rebuild / restart an application on some of our environments, the first thing that comes to mind as a possible trigger for such a reassembly is the mechanism provided by the same github Web Hooks: when an event occurs with our remote repository (since the appearance of a new commit in some of its tracked branches), the githab uses the corresponding web hook and “pulls” the service specified in its settings, which Start the process of rebuilding / restart of the application. This is a standard, widely used and simple mechanism for such cases, everyone is doing it, and so on ...
But what if our application lives on a host that, for some reason, is not supposed to be accessed by a githaba? For example, is the host on a closed network or behind a NAT and unavailable from the Internet?
In this case, you can use the mechanism of local hooks of Git itself, about which (the mechanism), as it turns out, very few people know even those who have been using Git for quite some time.
')
When we create a local repository (we initialize an empty one, we clone a remote one, ...), then in the .git folder, which is in its root, or at the very root, if it is a bare-repository, the hooks folder is present. After the repository is initialized, the hook templates for various events are saved in this folder, such as, for example: post-merge, post-receive, post-update, etc.
A full description of supported events for hooks can be found, for example,
here .
We will use this mechanism and implement a simple push-to-deploy scheme for our hapless application.
We need two local repositories. Create them, for example, along the specified paths:
1. / opt / repo-dev / example-repo /
2. /opt/repo-remote.git/
The first repository is a clone of our example-repo remote repository on the github.
The second is the bare repository, a copy of the first, which will serve us exclusively to handle the post-update event when updates appear in the remote repository. So, how do we realize this?
The scheme is very simple (suppose we are tracking the test branch, and our application is the node.js, managed by the manager pm2):
1. Periodically update the first local repository to a state that fully corresponds to the state of the remote repository.
2. Update the second from the first local repository.
3. As soon as HEAD has moved - a new commit has appeared - the post-update hook will be activated in the second repository, which is performed when any changes appear in the repository, and which will perform the necessary actions to rebuild and restart the application.
To do this, we do the following:
1. In the first local repository add remote - the second local repository:
cd /opt/repo-dev/example-repo/ && git remote add prod /opt/repo-remote.git
Now we can execute git push from the first local repository to the second.
2. In the /opt/repo-remote.git/hooks/ folder, create a post-update file and make it executable:
touch /opt/repo-remote.git/hooks/post-update && chmod +x /opt/repo-remote.git/hooks/post-update
This is a regular shell script, but according to the internal Git convention
without the .sh extension !
Let's add some commands to it:
What does the script do? At first, it simply unloads the working tree of our bare repository into the folder with our running application, and then recompiles the dependencies and restarts the pm2 services. As you can see, no magic.
3. Configure cron, which will update the first repository from the remote repository every n minutes:
git fetch origin && git reset --hard -f origin/test
So Now our host will be the regular initiator of checking the remote repository - but have there been any updates?
Please note that we are updating the local repository not by git pull, but by git reset --hard. This is done in order to eliminate the need for merdzhey with a certain content of the next commit - we make the local repository a complete copy of the remote.
4. Immediately after synchronizing the first local repository with the remote one, we push all changes to our pseudo-remote second local repository:
git push prod test
That's all. As soon as our pseudo-remote local repository receives non-zero changes, Git pulls its post-update hook, which executes the corresponding script. And we get a simple push-to-deploy workflow, which, if desired, can be further improved according to our needs.
“Why make such an indigestible monstrous scheme ?!” - you ask. I first asked the same question, but it turned out that with the existing list of Git hooks, it is only in this way that we can call up the necessary processing whenever we update our branch in the remote repository. The post-update hook we need is designed to run on the remote repository (and some of the hooks are intended to run on the local repository). And we are such a not very elegant way, this pseudo-remote repository and emulated. Perhaps some other convenient hook running locally will appear soon, and the scheme can be simplified. But so far.
In conclusion, I want to give some advice to those who decide to implement it and, faced with the problems of a non-working hook or some parts of it, will furiously curse me, Git, the author of the original article and everyone else on the list:
1. Remember about the specifics of cron work - the programs in it, by default, are not run in the environment that you probably expect.
2. Check the versions of your utilities (npm, node etc) when calling them from scripts and using cron - they may not be the same as during manual start due to the difference in the paths to executable files in environment variables, for example. And the launch of their other versions can lead to unpredictable results.
3. Spend 20 minutes watching the next Simpsons series and return to experimenting with new powers and good mood.
I will welcome any comments and clarifications on the merits.
Have a nice day!