metaforce-beta 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +22 -0
  7. data/README.md +193 -0
  8. data/Rakefile +10 -0
  9. data/bin/metaforce +14 -0
  10. data/examples/example.rb +52 -0
  11. data/lib/metaforce.rb +34 -0
  12. data/lib/metaforce/abstract_client.rb +86 -0
  13. data/lib/metaforce/cli.rb +130 -0
  14. data/lib/metaforce/client.rb +31 -0
  15. data/lib/metaforce/config.rb +100 -0
  16. data/lib/metaforce/job.rb +203 -0
  17. data/lib/metaforce/job/crud.rb +13 -0
  18. data/lib/metaforce/job/deploy.rb +87 -0
  19. data/lib/metaforce/job/retrieve.rb +102 -0
  20. data/lib/metaforce/login.rb +39 -0
  21. data/lib/metaforce/manifest.rb +106 -0
  22. data/lib/metaforce/metadata/client.rb +18 -0
  23. data/lib/metaforce/metadata/client/crud.rb +86 -0
  24. data/lib/metaforce/metadata/client/file.rb +113 -0
  25. data/lib/metaforce/reporters.rb +2 -0
  26. data/lib/metaforce/reporters/base_reporter.rb +56 -0
  27. data/lib/metaforce/reporters/deploy_reporter.rb +69 -0
  28. data/lib/metaforce/reporters/retrieve_reporter.rb +11 -0
  29. data/lib/metaforce/services/client.rb +84 -0
  30. data/lib/metaforce/version.rb +3 -0
  31. data/metaforce.gemspec +34 -0
  32. data/spec/fixtures/package.xml +17 -0
  33. data/spec/fixtures/payload.zip +0 -0
  34. data/spec/fixtures/requests/check_deploy_status/done.xml +33 -0
  35. data/spec/fixtures/requests/check_deploy_status/error.xml +26 -0
  36. data/spec/fixtures/requests/check_retrieve_status/success.xml +37 -0
  37. data/spec/fixtures/requests/check_status/done.xml +19 -0
  38. data/spec/fixtures/requests/check_status/not_done.xml +19 -0
  39. data/spec/fixtures/requests/create/in_progress.xml +12 -0
  40. data/spec/fixtures/requests/delete/in_progress.xml +12 -0
  41. data/spec/fixtures/requests/deploy/in_progress.xml +13 -0
  42. data/spec/fixtures/requests/describe_layout/success.xml +15 -0
  43. data/spec/fixtures/requests/describe_metadata/success.xml +230 -0
  44. data/spec/fixtures/requests/foo/invalid_session.xml +15 -0
  45. data/spec/fixtures/requests/list_metadata/no_result.xml +6 -0
  46. data/spec/fixtures/requests/list_metadata/objects.xml +33 -0
  47. data/spec/fixtures/requests/login/failure.xml +15 -0
  48. data/spec/fixtures/requests/login/success.xml +39 -0
  49. data/spec/fixtures/requests/retrieve/in_progress.xml +12 -0
  50. data/spec/fixtures/requests/send_email/success.xml +1 -0
  51. data/spec/fixtures/requests/update/in_progress.xml +12 -0
  52. data/spec/lib/cli_spec.rb +42 -0
  53. data/spec/lib/client_spec.rb +39 -0
  54. data/spec/lib/config_spec.rb +12 -0
  55. data/spec/lib/job/deploy_spec.rb +54 -0
  56. data/spec/lib/job/retrieve_spec.rb +28 -0
  57. data/spec/lib/job_spec.rb +111 -0
  58. data/spec/lib/login_spec.rb +18 -0
  59. data/spec/lib/manifest_spec.rb +35 -0
  60. data/spec/lib/metadata/client_spec.rb +135 -0
  61. data/spec/lib/metaforce_spec.rb +42 -0
  62. data/spec/lib/reporters/base_reporter_spec.rb +79 -0
  63. data/spec/lib/reporters/deploy_reporter_spec.rb +124 -0
  64. data/spec/lib/reporters/retrieve_reporter_spec.rb +14 -0
  65. data/spec/lib/services/client_spec.rb +37 -0
  66. data/spec/spec_helper.rb +37 -0
  67. data/spec/support/client.rb +39 -0
  68. data/wsdl/23.0/metadata.xml +3520 -0
  69. data/wsdl/23.0/partner.xml +3190 -0
  70. data/wsdl/26.0/metadata.xml +4750 -0
  71. data/wsdl/26.0/partner.xml +3340 -0
  72. data/wsdl/34.0/metadata.xml +7981 -0
  73. data/wsdl/34.0/partner.xml +5398 -0
  74. data/wsdl/35.0/metadata.xml +8183 -0
  75. data/wsdl/35.0/partner.xml +5755 -0
  76. data/wsdl/40.0/metadata.xml +29052 -0
  77. data/wsdl/40.0/partner.xml +7642 -0
  78. metadata +327 -0
