diff options
author | Chris Rebert <github@rebertia.com> | 2014-11-11 05:12:42 +0300 |
---|---|---|
committer | Chris Rebert <code@rebertia.com> | 2014-12-03 11:05:33 +0300 |
commit | a4a2ccf36c63c9416d40c87c7f0bf60e22a1d91c (patch) | |
tree | ea5c2df6e2c8402ba55740181074eecf632cd73a | |
parent | 39fa648ccdddeb7d7efbb3192b71fa6e29b895c4 (diff) |
v3
-rw-r--r-- | .gitattributes | 8 | ||||
-rw-r--r-- | .gitignore | 54 | ||||
-rw-r--r-- | .travis.yml | 3 | ||||
-rw-r--r-- | Dockerfile | 40 | ||||
-rw-r--r-- | LICENSE.txt (renamed from LICENSE) | 2 | ||||
-rw-r--r-- | gruntworker.crontab | 1 | ||||
-rwxr-xr-x | gruntworker.py | 136 | ||||
-rwxr-xr-x | gruntworker.sh | 2 | ||||
-rwxr-xr-x | setup_droplet.sh | 26 |
9 files changed, 271 insertions, 1 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a97ce0b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Enforce Unix newlines +*.conf text eol=lf +*.sbt text eol=lf +*.scala text eol=lf +*.sh text eol=lf +*.md text eol=lf +*.txt text eol=lf +*.yml text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db4561e --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3f0bc12 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: scala +scala: + - 2.10.4 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..906a003 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +# Written against Docker v1.3.1 +FROM node:0.10 +MAINTAINER Chris Rebert <code@rebertia.com> + +WORKDIR / + +ENV DEBIAN_FRONTEND noninteractive +RUN ["apt-get", "update"] +RUN ["apt-get", "-y", "install", "apt-utils"] +RUN ["apt-get", "-y", "--no-install-recommends", "install", "build-essential", "openssh-client", "git", "python3", "python3-dev"] +# Grunt +RUN ["npm", "install", "-g", "grunt-cli"] + +RUN ["useradd", "gruntworker"] + +ADD gruntworker.py /app/gruntworker.py +ADD gruntworker.sh /app/gruntworker.sh +ADD git-repo /git-repo + +# Setup SSH keys +ADD ssh/id_rsa.pub /home/gruntworker/.ssh/id_rsa.pub +ADD ssh/id_rsa /home/gruntworker/.ssh/id_rsa +RUN ssh-keyscan -t rsa github.com > /home/gruntworker/.ssh/known_hosts + +# Fix permissions +RUN ["chown", "-R", "gruntworker:gruntworker", "/git-repo"] +RUN ["chown", "-R", "gruntworker:gruntworker", "/home/gruntworker"] +# chmod must happen AFTER chown, due to https://github.com/docker/docker/issues/6047 +RUN ["chmod", "-R", "go-rwx", "/home/gruntworker/.ssh"] + +USER gruntworker +WORKDIR /git-repo + +RUN ["git", "remote", "set-url", "origin", "https://github.com/twbs/bootstrap.git"] +RUN ["git", "remote", "set-url", "--push", "origin", "git@github.com:twbs/bootstrap.git"] +RUN ["git", "config", "user.name", "Bootstrap's Grunt bot"] +RUN ["git", "config", "user.email", "gruntworker@getbootstrap.com"] +RUN ["npm", "install"] + +ENTRYPOINT ["/app/gruntworker.sh"] @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 Chris Rebert +Copyright (c) 2014 Christopher Rebert Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/gruntworker.crontab b/gruntworker.crontab new file mode 100644 index 0000000..9380831 --- /dev/null +++ b/gruntworker.crontab @@ -0,0 +1 @@ +*/10 * * * * gruntworker docker run gruntworker diff --git a/gruntworker.py b/gruntworker.py new file mode 100755 index 0000000..297710c --- /dev/null +++ b/gruntworker.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# coding=utf-8 + +from sys import exit +from os import devnull as DEV_NULL +from subprocess import check_call, check_output, CalledProcessError +from shutil import rmtree +from datetime import datetime + + +def log(*args): + now = datetime.now().replace(microsecond=0).isoformat(' ') + print(now, "gruntworker: ", end='') + print(*args, flush=True) + + +def run_expecting_success(cmd): + log("\trunning:", b' '.join(cmd).decode('utf8', 'replace')) + with open(DEV_NULL) as void: + check_call(cmd, stdin=void) + + +def run_for_output(cmd): + log("\trunning:", b' '.join(cmd).decode('utf8', 'replace')) + return check_output(cmd, input=b'') + + +def reset_to_master_and_die(): + log("Attempting to reset current checkout & branch to local master...") + try: + run_expecting_success([b'git', b'checkout', b'-f', b'master']) + except CalledProcessError: + log("Error forcibly checking out master; Failed!") + exit(1) + + +def fetch_origin(): + log("Fetching from origin...") + try: + run_expecting_success([b'git', b'fetch', b'origin', b'+master']) + except CalledProcessError: + log("Error fetching from origin; Failed!") + exit(1) + + +def update_master(to_commitish=b'FETCH_HEAD'): + log("Setting local master to {0}...".format(to_commitish.decode('utf8', 'replace'))) + try: + run_expecting_success([b'git', b'checkout', b'-f', to_commitish]) + run_expecting_success([b'git', b'branch', b'-f', b'master', to_commitish]) + run_expecting_success([b'git', b'checkout', b'-f', b'master']) + except CalledProcessError: + log("Error setting local master to {0}!".format(to_commitish)) + reset_to_master_and_die() + + +def update_npm(): + try: + log("Pruning unnecessary npm modules...") + run_expecting_success([b'npm', b'prune']) + log("Installing/updating npm modules per package.json ...") + run_expecting_success([b'npm', b'install']) + except CalledProcessError: + log("Error performing npm operations!") + log("Purging node_modules due to errors.") + try: + rmtree('./node_modules', ignore_errors=True) + except (IOError, OSError) as io_err: + log("Error purging node_modules: {!r}".format(io_err)) + else: + log("Successfully purged node_modules.") + log("Failed!") + exit(1) + + +def get_head_commit_sha(): + commit_sha = run_for_output([b'git', b'rev-parse', b'HEAD']).strip() + if len(commit_sha) != 40: + log("Got malformed commit SHA for HEAD:", commit_sha.decode('utf8')) + log("Exiting due to insanity; Failed!") + exit(1) + return commit_sha + + +def grunt_or_err(): + log("Grunting...") + try: + run_expecting_success([b'grunt', b'dist']) + except CalledProcessError: + log("Error while grunting!") + raise + + +def get_modified_files(): + output = run_for_output([b'git', b'status', b'-z', b'-uno', b'--ignore-submodules=all']) + lines = output.split(b'\x00') + return [line[3:] for line in lines if line[:2] == b' M'] + + +def push_or_err(): + log("Pushing to origin...") + try: + run_expecting_success([b'git', b'push', b'origin', b'master']) + except CalledProcessError: + log("Error pushing to origin!") + raise + + +def main(): + orig_commit_sha = get_head_commit_sha() + fetch_origin() + update_master() + post_fetch_commit_sha = get_head_commit_sha() + if post_fetch_commit_sha == orig_commit_sha: + log("Fetch didn't change HEAD commit; Done.") + return + update_npm() + try: + grunt_or_err() + modified_files = get_modified_files() + if not modified_files: + log("No files modified by grunt; Done.") + return + run_expecting_success([b'git', b'add', b'--'] + modified_files) + run_expecting_success([b'git', b'commit', b'-m', b"automatic grunt dist"]) + push_or_err() + except Exception: + log("Resetting master branch & checkout back to commit {} ...".format(post_fetch_commit_sha)) + update_master(to_commitish=post_fetch_commit_sha) + log("Failed!") + else: + log("Successfully pushed changes; Done.") + + +if __name__ == '__main__': + main() diff --git a/gruntworker.sh b/gruntworker.sh new file mode 100755 index 0000000..04dedd8 --- /dev/null +++ b/gruntworker.sh @@ -0,0 +1,2 @@ +#!/bin/sh +flock -xn /var/lock/gruntworker /app/gruntworker.py diff --git a/setup_droplet.sh b/setup_droplet.sh new file mode 100755 index 0000000..92c7752 --- /dev/null +++ b/setup_droplet.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Step 0.0: Put SSH keys in ./ssh +# Step 0.1: Checkout git repo to ./git-repo + +set -e -x + +# set to Pacific Time (for @cvrebert) +# ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime + +# remove useless crap +aptitude remove wpasupplicant wireless-tools +aptitude remove pppconfig pppoeconf ppp + +# setup firewall +ufw default allow outgoing +ufw default deny incoming +ufw allow ssh +ufw allow www # not necessary for gruntworker itself +ufw enable +ufw status verbose + +# setup Docker; written against Docker v1.2.0 +docker rmi gruntworker +docker build --tag gruntworker . 2>&1 | tee docker.build.log +cp ./gruntworker.crontab /etc/cron.d/gruntworker +restart cron # until upstart goes away |