mirror of
https://github.com/ViViDboarder/abuse-the-force.git
synced 2024-11-23 12:36:36 +00:00
Initial commit
This commit is contained in:
commit
4ba47ac90a
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
atf.yaml
|
||||
*.gem
|
17
README.md
Normal file
17
README.md
Normal 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
8
Rakefile
Normal 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
27
abusetheforce.gemspec
Normal 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
14
bin/abusetheforce
Executable 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
14
bin/atf
Executable 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
221
lib/abusetheforce.rb
Normal 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
291
lib/abusetheforce/cli.rb
Normal 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
|
||||
|
||||
|
3
lib/abusetheforce/version.rb
Normal file
3
lib/abusetheforce/version.rb
Normal file
@ -0,0 +1,3 @@
|
||||
module AbuseTheForce
|
||||
VERSION = '0.0.0'
|
||||
end
|
11
test/test.rb
Normal file
11
test/test.rb
Normal 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
|
Loading…
Reference in New Issue
Block a user