@@ -0,0 +1,130 @@
1
+ require 'listen'
2
+ require 'thor'
3
+ require 'metaforce/reporters'
4
+
5
+ Metaforce.configure do |config|
6
+ config.log = false
7
+ config.threading = false
8
+ end
9
+
10
+ module Metaforce
11
+ class CLI < Thor
12
+ CONFIG_FILE = '.metaforce.yml'
13
+
14
+ include Thor::Actions
15
+
16
+ class << self
17
+ def credential_options
18
+ method_option :username, :aliases => '-u', :desc => 'Username.'
19
+ method_option :password, :aliases => '-p', :desc => 'Password.'
20
+ method_option :security_token, :aliases => '-t', :desc => 'Security Token.'
21
+ method_option :environment, :aliases => '-e', :default => 'default', :desc => 'Environment to use from config file (if present).'
22
+ method_option :host, :aliases => '-h', :desc => 'Salesforce host to connect to.'
23
+ end
24
+
25
+ def deploy_options
26
+ method_option :deploy_options, :aliases => '-o', :type => :hash, :default => { :run_all_tests => true }, :desc => 'Deploy Options'
27
+ end
28
+
29
+ def retrieve_options
30
+ method_option :retrieve_options, :aliases => '-o', :type => :hash, :default => {}, :desc => 'Retrieve Options'
31
+ end
32
+ end
33
+
34
+ desc 'deploy <path>', 'Deploy <path> to the target organization.'
35
+
36
+ credential_options
37
+ deploy_options
38
+
39
+ def deploy(path)
40
+ say "Deploying: #{path} ", :cyan
41
+ say "#{options[:deploy_options].inspect}"
42
+ client.deploy(path, options[:deploy_options].symbolize_keys!)
43
+ .on_complete { |job| report job.result, :deploy }
44
+ .on_error(&error)
45
+ .on_poll(&polling)
46
+ .perform
47
+ end
48
+
49
+ desc 'retrieve <manifest> <path>', 'Retrieve the components specified in <manifest> (package.xml) to <path>.'
50
+
51
+ credential_options
52
+ retrieve_options
53
+
54
+ def retrieve(manifest, path=nil)
55
+ unless path
56
+ path = manifest
57
+ manifest = File.join(path, 'package.xml')
58
+ end
59
+ say "Retrieving: #{manifest} ", :cyan
60
+ say "#{options[:retrieve_options].inspect}"
61
+ client.retrieve_unpackaged(manifest, options[:retrieve_options].symbolize_keys!)
62
+ .extract_to(path)
63
+ .on_complete { |job| report(job.result, :retrieve) }
64
+ .on_complete { |job| say "Extracted: #{path}", :green }
65
+ .on_error(&error)
66
+ .on_poll(&polling)
67
+ .perform
68
+ end
69
+
70
+ desc 'watch <path>', 'Deploy <path> to the target organization whenever files are changed.'
71
+
72
+ credential_options
73
+ deploy_options
74
+
75
+ def watch(path)
76
+ say "Watching: #{path}"
77
+ @watching = true
78
+ Listen.to(path) { deploy path }
79
+ end
80
+
81
+ private
82
+
83
+ def report(results, type)
84
+ reporter = "Metaforce::Reporters::#{type.to_s.camelize}Reporter".constantize.new(results)
85
+ reporter.report
86
+ exit 1 if reporter.issues?
87
+ end
88
+
89
+ def exit(status)
90
+ super(status) if not watching?
91
+ end
92
+
93
+ def watching?
94
+ !!@watching
95
+ end
96
+
97
+ def error
98
+ proc { |job| say "Error: #{job.result.inspect}" }
99
+ end
100
+
101
+ def polling
102
+ proc { |job| say 'Polling ...', :cyan }
103
+ end
104
+
105
+ def client
106
+ credentials = Thor::CoreExt::HashWithIndifferentAccess.new(environment_config)
107
+ credentials.merge!(options.slice(:username, :password, :security_token, :host))
108
+ credentials.tap do |credentials|
109
+ credentials[:username] ||= ask('username:')
110
+ credentials[:password] ||= ask('password:')
111
+ credentials[:security_token] ||= ask('security token:')
112
+ end
113
+ Metaforce.configuration.host = credentials[:host]
114
+ Metaforce.new credentials
115
+ end
116
+
117
+ def environment_config
118
+ (config && config[options[:environment]]) ? config[options[:environment]] : {}
119
+ end
120
+
121
+ def config
122
+ YAML.load(File.open(config_file)) if File.exists?(config_file)
123
+ end
124
+
125
+ def config_file
126
+ File.expand_path(CONFIG_FILE)
127
+ end
128
+
129
+ end
130
+ end
@@ -0,0 +1,31 @@
1
+ module Metaforce
2
+ class Client
3
+ def initialize(options = {})
4
+ @options = options
5
+ end
6
+
7
+ # Public: Used to interact with the Metadata API.
8
+ def metadata
9
+ @metadata ||= Metaforce::Metadata::Client.new(@options)
10
+ end
11
+
12
+ # Public: Used to interact with the Services API.
13
+ def services
14
+ @services ||= Metaforce::Services::Client.new(@options)
15
+ end
16
+
17
+ def inspect
18
+ "#<#{self.class} @options=#{@options.inspect}>"
19
+ end
20
+
21
+ def method_missing(method, *args, &block)
22
+ if metadata.respond_to? method, false
23
+ metadata.send(method, *args, &block)
24
+ elsif services.respond_to? method, false
25
+ services.send(method, *args, &block)
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,100 @@
1
+ module Metaforce
2
+ class << self
3
+ attr_writer :log
4
+
5
+ # Returns the current Configuration
6
+ #
7
+ # Metaforce.configuration.username = "username"
8
+ # Metaforce.configuration.password = "password"
9
+ def configuration
10
+ @configuration ||= Configuration.new
11
+ end
12
+
13
+ # Yields the Configuration
14
+ #
15
+ # Metaforce.configure do |config|
16
+ # config.username = "username"
17
+ # config.password = "password"
18
+ # end
19
+ def configure
20
+ yield configuration
21
+ end
22
+
23
+ def log?
24
+ @log ||= false
25
+ end
26
+
27
+ def log(message)
28
+ return unless Metaforce.log?
29
+ Metaforce.configuration.logger.send :debug, message
30
+ end
31
+ end
32
+
33
+ class Configuration
34
+ # The Salesforce API version to use. Defaults to 23.0
35
+ attr_accessor :api_version
36
+ # The username to use during login.
37
+ attr_accessor :username
38
+ # The password to use during login.
39
+ attr_accessor :password
40
+ # The security token to use during login.
41
+ attr_accessor :security_token
42
+ # Set this to true if you're authenticating with a Sandbox instance.
43
+ # Defaults to false.
44
+ attr_accessor :host
45
+ # A block that gets called when the session becomes invalid and the
46
+ # client needs to reauthenticate. Passes in the client and the client
47
+ # options. The block should set the options to a hash containing a valid
48
+ # session_id and service urls.
49
+ attr_accessor :authentication_handler
50
+ # Enables or disables threading when polling for job status. If disabled,
51
+ # calling .perform on a job will block until completion and all callbacks
52
+ # have run. (default: true).
53
+ attr_accessor :threading
54
+
55
+ def initialize
56
+ @threading = false
57
+ end
58
+
59
+ def api_version
60
+ @api_version ||= '26.0'
61
+ end
62
+
63
+ def host
64
+ @host ||= 'login.salesforce.com'
65
+ end
66
+
67
+ def authentication_handler
68
+ @authentication_handler ||= lambda { |client, options|
69
+ Metaforce.login(options)
70
+ }
71
+ end
72
+
73
+ def log=(log)
74
+ Savon.configure do |config|
75
+ config.log = log
76
+ end
77
+ HTTPI.log = log
78
+ end
79
+
80
+ def partner_wsdl
81
+ File.join(wsdl, 'partner.xml')
82
+ end
83
+
84
+ def metadata_wsdl
85
+ File.join(wsdl, 'metadata.xml')
86
+ end
87
+
88
+ def endpoint
89
+ "https://#{host}/services/Soap/u/#{api_version}"
90
+ end
91
+
92
+ def wsdl
93
+ File.expand_path("../../../wsdl/#{api_version}", __FILE__)
94
+ end
95
+
96
+ def logger
97
+ @logger ||= ::Logger.new STDOUT
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,203 @@
1
+ require 'zip'
2
+ require 'base64'
3
+
4
+ module Metaforce
5
+ class Job
6
+ DELAY_START = 1
7
+ DELAY_MULTIPLIER = 2
8
+
9
+ autoload :Deploy, 'metaforce/job/deploy'
10
+ autoload :Retrieve, 'metaforce/job/retrieve'
11
+ autoload :CRUD, 'metaforce/job/crud'
12
+
13
+ # Public: The id of the AsyncResult returned from Salesforce for
14
+ # this job.
15
+ attr_reader :id
16
+
17
+ # Public: Instantiate a new job. Doesn't actually do anything until
18
+ # .perform is called.
19
+ #
20
+ # Examples
21
+ #
22
+ # job = Metaforce::Job.new(client)
23
+ # # => #<Metaforce::Job @id=nil>
24
+ #
25
+ # Returns self.
26
+ def initialize(client)
27
+ @_callbacks = Hash.new { |h,k| h[k] = [] }
28
+ @client = client
29
+ end
30
+
31
+ # Public: Perform the job.
32
+ #
33
+ # Examples
34
+ #
35
+ # job = Metaforce::Job.new
36
+ # job.perform
37
+ # # => #<Metaforce::Job @id=nil>
38
+ #
39
+ # Returns self.
40
+ def perform
41
+ start_heart_beat
42
+ self
43
+ end
44
+
45
+ # Public: Utility method to determine if .perform has been called yet.
46
+ #
47
+ # Returns true if @id is set, false otherwise.
48
+ def started?
49
+ !!@id
50
+ end
51
+
52
+ # Public: Register a block to be called when an event occurs.
53
+ #
54
+ # Yields the job.
55
+ #
56
+ # &block - Proc or Lambda to be run when the event is triggered.
57
+ #
58
+ # Examples
59
+ #
60
+ # job.on_complete do |job|
61
+ # puts "Job ##{job.id} completed!"
62
+ # end
63
+ #
64
+ # job.on_error do |job|
65
+ # puts "Job failed!"
66
+ # end
67
+ #
68
+ # job.on_poll do |job|
69
+ # puts "Polled status for #{job.id}"
70
+ # end
71
+ #
72
+ # Returns self.
73
+ #
74
+ # Signature
75
+ #
76
+ # on_complete(&block)
77
+ # on_error(&block)
78
+ # on_poll(&block)
79
+ %w[complete error poll].each do |type|
80
+ define_method :"on_#{type}" do |&block|
81
+ @_callbacks[:"on_#{type}"] << block
82
+ self
83
+ end
84
+ end
85
+
86
+ # Public: Queries the job status from the API.
87
+ #
88
+ # Examples
89
+ #
90
+ # job.status
91
+ # # => { :id => '1234', :done => false, ... }
92
+ #
93
+ # Returns the AsyncResult (http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_asyncresult.htm).
94
+ def status
95
+ @status ||= client.status(id)
96
+ end
97
+
98
+ # Public: Returns true if the job has completed.
99
+ #
100
+ # Examples
101
+ #
102
+ # job.done
103
+ # # => true
104
+ #
105
+ # Returns true if the job has completed, false otherwise.
106
+ def done?
107
+ status.done
108
+ end
109
+
110
+ # Public: Returns the state if the job has finished processing.
111
+ #
112
+ # Examples
113
+ #
114
+ # job.state
115
+ # # => 'Completed'
116
+ #
117
+ # Returns the state of the job.
118
+ def state
119
+ status.state
120
+ end
121
+
122
+ # Public: Check if the job is in a given state.
123
+ #
124
+ # Examples
125
+ #
126
+ # job.queued?
127
+ # # => false
128
+ #
129
+ # Returns true or false.
130
+ #
131
+ # Signature
132
+ #
133
+ # queued?
134
+ # in_progress?
135
+ # completed?
136
+ # error?
137
+ %w[Queued InProgress Completed Error].each do |state|
138
+ define_method :"#{state.underscore}?" do; self.state == state end
139
+ end
140
+
141
+ def inspect
142
+ "#<#{self.class} @id=#{@id.inspect}>"
143
+ end
144
+
145
+ def self.disable_threading!
146
+ ActiveSupport::Deprecation.warn <<-WARNING.strip_heredoc
147
+ Metaforce::Job.disable_threading! is deprecated. Use Metaforce.configuration.threading = false instead.
148
+ WARNING
149
+ Metaforce.configuration.threading = false
150
+ end
151
+
152
+ private
153
+ attr_reader :client
154
+
155
+ # Internal: Starts a heart beat in a thread, which polls the job status
156
+ # until it has completed or timed out.
157
+ def start_heart_beat
158
+ if threading?
159
+ Thread.abort_on_exception = true
160
+ @heart_beat ||= Thread.new &run_loop
161
+ else
162
+ run_loop.call
163
+ end
164
+ end
165
+
166
+ # Internal: Starts the run loop, and blocks until the job has completed or
167
+ # failed.
168
+ def run_loop
169
+ proc {
170
+ delay = DELAY_START
171
+ loop do
172
+ @status = nil
173
+ sleep (delay = delay * DELAY_MULTIPLIER)
174
+ trigger :on_poll
175
+ if completed? || error?
176
+ trigger callback_type
177
+ Thread.stop if threading?
178
+ break
179
+ end
180
+ end
181
+ }
182
+ end
183
+
184
+ def trigger(type)
185
+ @_callbacks[type].each do |block|
186
+ block.call(self)
187
+ end
188
+ end
189
+
190
+ def callback_type
191
+ if completed?
192
+ :on_complete
193
+ elsif error?
194
+ :on_error
195
+ end
196
+ end
197
+
198
+ def threading?
199
+ Metaforce.configuration.threading
200
+ end
201
+
202
+ end
203
+ end