Created
February 1, 2010 09:40
-
-
Save chrisk/291561 to your computer and use it in GitHub Desktop.
Copy Pivotal Tracker members to all projects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# This script uses Pivotal Tracker's API to generate a list of unique members | |
# from all of your projects; it then iterates over each project, adding any of | |
# those members who are missing. That way, everyone has access to everything. | |
# | |
# It only acts on projects belonging to accounts called ACCOUNT_NAME below. | |
# | |
# By default, the script doesn't proceed with the modifications, so you can | |
# double-check the pending changes. Run it again with --no-dry-run to proceed. | |
ACCOUNT_NAME = "MyCompany" | |
$api_token = "" | |
# Members listed here will not be copied. This is useful for bots that only | |
# access one project, consultants, etc. | |
NAMES_TO_NOT_ADD_TO_OTHER_PROJECTS = ["NewRelic RPM", "GitHub Web Hooks"] | |
require 'rubygems' | |
require 'highline/import' | |
gem "activeresource", "~>2.3.4" | |
require 'active_resource' | |
NO_DRY_RUN = ARGV.include?("--no-dry-run") | |
$api_token = ask("Please enter your Pivotal Tracker API Token: ") { |q| q.echo = "*" } | |
abort if $api_token.blank? | |
# Change ActiveResource errors to include the response body | |
class ActiveResource::ConnectionError | |
def to_s_with_body | |
if response.respond_to?(:body) | |
to_s_without_body + "\n" + response.body | |
else | |
to_s_without_body | |
end | |
end | |
alias_method_chain :to_s, :body | |
end | |
# ActiveResource::Base.logger = Logger.new(STDOUT) | |
class Project < ActiveResource::Base | |
self.site = "https://www.pivotaltracker.com/services/v3/" | |
headers['X-TrackerToken'] = $api_token | |
end | |
class Membership < ActiveResource::Base | |
self.site = "https://www.pivotaltracker.com/services/v3/projects/:project_id/" | |
headers['X-TrackerToken'] = $api_token | |
end | |
def stringify_person(hash) | |
string = "#{hash['name']} (#{hash['initials']}) <#{hash['email']}>" | |
string += " \e[33m(skipping)\e[0m" if NAMES_TO_NOT_ADD_TO_OTHER_PROJECTS.include?(hash['name']) | |
string | |
end | |
def print_people(people) | |
puts people.map { |person| "* " + stringify_person(person) }.join("\n") | |
end | |
def puts_header(header) | |
puts "\e[7m #{header} \e[0m" | |
end | |
# Find all people from all projects that belong to the account called ACCOUNT_NAME | |
all_projects = Project.find(:all) | |
projects, skipped_projects = all_projects.partition { |p| p.account == ACCOUNT_NAME } | |
memberships = projects.map(&:memberships).map(&:membership).flatten | |
people = memberships.map(&:person).map(&:attributes).uniq.sort_by { |a| a["name"] } | |
# Detect ambiguities in people's email or initials and abort | |
previous_person = {"name" => ""} | |
duplicates = people.each_with_object([]) do |person, dupes| | |
if previous_person["name"] == person["name"] | |
dupes << previous_person << person | |
end | |
previous_person = person | |
end | |
if duplicates.any? | |
warn "\n\e[1m\e[31mError: Similar users exist with different details." | |
warn " Please resolve this manually before continuing.\e[0m\n" | |
print_people(duplicates) | |
puts | |
abort | |
end | |
# Print header with all members | |
puts | |
puts_header "All members of all #{ACCOUNT_NAME} projects:" | |
print_people(people) | |
puts | |
# Iterate over the projects and add members where needed | |
all_projects.each do |project| | |
puts | |
puts_header "Project: #{project.name}" | |
puts "\e[1m\e[31mWarning: SSL is not enabled for this project\e[0m" if project.use_https != "true" | |
if skipped_projects.include?(project) | |
puts "\e[33mSkipping this project, because it doesn't belong to the #{ACCOUNT_NAME} account (current account: #{project.account})\e[0m" | |
next | |
end | |
people_in_project = [] | |
if project.memberships.membership.try(:many?) | |
people_in_project = project.memberships.membership.map(&:person).map(&:name) | |
else | |
people_in_project = project.memberships.membership.person.name | |
end | |
people_to_add = people.select { |person| !people_in_project.include?(person["name"]) } | |
people_to_add.reject! { |person| NAMES_TO_NOT_ADD_TO_OTHER_PROJECTS.include?(person["name"]) } | |
if people_to_add.empty? | |
puts "* all members already added" | |
next | |
end | |
people_to_add.each do |person_to_add| | |
if NO_DRY_RUN | |
puts "* adding #{stringify_person(person_to_add)}" | |
Membership.create :role => "Owner", :person => person_to_add, :project_id => project.id | |
sleep 0.2 | |
else | |
puts "* would add #{stringify_person(person_to_add)}" | |
end | |
end | |
end | |
if !NO_DRY_RUN | |
warn "\n\e[1m\e[31mNote: that was a dry run, so that you can double-check everything above." | |
warn " Re-run with --no-dry-run to proceed.\e[0m\n\n" | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment