mirror of
https://github.com/czhu12/canine.git
synced 2025-12-30 07:39:43 -06:00
support for github / gitlab enterprise
This commit is contained in:
@@ -6,7 +6,11 @@ class Providers::CreateGithubProvider
|
||||
promises :provider
|
||||
|
||||
executed do |context|
|
||||
client = Octokit::Client.new(access_token: context.provider.access_token)
|
||||
client_options = { access_token: context.provider.access_token }
|
||||
if context.provider.enterprise?
|
||||
client_options[:api_endpoint] = "#{context.provider.api_base_url}/api/v3/"
|
||||
end
|
||||
client = Octokit::Client.new(client_options)
|
||||
username = client.user[:login]
|
||||
context.provider.auth = {
|
||||
info: {
|
||||
@@ -14,16 +18,23 @@ class Providers::CreateGithubProvider
|
||||
}
|
||||
}.to_json
|
||||
|
||||
if (client.scopes & EXPECTED_SCOPES).sort != EXPECTED_SCOPES.sort
|
||||
message = "Invalid scopes. Please check that your personal access token has the following scopes: #{EXPECTED_SCOPES.join(", ")}"
|
||||
context.fail_and_return!(message)
|
||||
context.provider.errors.add(:access_token, message)
|
||||
next
|
||||
# Skip scope validation for enterprise (some GHE instances don't expose scopes)
|
||||
unless context.provider.enterprise?
|
||||
if (client.scopes & EXPECTED_SCOPES).sort != EXPECTED_SCOPES.sort
|
||||
message = "Invalid scopes. Please check that your personal access token has the following scopes: #{EXPECTED_SCOPES.join(", ")}"
|
||||
context.fail_and_return!(message)
|
||||
context.provider.errors.add(:access_token, message)
|
||||
next
|
||||
end
|
||||
end
|
||||
context.provider.save!
|
||||
rescue Octokit::Unauthorized
|
||||
message = "Invalid access token"
|
||||
context.provider.errors.add(:access_token, message)
|
||||
context.fail_and_return!(message)
|
||||
rescue Faraday::ConnectionFailed => e
|
||||
message = "Could not connect to GitHub server: #{e.message}"
|
||||
context.provider.errors.add(:registry_url, message)
|
||||
context.fail_and_return!(message)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
class Providers::CreateGitlabProvider
|
||||
EXPECTED_SCOPES = %w[ api read_repository read_registry write_registry ]
|
||||
GITLAB_PAT_API_URL = "https://gitlab.com/api/v4/personal_access_tokens/self"
|
||||
GITLAB_USER_API_URL = "https://gitlab.com/api/v4/user"
|
||||
extend LightService::Action
|
||||
|
||||
expects :provider
|
||||
promises :provider
|
||||
|
||||
executed do |context|
|
||||
response = HTTParty.get(GITLAB_PAT_API_URL,
|
||||
base_url = context.provider.api_base_url
|
||||
pat_api_url = "#{base_url}/api/v4/personal_access_tokens/self"
|
||||
user_api_url = "#{base_url}/api/v4/user"
|
||||
|
||||
response = HTTParty.get(pat_api_url,
|
||||
headers: {
|
||||
"Authorization" => "Bearer #{context.provider.access_token}"
|
||||
},
|
||||
@@ -20,15 +22,18 @@ class Providers::CreateGitlabProvider
|
||||
next
|
||||
end
|
||||
|
||||
if (response["scopes"] & EXPECTED_SCOPES).sort != EXPECTED_SCOPES.sort
|
||||
message = "Invalid scopes. Please check that your personal access token has the following scopes: #{EXPECTED_SCOPES.join(", ")}"
|
||||
context.provider.errors.add(:access_token, message)
|
||||
context.fail_and_return!(message)
|
||||
next
|
||||
# Skip scope validation for enterprise (some instances may have different scope requirements)
|
||||
unless context.provider.enterprise?
|
||||
if (response["scopes"] & EXPECTED_SCOPES).sort != EXPECTED_SCOPES.sort
|
||||
message = "Invalid scopes. Please check that your personal access token has the following scopes: #{EXPECTED_SCOPES.join(", ")}"
|
||||
context.provider.errors.add(:access_token, message)
|
||||
context.fail_and_return!(message)
|
||||
next
|
||||
end
|
||||
end
|
||||
# Get username data
|
||||
|
||||
response = HTTParty.get(GITLAB_USER_API_URL,
|
||||
response = HTTParty.get(user_api_url,
|
||||
headers: {
|
||||
"Authorization" => "Bearer #{context.provider.access_token}"
|
||||
},
|
||||
@@ -43,5 +48,9 @@ class Providers::CreateGitlabProvider
|
||||
context.provider.auth = body
|
||||
|
||||
context.provider.save!
|
||||
rescue Errno::ECONNREFUSED, SocketError => e
|
||||
message = "Could not connect to GitLab server: #{e.message}"
|
||||
context.provider.errors.add(:registry_url, message)
|
||||
context.fail_and_return!(message)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,8 +28,10 @@
|
||||
class Provider < ApplicationRecord
|
||||
attr_accessor :username_param
|
||||
GITHUB_PROVIDER = "github"
|
||||
GITHUB_API_BASE = "https://api.github.com"
|
||||
CUSTOM_REGISTRY_PROVIDER = "container_registry"
|
||||
GITLAB_PROVIDER = "gitlab"
|
||||
GITLAB_API_BASE = "https://gitlab.com"
|
||||
GIT_TYPE = "git"
|
||||
REGISTRY_TYPE = "registry"
|
||||
PROVIDER_TYPES = {
|
||||
@@ -83,6 +85,20 @@ class Provider < ApplicationRecord
|
||||
provider == GITLAB_PROVIDER
|
||||
end
|
||||
|
||||
def enterprise?
|
||||
(github? || gitlab?) && registry_url.present?
|
||||
end
|
||||
|
||||
def api_base_url
|
||||
if registry_url.present?
|
||||
registry_url.chomp("/")
|
||||
elsif github?
|
||||
GITHUB_API_BASE
|
||||
elsif gitlab?
|
||||
GITLAB_API_BASE
|
||||
end
|
||||
end
|
||||
|
||||
def twitter_refresh_token!(token); end
|
||||
|
||||
def used!
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
class Git::Client
|
||||
def self.from_provider(provider:, repository_url:)
|
||||
if provider.github?
|
||||
Git::Github::Client.new(access_token: provider.access_token, repository_url:)
|
||||
Git::Github::Client.new(access_token: provider.access_token, repository_url:, api_base_url: provider.api_base_url)
|
||||
elsif provider.gitlab?
|
||||
Git::Gitlab::Client.new(access_token: provider.access_token, repository_url:)
|
||||
Git::Gitlab::Client.new(access_token: provider.access_token, repository_url:, api_base_url: provider.api_base_url)
|
||||
else
|
||||
raise "Unsupported Git provider: #{provider}"
|
||||
end
|
||||
|
||||
@@ -4,9 +4,11 @@ class Git::Github::Client < Git::Client
|
||||
attr_accessor :client, :repository_url
|
||||
|
||||
def self.from_project(project)
|
||||
provider = project.project_credential_provider.provider
|
||||
new(
|
||||
access_token: project.project_credential_provider.access_token,
|
||||
repository_url: project.repository_url
|
||||
access_token: provider.access_token,
|
||||
repository_url: project.repository_url,
|
||||
api_base_url: provider.api_base_url
|
||||
)
|
||||
end
|
||||
|
||||
@@ -26,8 +28,12 @@ class Git::Github::Client < Git::Client
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(access_token:, repository_url:)
|
||||
@client = Octokit::Client.new(access_token:)
|
||||
def initialize(access_token:, repository_url:, api_base_url: nil)
|
||||
client_options = { access_token: }
|
||||
if api_base_url && api_base_url != "https://api.github.com"
|
||||
client_options[:api_endpoint] = "#{api_base_url}/api/v3/"
|
||||
end
|
||||
@client = Octokit::Client.new(client_options)
|
||||
@repository_url = repository_url
|
||||
end
|
||||
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
class Git::Gitlab::Client < Git::Client
|
||||
GITLAB_API_BASE = "https://gitlab.com/api/v4"
|
||||
GITLAB_WEBHOOK_SECRET = ENV["GITLAB_WEBHOOK_SECRET"]
|
||||
attr_accessor :access_token, :repository_url
|
||||
attr_accessor :access_token, :repository_url, :api_base_url
|
||||
|
||||
def self.from_project(project)
|
||||
raise "Project is not a GitLab project" unless project.project_credential_provider.provider.gitlab?
|
||||
provider = project.project_credential_provider.provider
|
||||
raise "Project is not a GitLab project" unless provider.gitlab?
|
||||
new(
|
||||
access_token: project.project_credential_provider.access_token,
|
||||
repository_url: project.repository_url
|
||||
access_token: provider.access_token,
|
||||
repository_url: project.repository_url,
|
||||
api_base_url: provider.api_base_url
|
||||
)
|
||||
end
|
||||
|
||||
def initialize(access_token:, repository_url:)
|
||||
def initialize(access_token:, repository_url:, api_base_url: nil)
|
||||
@access_token = access_token
|
||||
@repository_url = repository_url
|
||||
@api_base_url = api_base_url || "https://gitlab.com"
|
||||
end
|
||||
|
||||
def gitlab_api_base
|
||||
"#{@api_base_url}/api/v4"
|
||||
end
|
||||
|
||||
def repository_exists?
|
||||
@@ -22,7 +28,7 @@ class Git::Gitlab::Client < Git::Client
|
||||
|
||||
def commits(branch)
|
||||
response = HTTParty.get(
|
||||
"#{GITLAB_API_BASE}/projects/#{encoded_url}/repository/commits?ref=#{branch}",
|
||||
"#{gitlab_api_base}/projects/#{encoded_url}/repository/commits?ref=#{branch}",
|
||||
headers: { "Authorization" => "Bearer #{access_token}" }
|
||||
)
|
||||
unless response.success?
|
||||
@@ -53,7 +59,7 @@ class Git::Gitlab::Client < Git::Client
|
||||
return
|
||||
end
|
||||
response = HTTParty.post(
|
||||
"#{GITLAB_API_BASE}/projects/#{encoded_url}/hooks",
|
||||
"#{gitlab_api_base}/projects/#{encoded_url}/hooks",
|
||||
headers: { "Authorization" => "Bearer #{access_token}", "Content-Type" => "application/json" },
|
||||
body: {
|
||||
url: Rails.application.routes.url_helpers.inbound_webhooks_gitlab_index_url,
|
||||
@@ -71,7 +77,7 @@ class Git::Gitlab::Client < Git::Client
|
||||
|
||||
def webhooks
|
||||
response = HTTParty.get(
|
||||
"#{GITLAB_API_BASE}/projects/#{encoded_url}/hooks",
|
||||
"#{gitlab_api_base}/projects/#{encoded_url}/hooks",
|
||||
headers: { "Authorization" => "Bearer #{access_token}" },
|
||||
format: :json
|
||||
)
|
||||
@@ -84,7 +90,7 @@ class Git::Gitlab::Client < Git::Client
|
||||
def repository
|
||||
@repository ||= begin
|
||||
project_response = HTTParty.get(
|
||||
"#{GITLAB_API_BASE}/projects/#{encoded_url}",
|
||||
"#{gitlab_api_base}/projects/#{encoded_url}",
|
||||
headers: { "Authorization" => "Bearer #{access_token}" }
|
||||
)
|
||||
end
|
||||
@@ -105,7 +111,7 @@ class Git::Gitlab::Client < Git::Client
|
||||
def remove_webhook!
|
||||
if webhook_exists?
|
||||
HTTParty.delete(
|
||||
"#{GITLAB_API_BASE}/projects/#{encoded_url}/hooks/#{webhook['id']}",
|
||||
"#{gitlab_api_base}/projects/#{encoded_url}/hooks/#{webhook['id']}",
|
||||
headers: { "Authorization" => "Bearer #{access_token}" }
|
||||
)
|
||||
end
|
||||
@@ -113,7 +119,7 @@ class Git::Gitlab::Client < Git::Client
|
||||
|
||||
def pull_requests
|
||||
HTTParty.get(
|
||||
"#{GITLAB_API_BASE}/projects/#{encoded_url}/merge_requests",
|
||||
"#{gitlab_api_base}/projects/#{encoded_url}/merge_requests",
|
||||
headers: { "Authorization" => "Bearer #{access_token}" }
|
||||
).map do |row|
|
||||
Git::Common::PullRequest.new(
|
||||
@@ -131,7 +137,7 @@ class Git::Gitlab::Client < Git::Client
|
||||
|
||||
def pull_request_status(pr_number)
|
||||
response = HTTParty.get(
|
||||
"#{GITLAB_API_BASE}/projects/#{encoded_url}/merge_requests/#{pr_number}",
|
||||
"#{gitlab_api_base}/projects/#{encoded_url}/merge_requests/#{pr_number}",
|
||||
headers: { "Authorization" => "Bearer #{access_token}" }
|
||||
)
|
||||
return 'not_found' unless response.success?
|
||||
@@ -141,7 +147,7 @@ class Git::Gitlab::Client < Git::Client
|
||||
|
||||
def get_file(file_path, branch)
|
||||
response = HTTParty.get(
|
||||
"#{GITLAB_API_BASE}/projects/#{encoded_url}/repository/files/#{URI.encode_www_form_component(file_path)}/raw?ref=#{branch}",
|
||||
"#{gitlab_api_base}/projects/#{encoded_url}/repository/files/#{URI.encode_www_form_component(file_path)}/raw?ref=#{branch}",
|
||||
headers: { "Authorization" => "Bearer #{access_token}" }
|
||||
)
|
||||
response.success? ? Git::Common::File.new(file_path, response.body, branch) : nil
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
<%= form_with model: provider do |form| %>
|
||||
<%= render "shared/error_messages", resource: form.object %>
|
||||
<%= form.hidden_field :provider, value: params[:provider_type] %>
|
||||
<% if params[:provider_type] == Provider::GITHUB_PROVIDER || params[:provider_type] == Provider::GITLAB_PROVIDER %>
|
||||
<div class="form-control mt-1 w-full max-w-sm">
|
||||
<label class="label">
|
||||
<span class="label-text">Host URL <span class="text-gray-400">(optional, for Enterprise)</span></span>
|
||||
</label>
|
||||
<%= form.text_field :registry_url, class: "input input-bordered", placeholder: params[:provider_type] == Provider::GITHUB_PROVIDER ? "https://github.example.com" : "https://gitlab.example.com" %>
|
||||
<label class="label">
|
||||
<span class="label-text-alt text-gray-400">Leave blank for <%= params[:provider_type] == Provider::GITHUB_PROVIDER ? "github.com" : "gitlab.com" %></span>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if params[:provider_type] == Provider::CUSTOM_REGISTRY_PROVIDER || form.object.provider == Provider::CUSTOM_REGISTRY_PROVIDER %>
|
||||
<div data-controller="registry-selector">
|
||||
<div class="form-control mt-1 w-full max-w-sm">
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
<% end %>
|
||||
permissions.
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
For GitHub Enterprise, enter your server's host URL below.
|
||||
</div>
|
||||
<% elsif params[:provider_type] == Provider::GITLAB_PROVIDER %>
|
||||
<%= link_to "Find your Gitlab token →", "https://gitlab.com/-/user_settings/personal_access_tokens", target: "_blank", class: "text-sm text-gray-500 " %>
|
||||
<div class="mt-2">
|
||||
@@ -31,6 +34,9 @@
|
||||
<% end %>
|
||||
permissions.
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
For GitLab Enterprise/Self-Managed, enter your server's host URL below.
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,16 +12,19 @@ RSpec.describe Providers::CreateGitlabProvider do
|
||||
JSON.parse(File.read(Rails.root.join('spec/resources/integrations/gitlab/user.json')))
|
||||
end
|
||||
|
||||
let(:gitlab_pat_api_url) { "#{provider.api_base_url}/api/v4/personal_access_tokens/self" }
|
||||
let(:gitlab_user_api_url) { "#{provider.api_base_url}/api/v4/user" }
|
||||
|
||||
describe '.execute' do
|
||||
context 'when the access token is valid and has correct scopes' do
|
||||
before do
|
||||
stub_request(:get, Providers::CreateGitlabProvider::GITLAB_PAT_API_URL)
|
||||
stub_request(:get, gitlab_pat_api_url)
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: personal_access_tokens_data.to_json,
|
||||
headers: { 'Content-Type' => 'application/json' }
|
||||
)
|
||||
stub_request(:get, Providers::CreateGitlabProvider::GITLAB_USER_API_URL)
|
||||
stub_request(:get, gitlab_user_api_url)
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: user_data.to_json,
|
||||
@@ -38,7 +41,7 @@ RSpec.describe Providers::CreateGitlabProvider do
|
||||
|
||||
context 'when the access token is invalid' do
|
||||
before do
|
||||
stub_request(:get, Providers::CreateGitlabProvider::GITLAB_PAT_API_URL)
|
||||
stub_request(:get, gitlab_pat_api_url)
|
||||
.to_return(
|
||||
status: 401,
|
||||
body: { error: "Unauthorized" }.to_json,
|
||||
@@ -60,7 +63,7 @@ RSpec.describe Providers::CreateGitlabProvider do
|
||||
before do
|
||||
error_response = personal_access_tokens_data.deep_dup
|
||||
error_response["scopes"] = []
|
||||
stub_request(:get, Providers::CreateGitlabProvider::GITLAB_PAT_API_URL)
|
||||
stub_request(:get, gitlab_pat_api_url)
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: error_response.to_json,
|
||||
|
||||
@@ -4,6 +4,7 @@ RSpec.describe Git::Gitlab::Client do
|
||||
let(:access_token) { 'test_token' }
|
||||
let(:repository_url) { 'czhu12/echo' }
|
||||
let(:client) { described_class.new(access_token:, repository_url:) }
|
||||
let(:gitlab_api_base) { "#{Provider::GITLAB_API_BASE}/api/v4" }
|
||||
|
||||
describe '#register_webhook!' do
|
||||
let(:webhook_url) { 'http://localhost:3000/inbound_webhooks/gitlab' }
|
||||
@@ -32,7 +33,7 @@ RSpec.describe Git::Gitlab::Client do
|
||||
|
||||
it 'creates a new webhook' do
|
||||
expect(HTTParty).to receive(:post).with(
|
||||
"#{described_class::GITLAB_API_BASE}/projects/#{client.encoded_url}/hooks",
|
||||
"#{gitlab_api_base}/projects/#{client.encoded_url}/hooks",
|
||||
headers: { "Authorization" => "Bearer #{access_token}", "Content-Type" => "application/json" },
|
||||
body: {
|
||||
url: webhook_url,
|
||||
@@ -66,7 +67,7 @@ RSpec.describe Git::Gitlab::Client do
|
||||
|
||||
it 'deletes the webhook' do
|
||||
expect(HTTParty).to receive(:delete).with(
|
||||
"#{described_class::GITLAB_API_BASE}/projects/#{client.encoded_url}/hooks/#{webhook['id']}",
|
||||
"#{gitlab_api_base}/projects/#{client.encoded_url}/hooks/#{webhook['id']}",
|
||||
headers: { "Authorization" => "Bearer #{access_token}" }
|
||||
)
|
||||
|
||||
@@ -96,7 +97,7 @@ RSpec.describe Git::Gitlab::Client do
|
||||
context 'when file exists' do
|
||||
before do
|
||||
allow(HTTParty).to receive(:get).with(
|
||||
"#{described_class::GITLAB_API_BASE}/projects/#{client.encoded_url}/repository/files/#{URI.encode_www_form_component(file_path)}/raw?ref=#{branch}",
|
||||
"#{gitlab_api_base}/projects/#{client.encoded_url}/repository/files/#{URI.encode_www_form_component(file_path)}/raw?ref=#{branch}",
|
||||
headers: { "Authorization" => "Bearer #{access_token}" }
|
||||
).and_return(success_response)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user