Skip to content

Commit

Permalink
EXPLAIN ME: GitService
Browse files Browse the repository at this point in the history
This commit implements a method to know which Git service (ie. GitHub or
GitLab) is used to host source code.
  • Loading branch information
neomilium committed Apr 23, 2021
1 parent ef4f249 commit 4479a1f
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 11 deletions.
2 changes: 1 addition & 1 deletion features/update/pull_request.feature
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Feature: Create a pull-request/merge-request after update
<%= @configs['name'] %>
"""
When I run `msync update --noop --pr`
Then the stderr should contain "No GitLab token specified to create a merge request"
Then the stderr should contain "A token is require to use services from gitlab"
And the exit status should be 1
And the puppet module "puppet-test" from "fakenamespace" should have no commits made by "Aruba"

Expand Down
107 changes: 100 additions & 7 deletions lib/modulesync/git_service.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,117 @@
module ModuleSync
class Error < StandardError; end

# Namespace for Git service classes (ie. GitHub, GitLab)
module GitService
def self.instantiate(type:, options:)
options ||= {}
class MissingCredentialsError < Error; end

def self.instantiate(type:, endpoint:, token:)
case type
when :github
endpoint = options[:base_url] || ENV.fetch('GITHUB_BASE_URL', 'https://api.github.com')
token = options[:token] || ENV['GITHUB_TOKEN']
raise ModuleSync::Error, 'No GitHub token specified to create a pull request' if token.nil?
require 'modulesync/git_service/github'
ModuleSync::GitService::GitHub.new(token, endpoint)
when :gitlab
endpoint = options[:base_url] || ENV.fetch('GITLAB_BASE_URL', 'https://gitlab.com/api/v4')
token = options[:token] || ENV['GITLAB_TOKEN']
raise ModuleSync::Error, 'No GitLab token specified to create a merge request' if token.nil?
require 'modulesync/git_service/gitlab'
ModuleSync::GitService::GitLab.new(token, endpoint)
else
raise ModuleSync::Error, "Unable to manage a PR/MR for Git service: '#{type}'"
raise NotImplementedError, "Unable to manage a PR/MR for Git service: '#{type}'"
end
end

def self.configuration_for(sourcecode:)
type = type_for(sourcecode: sourcecode)

{
type: type,
endpoint: endpoint_for(sourcecode: sourcecode, type: type),
token: token_for(sourcecode: sourcecode, type: type),
}
end

# 1. module specific
# 2. remote url based
# 3. environment variables (if only one)
# 4. fails
def self.type_for(sourcecode:)
return :github unless sourcecode.options[:github].nil?
return :gitlab unless sourcecode.options[:gitlab].nil?
return :github if sourcecode.repository_remote.include? 'github'
return :gitlab if sourcecode.repository_remote.include? 'gitlab'

if ENV['GITLAB_TOKEN'].nil? && ENV['GITHUB_TOKEN'].nil?
raise ModuleSync::Error, <<~MESSAGE
Unable to guess Git service type without GITLAB_TOKEN or GITHUB_TOKEN sets.
MESSAGE
end

unless ENV['GITLAB_TOKEN'].nil? || ENV['GITHUB_TOKEN'].nil?
raise ModuleSync::Error, <<~MESSAGE
Unable to guess Git service type with both GITLAB_TOKEN and GITHUB_TOKEN sets.
Please set the wanted one in configuration (ie. add `gitlab:` or `github:` key)
MESSAGE
end

return :github unless ENV['GITHUB_TOKEN'].nil?
return :gitlab unless ENV['GITLAB_TOKEN'].nil?

raise NotImplementedError
end

# 1. module specific
# 2. environment variable dependending on type
# 3. remote url based
# 4. defaults
def self.endpoint_for(sourcecode:, type:)
endpoint = sourcecode.options.dig(type, :base_url)

endpoint ||= case type
when :github
ENV['GITHUB_BASE_URL']
when :gitlab
ENV['GITLAB_BASE_URL']
end

endpoint ||= guess_endpoint_from(remote: sourcecode.repository_remote, type: type)

raise NotImplementedError, <<~MESSAGE if endpoint.nil?
Unable to guess endpoint for remote: '#{sourcecode.repository_remote}'
Please provide `base_url` option in configuration file
MESSAGE

endpoint
end

def self.guess_endpoint_from(remote:, type:)
pattern = /^git@(.*):(.*)(\.git)*$/
return nil unless remote.match?(pattern)

endpoint = remote.sub(pattern, 'https://\1')
endpoint += '/api/v4' if type == :gitlab
endpoint
end

# 1. module specific
# 2. environment variable depending on type
# 3. fails
def self.token_for(sourcecode:, type:)
token = sourcecode.options.dig(type, :token)

token ||= case type
when :github
ENV['GITHUB_TOKEN']
when :gitlab
ENV['GITLAB_TOKEN']
end

raise MissingCredentialsError, <<~MESSAGE if token.nil?
A token is require to use services from #{type}:
Please set environment variable: "#{type.upcase}_TOKEN" or set the token entry in module options.
MESSAGE

token
end
end
end
3 changes: 2 additions & 1 deletion lib/modulesync/source_code.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def path(*parts)
end

def open_pull_request
git_service = GitService.instantiate type: git_service_type, options: @options[git_service_type]
git_service_options = GitService.configuration_for(sourcecode: self)
git_service = GitService.instantiate(**git_service_options)
git_service.open_pull_request(
repo_path: repository_path,
namespace: repository_namespace,
Expand Down
1 change: 1 addition & 0 deletions modulesync.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'octokit', '~>4.0'
spec.add_runtime_dependency 'puppet-blacksmith', '>= 3.0', '< 7'
spec.add_runtime_dependency 'thor'
spec.add_runtime_dependency 'byebug'
end
156 changes: 154 additions & 2 deletions spec/unit/modulesync/git_service_spec.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,167 @@
require 'modulesync/git_service'

describe ModuleSync::GitService do
before do
options = ModuleSync.config_defaults.merge({
git_base: 'file:///tmp/dummy',
})
ModuleSync.instance_variable_set '@options', options
end

context 'when instantiate a GitHub service without credentials' do
it 'raises an error' do
expect { ModuleSync::GitService.instantiate(type: :github, options: nil) }.to raise_error(ModuleSync::Error, 'No GitHub token specified to create a pull request')
expect { ModuleSync::GitService.instantiate(type: :github, endpoint: nil, token: nil) }.to raise_error(ModuleSync::Error, 'No GitHub token specified to create a pull request')
end
end

context 'when instantiate a GitLab service without credentials' do
it 'raises an error' do
expect { ModuleSync::GitService.instantiate(type: :gitlab, options: nil) }.to raise_error(ModuleSync::Error, 'No GitLab token specified to create a merge request')
expect { ModuleSync::GitService.instantiate(type: :gitlab, endpoint: nil, token: nil) }.to raise_error(ModuleSync::Error, 'No GitLab token specified to create a merge request')
end
end

context 'when guessing the git service configuration' do
before do
allow(ENV).to receive(:[])
.and_return(nil)
end

let(:sourcecode) do
ModuleSync::SourceCode.new 'puppet-test', sourcecode_options
end

context 'when using a complete git service configuration entry' do
let(:sourcecode_options) do
{
gitlab: {
base_url: 'https://vcs.example.com/api/v4',
token: 'secret',
},
}
end

it 'build git service arguments from configuration entry' do
expect(ModuleSync::GitService.configuration_for(sourcecode: sourcecode)).to eq({
type: :gitlab,
endpoint: 'https://vcs.example.com/api/v4',
token: 'secret',
})
end
end

context 'when using a simple git service key entry' do
let(:sourcecode_options) do
{
gitlab: {},
remote: '[email protected]:namespace/puppet-test',
}
end

context 'with GITLAB_BASE_URL and GITLAB_TOKEN environment variables sets' do
it 'build git service arguments from environment variables' do
allow(ENV).to receive(:[])
.with('GITLAB_BASE_URL')
.and_return('https://vcs.example.com/api/v4')
allow(ENV).to receive(:[])
.with('GITLAB_TOKEN')
.and_return('secret')

expect(ModuleSync::GitService.configuration_for(sourcecode: sourcecode)).to eq({
type: :gitlab,
endpoint: 'https://vcs.example.com/api/v4',
token: 'secret',
})
end
end

context 'with only GITLAB_TOKEN environment variable sets' do
it 'guesses the endpoint based on repository remote' do
allow(ENV).to receive(:[])
.with('GITLAB_TOKEN')
.and_return('secret')

expect(ModuleSync::GitService.configuration_for(sourcecode: sourcecode)).to eq({
type: :gitlab,
endpoint: 'https://git.example.com/api/v4',
token: 'secret',
})
end
end

context 'without any environment variable sets' do
it 'raises an error about missing credential' do
expect{ModuleSync::GitService.configuration_for(sourcecode: sourcecode)}
.to raise_error ModuleSync::GitService::MissingCredentialsError
end
end
end

context 'without git service configuration entry' do
context 'with a guessable endpoint based on repository remote' do
let(:sourcecode_options) do
{
remote: '[email protected]:namespace/puppet-test',
}
end

context 'with a GITLAB_TOKEN environment variable sets' do
it 'guesses git service configuration' do
allow(ENV).to receive(:[])
.with('GITLAB_TOKEN')
.and_return('secret')

expect(ModuleSync::GitService.configuration_for(sourcecode: sourcecode)).to eq({
type: :gitlab,
endpoint: 'https://gitlab.example.com/api/v4',
token: 'secret',
})
end
end

context 'without a GITLAB_TOKEN environment variable sets' do
it 'raise an error about missing credential' do
expect{ModuleSync::GitService.configuration_for(sourcecode: sourcecode)}
.to raise_error ModuleSync::Error
end
end
end

context 'with a unguessable endpoint' do
let(:sourcecode_options) do
{
remote: '[email protected]:namespace/puppet-test',
}
end

context 'with GITHUB_TOKEN environments variable sets' do
it 'guesses git service configuration' do
allow(ENV).to receive(:[])
.with('GITHUB_TOKEN')
.and_return('secret')

expect(ModuleSync::GitService.configuration_for(sourcecode: sourcecode)).to eq({
type: :github,
endpoint: 'https://vcs.example.com',
token: 'secret',
})
end
end

context 'with GITLAB_TOKEN and GITHUB_TOKEN environments variables sets' do
it 'raises an error' do
allow(ENV).to receive(:[])
.with('GITHUB_TOKEN')
.and_return('secret')

allow(ENV).to receive(:[])
.with('GITLAB_TOKEN')
.and_return('secret')

expect{ModuleSync::GitService.configuration_for(sourcecode: sourcecode)}
.to raise_error ModuleSync::Error
end
end
end
end
end
end
22 changes: 22 additions & 0 deletions spec/unit/modulesync/source_code_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require 'spec_helper'

describe ModuleSync::SourceCode do
before do
options = ModuleSync.config_defaults.merge({
git_base: 'file:///tmp/dummy',
})
ModuleSync.instance_variable_set '@options', options
end

subject do
ModuleSync::SourceCode.new('namespace/name', nil)
end

it 'has a repository namespace sets to "namespace"' do
expect(subject.repository_namespace).to eq 'namespace'
end

it 'has a repository name sets to "name"' do
expect(subject.repository_name).to eq 'name'
end
end

0 comments on commit 4479a1f

Please sign in to comment.