Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for GitLab merge requests (MRs) #175

Merged
merged 5 commits into from
Jun 2, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
tmp/
modules/
*.gem
Gemfile.lock
.bundle/
.ruby-version
.vscode/
Gemfile.lock
bin/bundle
bin/rspec
modules/
tmp/
vendor/
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,27 +176,29 @@ touching the modules, you can deactivate the hook.
msync hook deactivate
```

#### Submitting PRs to GitHub
#### Submitting PRs/MRs to GitHub or GitLab

You can have modulesync submit Pull Requests on GitHub automatically with the
`--pr` CLI option.
You can have modulesync submit Pull Requests on GitHub or Merge Requests on
GitLab automatically with the `--pr` CLI option.

```
msync update --pr
```

You must set `GITHUB_TOKEN` in your environment for this to work. Other options
include:
You must set the `GITHUB_TOKEN` or `GITLAB_TOKEN` environment variable
for GitHub PRs or GitLab MRs to work. Additional options:

* Set the PR title with `--pr-title` or in `modulesync.yml` with the `pr_title`
attribute.
* Assign labels to the PR with `--pr-labels` or in `modulesync.yml` with the
`pr_labels` attribute. **NOTE:** `pr_labels` should be a list. When using
the `--pr-labels` CLI option, you should use a comma separated list.
* Set the PR/MR title with `--pr-title` or in `modulesync.yml` with the
`pr_title` attribute.
* Assign labels to the PR/MR with `--pr-labels` or in `modulesync.yml` with
the `pr_labels` attribute. **NOTE:** `pr_labels` should be a list. When
using the `--pr-labels` CLI option, you should use a comma separated list.

You can optionally set the `GITHUB_BASE_URL` environment variable to use GitHub
Enterprise. This is passed to Octokit's [`api_endpoint`](https://github.com/octokit/octokit.rb#interacting-with-the-githubcom-apis-in-github-enterprise)
configuration option.
For GitHub Enterprise and self-hosted GitLab instances you need to set the
`GITHUB_BASE_URL` or `GITLAB_BASE_URL` environment variable in addition.
More details for GitHub:

* Octokit [`api_endpoint`](https://github.com/octokit/octokit.rb#interacting-with-the-githubcom-apis-in-github-enterprise)

### Using Forks and Non-master branches

Expand Down Expand Up @@ -258,8 +260,8 @@ Available parameters for modulesync.yml
* `remote_branch` : Remote branch to push to (Default: Same value as branch)
* `message` : Commit message to apply to updated modules.
* `pre_commit_script` : A script to be run before commiting (e.g. 'contrib/myfooscript.sh')
* `pr_title` : The title to use when submitting PRs to GitHub.
* `pr_labels` : A list of labels to assign PRs created on GitHub.
* `pr_title` : The title to use when submitting PRs/MRs to GitHub or GitLab.
* `pr_labels` : A list of labels to assign PRs/MRs created on GitHub or GitLab.

##### Example

Expand Down
63 changes: 23 additions & 40 deletions lib/modulesync.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
require 'fileutils'
require 'octokit'
require 'pathname'
require 'modulesync/cli'
require 'modulesync/constants'
Expand All @@ -11,10 +10,7 @@
require 'monkey_patches'

GITHUB_TOKEN = ENV.fetch('GITHUB_TOKEN', '')

Octokit.configure do |c|
c.api_endpoint = ENV.fetch('GITHUB_BASE_URL', 'https://api.github.com')
end
GITLAB_TOKEN = ENV.fetch('GITLAB_TOKEN', '')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can avoid these constants and only use them inline since they're only used in a single place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, totally missed it. Will adjust accordingly


module ModuleSync # rubocop:disable Metrics/ModuleLength
include Constants
Expand Down Expand Up @@ -136,48 +132,22 @@ def self.manage_module(puppet_module, module_files, module_options, defaults, op
if options[:noop]
Git.update_noop(git_repo, options)
elsif !options[:offline]
# Git.update() returns a boolean: true if files were pushed, false if not.
pushed = Git.update(git_repo, files_to_manage, options)
return nil unless pushed && options[:pr]

manage_pr(namespace, module_name, options)
end
end

def self.manage_pr(namespace, module_name, options)
if options[:pr] && GITHUB_TOKEN.empty?
$stderr.puts 'Environment variable GITHUB_TOKEN must be set to use --pr!'
raise unless options[:skip_broken]
end

# We only do GitHub PR work if the GITHUB_TOKEN variable is set in the environment.
repo_path = File.join(namespace, module_name)
github = Octokit::Client.new(:access_token => GITHUB_TOKEN)

# Skip creating the PR if it exists already.
head = "#{namespace}:#{options[:branch]}"
pull_requests = github.pull_requests(repo_path, :state => 'open', :base => 'master', :head => head)
if pull_requests.empty?
pr = github.create_pull_request(repo_path, 'master', options[:branch], options[:pr_title], options[:message])
$stdout.puts "Submitted PR '#{options[:pr_title]}' to #{repo_path} - merges #{options[:branch]} into master"
else
$stdout.puts "Skipped! #{pull_requests.length} PRs found for branch #{options[:branch]}"
pushed && options[:pr] && @pr.manage(namespace, module_name, options)
end

# PR labels can either be a list in the YAML file or they can pass in a comma
# separated list via the command line argument.
pr_labels = Util.parse_list(options[:pr_labels])

# We only assign labels to the PR if we've discovered a list > 1. The labels MUST
# already exist. We DO NOT create missing labels.
return if pr_labels.empty?
$stdout.puts "Attaching the following labels to PR #{pr['number']}: #{pr_labels.join(', ')}"
github.add_labels_to_an_issue(repo_path, pr['number'], pr_labels)
end

def self.update(options)
options = config_defaults.merge(options)
defaults = Util.parse_config(File.join(options[:configs], CONF_FILE))
if options[:pr]
unless options[:branch]
$stderr.puts 'A branch must be specified with --branch to use --pr!'
raise
end

@pr = create_pr_manager if options[:pr]
end

local_template_dir = File.join(options[:configs], MODULE_FILES_DIR)
local_files = find_template_files(local_template_dir)
Expand All @@ -201,4 +171,17 @@ def self.update(options)
end
exit 1 if errors && options[:fail_on_warnings]
end

def self.create_pr_manager
if !GITHUB_TOKEN.empty?
require 'modulesync/pr/github'
ModuleSync::PR::GitHub.new(GITHUB_TOKEN, ENV.fetch('GITHUB_BASE_URL', 'https://api.github.com'))
elsif !GITLAB_TOKEN.empty?
require 'modulesync/pr/github'
bittner marked this conversation as resolved.
Show resolved Hide resolved
ModuleSync::PR::GitLab.new(GITLAB_TOKEN, ENV.fetch('GITLAB_BASE_URL', 'https://gitlab.com/api/v4'))
else
$stderr.puts 'Environment variables GITHUB_TOKEN or GITLAB_TOKEN must be set to use --pr!'
raise
end
end
end
6 changes: 3 additions & 3 deletions lib/modulesync/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ class Base < Thor
:default => false
option :pr,
:type => :boolean,
:desc => 'Submit GitHub PR',
:desc => 'Submit pull/merge request',
:default => false
option :pr_title,
:desc => 'Title of GitHub PR',
:desc => 'Title of pull/merge request',
:default => CLI.defaults[:pr_title] || 'Update to module template files'
option :pr_labels,
:type => :array,
:desc => 'Labels to add to the GitHub PR',
:desc => 'Labels to add to the pull/merge request',
:default => CLI.defaults[:pr_labels] || []
option :offline,
:type => :boolean,
Expand Down
41 changes: 41 additions & 0 deletions lib/modulesync/pr/github.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require 'octokit'
require 'modulesync/util'

module ModuleSync
module PR
# GitHub creates and manages pull requests on github.com or GitHub
# Enterprise installations.
class GitHub
def initialize(token, endpoint)
Octokit.configure do |c|
c.api_endpoint = endpoint
end
@api = Octokit::Client.new(:access_token => token)
end

def manage(namespace, module_name, options)
repo_path = File.join(namespace, module_name)
head = "#{namespace}:#{options[:branch]}"

pull_requests = @api.pull_requests(repo_path, :state => 'open', :base => 'master', :head => head)
if pull_requests.empty?
pr = @api.create_pull_request(repo_path, 'master', options[:branch], options[:pr_title], options[:message])
$stdout.puts "Submitted PR '#{options[:pr_title]}' to #{repo_path} - merges #{options[:branch]} into master"
else
# Skip creating the PR if it exists already.
$stdout.puts "Skipped! #{pull_requests.length} PRs found for branch #{options[:branch]}"
end

# PR labels can either be a list in the YAML file or they can pass in a comma
# separated list via the command line argument.
pr_labels = ModuleSync::Util.parse_list(options[:pr_labels])

# We only assign labels to the PR if we've discovered a list > 1. The labels MUST
# already exist. We DO NOT create missing labels.
return if pr_labels.empty?
$stdout.puts "Attaching the following labels to PR #{pr['number']}: #{pr_labels.join(', ')}"
@api.add_labels_to_an_issue(repo_path, pr['number'], pr_labels)
end
end
end
end
40 changes: 40 additions & 0 deletions lib/modulesync/pr/gitlab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require 'gitlab'
require 'modulesync/util'

module ModuleSync
module PR
# GitLab creates and manages merge requests on gitlab.com or private GitLab
# installations.
class GitLab
def initialize(token, endpoint)
@api = Gitlab::Client.new(
:endpoint => endpoint,
:private_token => token
)
end

def manage(namespace, module_name, options)
repo_path = File.join(namespace, module_name)

head = "#{namespace}:#{options[:branch]}"
merge_requests = @api.merge_requests(repo_path,
:state => 'opened',
:source_branch => head,
:target_branch => 'master')
if merge_requests.empty?
mr_labels = ModuleSync::Util.parse_list(options[:pr_labels])
mr = @api.create_merge_request(repo_path, options[:pr_title],
:source_branch => options[:branch],
:target_branch => 'master',
:labels => mr_labels)
$stdout.puts "Submitted MR '#{options[:pr_title]}' to #{repo_path} - merges #{options[:branch]} into master"
return if mr_labels.empty?
$stdout.puts "Attached the following labels to MR #{mr.iid}: #{mr_labels.join(', ')}"
else
# Skip creating the MR if it exists already.
$stdout.puts "Skipped! #{merge_requests.length} MRs found for branch #{options[:branch]}"
end
end
end
end
end
1 change: 1 addition & 0 deletions modulesync.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'rubocop', '~> 0.50.0'

spec.add_runtime_dependency 'git', '~>1.3'
spec.add_runtime_dependency 'gitlab', '~>4.0'
spec.add_runtime_dependency 'octokit', '~>4.0'
spec.add_runtime_dependency 'puppet-blacksmith', '~>3.0'
spec.add_runtime_dependency 'thor'
Expand Down
49 changes: 49 additions & 0 deletions spec/unit/modulesync/pr/github_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require 'spec_helper'
require 'modulesync/pr/github'

describe ModuleSync::PR::GitHub do
context '::manage' do
before(:each) do
@git_repo = 'test/modulesync'
@namespace, @repo_name = @git_repo.split('/')
@options = {
:pr => true,
:pr_title => 'Test PR is submitted',
:branch => 'test',
:message => 'Hello world',
:pr_auto_merge => false,
}

@client = double()
allow(Octokit::Client).to receive(:new).and_return(@client)
@it = ModuleSync::PR::GitHub.new('test', 'https://api.github.com')
end

it 'submits PR when --pr is set' do
allow(@client).to receive(:pull_requests).with(@git_repo, :state => 'open', :base => 'master', :head => "#{@namespace}:#{@options[:branch]}").and_return([])
expect(@client).to receive(:create_pull_request).with(@git_repo, 'master', @options[:branch], @options[:pr_title], @options[:message]).and_return({"html_url" => "http://example.com/pulls/22"})
expect { @it.manage(@namespace, @repo_name, @options) }.to output(/Submitted PR/).to_stdout
end

it 'skips submitting PR if one has already been issued' do
pr = {
"title" => "Test title",
"html_url" => "https://example.com/pulls/44",
"number" => "44"
}

expect(@client).to receive(:pull_requests).with(@git_repo, :state => 'open', :base => 'master', :head => "#{@namespace}:#{@options[:branch]}").and_return([pr])
expect { @it.manage(@namespace, @repo_name, @options) }.to output(/Skipped! 1 PRs found for branch test/).to_stdout
end

it 'adds labels to PR when --pr-labels is set' do
@options[:pr_labels] = "HELLO,WORLD"

allow(@client).to receive(:create_pull_request).and_return({"html_url" => "http://example.com/pulls/22", "number" => "44"})
allow(@client).to receive(:pull_requests).with(@git_repo, :state => 'open', :base => 'master', :head => "#{@namespace}:#{@options[:branch]}").and_return([])

expect(@client).to receive(:add_labels_to_an_issue).with(@git_repo, "44", ["HELLO", "WORLD"])
expect { @it.manage(@namespace, @repo_name, @options) }.to output(/Attaching the following labels to PR 44: HELLO, WORLD/).to_stdout
end
end
end
Loading