Last Updated: 08/17/2012 09:54:00 AM
How I deploy websites using git.
Today I presented my topic: "Choosing Git workflows that make sense" to Charlie Arehart's CFMeetup group. If you missed it you can watch it here: http://experts.adobeconnect.com/p3qdsfghenj/ I had mentioned how I deploy to our webservers instantly by calling one ColdFusion page, using a mix of Git bash command line, windows .bat files and ColdFusion CFEXECUTE on our windows machines. Several people asked me to share how I have set this up. So I am sharing it here.
Step 1. Install git bash onto the webserver.
Download and install msysgit currently hosted at google code: http://code.google.com/p/msysgit/ Install it using the defaults.
Step 2. Set up your ssh key on the webserver.
Our server runs the ColdFusion service as administrator, so you will need to put an SSH key for a user that has read permissions on your private repository. If your repo is public you can probably skip this step. In our case we put a folder named .ssh in the C:\Documents and Settings\administrator.IDMI folder. If your ColdFusion service is running under a different account name you will need to put it in the corresponding "HOME" that git bash launches in by default. The easiest thing for me to do was to make sure on my desktop machine that my key was working and confirm I could push and pull to my GitHub private repo. Once I had that working I copied my entire.ssh folder to the server.
Step 3. Set up the repo on webserver.
Let's assume your website is in C:\inetpub\wwwroot.
Open git bash. Change directories to C:\inetpub\wwwroot.
Clone the repository from Github: git clone firstname.lastname@example.org:timcunningham/ContentBox.git
Check that the website works.
Step 4. Hide the .git folder from the www
I am not sure what damage people could do with an unprotected .git folder. But I do not want to find out. In IIS right click the .git folder and change directory permissions. Remove the READ check box, also don't log visits and don't index the resource.
Step 5: Create a git custom commands for your server
Many git commands are just shell scripts. Go to c:\Program Files\Git\libexec\git-core and open git-pull in a text editor. You will see that the pull command in git is just a script! In order to create your own git command just put a file that is prefixed git- with no file extension in the c:\Program Files\Git\libexec\git-core folder. In this case I created two commands that are custom to the server I am deploying on the files are named: git-updateweb & git-rollback
#!/bin/sh # # git-updateweb # # Tim Cunningham # # How to modify: cd to the actual directory # cd /C/Inetpub/wwwroot/mycoolWebsite git reset --hard origin/master git clean -f git pull git checkout master echo "********************************" echo "Execution Completed" echo "********************************"
What the above file is doing is changing to the repo directory. Resetting the local master branch to look exactly like remote origin master branch, regardless of any local changes that may be unstaged or commited. If I did a merge, pull or rebase here and there were conflicts, git would add "marker" text to the effected files, this would throw errors in production and I don't want that. So resetting says to git: "Look I don't care what differences there are make my local files look *exactly* like the origin/master branch." The clean command cleans off any files that don't need to be there. If that line bothers you, you can take it out. But it might be better just to add files that need to be in the web directory but not under version control to the .gitignore file. (We do that with any *.xml files, *.txt files, or *.csv files that our system generates. Putting them in the .gitignore file means that git pretends those files are not there and won't track them.) Then the script does a pull just to be doubly sure we have all the latest changes and then switches branches to master just to be extra sure that we are on the right branch and our working tree (which is also the web root) has the right files.
The other file created is the git-rollback file:
#!/bin/sh # # git-rollback # # Tim Cunningham # # How to modify: cd to the actual directory # cd /C/Inetpub/wwwroot/myCoolWebsite git reset --hard HEAD^ git clean echo "********************************" echo "Code has been rolled back to last commit" echo "********************************"
this rolls the code back one commit and cleans off any files that don't need to be there anymore
Save both these files to c:\Program Files\Git\libexec\git-core.
I suggest testing this from git bash manually first to make sure they don't throw errors and behave how you expect before you move to the next steps.
Step 6: Create a some .bat files
I had issues calling these newly created git commands directly using cfexecute. I had much more success with .bat files. So I created a folder on the server called C:\Scripts and created a file named gitUpdateWeb.bat which has one line:
"C:\Program files\git\cmd\git.cmd" updateWeb
The git.cmd file takes your argument and executes it, it is the same as if you typed git updateWeb at the git bash. The other .bat files is called gitRollback.bat and it contains one line:
"C:\Program files\git\cmd\git.cmd" rollback
Step 7: Create a ColdFusion page to execute the .bat file
<cfoutput>Updating...<br></cfoutput> <cfexecute name="C:\WINDOWS\system32\cmd.exe" arguments="/c C:\scripts\gitupdateweb.bat" variable="return" timeout=30></cfexecute> <cfoutput> <pre> --- Message from Git ---- #return# --- End Message from Git ---- </pre> </cfoutput> <cfoutput>Complete<br></cfoutput>
The above code will execute the windows command line. You must pass the /c argument and you need to set a timeout that gives the command long enough execute, otherwise you won't get any output to look at. The rollback code is exactly the same but it calls the gitRollback.bat Run these cfm pages to ensure they work. Read the output on the screen from the git commands to ensure it is doing what you expect.
The Promotion process
I like my system better than most githooks that I have seen. Most post-commit hooks run after a commit command has been issued on the repo and then it fires of commands to update a detached work tree. This means that anytime you commit to the production repo the files change. My process allows you to get your master branch 100% ready and then at a later time actually change the production code. It also can be called from a scheduled task, if you have set times to deploy production code.
Here is our process: Merge all the feature branches into master. Check that the repo looks clean. Pull it down locally and test it on your workstation to do one last sanity check that nothing is completely broken. Once you are confident it is ready to go or if you have certain day/time that you are allowed to promote changes to production, call the .cfm page. It will report back to you what ever the command line says about the operation. Do your QA checks on production. If production is broken, call the rollback ColdFusion page. The rollback page will report what commit it has been rolled back to. Note, you may need to refresh the page several times in order to get to the correct commit. You can then work locally on your workstation copy of the master branch, make fixes, test it locally, push back to master and run the updateWeb.cfm (or whatever you named it) to make production right.
So what do you think? I am sure that now that I have shared this, someone will post a comment with a link to an infinitely more simple and elegant way to do this But this was a problem that stumped me for a long time and I didn't like any of the suggested methods that I had found on the internet. If you find this methodology helpful or if you modify it to fit languages other than ColdFusion, please drop me a line and let me know!