From 4e8fd8c296e22cba0e198250e8a80c26908cf6ec Mon Sep 17 00:00:00 2001 From: Adnan Maolood Date: Tue, 15 Feb 2022 09:43:05 -0500 Subject: [PATCH] api: Handle clone errors This adds two new columns to the database: clone_status and clone_error, which are used to store the clone status as well as any error that occurs. The error is then displayed to the user in the frontend. --- api/graph/resolver.go | 9 ++++ api/graph/schema.resolvers.go | 11 +++-- api/repos/middleware.go | 45 +++++++++-------- ...114e8a18_add_clone_status_to_repository.py | 49 +++++++++++++++++++ gitsrht/templates/empty-repo.html | 8 ++- gitsrht/types/__init__.py | 5 +- 6 files changed, 101 insertions(+), 26 deletions(-) create mode 100644 gitsrht/alembic/versions/0a3d114e8a18_add_clone_status_to_repository.py diff --git a/api/graph/resolver.go b/api/graph/resolver.go index f3352c4..420083e 100644 --- a/api/graph/resolver.go +++ b/api/graph/resolver.go @@ -15,3 +15,12 @@ var allowedCloneSchemes = map[string]struct{}{ "http": struct{}{}, "git": struct{}{}, } + +type CloneStatus string + +const ( + CloneNone CloneStatus = "NONE" + CloneInProgress CloneStatus = "IN_PROGRESS" + CloneComplete CloneStatus = "COMPLETE" + CloneError CloneStatus = "ERROR" +) diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index ea51a5d..f77b82b 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -128,12 +128,15 @@ func (r *mutationResolver) CreateRepository(ctx context.Context, name string, vi panic(fmt.Errorf("Unknown visibility %s", visibility)) // Invariant } - cloneInProgress := cloneURL != nil + cloneStatus := CloneNone + if cloneURL != nil { + cloneStatus = CloneInProgress + } row := tx.QueryRowContext(ctx, ` INSERT INTO repository ( created, updated, name, description, path, visibility, owner_id, - clone_in_progress + clone_status ) VALUES ( NOW() at time zone 'utc', NOW() at time zone 'utc', @@ -141,7 +144,7 @@ func (r *mutationResolver) CreateRepository(ctx context.Context, name string, vi ) RETURNING id, created, updated, name, description, visibility, path, owner_id; - `, name, description, repoPath, dvis, user.UserID, cloneInProgress) + `, name, description, repoPath, dvis, user.UserID, cloneStatus) if err := row.Scan(&repo.ID, &repo.Created, &repo.Updated, &repo.Name, &repo.Description, &repo.RawVisibility, &repo.Path, &repo.OwnerID); err != nil { @@ -188,7 +191,7 @@ func (r *mutationResolver) CreateRepository(ctx context.Context, name string, vi } } - if cloneInProgress { + if cloneURL != nil { u, err := url.Parse(*cloneURL) if err != nil { return valid.Errorf(ctx, "cloneUrl", "Invalid clone URL: %s", err) diff --git a/api/repos/middleware.go b/api/repos/middleware.go index ef39832..b9d6593 100644 --- a/api/repos/middleware.go +++ b/api/repos/middleware.go @@ -33,6 +33,15 @@ func Middleware(queue *work.Queue) func(next http.Handler) http.Handler { } } +type CloneStatus string + +const ( + CloneNone CloneStatus = "NONE" + CloneInProgress CloneStatus = "IN_PROGRESS" + CloneComplete CloneStatus = "COMPLETE" + CloneError CloneStatus = "ERROR" +) + // Schedules a clone. func Clone(ctx context.Context, repoID int, repo *git.Repository, cloneURL string) { queue, ok := ctx.Value(ctxKey).(*work.Queue) @@ -40,33 +49,29 @@ func Clone(ctx context.Context, repoID int, repo *git.Repository, cloneURL strin panic("No repos worker for this context") } task := work.NewTask(func(ctx context.Context) error { - defer func() { - recovered := recover() - err := database.WithTx(ctx, nil, func(tx *sql.Tx) error { - _, err := tx.Exec( - `UPDATE repository SET clone_in_progress = false WHERE id = $1;`, - repoID, - ) - if err != nil { - return err - } - return nil - }) - if err != nil { - panic(err) - } - if recovered != nil { - panic(recovered) - } - }() cloneCtx, cancel := context.WithTimeout(ctx, 10*time.Minute) defer cancel() err := repo.Clone(cloneCtx, &git.CloneOptions{ URL: cloneURL, RecurseSubmodules: git.NoRecurseSubmodules, }) + cloneStatus := CloneComplete + var cloneError sql.NullString if err != nil { - // TODO: Set repo to error state. Email error to user. + cloneStatus = CloneError + cloneError.String = err.Error() + cloneError.Valid = true + } + if err := database.WithTx(ctx, nil, func(tx *sql.Tx) error { + _, err := tx.Exec(` + UPDATE repository + SET clone_status = $2, clone_error = $3 + WHERE id = $1;`, repoID, cloneStatus, cloneError) + if err != nil { + return err + } + return nil + }); err != nil { panic(err) } return nil diff --git a/gitsrht/alembic/versions/0a3d114e8a18_add_clone_status_to_repository.py b/gitsrht/alembic/versions/0a3d114e8a18_add_clone_status_to_repository.py new file mode 100644 index 0000000..7acd8bd --- /dev/null +++ b/gitsrht/alembic/versions/0a3d114e8a18_add_clone_status_to_repository.py @@ -0,0 +1,49 @@ +"""Add clone_status to repository + +Revision ID: 0a3d114e8a18 +Revises: 163a7732e6a0 +Create Date: 2022-02-15 08:25:12.564021 + +""" + +# revision identifiers, used by Alembic. +revision = '0a3d114e8a18' +down_revision = '163a7732e6a0' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.execute(""" + CREATE TYPE clone_status AS ENUM ( + 'NONE', + 'IN_PROGRESS', + 'COMPLETE', + 'ERROR' + ); + + ALTER TABLE repository + ADD COLUMN clone_status clone_status NOT NULL DEFAULT 'NONE'; + ALTER TABLE repository + ALTER COLUMN clone_status DROP DEFAULT; + ALTER TABLE repository + ADD COLUMN clone_error varchar + CHECK ((clone_status = 'ERROR') != (clone_error IS NULL)); + ALTER TABLE repository + DROP COLUMN clone_in_progress; + """) + + +def downgrade(): + op.execute(""" + ALTER TABLE repository + DROP COLUMN clone_error; + ALTER TABLE repository + DROP COLUMN clone_status; + DROP TYPE clone_status; + ALTER TABLE repository + ADD COLUMN clone_in_progress boolean NOT NULL DEFAULT false; + ALTER TABLE repository + ALTER COLUMN clone_in_progress DROP DEFAULT; + """) diff --git a/gitsrht/templates/empty-repo.html b/gitsrht/templates/empty-repo.html index 154219a..1c45851 100644 --- a/gitsrht/templates/empty-repo.html +++ b/gitsrht/templates/empty-repo.html @@ -1,6 +1,8 @@ {% extends "repo.html" %} {% block repohead %} +{% if repo.clone_status == 'IN_PROGRESS' %} +{% endif %} {% endblock %} {% block content %} @@ -23,10 +25,14 @@
- {% if repo.clone_in_progress %} + {% if repo.clone_status == 'IN_PROGRESS' %}
A clone operation is currently in progress.
+ {% elif repo.clone_status == 'ERROR' %} +
+ An error occured while cloning: {{repo.clone_error}} +
{% endif %}

Nothing here yet! diff --git a/gitsrht/types/__init__.py b/gitsrht/types/__init__.py index 8319409..ff05db0 100644 --- a/gitsrht/types/__init__.py +++ b/gitsrht/types/__init__.py @@ -3,6 +3,7 @@ import sqlalchemy as sa from sqlalchemy import event import sqlalchemy_utils as sau from sqlalchemy.ext.declarative import declared_attr +from sqlalchemy.dialects import postgresql from srht.database import Base from srht.oauth import ExternalUserMixin, ExternalOAuthTokenMixin from gitsrht.git import Repository as GitRepository @@ -45,7 +46,9 @@ class Repository(Base): nullable=False, default=RepoVisibility.public) readme = sa.Column(sa.Unicode) - clone_in_progress = sa.Column(sa.Boolean, nullable=False) + clone_status = sa.Column(postgresql.ENUM( + 'NONE', 'IN_PROGRESS', 'COMPLETE', 'ERROR'), nullable=False) + clone_error = sa.Column(sa.Unicode) @declared_attr def owner_id(cls): -- 2.38.4