diff --git a/app/actions/providers/create_github_provider.rb b/app/actions/providers/create_github_provider.rb index 27b82b41..db5023db 100644 --- a/app/actions/providers/create_github_provider.rb +++ b/app/actions/providers/create_github_provider.rb @@ -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 diff --git a/app/actions/providers/create_gitlab_provider.rb b/app/actions/providers/create_gitlab_provider.rb index 3bce6750..bff9e8aa 100644 --- a/app/actions/providers/create_gitlab_provider.rb +++ b/app/actions/providers/create_gitlab_provider.rb @@ -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 diff --git a/app/models/provider.rb b/app/models/provider.rb index 4b650668..8cb7fa23 100644 --- a/app/models/provider.rb +++ b/app/models/provider.rb @@ -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! diff --git a/app/services/git/client.rb b/app/services/git/client.rb index 3a64382e..e5b6d4b6 100644 --- a/app/services/git/client.rb +++ b/app/services/git/client.rb @@ -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 diff --git a/app/services/git/github/client.rb b/app/services/git/github/client.rb index 9ba540b8..60e5892e 100644 --- a/app/services/git/github/client.rb +++ b/app/services/git/github/client.rb @@ -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 diff --git a/app/services/git/gitlab/client.rb b/app/services/git/gitlab/client.rb index a682c3ab..f239daed 100644 --- a/app/services/git/gitlab/client.rb +++ b/app/services/git/gitlab/client.rb @@ -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 diff --git a/app/views/providers/_form.html.erb b/app/views/providers/_form.html.erb index af6d7176..c385666e 100644 --- a/app/views/providers/_form.html.erb +++ b/app/views/providers/_form.html.erb @@ -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 %> +