From 1968f5e88ed0cc59f5db07be1d162b12120d7f96 Mon Sep 17 00:00:00 2001 From: Paul Liverman III Date: Fri, 11 May 2018 13:51:27 -0700 Subject: [PATCH] Squashed 'applications/githook/' content from commit 853c905 git-subtree-dir: applications/githook git-subtree-split: 853c905433ef1ecd7d9ea4b1f6fd6f73f28d7b20 --- .gitignore | 1 + LICENSE | 21 ++++++ ReadMe.md | 55 +++++++++++++++ githook.moon | 152 ++++++++++++++++++++++++++++++++++++++++ migrations.moon | 25 +++++++ models/GithookLogs.moon | 4 ++ views/githook_get.moon | 10 +++ 7 files changed, 268 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 ReadMe.md create mode 100644 githook.moon create mode 100644 migrations.moon create mode 100644 models/GithookLogs.moon create mode 100644 views/githook_get.moon diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d907c43 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.lua diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7786f75 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016-2018 Paul Liverman III + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..03ba29e --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,55 @@ +## Installation + +(Note: I'm going to rewrite this to explain how to use with locator, a simple +server locator I designed for use with Lapis and these sub-applications.) + +Dependencies: + +- Lapis (duh) +- MoonScript +- OpenResty user needs a bash shell (ch -s /bin/bash user) + +From the shell: + +```bash +git subtree add --prefix githook https://github.com/lazuscripts/githook.git master --squash +``` + +(`--prefix` specifies where it will be saved.) + +Alternately, you can add it as a remote for easier maintenance: + +```bash +git remote add -f githook https://github.com/lazuscripts/githook.git +git subtree add --prefix githook githook master --squash +``` + +From your main application class: `@include "githook.githook"` (or wherever you put it) + +### Updating + +From the shell: + +```bash +git subtree pull --prefix githook https://github.com/lazuscripts/githook.git master --squash +``` + +Or, if it is set up as remote: + +```bash +git subtree pull --prefix githook githook master --squash +``` + +## Config + +All configuration is optional. Without configuration, will attempt to update any +time it is visited. + +- `githook_branch "branch"` which branch you want updating (as string) + (to prevent updates triggering when pushing unrelated branches) +- `githook_secret "secret"` the secret string used on GitHub + +Will attempt to checkout, pull, update submodules if needed, compile all code, +then run migrations, and finally update the running server without interruption. + +Returns a log along with exit codes on success or failure. diff --git a/githook.moon b/githook.moon new file mode 100644 index 0000000..158bd24 --- /dev/null +++ b/githook.moon @@ -0,0 +1,152 @@ +lapis = require "lapis" +config = require("lapis.config").get! + +import respond_to, json_params from require "lapis.application" +import hmac_sha1, hmac_sha256 from require "lapis.util.encoding" +import encode from require "cjson" +import GithookLogs from require "models" +import locate, autoload, registry from require "locator" +import settings from autoload "utility" +import execute from locate "utility.shell" +import insert, concat from table + +const_compare = (string1, string2) -> + local fail, dummy + + for i = 1, math.max #string1, #string2, 100 + if string1\sub(i,i) ~= string2\sub(i,i) + fail = true + else + dummy = true -- attempting to make execution time equal + + return not fail + +hex_dump = (str) -> + len = string.len str + hex = "" + + for i = 1, len + hex ..= string.format( "%02x", string.byte( str, i ) ) + + return hex + +run_update = (branch) -> + exit_codes, logs = {}, {} + failure = false + + commands = registry.githook_commands branch, config._name + unless commands + commands = { + {"git checkout #{branch} 2> /dev/stdout"} + {"git pull origin 2> /dev/stdout"} + {"git submodule init 2> /dev/stdout"} + {"git submodule update 2> /dev/stdout"} + {"code=0\nfor file in $(find . -type f -name \"*.moon\"); do moonc \"$file\" 2> /dev/stdout\ntmp=$?\nif [ ! $tmp -eq 0 ]; then code=$tmp\nfi; done\necho $code", false} + {"lapis migrate #{config._name} 2> /dev/stdout"} + {"lapis build #{config._name} 2> /dev/stdout"} + } + for cmd in *commands + code, output = execute unpack cmd + insert exit_codes, code + insert logs, cmd[1] + insert logs, " #{output\gsub "\n", "\n "}" + if code != 0 + failure = true + break + + log = concat logs, "\n" + + if failure + if settings["githook.save_logs"] + GithookLogs\create { + success: false + exit_codes: encode exit_codes + :log + } + return status: 500, json: { + status: "failure" + message: "a subprocess returned a non-zero exit code" + :log + :exit_codes + } + else + if settings["githook.save_logs"] and settings["githook.save_on_success"] + GithookLogs\create { + exit_codes: encode exit_codes + :log + } + elseif settings["githook.save_logs"] + GithookLogs\create! -- we still record WHEN there was a success + return status: 200, json: { + status: "success" + message: "server updated to latest version of '#{branch}'" + :log + :exit_codes + } + +ignored = (branch) -> + return status: 200, json: { + status: "success" + message: "ignored push (looking for updates to '#{branch}')" + } + +unauthorized = -> + return status: 401, json: { + status: "unauthorized", + message: "invalid credentials or no credentials were sent" + } + +invalid = (reason) -> + return status: 400, json: { + status: "invalid request" + message: reason + } + +class extends lapis.Application + [githook: "/githook"]: respond_to { + before: => + @branch = config.githook_branch or settings["githook.branch"] or "master" + + GET: => + unless settings["githook.allow_get"] + return status: 405, json: { + status: "method not allowed", + message: "Githook is not accepting GET requests." + } + + unless settings["githook.run_without_auth"] + return unauthorized! + + @results = run_update(@branch) + return render: locate "views.githook_get" + + POST: json_params => + secret = config.githook_secret or settings["githook.secret"] + if secret + ngx.req.read_body! + if body = ngx.req.get_body_data! + local authorized + if github_hash = @req.headers["X-Hub-Signature"] + authorized = const_compare "sha1=#{hex_dump hmac_sha1 secret, body}", github_hash + elseif gogs_hash = @req.headers["X-Gogs-Signature"] + authorized = const_compare gogs_hash, hex_dump hmac_sha256 secret, body + elseif @params.secret + authorized = const_compare @params.secret, secret + unless authorized + return unauthorized! + if @params.ref == "refs/heads/#{@branch}" + return run_update(@branch) + elseif @params.ref == nil + return invalid "'ref' not defined in request body" + else + return ignored(@branch) + else + return invalid "no request body" + elseif settings["githook.run_without_auth"] + if @params.ref == "refs/heads/#{@branch}" + return run_update(@branch) + else + return ignored(@branch) + else + return unauthorized! + } diff --git a/migrations.moon b/migrations.moon new file mode 100644 index 0000000..f4b7f71 --- /dev/null +++ b/migrations.moon @@ -0,0 +1,25 @@ +import create_table, types, create_index from require "lapis.db.schema" + +import autoload from require "locator" +import settings from autoload "utility" + +{ + [1519992142]: => + create_table "githook_logs", { + {"id", types.serial primary_key: true} + {"success", types.boolean default: true} + {"exit_codes", types.text null: true} + {"log", types.text null: true} + + {"created_at", types.time} + {"updated_at", types.time} + } + create_index "githook_logs", "id", unique: true + create_index "githook_logs", "success" + settings["githook.save_logs"] = true + settings["githook.save_on_success"] = true + settings["githook.allow_get"] = true + settings["githook.run_without_auth"] = false + -- settings["githook.branch"] = "master" + settings.save! +} diff --git a/models/GithookLogs.moon b/models/GithookLogs.moon new file mode 100644 index 0000000..fe94e48 --- /dev/null +++ b/models/GithookLogs.moon @@ -0,0 +1,4 @@ +import Model from require "lapis.db.model" + +class GithookLogs extends Model + @timestamp: true diff --git a/views/githook_get.moon b/views/githook_get.moon new file mode 100644 index 0000000..c7d49e7 --- /dev/null +++ b/views/githook_get.moon @@ -0,0 +1,10 @@ +import Widget from require "lapis.html" + +class extends Widget + content: => + h2 "#{@results.json.status\sub(1, 1)\upper!}#{@results.json.status\sub 2}" + element "table", -> + tr -> + for code in *@results.json.exit_codes + th code + pre @results.json.log