Initial commit

This commit is contained in:
ViViDboarder 2013-04-18 09:09:05 -07:00
commit 4ba47ac90a
10 changed files with 608 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
atf.yaml
*.gem

17
README.md Normal file
View File

@ -0,0 +1,17 @@
AbUse the Force
===============
A tool expanding upon [Metaforce](https://github.com/ejholmes/metaforce) for deploying to
multiple orgs as well as simpler setup for use as a pseudo compiler
Features
========
* Many
Why not Metaforce?
==================
* Vim and Sublime plugins (coming soon...)
* Command line configuration management
* Options for deploying or retrieving a sigle file
* Encrypted Passwords coming soon

8
Rakefile Normal file
View File

@ -0,0 +1,8 @@
require 'rake/testtask'
Rake::TestTask.new do |t|
t.libs << 'test'
end
desc "Run tests"
task :default => :test

27
abusetheforce.gemspec Normal file
View File

@ -0,0 +1,27 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path('../lib', __FILE__)
require 'abusetheforce/version'
Gem::Specification.new do |s|
s.name = 'abusetheforce'
s.version = AbuseTheForce::VERSION
s.authors = ['Ian']
s.email = ['ViViDboarder@gmail.com']
s.homepage = 'https://github.com/ViViDboarder/abusetheforce'
s.summary = %q{A Ruby gem for configuring and interacting with Metaforce}
s.description = %q{A Ruby gem for configuring and interacting with Metaforce}
#s.rubyforge_project = 'abusetheforce'
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ['lib']
s.add_dependency 'thor', '~> 0.16.0'
s.add_dependency 'listen', '~> 0.6.0'
s.add_dependency 'metaforce', '~> 1.0.7'
s.add_dependency 'highline'
s.add_development_dependency 'rake'
end

14
bin/abusetheforce Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env ruby
require 'rubygems'
#require 'bundler/setup'
require 'abusetheforce'
begin
require 'abusetheforce/cli'
AbuseTheForce::AtfCLI.start
rescue Interrupt => e
puts "\nQuitting..."
exit 1
rescue SystemExit => e
exit e.status
end

14
bin/atf Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env ruby
require 'rubygems'
#require 'bundler/setup'
require 'abusetheforce'
begin
require 'abusetheforce/cli'
AbuseTheForce::AtfCLI.start
rescue Interrupt => e
puts "\nQuitting..."
exit 1
rescue SystemExit => e
exit e.status
end

221
lib/abusetheforce.rb Normal file
View File

@ -0,0 +1,221 @@
require "metaforce"
require "base64"
module AbuseTheForce
attr_accessor :client
# Write error to screen
def self.pute(s, fatal=false)
puts "Error: #{s}"
# If fatal error, exit
if fatal
exit 1
end
end
# Write warning to screen
def self.putw(s)
puts "Warning: #{s}"
end
# builds the client instance of Metaforce
def self.build_client
target = Atf_Config.active_target
@client = Metaforce.new :username => target.username,
:password => target.get_password,
:security_token => target.security_token
Metaforce.configuration.host = target.host
end
# Fetches a single file from the server
def self.retrieve_file(metadata_type, full_name)
if @client == nil
build_client
end
manifest = Metaforce::Manifest.new(metadata_type => [full_name])
@client.retrieve_unpackaged(manifest).
extract_to(Atf_Config.src).
on_complete { |job| puts "Finished retrieve #{job.id}!" }.
on_error { |job| puts "Something bad happened!" }.
perform
end
# Fetches a whole project from the server
def self.retrieve_project()
if @client == nil
build_client
end
if File.file?(Atf_Config.src + '/package.xml')
@client.retrieve_unpackaged(File.expand_path(Atf_Config.src + '/package.xml')).
extract_to(Atf_Config.src).
on_complete { |job| puts "Finished retrieve #{job.id}!" }.
on_error { |job| puts "Something bad happened!" }.
perform
else
puts "#{Atf_Config.src}: Not a valid project path"
end
end
def self.deploy_project(dpath=Atf_Config.src)
if @client == nil
build_client
end
if File.file?(dpath + '/package.xml')
@client.deploy(File.expand_path(dpath)).
on_complete { |job| puts "Finished deploy #{job.id}!" }.
on_error { |job| puts "Something bad happened!" }.
perform
else
puts "#{dpath}: Not a valid project path"
end
end
class Atf_Config
class << self
attr_accessor :targets, :active_target, :src
SETTINGS_FILE="./atf.yaml"
# Loads configurations from yaml
def load()
if File.file?(SETTINGS_FILE) == false
puts "No settings file found, creating one now"
# Settings file doesn't exist
# Create it
@targets = {}
@active_target = nil
@src = './src'
dump_settings
else
settings = YAML.load_file(SETTINGS_FILE)
@targets = settings[:targets]
@src = settings[:src]
end
# Set the default target
@targets.values.each do |target|
# Check if this one is active
if target.active == true
# Set it if there is no default target set yet
if @active_target == nil
@active_target = target
else
puts "Two active targets set. Using #{@active_target.print}"
end
end
end
end
# write settings to a yaml file
def dump_settings
File.open(SETTINGS_FILE, 'w') do |out|
YAML.dump( { :targets => @targets, :src => @src }, out)
end
end
# Adds a new target to the config
def add_target(target)
# If there are no targets yet, use this one as the default
if @active_target == nil #@targets.empty?
target.active = true
@active_target = target
end
# Push the new target
@targets[target.name] = target
#write out the config
dump_settings
end
# Selects one target as the active target for deployment
def set_active_target(name)
# Empty out current active target
@active_target = nil
# Go through each pair
@targets.each_pair do |target_name, target|
# If you find a matching item
if name == target_name
# Check if there is already a default
if @active_target == nil
# Set active
target.active = true
# Make it default
@active_target = target
else
# Error since there are two defaults
AbuseTheForce.putw "Two defaults set. Using #{@active_target.print}"
end
else # name != name
# make not active
target.active = false
end
end
if @active_target != nil
# Save to yaml
dump_settings
else
AbuseTheForce.pute "Target with alias #{name} was not found."
end
end
# Sets project path from default ./src
def set_project_path(ppath)
if File.file?(ppath + '/package.xml')
@src = ppath
dump_settings
else
pute("No package.xml found in #{ppath}", true)
end
end
end
end
# Class for holding a target
class Atf_Target
attr_accessor :name, :username, :password, :security_token, :host, :active
def initialize(name, username, password, security_token, host="login.salesforce.com")
@name = name
@username = username
set_password(password)
@security_token = security_token
@host = host
@active = false;
end
# TODO: Provide 2 way encryption with a lock to decode passwords
def set_password(password)
@password = Base64.encode64(password)
end
def get_password()
return Base64.decode64(@password)
end
def print
puts "#{@name}\t#{@username}\t#{@host}\t#{(@active && 'Active') || ''}"
end
end
end

291
lib/abusetheforce/cli.rb Normal file
View File

@ -0,0 +1,291 @@
require 'thor'
require 'highline/import'
module AbuseTheForce
# MODULE METHODS
# Toggle to a new target temporarily
def self.temp_switch_target(name=nil)
# If a name was provided, switch to that
if name != nil
# Store the original target's name
@last_target = Atf_Config.active_target.name
# Switch to the new target
Atf_Config.set_active_target(name)
else
# Switch back to the old target
Atf_Config.set_active_target(@last_target)
end
end
# Safe prompt for password
def self.get_password(prompt="Enter Password: ")
ask(prompt) {|q| q.echo = false}
end
class TargetCLI < Thor
desc "add <alias> <username> <security token> [--sandbox]", "Adds a new remote target"
long_desc <<-LONG_DESC
Adds a new target org with the alias <alias> to your atf.yaml file
with the provided <username>, <security token> and prompted password.
With -s or --sandbox option, sets host to "test.salesforce.com"
To perform any actions you must have a valid target added
LONG_DESC
option :sandbox, :type => :boolean, :aliases => :s, :default => false
def add(name, username, security_token)
puts "Add! and prompt pass"
password = AbuseTheForce.get_password()
host = (options[:sandbox] ? "test.salesforce.com" : "login.salesforce.com")
# Add the target to the config
Atf_Config.add_target(Atf_Target.new(name, username, password, security_token, host))
end
desc "update <alias> [--password | --sandbox=<true/false> | --securitytoken=<token>]", "Updates a remote target"
long_desc <<-LONG_DESC
Updates a part of target with <alias>
-p or --password option, prompts you for an updated password
-s or --sandbox <true/false> option, switches the host to sandbox or production
--token=<security token> option, sets the security token to the value provided
The changes will then be written to the atf.yaml file
LONG_DESC
option :password, :type => :boolean, :aliases => :p, :default => false, :desc => "Prompt for password"
option :token, :banner => "<security token>"
option :sandbox, :type => :boolean, :aliases => :s, :desc => "Add this if deploying to a sandbox"
def update(name)
target = Atf_Config.targets[name]
if target != nil
if options[:password]
target.set_password(AbuseTheForce.get_password)
end
if options[:token] != nil
target.security_token = options[:token]
end
if options[:sandbox] != nil
target.host = (options[:sandbox] ? "test.salesforce.com" : "login.salesforce.com")
end
else
AbuseTheForce.pute("Target not found", true)
end
# Save to yaml
Atf_Config.dump_settings
end
desc "remove <alias>", "Removes a remote target"
def remove(name)
Atf_Config.targets.delete(name)
# Save to yaml
Atf_Config.dump_settings
end
desc "activate <alias>", "Activates specified target"
long_desc <<-LONG_DESC
Activates the target specified by <alias>.
You must have an active target to perform any actions
LONG_DESC
def activate(name)
# Activate the target
Atf_Config.set_active_target(name)
end
desc "current", "Shows currently active target"
def current()
if Atf_Config.active_target != nil
Atf_Config.active_target.print
else
AbuseTheForce.putw "No active target set"
end
end
desc "list", "Lists all targets"
def list()
puts "Name\tUsername\tHost"
Atf_Config.targets.values.each do |target|
target.print
end
end
# By default, display the current target
default_task :current
end
class DeployCLI < Thor
class_option :target, :banner => "<target alias>", :aliases => :t
#class_option :delete, :aliases => :d
TEMP_DIR="./.atf_tmp"
desc "file <path to file>", "Deploy a single file"
long_desc <<-LONG_DESC
Deploys file at path <path to file> to the active target.
LONG_DESC
def file(fpath)
# If a new target was provided, switch to it
if options[:target] != nil
AbuseTheForce.temp_switch_target(options[:target])
end
# Clear temp dir
if Dir.exists?('./.atf_tmp')
FileUtils.rm_r('./.atf_tmp')
end
# Make it again
Dir.mkdir('./.atf_tmp')
mdir = File.dirname(fpath).split('/').last
FileUtils.copy(Atf_Config.src + '/package.xml', TEMP_DIR + '/package.xml')
FileUtils.mkdir_p('./.atf_tmp/' + mdir)
basename = File.basename(fpath)
FileUtils.cp(Atf_Config.src + '/' + mdir + '/' + basename, TEMP_DIR + '/' + mdir + '/')
FileUtils.cp(Atf_Config.src + '/' + mdir + '/' + basename + '-meta.xml', TEMP_DIR + '/' + mdir + '/')
AbuseTheForce.deploy_project('./.atf_tmp')
# if using a temp target, switch back
if options[:target] != nil
AbuseTheForce.temp_switch_target
end
end
desc "project", "Deploy a whole project"
def project()
# If a new target was provided, switch to it
if options[:target] != nil
AbuseTheForce.temp_switch_target(options[:target])
end
# Deploy the project
AbuseTheForce.deploy_project()
# if using a temp target, switch back
if options[:target] != nil
AbuseTheForce.temp_switch_target
end
end
end
class RetrieveCLI < Thor
class_option :target, :banner => "<target alias>", :aliases => :t
#class_option :delete, :aliases => :d
desc "file <file> [metadata type]", "Retrieve a single file"
long_desc <<-LONG_DESC
Retrieves one file from the active target
This has two uses:
To retrieve a file not on the local machine, provide the name of the
file and the metadata type.
Example: $atf retrieve file MyClassName ApexClass
To retrieve a new version of a file already local to you, you just
provide the path to the file.
Example: $atf retrieve file ./src/classes/MyClass
NOTE: Must be called from the root project directory
LONG_DESC
def file(full_name, metadata_type=nil)
# TODO: Work backwards if called not in child of project directory
# If a new target was provided, switch to it
if options[:target] != nil
AbuseTheForce.temp_switch_target(options[:target])
end
# No metadata passed in, this should be a file path
if metadata_type == nil
if File.file?(full_name)
# Get the file extension
extname = File.extname(full_name)
# Get the base name of the metadata
full_name = File.basename(full_name, extname)
puts full_name
puts extname
# Detect metadata type by file extension
case extname
when '.cls'
metadata_type = 'ApexClass'
when '.trigger'
metadata_type = 'ApexTrigger'
when '.object'
metadata_type = 'CustomObject'
when '.page'
metadata_type = 'ApexPage'
when '.component'
metadata_type = 'ApexComponent'
else
AbuseTheForce.pute('Unrecognized file type', true)
end
end
end
# retrieve the file using metaforce
AbuseTheForce.retrieve_file(metadata_type, full_name)
# if using a temp target, switch back
if options[:target] != nil
AbuseTheForce.temp_switch_target
end
end
desc "project", "Retrieve a whole project"
def project()
# If a new target was provided, switch to it
if options[:target] != nil
AbuseTheForce.temp_switch_target(options[:target])
end
# Retrieve the project
AbuseTheForce.retrieve_project
# if using a temp target, switch back
if options[:target] != nil
AbuseTheForce.temp_switch_target
end
end
end
# AbUse The Force
class AtfCLI < Thor
# Load the abuse-the-force config
Atf_Config.load
desc "target SUBCOMMAND ...ARGS", "Manage deploy targets"
subcommand "target", TargetCLI
desc "deploy SUBCOMMAND ...ARGS", "Deploy code to Salesforce.com"
subcommand "deploy", DeployCLI
desc "retrieve SUBCOMMAND ...ARGS", "Retrieve code from Salesforce.com"
subcommand "retrieve", RetrieveCLI
end
# Start the command line
#AtfCLI.start(ARGV)
end # end module AbuseTheForce

View File

@ -0,0 +1,3 @@
module AbuseTheForce
VERSION = '0.0.0'
end

11
test/test.rb Normal file
View File

@ -0,0 +1,11 @@
require 'test/unit'
require 'abusetheforce'
class AbuseTheForceTest < Test::Unit::TestCase
def test_first_test
# TODO: Unstub this
assert_equal "a", "a"
end
end