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,13 @@
1
+ module Metaforce
2
+ class Job::CRUD < Job
3
+ def initialize(client, method, args)
4
+ super(client)
5
+ @method, @args = method, args
6
+ end
7
+
8
+ def perform
9
+ @id = @client.send(@method, *@args).id
10
+ super
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,87 @@
1
+ module Metaforce
2
+ class Job::Deploy < Job
3
+
4
+ # Public: Instantiate a new deploy job.
5
+ #
6
+ # Examples
7
+ #
8
+ # job = Metaforce::Job::Deploy.new(client, './path/to/deploy')
9
+ # # => #<Metaforce::Job::Deploy @id=nil>
10
+ #
11
+ # Returns self.
12
+ def initialize(client, path, options={})
13
+ super(client)
14
+ @path, @options = path, options
15
+ end
16
+
17
+ # Public: Perform the job.
18
+ #
19
+ # Examples
20
+ #
21
+ # job = Metaforce::Job::Deploy.new(client, './path/to/deploy')
22
+ # job.perform
23
+ # # => #<Metaforce::Job::Deploy @id='1234'>
24
+ #
25
+ # Returns self.
26
+ def perform
27
+ @id = client._deploy(payload, @options).id
28
+ super
29
+ end
30
+
31
+ # Public: Get the detailed status of the deploy.
32
+ #
33
+ # Examples
34
+ #
35
+ # job.result
36
+ # # => { :id => '1234', :success => true, ... }
37
+ #
38
+ # Returns the DeployResult (http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_deployresult.htm).
39
+ def result
40
+ @result ||= client.status(id, :deploy)
41
+ end
42
+
43
+ # Public: Returns true if the deploy was successful.
44
+ #
45
+ # Examples
46
+ #
47
+ # job.success?
48
+ # # => true
49
+ #
50
+ # Returns true or false based on the DeployResult.
51
+ def success?
52
+ result.success
53
+ end
54
+
55
+ private
56
+
57
+ # Internal: Base64 encodes the contents of the zip file.
58
+ #
59
+ # Examples
60
+ #
61
+ # job.payload
62
+ # # => '<lots of base64 encoded content>'
63
+ #
64
+ # Returns the content of the zip file encoded to base64.
65
+ def payload
66
+ Base64.encode64(File.open(file, 'rb').read)
67
+ end
68
+
69
+ # Internal: Returns the path to the zip file.
70
+ def file
71
+ File.file?(@path) ? @path : zip_file
72
+ end
73
+
74
+ # Internal: Creates a zip file with the contents of the directory.
75
+ def zip_file
76
+ path = Dir.mktmpdir
77
+ File.join(path, 'deploy.zip').tap do |path|
78
+ Zip::File.open(path, Zip::File::CREATE) do |zip|
79
+ Dir["#{@path}/**/**"].each do |file|
80
+ zip.add(file.sub("#{File.dirname(@path)}/", ''), file)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,102 @@
1
+ module Metaforce
2
+ class Job::Retrieve < Job
3
+
4
+ # Public: Instantiate a new retrieve job.
5
+ #
6
+ # Examples
7
+ #
8
+ # job = Metaforce::Job::Retrieve.new(client)
9
+ # # => #<Metaforce::Job::Retrieve @id=nil>
10
+ #
11
+ # Returns self.
12
+ def initialize(client, options={})
13
+ super(client)
14
+ @options = options
15
+ end
16
+
17
+ # Public: Perform the job.
18
+ #
19
+ # Examples
20
+ #
21
+ # job = Metaforce::Job::Retrieve.new(client)
22
+ # job.perform
23
+ # # => #<Metaforce::Job::Retrieve @id='1234'>
24
+ #
25
+ # Returns self.
26
+ def perform
27
+ @id = client._retrieve(@options).id
28
+ super
29
+ end
30
+
31
+ # Public: Get the detailed status of the retrieve.
32
+ #
33
+ # Examples
34
+ #
35
+ # job.result
36
+ # # => { :id => '1234', :zip_file => '<base64 encoded content>', ... }
37
+ #
38
+ # Returns the RetrieveResult (http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_retrieveresult.htm).
39
+ def result
40
+ @result ||= client.status(id, :retrieve)
41
+ end
42
+
43
+ # Public: Decodes the content of the returned zip file.
44
+ #
45
+ # Examples
46
+ #
47
+ # job.zip_file
48
+ # # => '<binary content>'
49
+ #
50
+ # Returns the decoded content.
51
+ def zip_file
52
+ Base64.decode64(result.zip_file)
53
+ end
54
+
55
+ # Public: Unzips the returned zip file to the location.
56
+ #
57
+ # destination - Path to extract the contents to.
58
+ #
59
+ # Examples
60
+ #
61
+ # job.extract_to('./path')
62
+ # # => #<Metaforce::Job::Retrieve @id='1234'>
63
+ #
64
+ # Returns self.
65
+ def extract_to(destination)
66
+ return on_complete { |job| job.extract_to(destination) } unless started?
67
+ with_tmp_zip_file do |file|
68
+ unzip(file, destination)
69
+ end
70
+ self
71
+ end
72
+
73
+ private
74
+
75
+ # Internal: Unzips source to destination.
76
+ def unzip(source, destination)
77
+ Zip::File.open(source) do |zip|
78
+ zip.each do |f|
79
+ path = File.join(destination, f.name)
80
+ FileUtils.mkdir_p(File.dirname(path))
81
+ zip.extract(f, path) { true }
82
+ end
83
+ end
84
+ end
85
+
86
+ # Internal: Writes the zip file content to a temporary location so it can
87
+ # be extracted.
88
+ def with_tmp_zip_file
89
+ file = Tempfile.new('retrieve')
90
+ begin
91
+ file.binmode
92
+ file.write(zip_file)
93
+ file.rewind
94
+ yield file
95
+ ensure
96
+ file.close
97
+ file.unlink
98
+ end
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,39 @@
1
+ module Metaforce
2
+ class Login
3
+ def initialize(username, password, security_token=nil)
4
+ @username, @password, @security_token = username, password, security_token
5
+ end
6
+
7
+ # Public: Perform the login request.
8
+ #
9
+ # Returns a hash with the session id and server urls.
10
+ def login
11
+ response = client.request(:login) do
12
+ soap.body = {
13
+ :username => username,
14
+ :password => password
15
+ }
16
+ end
17
+ response.body[:login_response][:result]
18
+ end
19
+
20
+ private
21
+
22
+ # Internal: Savon client.
23
+ def client
24
+ @client ||= Savon.client(Metaforce.configuration.partner_wsdl) do |wsdl|
25
+ wsdl.endpoint = Metaforce.configuration.endpoint
26
+ end.tap { |client| client.http.auth.ssl.verify_mode = :none }
27
+ end
28
+
29
+ # Internal: Usernamed passed in from options.
30
+ def username
31
+ @username
32
+ end
33
+
34
+ # Internal: Password + Security Token combined.
35
+ def password
36
+ [@password, @security_token].join('')
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,106 @@
1
+ require 'nokogiri'
2
+ require 'active_support/core_ext'
3
+
4
+ module Metaforce
5
+ class Manifest < Hash
6
+
7
+ # Public: Initializes a new instance of a manifest (package.xml) file.
8
+ #
9
+ # It can either take a hash:
10
+ # {
11
+ # :apex_class => [
12
+ # "TestController",
13
+ # "TestClass"
14
+ # ],
15
+ # :apex_component => [
16
+ # "SiteLogin"
17
+ # ]
18
+ # }
19
+ #
20
+ # Or an xml string containing the contents of a packge.xml file:
21
+ # <?xml version="1.0"?>
22
+ # <Package xmlns="http://soap.sforce.com/2006/04/metadata">
23
+ # <types>
24
+ # <members>TestClass</members>
25
+ # <members>AnotherClass</members>
26
+ # <name>ApexClass</name>
27
+ # </types>
28
+ # <types>
29
+ # <members>Component</members>
30
+ # <name>ApexComponent</name>
31
+ # </types>
32
+ # <types>
33
+ # <members>Assets</members>
34
+ # <name>StaticResource</name>
35
+ # </types>
36
+ # <version>23.0</version>
37
+ # </Package>
38
+ #
39
+ def initialize(components={})
40
+ self.replace Hash.new { |h,k| h[k] = [] }
41
+ if components.is_a?(Hash)
42
+ self.merge!(components)
43
+ elsif components.is_a?(String)
44
+ self.parse(components)
45
+ end
46
+ end
47
+
48
+ # Public: Returns a string containing a package.xml file
49
+ #
50
+ # <?xml version="1.0"?>
51
+ # <Package xmlns="http://soap.sforce.com/2006/04/metadata">
52
+ # <types>
53
+ # <members>TestClass</members>
54
+ # <members>AnotherClass</members>
55
+ # <name>ApexClass</name>
56
+ # </types>
57
+ # <types>
58
+ # <members>Component</members>
59
+ # <name>ApexComponent</name>
60
+ # </types>
61
+ # <types>
62
+ # <members>Assets</members>
63
+ # <name>StaticResource</name>
64
+ # </types>
65
+ # <version>23.0</version>
66
+ # </Package>
67
+ def to_xml
68
+ xml_builder = Nokogiri::XML::Builder.new do |xml|
69
+ xml.Package('xmlns' => 'http://soap.sforce.com/2006/04/metadata') {
70
+ self.each do |key, members|
71
+ xml.types {
72
+ members.each do |member|
73
+ xml.members member
74
+ end
75
+ xml.name key.to_s.camelize
76
+ }
77
+ end
78
+ xml.version Metaforce.configuration.api_version
79
+ }
80
+ end
81
+ xml_builder.to_xml
82
+ end
83
+
84
+ # Public: Converts the manifest into a format that can be used by the
85
+ # metadata api.
86
+ def to_package
87
+ self.map do |type, members|
88
+ { :members => members, :name => type.to_s.camelize }
89
+ end
90
+ end
91
+
92
+ # Public: Parses a package.xml file
93
+ def parse(file)
94
+ document = Nokogiri::XML(file).remove_namespaces!
95
+ document.xpath('//types').each do |type|
96
+ name = type.xpath('name').first.content
97
+ key = name.underscore.to_sym
98
+ type.xpath('members').each do |member|
99
+ self[key] << member.content
100
+ end
101
+ end
102
+ self
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,18 @@
1
+ module Metaforce
2
+ module Metadata
3
+ class Client < Metaforce::AbstractClient
4
+ require 'metaforce/metadata/client/file'
5
+ require 'metaforce/metadata/client/crud'
6
+
7
+ include Metaforce::Metadata::Client::File
8
+ include Metaforce::Metadata::Client::CRUD
9
+
10
+ endpoint :metadata_server_url
11
+ wsdl Metaforce.configuration.metadata_wsdl
12
+
13
+ def inspect
14
+ "#<#{self.class} @options=#{@options.inspect}>"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,86 @@
1
+ module Metaforce
2
+ module Metadata
3
+ class Client
4
+ module CRUD
5
+
6
+ # Public: Create metadata
7
+ #
8
+ # Examples
9
+ #
10
+ # client._create(:apex_page, :full_name => 'TestPage', label: 'Test page', :content => '<apex:page>foobar</apex:page>')
11
+ def _create(type, metadata={})
12
+ type = type.to_s.camelize
13
+ request :create do |soap|
14
+ soap.body = {
15
+ :metadata => prepare(metadata)
16
+ }.merge(attributes!(type))
17
+ end
18
+ end
19
+
20
+ # Public: Delete metadata
21
+ #
22
+ # Examples
23
+ #
24
+ # client._delete(:apex_component, 'Component')
25
+ def _delete(type, *args)
26
+ type = type.to_s.camelize
27
+ metadata = args.map { |full_name| {:full_name => full_name} }
28
+ request :delete do |soap|
29
+ soap.body = {
30
+ :metadata => metadata
31
+ }.merge(attributes!(type))
32
+ end
33
+ end
34
+
35
+ # Public: Update metadata
36
+ #
37
+ # Examples
38
+ #
39
+ # client._update(:apex_page, 'OldPage', :full_name => 'TestPage', :label => 'Test page', :content => '<apex:page>hello world</apex:page>')
40
+ def _update(type, current_name, metadata={})
41
+ type = type.to_s.camelize
42
+ request :update do |soap|
43
+ soap.body = {
44
+ :metadata => {
45
+ :current_name => current_name,
46
+ :metadata => prepare(metadata),
47
+ :attributes! => { :metadata => { 'xsi:type' => "ins0:#{type}" } }
48
+ }
49
+ }
50
+ end
51
+ end
52
+
53
+ def create(*args)
54
+ Job::CRUD.new(self, :_create, args)
55
+ end
56
+
57
+ def update(*args)
58
+ Job::CRUD.new(self, :_update, args)
59
+ end
60
+
61
+ def delete(*args)
62
+ Job::CRUD.new(self, :_delete, args)
63
+ end
64
+
65
+ private
66
+
67
+ def attributes!(type)
68
+ {:attributes! => { 'ins0:metadata' => { 'xsi:type' => "ins0:#{type}" } }}
69
+ end
70
+
71
+ # Internal: Prepare metadata by base64 encoding any content keys.
72
+ def prepare(metadata)
73
+ metadata = Array[metadata].compact.flatten
74
+ metadata.each { |m| encode_content(m) }
75
+ metadata
76
+ end
77
+
78
+ # Internal: Base64 encodes any :content keys.
79
+ def encode_content(metadata)
80
+ metadata[:content] = Base64.encode64(metadata[:content]) if metadata.has_key?(:content)
81
+ end
82
+
83
+ end
84
+ end
85
+ end
86
+ end