From 04f0bbb05b22f916edd3c29468dbba040397902d Mon Sep 17 00:00:00 2001 From: Mikko Ahlroth Date: Sun, 25 Feb 2018 11:25:07 +0200 Subject: [PATCH] Transform into GitLab Push Triggerer Takes in GitLab push webhook events instead of normal triggers --- README.md | 30 +++++++++++++++++++----------- lib/router.ex | 44 ++++++++++++++++++++++++++++++++------------ lib/triggerer.ex | 15 +++++++++------ mix.exs | 5 +++-- mix.lock | 1 + 5 files changed, 64 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index cac59c7..7c2e123 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # GitLabPushTriggerer -GitLabPushTriggerer is a small application that triggers execution of scripts when given endpoints are hit -with GET requests. It contains no permissions handling, no authentication, no request limiting. So -these must be handled in some other way, like with an Nginx reverse proxy. +GitLabPushTriggerer is a small application that triggers execution of scripts when given endpoints +are hit with GitLab push webhooks. It contains no permissions handling, no authentication, no +request limiting. So these must be handled in some other way, like with an Nginx reverse proxy. Should work on any \*nix system that has `sh`. @@ -21,12 +21,17 @@ Configuration of endpoints and scripts is done via environment variables. Enviro are set in the following format: ``` -TRIGGERER_FOO=/path/to/bar.sh +GLPT_FOO='master;/path/to/bar.sh' ``` -The variable consists of `TRIGGERER_` prefix, after which is the name of the endpoint. The value is -the path to the script to execute. The above variable would result in an endpoint `/foo`, which would -execute `/path/to/bar.sh`. +The variable consists of `GLPT_` prefix, after which is the name of the endpoint. The value is +the branch to match and path to the script to execute. The above variable would result in +an endpoint `/foo`, which would execute `/path/to/bar.sh` if a push to branch `master` was +called on the endpoint. + +**NOTE:** Add the single quotes `'` to encase the value to prevent the shell messing up. + +**LIMITATION:** The branch name cannot contain `;`. The endpoint path is transformed to lowercase and underscores are replaced with dashes. See the following examples: @@ -36,10 +41,13 @@ following examples: * `TRIGGERER_AAA_` → `/aaa-` The path to the script can be absolute or relative, as long as it can be reached from the current -working dir. +working dir (for releases that can be the release dir; it is easier to use absolute paths). Scripts +are executed with `sh -c script`. ## Return value -If the script was triggered, 200 OK is returned. If the script is not found or the request path is -wrong, 404 File not found is returned. If there is an error in script execution, 500 Internal server -error is returned and the error is logged. +* If the script was triggered, `200 OK` is returned. +* If the script is not found or the request path is wrong, `404 File not found` is returned. +* If the branch does not match, `400 Bad request` is returned. +* If there is an error in script execution, `500 Internal server error` is returned and the error + is logged. diff --git a/lib/router.ex b/lib/router.ex index 259e382..e6ccb52 100644 --- a/lib/router.ex +++ b/lib/router.ex @@ -9,6 +9,14 @@ defmodule GitLabPushTriggerer.Router do plug(Plug.Logger) plug(:match) + + plug( + Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Jason + ) + plug(:dispatch) paths = @@ -16,23 +24,25 @@ defmodule GitLabPushTriggerer.Router do GitLabPushTriggerer.get_envs() end - match "/:path" do - case Map.get(unquote(paths), path) do + post "/:path" do + with {branch, script} <- Map.get(unquote(paths), path), + :ok <- check_branch(branch, conn.body_params), + :ok <- GitLabPushTriggerer.run(script) do + send_resp(conn, 200, "OK") + else + # Nil returned by Map.get nil -> route_not_found(conn) - script -> - case GitLabPushTriggerer.run(script) do - :ok -> - send_resp(conn, 200, "OK") + {:error, :file_not_found} -> + send_resp(conn, 404, "File not found.") - {:error, :file_not_found} -> - send_resp(conn, 404, "File not found.") + {:error, :wrong_branch} -> + send_resp(conn, 400, "Branch not matching.") - error -> - Logger.error("Unknown error: #{inspect(error)}") - send_resp(conn, 500, "Unknown error.") - end + error -> + Logger.error("Unknown error: #{inspect(error)}") + send_resp(conn, 500, "Unknown error.") end end @@ -46,4 +56,14 @@ defmodule GitLabPushTriggerer.Router do Logger.error(inspect(data)) send_resp(conn, conn.status, "Something went wrong.") end + + # Check that branch in data matches branch in configuration + defp check_branch(branch, data) when is_binary(branch) and is_map(data) do + with "refs/heads/" <> data_branch <- Map.get(data, "ref"), + ^branch <- data_branch do + :ok + else + _ -> {:error, :wrong_branch} + end + end end diff --git a/lib/triggerer.ex b/lib/triggerer.ex index 3418e2d..5622266 100644 --- a/lib/triggerer.ex +++ b/lib/triggerer.ex @@ -1,6 +1,8 @@ defmodule GitLabPushTriggerer do require Logger + @split_char ";" + @doc """ Get all matching environment variables and their values. Returns a map where keys are the transformed paths and values are the given script paths. @@ -9,14 +11,14 @@ defmodule GitLabPushTriggerer do In these examples, sample env is given as argument. If no argument is given, system env is used. - iex> GitLabPushTriggerer.get_envs(%{"TRIGGERER_FOO" => "./test.sh"}) - %{"foo" => "./test.sh"} + iex> GitLabPushTriggerer.get_envs(%{"GLPT_FOO" => "master;./test.sh"}) + %{"foo" => {"master", "./test.sh"}} iex> GitLabPushTriggerer.get_envs(%{}) %{} - iex> GitLabPushTriggerer.get_envs(%{"WRONG_FORMAT" => "no", "TRIGGERER_BAR_" => "baz", "TRIGGERER_" => "no", "TRIGGERER_GO_WILD" => "/bin/true"}) - %{"bar-" => "baz", "go-wild" => "/bin/true"} + iex> GitLabPushTriggerer.get_envs(%{"WRONG_FORMAT" => "no", "GLPT_BAR_" => "um;baz", "GLPT_" => "no", "GLPT_GO_WILD" => "jeffum;/bin/true"}) + %{"bar-" => {"um", "baz"}, "go-wild" => {"jeffum", "/bin/true"}} """ @spec get_envs(%{optional(String.t()) => String.t()}) :: map def get_envs(envs \\ nil) do @@ -25,9 +27,10 @@ defmodule GitLabPushTriggerer do envs |> Map.keys() |> Enum.reduce(%{}, fn - "TRIGGERER_" <> rest = key, acc when rest != "" -> + "GLPT_" <> rest = key, acc when rest != "" -> path = transform_path(rest) - Map.put(acc, path, envs[key]) + [branch | rest] = String.split(envs[key], @split_char) + Map.put(acc, path, {branch, Enum.join(rest, @split_char)}) _, acc -> acc diff --git a/mix.exs b/mix.exs index c701295..7017dda 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule GitLabPushTriggerer.MixProject do def project do [ - app: :triggerer, + app: :gitlab_push_triggerer, version: "1.0.0", elixir: "~> 1.6", start_permanent: Mix.env() == :prod, @@ -24,7 +24,8 @@ defmodule GitLabPushTriggerer.MixProject do [ {:plug, "~> 1.4.5"}, {:distillery, "~> 1.5"}, - {:cowboy, "~> 1.1"} + {:cowboy, "~> 1.1"}, + {:jason, "~> 1.0.0"} ] end end diff --git a/mix.lock b/mix.lock index 15a27b3..1c91c9f 100644 --- a/mix.lock +++ b/mix.lock @@ -2,6 +2,7 @@ "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, "distillery": {:hex, :distillery, "1.5.2", "eec18b2d37b55b0bcb670cf2bcf64228ed38ce8b046bb30a9b636a6f5a4c0080", [:mix], [], "hexpm"}, + "jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "mime": {:hex, :mime, "1.2.0", "78adaa84832b3680de06f88f0997e3ead3b451a440d183d688085be2d709b534", [:mix], [], "hexpm"}, "plug": {:hex, :plug, "1.4.5", "7b13869283fff6b8b21b84b8735326cc012c5eef8607095dc6ee24bd0a273d8e", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},