Skip to content

Commit

Permalink
Merge pull request OpenFn#1147 from OpenFn/1112-crash-when-setting-up…
Browse files Browse the repository at this point in the history
…-version-control

Crash and wrong error message when setting up version control
  • Loading branch information
taylordowns2000 committed Sep 27, 2023
2 parents 1229177 + 2db65f4 commit 6d78be7
Show file tree
Hide file tree
Showing 13 changed files with 290 additions and 154 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ and this project adheres to
- Creating a new user without a password fails and there is no user feedback
[#731](https://github.com/OpenFn/Lightning/issues/731)

- Crash when setting up version control
[#1112](https://github.com/OpenFn/Lightning/issues/1112)

## [v0.9.2] - 2023-09-20

### Added
Expand Down
153 changes: 59 additions & 94 deletions lib/lightning/version_control/github_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,132 +5,97 @@ defmodule Lightning.VersionControl.GithubClient do
"""
use Tesla
require Logger
alias Lightning.VersionControl.GithubError
alias Lightning.VersionControl.GithubToken

plug(Tesla.Middleware.BaseUrl, "https://api.github.com")
plug(Tesla.Middleware.JSON)

def installation_repos(installation_id) do
with {:ok, installation_client} <- build_client(installation_id),
{:ok, %{status: 200} = repos_resp} <-
installation_client
|> get("/installation/repositories") do
{:ok,
repos_resp.body["repositories"]
|> Enum.map(fn g_repo -> g_repo["full_name"] end)}
else
{:error, :installation_not_found, meta} ->
installation_id_error(meta)

{:error, :invalid_pem} ->
invalid_pem_error()
with {:ok, client} <- build_client(installation_id),
{:ok, %Tesla.Env{status: 200, body: body}} <-
get(client, "/installation/repositories") do
{:ok, Enum.map(body["repositories"], fn g_repo -> g_repo["full_name"] end)}
end
end

def get_repo_branches(installation_id, repo_name) do
with {:ok, installation_client} <- build_client(installation_id),
{:ok, %{status: 200} = branches} <-
installation_client
|> get("/repos/#{repo_name}/branches") do
branch_names =
branches.body
|> Enum.map(fn b -> b["name"] end)

{:ok, branch_names}
else
{:error, :installation_not_found, meta} ->
installation_id_error(meta)

{:error, :invalid_pem} ->
invalid_pem_error()
with {:ok, client} <- build_client(installation_id),
{:ok, %Tesla.Env{status: 200, body: body}} <-
get(client, "/repos/#{repo_name}/branches") do
{:ok, Enum.map(body, fn b -> b["name"] end)}
end
end

def fire_repository_dispatch(installation_id, repo_name, user_email) do
with {:ok, installation_client} <- build_client(installation_id),
{:ok, %{status: 204}} <-
installation_client
|> post("/repos/#{repo_name}/dispatches", %{
with {:ok, client} <- build_client(installation_id),
{:ok, %Tesla.Env{status: 204}} <-
post(client, "/repos/#{repo_name}/dispatches", %{
event_type: "sync_project",
client_payload: %{
message: "#{user_email} initiated a sync from Lightning"
}
}) do
{:ok, :fired}
else
{:error, :installation_not_found, meta} ->
installation_id_error(meta)

{:error, :invalid_pem} ->
invalid_pem_error()

err ->
Logger.error(inspect(err))
{:error, "Error Initiating sync"}
end
end

def send_sentry_error(msg, meta \\ %{}) do
Sentry.capture_message("Github configuration error",
level: "warning",
extra: meta,
message: msg,
tags: %{type: "github"}
)
end

defp installation_id_error(meta) do
send_sentry_error("Github Installation APP ID is misconfigured", meta)

{:error,
%{
message:
"Sorry, it seems that the GitHub App ID has not been properly configured for this instance of Lightning. Please contact the instance administrator"
}}
end

defp invalid_pem_error do
send_sentry_error("Github Cert is misconfigured")

{:error,
%{
message:
"Sorry, it seems that the GitHub cert has not been properly configured for this instance of Lightning. Please contact the instance administrator"
}}
end

defp build_client(installation_id) do
%{cert: cert, app_id: app_id} =
Application.get_env(:lightning, :github_app)
|> Map.new()

with {:ok, auth_token, _} <- GithubToken.build(cert, app_id),
client <-
with {:ok, auth_token, _} <- GithubToken.build(cert, app_id) do
client =
Tesla.client([
{Tesla.Middleware.Headers,
[
{"Authorization", "Bearer #{auth_token}"}
]}
])

case post(
client,
"/app/installations/#{installation_id}/access_tokens",
""
) do
{:ok, %{status: 201} = installation_token_resp} ->
installation_token = installation_token_resp.body["token"]

{:ok,
Tesla.client([
{Tesla.Middleware.Headers,
[
{"Authorization", "Bearer #{auth_token}"}
{"Authorization", "Bearer " <> installation_token}
]}
]),
{:ok, installation_token_resp} <-
client
|> post("/app/installations/#{installation_id}/access_tokens", ""),
%{status: 201} <- installation_token_resp do
installation_token = installation_token_resp.body["token"]

{:ok,
Tesla.client([
{Tesla.Middleware.Headers,
[
{"Authorization", "Bearer " <> installation_token}
]}
])}
else
%{status: 404} = err ->
{:error, :installation_not_found, err}

_unused_status ->
{:error, :invalid_pem}
])}

{:ok, %{status: 404, body: body}} ->
Logger.error("Unexpected Github Response: #{inspect(body)}")

error =
GithubError.installation_not_found(
"Github Installation APP ID is misconfigured",
body
)

Sentry.capture_exception(error)

{:error, error}

{:ok, %{status: 401, body: body}} ->
Logger.error("Unexpected Github Response: #{inspect(body)}")

error =
GithubError.invalid_certificate(
"Github Certificate is misconfigured",
body
)

Sentry.capture_exception(error)

{:error, error}
end
end
end
end
Expand Down
22 changes: 22 additions & 0 deletions lib/lightning/version_control/github_error.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule Lightning.VersionControl.GithubError do
@moduledoc """
Github Error exception
"""
defexception [:code, :message, :meta]

def new(code, msg, meta) when is_binary(msg) do
%__MODULE__{code: code, message: msg, meta: Map.new(meta)}
end

def installation_not_found(msg, meta \\ %{}) do
new(:installation_not_found, msg, meta)
end

def misconfigured(msg, meta \\ %{}) do
new(:misconfigured, msg, meta)
end

def invalid_certificate(msg, meta \\ %{}) do
new(:invalid_certificate, msg, meta)
end
end
3 changes: 3 additions & 0 deletions lib/lightning/version_control/project_repo_connection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,8 @@ defmodule Lightning.VersionControl.ProjectRepoConnection do
project_repo_connection
|> cast(attrs, @fields ++ @required_fields)
|> validate_required(@required_fields)
|> unique_constraint(:project_id,
message: "project already has a repo connection"
)
end
end
15 changes: 13 additions & 2 deletions lib/lightning/version_control/version_control.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,20 @@ defmodule Lightning.VersionControl do
)
end

@spec get_pending_user_installation(Ecto.UUID.t()) ::
ProjectRepoConnection.t() | nil
def get_pending_user_installation(user_id) do
query =
from(prc in ProjectRepoConnection,
where: prc.user_id == ^user_id and is_nil(prc.github_installation_id)
)

Repo.one(query)
end

def add_github_installation_id(user_id, installation_id) do
pending_installation =
Repo.one(
Repo.one!(
from(prc in ProjectRepoConnection,
where: prc.user_id == ^user_id and is_nil(prc.github_installation_id)
)
Expand All @@ -51,7 +62,7 @@ defmodule Lightning.VersionControl do

def add_github_repo_and_branch(project_id, repo, branch) do
pending_installation =
Repo.one(
Repo.one!(
from(prc in ProjectRepoConnection,
where: prc.project_id == ^project_id
)
Expand Down
27 changes: 19 additions & 8 deletions lib/lightning_web/controllers/version_control_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,27 @@ defmodule LightningWeb.VersionControlController do
def index(conn, params) do
# add installation id to project repo
# {:error, %{reason: "Can't find a pending connection."}}
user_id = conn.assigns.current_user.id
pending_connection = VersionControl.get_pending_user_installation(user_id)

{:ok, project_repo_connection} =
VersionControl.add_github_installation_id(
conn.assigns.current_user.id,
params["installation_id"]
if params["setup_action"] == "update" and is_nil(pending_connection) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(
200,
"Github installation updated successfully; you may close this page or navigate to any OpenFn project which uses this installation: #{params["installation_id"]}"
)
else
{:ok, project_repo_connection} =
VersionControl.add_github_installation_id(
user_id,
params["installation_id"]
)

# get project repo connection and forward to project settings
redirect(conn,
to: ~p"/projects/#{project_repo_connection.project_id}/settings#vcs"
)
# get project repo connection and forward to project settings
redirect(conn,
to: ~p"/projects/#{project_repo_connection.project_id}/settings#vcs"
)
end
end
end
Loading

0 comments on commit 6d78be7

Please sign in to comment.