Skip to content

Instantly share code, notes, and snippets.

@chrisk
Created February 1, 2010 09:40
Show Gist options
  • Save chrisk/291561 to your computer and use it in GitHub Desktop.
Save chrisk/291561 to your computer and use it in GitHub Desktop.
Copy Pivotal Tracker members to all projects
#!/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