2013-04-18 16:09:05 +00:00
|
|
|
require 'thor'
|
|
|
|
require 'highline/import'
|
2013-04-19 00:56:22 +00:00
|
|
|
require 'abusetheforce/version'
|
2013-04-18 16:09:05 +00:00
|
|
|
|
|
|
|
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
|
2013-04-30 17:30:16 +00:00
|
|
|
Atf_Config.set_active_target name
|
2013-04-18 16:09:05 +00:00
|
|
|
else
|
|
|
|
# Switch back to the old target
|
2013-04-30 17:30:16 +00:00
|
|
|
Atf_Config.set_active_target @last_target
|
2013-04-18 16:09:05 +00:00
|
|
|
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)
|
2013-04-30 17:30:16 +00:00
|
|
|
password = AbuseTheForce.get_password
|
2013-04-18 16:09:05 +00:00
|
|
|
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
|
|
|
|
|
2013-05-07 16:40:29 +00:00
|
|
|
desc "update <alias> [--password | --sandbox=<true/false> | --token=<token>]", "Updates a remote target"
|
2013-04-18 16:09:05 +00:00
|
|
|
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
|
|
|
|
|
2013-04-19 00:56:22 +00:00
|
|
|
The changes will then be written to the .abusetheforce.yaml file
|
2013-04-18 16:09:05 +00:00
|
|
|
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]
|
2013-04-30 17:30:16 +00:00
|
|
|
target.set_password AbuseTheForce.get_password
|
2013-04-18 16:09:05 +00:00
|
|
|
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)
|
2013-04-30 17:30:16 +00:00
|
|
|
Atf_Config.targets.delete name
|
2013-04-18 16:09:05 +00:00
|
|
|
# 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
|
2013-04-30 17:30:16 +00:00
|
|
|
Atf_Config.set_active_target name
|
2013-04-18 16:09:05 +00:00
|
|
|
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()
|
2013-05-01 20:23:04 +00:00
|
|
|
puts "Name\t\tUsername"
|
2013-04-18 16:09:05 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2013-04-30 17:30:16 +00:00
|
|
|
TEMP_DIR=".atf_tmp"
|
|
|
|
RESOURCE_DIR="resources"
|
2013-04-18 16:09:05 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2013-04-30 17:30:16 +00:00
|
|
|
# Make the filepath absolute
|
|
|
|
fpath = File.absolute_path fpath
|
|
|
|
abs_root = File.absolute_path Atf_Config.root_dir
|
|
|
|
|
|
|
|
# Check that file path is in root dir
|
|
|
|
unless fpath.starts_with? abs_root
|
|
|
|
pute("File does not exist within root project: #{Atf_Config.root_dir}", true)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Check if in resource directory
|
|
|
|
if fpath.starts_with? File.join(abs_root, RESOURCE_DIR)
|
|
|
|
# This is a resource file
|
|
|
|
# Zip the resource up and then deploy new fpath
|
|
|
|
|
|
|
|
resource_path = File.dirname fpath
|
|
|
|
|
|
|
|
# Until we find the parent directory of the current location is the root resource dir
|
|
|
|
until File.dirname(resource_path) == File.join(abs_root, RESOURCE_DIR)
|
|
|
|
# Go up to the next parent
|
|
|
|
resource_path = File.dirname resource_path
|
|
|
|
end
|
|
|
|
|
|
|
|
# TODO: Extract this logic for full deploy as well
|
|
|
|
resource_path = resource_path
|
|
|
|
resource_name = File.basename resource_path
|
|
|
|
|
|
|
|
zip_path = File.join(Atf_Config.get_project_path, 'staticresources', resource_name + '.resource')
|
|
|
|
|
2013-05-01 20:23:04 +00:00
|
|
|
# TODO: Find a better place to stare XML templates
|
2013-04-30 17:30:16 +00:00
|
|
|
static_resource_xml = <<-eos
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<StaticResource xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
|
|
<cacheControl>Public</cacheControl>
|
|
|
|
<contentType>application/zip</contentType>
|
|
|
|
<description>Static resource uploaded with Abuse the Force</description>
|
|
|
|
</StaticResource>
|
|
|
|
eos
|
|
|
|
|
|
|
|
# Compress the resource
|
2013-04-30 20:36:15 +00:00
|
|
|
`cd $(dirname "#{resource_path}") && zip -r #{zip_path} #{resource_name}`
|
2013-04-30 17:30:16 +00:00
|
|
|
|
|
|
|
# Write the meta.xml
|
|
|
|
File.open(zip_path + '-meta.xml', 'w') {|f| f.write(static_resource_xml) }
|
|
|
|
|
|
|
|
# Set the fpath to the new zip file
|
|
|
|
fpath = zip_path
|
|
|
|
end
|
|
|
|
|
|
|
|
# Get path to temp project directory
|
|
|
|
temp_path = File.join(Atf_Config.root_dir, TEMP_DIR)
|
|
|
|
|
2013-04-18 16:09:05 +00:00
|
|
|
# If a new target was provided, switch to it
|
|
|
|
if options[:target] != nil
|
2013-04-30 17:30:16 +00:00
|
|
|
AbuseTheForce.temp_switch_target options[:target]
|
2013-04-18 16:09:05 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Clear temp dir
|
2013-04-30 17:30:16 +00:00
|
|
|
if Dir.exists? temp_path
|
|
|
|
FileUtils.rm_r temp_path
|
2013-04-18 16:09:05 +00:00
|
|
|
end
|
|
|
|
|
2013-04-30 17:30:16 +00:00
|
|
|
# Get the metadata directory right before filename
|
|
|
|
mdir = File.basename(File.dirname(fpath))
|
|
|
|
|
|
|
|
# Create the temp directories
|
|
|
|
FileUtils.mkdir_p File.join(temp_path, mdir)
|
|
|
|
|
|
|
|
# Copy the package file
|
|
|
|
FileUtils.copy(
|
|
|
|
File.join(Atf_Config.get_project_path, 'package.xml'),
|
|
|
|
File.join(temp_path, 'package.xml')
|
|
|
|
)
|
|
|
|
# File basename
|
|
|
|
basename = File.basename fpath
|
2013-04-18 16:09:05 +00:00
|
|
|
|
2013-04-30 17:30:16 +00:00
|
|
|
# Copy the file
|
|
|
|
FileUtils.copy(
|
|
|
|
File.join(Atf_Config.get_project_path, mdir, basename),
|
|
|
|
File.join(temp_path, mdir, '/')
|
|
|
|
)
|
2013-04-18 16:09:05 +00:00
|
|
|
|
2013-04-30 17:30:16 +00:00
|
|
|
# Copy the metadata
|
|
|
|
FileUtils.copy(
|
|
|
|
File.join(Atf_Config.get_project_path, mdir, basename + '-meta.xml'),
|
|
|
|
File.join(temp_path, mdir, '/')
|
|
|
|
)
|
2013-04-18 16:09:05 +00:00
|
|
|
|
2013-04-30 17:30:16 +00:00
|
|
|
AbuseTheForce.deploy_project temp_path
|
2013-04-18 16:09:05 +00:00
|
|
|
|
|
|
|
# if using a temp target, switch back
|
|
|
|
if options[:target] != nil
|
|
|
|
AbuseTheForce.temp_switch_target
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
desc "project", "Deploy a whole project"
|
2013-04-30 17:30:16 +00:00
|
|
|
def project(target=nil)
|
2013-04-18 16:09:05 +00:00
|
|
|
|
|
|
|
# If a new target was provided, switch to it
|
|
|
|
if options[:target] != nil
|
2013-05-01 20:21:33 +00:00
|
|
|
target = options[:target]
|
2013-04-18 16:09:05 +00:00
|
|
|
end
|
2013-04-30 17:30:16 +00:00
|
|
|
if target != nil
|
2013-05-01 20:21:33 +00:00
|
|
|
AbuseTheForce.temp_switch_target target
|
2013-04-30 17:30:16 +00:00
|
|
|
end
|
|
|
|
|
2013-04-18 16:09:05 +00:00
|
|
|
# Deploy the project
|
|
|
|
AbuseTheForce.deploy_project()
|
|
|
|
|
|
|
|
# if using a temp target, switch back
|
2013-05-01 20:21:33 +00:00
|
|
|
if target != nil
|
2013-04-18 16:09:05 +00:00
|
|
|
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
|
2013-05-01 20:21:33 +00:00
|
|
|
AbuseTheForce.temp_switch_target options[:target]
|
2013-04-18 16:09:05 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# No metadata passed in, this should be a file path
|
|
|
|
if metadata_type == nil
|
2013-04-30 17:30:16 +00:00
|
|
|
if File.file? full_name
|
2013-04-18 16:09:05 +00:00
|
|
|
# Get the file extension
|
2013-04-30 17:30:16 +00:00
|
|
|
extname = File.extname full_name
|
2013-04-18 16:09:05 +00:00
|
|
|
# Get the base name of the metadata
|
|
|
|
full_name = File.basename(full_name, 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"
|
2013-05-01 20:21:33 +00:00
|
|
|
def project(target=nil)
|
2013-04-18 16:09:05 +00:00
|
|
|
|
|
|
|
# If a new target was provided, switch to it
|
|
|
|
if options[:target] != nil
|
2013-05-01 20:21:33 +00:00
|
|
|
target = options[:target]
|
|
|
|
end
|
|
|
|
if target != nil
|
|
|
|
AbuseTheForce.temp_switch_target target
|
2013-04-18 16:09:05 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Retrieve the project
|
|
|
|
AbuseTheForce.retrieve_project
|
|
|
|
|
|
|
|
# if using a temp target, switch back
|
2013-05-01 20:21:33 +00:00
|
|
|
if target != nil
|
2013-04-18 16:09:05 +00:00
|
|
|
AbuseTheForce.temp_switch_target
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# AbUse The Force
|
|
|
|
class AtfCLI < Thor
|
|
|
|
|
|
|
|
# Load the abuse-the-force config
|
|
|
|
Atf_Config.load
|
|
|
|
|
2013-04-30 17:30:16 +00:00
|
|
|
# ATF Subcommands
|
2013-04-18 16:09:05 +00:00
|
|
|
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
|
2013-04-19 00:56:22 +00:00
|
|
|
|
|
|
|
desc "version", "Version information"
|
|
|
|
def version
|
|
|
|
puts "version #{AbuseTheForce::VERSION}"
|
|
|
|
end
|
|
|
|
|
2013-04-18 16:09:05 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
end # end module AbuseTheForce
|
|
|
|
|
|
|
|
|