Add GitHub updates endpoints and permissions

What does this MR do and why?

Adds the backend for the GitHub updates (continuous sync from GitHub) settings and status UI. Only projects imported while the github_continuous_import feature flag was on have the sync row these endpoints depend on.

  • Projects::Import::Sync::GithubUpdatesController serves status, update_now and stop. Errors return an error code (github_connection_lost, github_updates_not_stoppable, github_updates_not_updatable, github_updates_busy) and a message, which the UI shows. The endpoints build the status with Import::Sync::GithubUpdatesStatusPresenter.
  • Access uses three new permissions: read_github_update (Maintainer and Auditor) gates status, update_github_update (Maintainer) gates update_now, and stop_github_update (Maintainer) gates stop. stop is its own action because a stop can't be undone (needs Authorization team approval as an action outside the preferred set). update_github_update and stop_github_update are in the project:archived group, so archived and deletion-scheduled projects can't be updated or stopped.
  • A stop request writes a github_updates_stop_requested audit event, because it can't be undone. RequestStopService now takes current_user: for the audit author. Repeated requests while a stop is already running, and refused requests, write no event.
  • The endpoints depend only on the sync row, not on github_continuous_import. While the row exists the project stays read-only, so a flag rollback must not take away the way to stop updates.

Part 2 of 4, split from !263261 (closed).

  1. Mirroring: hide the mirroring form when mirroring isn't available.
  2. Backend: permissions, routes, controller, status presenter, stop audit event (this MR).
  3. Updates component: the Settings > Repository > GitHub updates Vue panel.
  4. Top banner: the read-only banner on project pages.

References

Related to #628593

Screenshots or screen recordings

Every string this MR adds, shown where it appears in the UI from part 3, for UX and technical writing review. Server messages are real responses from GDK with the database set to each state.

Messages after Update now
Case Screenshot
Update scheduled notice-update-scheduled
Refused: updates are not on (state changed since the page loaded) notice-update-inactive
Messages after Stop GitHub updates
Case Screenshot
Import still running notice-stop-import-running
Not in a state that can be stopped notice-stop-invalid-state
Connection lost notice-stop-connection-lost
Another update is still finishing notice-stop-lease-busy

How to set up and validate locally

Prerequisites

  1. Configure GitHub OAuth in GDK:
    1. On GitHub, go to Settings > Developer settings > OAuth Apps > New OAuth App. Set Homepage URL to your GDK URL (for example, https://gdk.test:3000) and Authorization callback URL to https://gdk.test:3000/users/auth. This path covers both GitHub sign-in (/users/auth/github/callback) and the import (/users/auth/-/import/github/callback), because GitHub accepts any redirect under the callback URL. Generate a client secret.

    2. Add the app to gdk.yml, then run gdk reconfigure and gdk restart:

      omniauth:
        github:
          enabled: true
          client_id: '<client ID>'
          client_secret: '<client secret>'
    3. In a Rails console, Gitlab::Auth::OAuth::Provider.enabled?(:github) returns true.

  2. Enable the feature flag before you authenticate with GitHub: Feature.enable(:github_continuous_import). GitLab stores the GitHub credential for updates only during the OAuth sign-in while the flag is on.
  3. Have a group on a Premium or Ultimate license to import into. A personal namespace doesn't offer updates.

Import a project with GitHub updates on

  1. Go to New project > Import project > GitHub and select Authenticate with GitHub. Updates aren't offered when you import with a personal access token.
  2. Authorize the OAuth app. If you authenticated before enabling the flag, the import fails with "To import with updates on, authorize GitHub again." Authenticate with GitHub again.
  3. Select Advanced import settings, then Keep this project updated from GitHub.
  4. Select the Premium or Ultimate group as the target and select Import.
  5. When the import finishes, check the sync row in a Rails console: Project.find_by_full_path('<group>/<project>').import_sync_repository.authority_state returns "provider_canonical".

GitHub can't reach a local GDK with webhooks, so changes made on GitHub reach GitLab only through the periodic repair pass or update_now.

Validate

This MR has no UI. Call the endpoints from the browser console on any page of the project, signed in as a Maintainer:

const csrf = { 'X-CSRF-Token': document.querySelector('meta[name=csrf-token]').content };
const base = `${location.pathname.split('/-/')[0]}/-/github_updates`;
await (await fetch(`${base}/status`)).json();
await (await fetch(`${base}/update_now`, { method: 'POST', headers: csrf })).json();
await (await fetch(`${base}/stop`, { method: 'POST', headers: csrf })).json();
  1. status returns the authority state and the state of every data type.
  2. update_now returns Update scheduled. with the status.
  3. In a Rails console, set the row to disconnected: Import::Sync::Repository.find_by!(project_id: <id>).disconnect!(:token_revoked). stop returns github_connection_lost. Restore it with .restore!.
  4. On a throwaway project (a stop can't be undone), call stop. The project's Secure > Audit events shows the stop request with your user (needs a license with audit events).
  5. Archive the project, or schedule it for deletion: update_now and stop return 404.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Rodrigo Tomonari

Merge request reports

Loading
Loading