worochi 0.0.7 → 0.0.10

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/lib/worochi/agent/{sample.rb → #example.rb} +2 -14
  3. data/lib/worochi/agent/dropbox.rb +3 -13
  4. data/lib/worochi/agent/github.rb +8 -23
  5. data/lib/worochi/agent.rb +46 -18
  6. data/lib/worochi/config/#example.yml +35 -0
  7. data/lib/worochi/config/dropbox.yml +14 -0
  8. data/lib/worochi/config/github.yml +17 -0
  9. data/lib/worochi/config.rb +9 -84
  10. data/lib/worochi/configurator.rb +120 -0
  11. data/lib/worochi/helper/github_helper.rb +111 -0
  12. data/lib/worochi/helper.rb +28 -7
  13. data/lib/worochi/item.rb +5 -3
  14. data/lib/worochi/log.rb +8 -14
  15. data/lib/worochi/oauth.rb +69 -0
  16. data/lib/worochi/version.rb +1 -1
  17. data/lib/worochi.rb +20 -3
  18. data/spec/cassettes/Worochi/_push/pushes_with_agents.yml +512 -0
  19. data/spec/cassettes/Worochi_Agent_Dropbox/_push_item/pushes_it_chunked_if_size_exceeds_limit.yml +1049 -0
  20. data/spec/cassettes/Worochi_Agent_Dropbox/_push_item_chunked/raises_an_error.yml +44 -0
  21. data/spec/cassettes/Worochi_Agent_Dropbox/_push_items/pushes_multiple_items.yml +165 -0
  22. data/spec/cassettes/Worochi_Agent_Dropbox/it_should_behave_like_a_service_agent/_files/raises_error_on_invalid_path.yml +38 -0
  23. data/spec/cassettes/Worochi_Agent_Dropbox/it_should_behave_like_a_service_agent/_files_and_folders/shows_detailed_listing.yml +79 -0
  24. data/spec/cassettes/Worochi_Agent_Dropbox/it_should_behave_like_a_service_agent/_files_and_folders/shows_detailed_listing_including_the_required_fields.yml +79 -0
  25. data/spec/cassettes/Worochi_Agent_Github/_list/works_with_absolute_paths.yml +12 -12
  26. data/spec/cassettes/Worochi_Agent_Github/_repos/lists_the_repos.yml +207 -0
  27. data/spec/cassettes/Worochi_Agent_Github/_source_branch/retrieves_the_master_branch_correctly.yml +6 -6
  28. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files/accepts_a_different_relative_path.yml +12 -12
  29. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files/contains_file1.yml +12 -12
  30. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files/does_not_contain_folder1.yml +12 -12
  31. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files/raises_error_on_invalid_path.yml +133 -0
  32. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files_and_folders/contains_folder1_and_file1.yml +24 -24
  33. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_files_and_folders/shows_detailed_listing_including_the_required_fields.yml +133 -0
  34. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_folders/accepts_a_different_relative_path.yml +12 -12
  35. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_folders/contains_folder1.yml +12 -12
  36. data/spec/cassettes/Worochi_Agent_Github/it_should_behave_like_a_service_agent/_folders/does_not_contain_file1.yml +12 -12
  37. data/spec/cassettes/Worochi_Agent_Github/{_push_all → modifies_the_repo/_push_all}/pushes_a_list_of_items_to_create_a_new_commit.yml +80 -80
  38. data/spec/cassettes/Worochi_Agent_Github/{_push_all → modifies_the_repo/_push_all}/pushes_the_file_to_the_right_place.yml +60 -60
  39. data/spec/cassettes/Worochi_Agent_Github/modifies_the_repo/_push_blob/pushes_the_blob_even_when_it_is_larger_than_block_size.yml +181 -0
  40. data/spec/cassettes/Worochi_Agent_Github/modifies_the_repo/_push_item/pushes_a_single_item_and_makes_a_commit.yml +687 -0
  41. data/spec/cassettes/Worochi_Agent_Github/modifies_the_repo/_stream_blob/streams_the_file_as_an_Base64_JSON_field.yml +181 -0
  42. data/spec/cassettes/Worochi_OAuth/_flow_end/rejects_bad_code.yml +56 -0
  43. data/spec/{helper.rb → spec_helper.rb} +14 -1
  44. data/spec/support/aws_uri_matcher.rb +1 -1
  45. data/spec/support/shared_exampes_for_agents.rb +13 -2
  46. data/spec/support/test_files.rb +4 -4
  47. data/spec/worochi/agent/dropbox_spec.rb +29 -3
  48. data/spec/worochi/agent/github_spec.rb +54 -26
  49. data/spec/worochi/agent_spec.rb +34 -1
  50. data/spec/worochi/config_spec.rb +46 -30
  51. data/spec/worochi/helper/github_helper_spec.rb +94 -0
  52. data/spec/worochi/helper_spec.rb +15 -3
  53. data/spec/worochi/item_spec.rb +9 -6
  54. data/spec/worochi/log_spec.rb +30 -0
  55. data/spec/worochi/oauth_spec.rb +33 -0
  56. data/spec/worochi_spec.rb +25 -1
  57. data/worochi.gemspec +5 -1
  58. metadata +104 -11
  59. data/lib/worochi/helper/github.rb +0 -100
  60. data/spec/worochi/helper/github_spec.rb +0 -57
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5aa10b9332e68182b3a9dbed21aff7f1fcc79c78
4
- data.tar.gz: 451929c1f40471004dc786839f1897965f04a803
3
+ metadata.gz: 0ba8b523ca7164198a72a0cb2b02230f6a394012
4
+ data.tar.gz: 30d3cba96de58d8c04c6c2498f23e747ee320eef
5
5
  SHA512:
6
- metadata.gz: 1c76eb196eb3a1c759ea9fbde0c75e23e3a34af09176086c2f91ee65aadb69de98df0ba5eb9e7cff769187d061d92f864f31dc72d83f458cd376bd0c25eb7e49
7
- data.tar.gz: 740a453516871166e8fe34b7f28b8e31d351d8d3f4bd796f4217978720a6bca2ccf0c9f763a39d711de9ea11e74eb3610d71e7ba506845de9ef40b18cb57ad8c
6
+ metadata.gz: 91d81134ec1ebd38502aadf22f4a90e19d648ae4cb8be0c15712961ad124ea8a57ef2ead0334af2d62a0dfb89270225f5a395880f10ab747238b2d8147806c25
7
+ data.tar.gz: 2f1665102e34f54b1f29f17c40da921854ebede6cadd1662fa5f8198f4d79c965f2bb1032b14700f98171f98a15d63c9b618e58c995f14c6bcd259793734f535
@@ -1,19 +1,7 @@
1
1
  class Worochi
2
- # The {Agent} for a sample API.
2
+ # The {Agent} for an example API.
3
3
  # This is a sample of methods that should be implemented by a service agent.
4
- class Agent::Sample < Agent
5
-
6
- # This is the minimum set of default options that should be specified.
7
- #
8
- # @return [Hash] default options for sample API
9
- # @see Agent#set_options
10
- def default_options
11
- {
12
- service: :sample, # service name
13
- dir: '/' # remote directory to act on
14
- }
15
- end
16
-
4
+ class Agent::Example < Agent
17
5
  # This is where the service-specific API client should be initialized.
18
6
  #
19
7
  # @return [ApiClient]
@@ -4,16 +4,6 @@ class Worochi
4
4
  # The {Agent} for Dropbox API. This wraps around the `dropbox-sdk` gem.
5
5
  # @see https://www.dropbox.com/developers/core/start/ruby
6
6
  class Agent::Dropbox < Agent
7
- # @return [Hash] default options for Dropbox
8
- def default_options
9
- {
10
- service: :dropbox,
11
- chunk_size: 2*1024*1024,
12
- overwrite: true,
13
- dir: '/'
14
- }
15
- end
16
-
17
7
  # Initializes Dropbox SDK client. Refer to
18
8
  # {https://www.dropbox.com/developers/core/start/ruby
19
9
  # official Dropbox documentation}.
@@ -27,16 +17,16 @@ class Worochi
27
17
  # Push a single {Item} to Dropbox.
28
18
  #
29
19
  # @param item [Item]
30
- # @return [nil]
20
+ # @return [Boolean] true if chunk uploader was used
31
21
  def push_item(item)
32
22
  Worochi::Log.debug "Uploading #{item.path} (#{item.size} bytes) to Dropbox..."
33
- if item.size > options[:chunk_size]
23
+ if chunked = item.size > options[:chunk_size]
34
24
  push_item_chunked(item)
35
25
  else
36
26
  @client.put_file(full_path(item), item.content, options[:overwrite])
37
27
  end
38
28
  Worochi::Log.debug "Uploaded"
39
- nil
29
+ chunked
40
30
  end
41
31
 
42
32
  # Returns a list of files and subdirectories at the remote path specified
@@ -1,25 +1,10 @@
1
1
  require 'octokit'
2
2
  require 'base64'
3
- require 'worochi/helper/github'
4
3
 
5
4
  class Worochi
6
5
  # The {Agent} for GitHub API. This wraps around the `octokit` gem.
7
6
  # @see https://github.com/octokit/octokit.rb
8
7
  class Agent::Github < Agent
9
-
10
- # @return [Hash] default options for GitHub
11
- def default_options
12
- {
13
- service: :github,
14
- source: 'worochi',
15
- target: 'worochi',
16
- repo: 'darkmirage/test',
17
- block_size: Worochi::Helper::Github::BLOCK_SIZE,
18
- commit_msg: 'Empty commit message',
19
- dir: '/'
20
- }
21
- end
22
-
23
8
  # Initializes Octokit client. Refer to
24
9
  # {https://github.com/octokit/octokit.rb
25
10
  # octokit.rb documentation}.
@@ -68,12 +53,14 @@ class Worochi
68
53
  x.path.split('/').size <=> y.path.split('/').size
69
54
  end
70
55
 
71
- # Checks that folders are at the requested path and not at a lower or
72
- # higher level
56
+ # Filters for folders containing the specified path
57
+ result.reject! { |elem| !elem.path.match(remote_path + '($|\/.+)') }
58
+ raise Error, 'Invalid GitHub path specified' if result.empty?
59
+
60
+ # Filters out lower levels
73
61
  result.reject! do |elem|
74
- !elem.path.match(remote_path + '($|\/.+)') ||
75
- (File.join(remote_path,
76
- elem.path.split('/').last).sub(/^\//, '') != elem.path)
62
+ filename = elem.path.split('/').last
63
+ File.join(remote_path, filename).sub(/^\//, '') != elem.path
77
64
  end
78
65
 
79
66
  result.map do |elem|
@@ -142,7 +129,7 @@ class Worochi
142
129
  # @return [String] SHA1 checksum of the created blob
143
130
  def stream_blob(item)
144
131
  Worochi::Log.debug "Using JSON streaming..."
145
- post_stream = Worochi::Helper::Github::StreamIO.new(item)
132
+ post_stream = Helper::StreamIO.new(item)
146
133
 
147
134
  uri = URI("https://api.github.com/repos/#{repo}/git/blobs")
148
135
  request = Net::HTTP::Post.new(uri.path)
@@ -156,8 +143,6 @@ class Worochi
156
143
  http.request request do |response|
157
144
  return JSON.parse(response.body)['sha']
158
145
  end
159
-
160
- raise Error, 'Failed to upload file to GitHub'
161
146
  end
162
147
 
163
148
  # @return [Hash] repo information
data/lib/worochi/agent.rb CHANGED
@@ -1,17 +1,15 @@
1
+ require 'yaml'
2
+
1
3
  class Worochi
2
4
  # The parent class for all service agents.
3
5
  class Agent
4
- # Service name.
5
- # @return [Symbol]
6
- attr_reader :type
7
6
  # Service options.
8
- # @return [Hash]
7
+ # @return [Hashie::Mash]
9
8
  attr_accessor :options
10
9
 
11
10
  # @param opts [Hash] service options
12
11
  def initialize(opts={})
13
12
  set_options(opts)
14
- @type = options[:service]
15
13
  init_client
16
14
  end
17
15
 
@@ -92,6 +90,11 @@ class Worochi
92
90
  list_helper(:folders, args)
93
91
  end
94
92
 
93
+ # Returns a list of files and folders at the remote path specified by
94
+ # `options[:dir]`. Relies on the service-specific implementation of
95
+ # `#list`. Refer to {#files} for overloaded prototypes.
96
+ #
97
+ # @return [Array<String>, Array<Hash>] list of files and folders
95
98
  def files_and_folders(*args)
96
99
  list_helper(:both, args)
97
100
  end
@@ -99,29 +102,35 @@ class Worochi
99
102
  # Updates {.options} using `opts`.
100
103
  #
101
104
  # @param opts [Hash] new options
102
- # @return [Hash] the updated options
105
+ # @return [Hashie::Mash] the updated options
103
106
  def set_options(opts={})
104
107
  self.options ||= default_options
105
- sym_opts = {}
106
- opts.each { |k, v| sym_opts[k.to_sym] = v }
107
- options.merge!(sym_opts)
108
+ opts = Hashie::Mash.new(opts)
109
+ options.merge!(opts)
108
110
  end
109
111
 
110
112
  # Sets the remote target directory path. This is the same as modifying
111
113
  # `options[:dir]`.
112
114
  #
113
115
  # @param path [String] the new path
114
- # @return [Hash] the updated options
116
+ # @return [Hashie::Mash] the updated options
115
117
  def set_dir(path)
116
- options[:dir] = path
118
+ options.dir = path
117
119
  options
118
120
  end
119
121
 
122
+ # Returns the service type for the agent.
123
+ #
124
+ # @return [Symbol] service type
125
+ def type
126
+ options.service
127
+ end
128
+
120
129
  # Returns the display name for the agent's service.
121
130
  #
122
131
  # @return [String] display name
123
132
  def name
124
- Worochi::Config.service_display_name(type)
133
+ Worochi::Config.service_display_name(options.service)
125
134
  end
126
135
 
127
136
  private
@@ -129,7 +138,7 @@ class Worochi
129
138
  #
130
139
  # @param mode [Symbol] display files, folders, or both
131
140
  # @param args [Array] argument list
132
- # @return [Array<String>, Array<Hash>] list of files or folders
141
+ # @return [Array<String>, Array<Hashie::Mash>] list of files or folders
133
142
  def list_helper(mode, args)
134
143
  details = true if args.first == true
135
144
  if args.first.kind_of?(String)
@@ -147,20 +156,39 @@ class Worochi
147
156
  end
148
157
 
149
158
  result = list(path).reject { |elem| elem[:type] == excluded }
150
- result.map! { |elem| elem[:name] } unless details
159
+ if details
160
+ result.map! { |elem| Hashie::Mash.new(elem) }
161
+ else
162
+ result.map! { |elem| elem[:name] }
163
+ end
151
164
  result
152
165
  end
153
166
 
154
167
  # @return [String] full path combining remote directory and item path
155
168
  def full_path(item)
156
- File.join(options[:dir], item.path)
169
+ File.join(options.dir, item.path)
157
170
  end
158
171
 
159
- # Agents should override this.
172
+ # Agents should either override this or have a YAML config file at
173
+ # config/service_name.yml.
160
174
  #
161
- # @return [nil]
175
+ # @return [Hashie::Mash] options parsed from YAML config
162
176
  def default_options
163
- raise Error, 'Default options not specified.'
177
+ service = service_name.to_sym
178
+ Worochi::Config.service_opts(service)
179
+ end
180
+
181
+ # Returns the service name based on the class name.
182
+ #
183
+ # @return [Symbol] service name
184
+ def service_name
185
+ name = self.class.to_s.split( '::' ).last
186
+ name.gsub!(/::/, '/')
187
+ name.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
188
+ name.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
189
+ name.tr!("-", "_")
190
+ name.downcase!
191
+ name.to_sym
164
192
  end
165
193
 
166
194
  class << self
@@ -0,0 +1,35 @@
1
+ # Use this as a template for configuring new services
2
+
3
+ # General configurations (must be present)
4
+ # id must be unique across all services
5
+ id: 0
6
+ display_name: Sample Agent
7
+ dir: /
8
+
9
+ # Required if using OAuth2 functionality
10
+ oauth:
11
+ # These are the ENV names and not the actual ID and secret. If not defined,
12
+ # then assumed to be SERVICE_NAME_ID and SERVICE_NAME_SECRET.
13
+ id_env: SAMPLE_ID
14
+ secret_env: SAMPLE_SECRET
15
+
16
+ # API endpoints for OAuth2
17
+ token_url: /1/oauth2/token
18
+ authorize_url: /1/oauth2/authorize
19
+
20
+ # Some services (e.g. Dropbox) have a different domain for the second step
21
+ # of the OAuth flow. In those cases, :site will be used as the endpoint for
22
+ # the start of the authorization flow while :token_site will be used as the
23
+ # endpoint for token retrieval.
24
+ site: https://www.sample.com
25
+ token_site: https://api.sample.com
26
+
27
+ # Scope if required by the service
28
+ scope: gallery
29
+
30
+ # Service specific (optional; example is for GitHub)
31
+ repo: darkmirage/test
32
+ source: master
33
+ target: worochi
34
+ block_size: 12288
35
+ commit_msg: Empty commit message
@@ -0,0 +1,14 @@
1
+ id: 2
2
+
3
+ display_name: Dropbox
4
+ dir: /
5
+ oauth:
6
+ id_env: DROPBOX_ID
7
+ secret_env: DROPBOX_SECRET
8
+ token_url: /1/oauth2/token
9
+ authorize_url: /1/oauth2/authorize
10
+ site: https://www.dropbox.com
11
+ token_site: https://api.dropbox.com
12
+
13
+ chunk_size: 2097152
14
+ overwrite: true
@@ -0,0 +1,17 @@
1
+ id: 1
2
+
3
+ display_name: GitHub
4
+ dir: /
5
+ oauth:
6
+ id_env: GITHUB_ID
7
+ secret_env: GITHUB_SECRET
8
+ token_url: /login/oauth/access_token
9
+ authorize_url: /login/oauth/authorize
10
+ site: https://github.com
11
+ scope: repo
12
+
13
+ repo: darkmirage/test
14
+ source: master
15
+ target: worochi
16
+ block_size: 12288
17
+ commit_msg: Empty commit message
@@ -1,85 +1,10 @@
1
- class Worochi
2
- # Configurations for Worochi.
3
- module Config
4
- @services = {
5
- github: [1, 'GitHub'],
6
- dropbox: [2, 'Dropbox']
7
- }
8
- @s3_bucket = 'data-pixelapse'
9
- @s3_prefix = 's3'
10
-
11
- class << self
12
- # Array of service names.
13
- #
14
- # @return [Array<Symbol>]
15
- def services
16
- @services.keys
17
- end
18
-
19
- # Returns display name for the service.
20
- #
21
- # @overload service_display_name(service)
22
- # @param service [Symbol]
23
- # @overload service_display_name(service_id)
24
- # @param service_id [Integer]
25
- # @return [String] display name
26
- def service_display_name(arg)
27
- service = arg.to_sym if arg.respond_to?(:to_sym)
28
- service = service_name(arg) unless @services.include?(service)
29
- if service.nil?
30
- nil
31
- else
32
- @services[service][1]
33
- end
34
- end
35
-
36
- # Returns the service ID for the service, which can be used as a
37
- # primary key for databases.
38
- #
39
- # @param service [Symbol]
40
- # @return [Integer] service ID
41
- def service_id(service)
42
- if @services[service.to_sym].nil?
43
- nil
44
- else
45
- @services[service.to_sym][0]
46
- end
47
- end
48
-
49
- # Returns the service name given the service ID.
50
- #
51
- # @param service_id [Integer]
52
- # @return [Symbol] if service exists
53
- # @return [nil] if service does not exist
54
- def service_name(service_id)
55
- @service_ids ||= {}
56
- return @service_ids[service_id] if @service_ids.include?(service_id)
57
- @services.each do |key, value|
58
- if value[0] == service_id
59
- @service_ids[value[0]] = key
60
- return key
61
- end
62
- end
63
- nil
64
- end
65
-
66
- # Name of S3 bucket.
67
- #
68
- # @return [String]
69
- attr_reader :s3_bucket
70
-
71
- # Prefix for S3 resource paths.
72
- #
73
- # @return [String]
74
- attr_reader :s3_prefix
75
-
76
- # Disable debug and error messages if `true`.
77
- #
78
- # @return [Boolean]
79
- attr_accessor :silent
80
-
81
- alias_method :silent?, :silent
82
- end
83
- end
84
- end
1
+ require 'worochi/configurator'
85
2
 
3
+ class Worochi
4
+ # Default configurations
5
+ Config.s3_enabled = true
6
+ Config.s3_bucket = 'worochi'
7
+ Config.s3_prefix = 's3'
8
+ Config.silent = true
9
+ Config.logdev = $stdout
10
+ end
@@ -0,0 +1,120 @@
1
+ class Worochi
2
+ # Configuration methods for loading and modifying options.
3
+ module Config
4
+ class << self
5
+ # Loads options from YAML configuration files.
6
+ #
7
+ # @return [nil]
8
+ def load_yaml
9
+ @options = {}
10
+ services.each do |service|
11
+ path = File.join(File.dirname(__FILE__), "config/#{service}.yml")
12
+ raise Error, "Missing config for #{service}" unless File.file?(path)
13
+ @options[service] = Hashie::Mash.new(YAML.load_file(path))
14
+ @options[service].service = service
15
+ end
16
+ end
17
+
18
+ # Returns the service configurations that was loaded from YAML.
19
+ #
20
+ # @return [Hashie::Mash]
21
+ def service_opts(service)
22
+ if service.nil? || @options.nil? || @options[service].nil?
23
+ raise Error, "Invalid service (#{service}) specified"
24
+ end
25
+ @options[service].clone
26
+ end
27
+
28
+ # Array of service names. Parsed from the file names of any .rb files in
29
+ # the worochi/agent directory that does not contain the `#` character.
30
+ #
31
+ # @return [Array<Symbol>]
32
+ def services
33
+ return @services if @services
34
+ files = Dir[File.join(File.dirname(__FILE__), 'agent/[^#]*.rb')]
35
+ @services = files.map do |file|
36
+ File.basename(file, '.rb').to_sym
37
+ end
38
+ @services
39
+ end
40
+
41
+ # Returns display name for the service.
42
+ #
43
+ # @overload service_display_name(service)
44
+ # @param service [Symbol]
45
+ # @overload service_display_name(service_id)
46
+ # @param service_id [Integer]
47
+ # @return [String] display name
48
+ def service_display_name(arg)
49
+ service = arg.to_sym if arg.respond_to?(:to_sym)
50
+ service = service_name(arg) unless @options.include?(service)
51
+ if service.nil?
52
+ nil
53
+ else
54
+ @options[service].display_name
55
+ end
56
+ end
57
+
58
+ # Returns the service ID for the service, which can be used as a
59
+ # primary key for databases.
60
+ #
61
+ # @param service [Symbol]
62
+ # @return [Integer] service ID
63
+ def service_id(service)
64
+ if @options[service.to_sym].nil?
65
+ nil
66
+ else
67
+ @options[service.to_sym].id
68
+ end
69
+ end
70
+
71
+ # Returns the service name given the service ID.
72
+ #
73
+ # @param id [Integer]
74
+ # @return [Symbol] if service exists
75
+ # @return [nil] if service does not exist
76
+ def service_name(id)
77
+ @service_names ||= {}
78
+ return @service_names[id] if @service_names.include?(id)
79
+ @options.each do |key, value|
80
+ if value.id == id
81
+ @service_names[value.id] = key
82
+ return key
83
+ end
84
+ end
85
+ nil
86
+ end
87
+
88
+ # Enables AWS S3 support. AWS_SECRET_ACCESS_KEY and AWS_ACCESS_KEY_ID
89
+ # should be present in ENV.
90
+ #
91
+ # @return [Boolean]
92
+ attr_accessor :s3_enabled
93
+
94
+ # Name of S3 bucket.
95
+ #
96
+ # @return [String]
97
+ attr_accessor :s3_bucket
98
+
99
+ # Prefix for S3 resource paths.
100
+ #
101
+ # @return [String]
102
+ attr_accessor :s3_prefix
103
+
104
+ # Logging device.
105
+ #
106
+ # @return [IO]
107
+ attr_accessor :logdev
108
+
109
+ # Disable debug and error messages if `true`.
110
+ #
111
+ # @return [Boolean]
112
+ attr_accessor :silent
113
+
114
+ alias_method :silent?, :silent
115
+ alias_method :s3_enabled?, :s3_enabled
116
+
117
+ end
118
+ end
119
+ end
120
+
@@ -0,0 +1,111 @@
1
+ require 'base64'
2
+
3
+ class Worochi
4
+ class Agent::Github
5
+ # Helper classes for GitHub JSON streaming.
6
+ module Helper
7
+ # Size to read in. Must be a multiple of 3 for Base64 streaming.
8
+ BLOCK_SIZE = 12288
9
+
10
+ # This is a wrapper that produces a JSON stream that works with
11
+ # `Net::HTTP::Post#body_stream`.
12
+ class StreamIO
13
+ def initialize(item)
14
+ item.content.rewind
15
+ @parts = [
16
+ StringIO.new('{"content":"'),
17
+ Base64IO.new(item.content),
18
+ StringIO.new('","encoding":"base64"}')
19
+ ]
20
+ @part_no = 0
21
+ @pos = 0
22
+ @size = @parts.inject(0) {|sum, p| sum + p.size}
23
+ end
24
+
25
+ # Rewind each component of the stream.
26
+ #
27
+ # @return [nil]
28
+ def rewind
29
+ @parts.each { |part| part.rewind }
30
+ @part_no = 0
31
+ @pos = 0
32
+ nil
33
+ end
34
+
35
+ # @return [Boolean] file has been fully read.
36
+ def eof?
37
+ @pos >= size
38
+ end
39
+
40
+ # @param length [Integer]
41
+ # @param outbuf [IO]
42
+ # @return [String]
43
+ def read(length=nil, outbuf=nil)
44
+ if eof?
45
+ outbuf.clear if outbuf
46
+ return length.nil? ? '' : nil
47
+ end
48
+
49
+ length ||= size
50
+
51
+ output = ''
52
+ while output.length < length
53
+ curr = @parts[@part_no]
54
+ output += curr.read(length - output.length).to_s
55
+ @part_no += 1 if curr.eof?
56
+ break if @part_no == @parts.size
57
+ end
58
+ @pos += output.length
59
+
60
+ unless outbuf.nil?
61
+ outbuf.clear
62
+ outbuf.insert(0, output)
63
+ end
64
+
65
+ output
66
+ end
67
+
68
+ # @return [Integer] size of the JSON
69
+ attr_reader :size
70
+ end
71
+
72
+ # This is a wrapper around the file content that streams the file as a
73
+ # Base64 encoded string.
74
+ class Base64IO
75
+ def initialize(file)
76
+ file.rewind
77
+ @file = file
78
+ @encoded_size = (@file.size / 3.0).ceil * 4
79
+ @buffer = ''
80
+ end
81
+
82
+ # @return [Integer] size of the JSON
83
+ def size
84
+ @encoded_size
85
+ end
86
+
87
+ # @return [Boolean] file has been fully read.
88
+ def eof?
89
+ @buffer.empty? && @file.eof?
90
+ end
91
+
92
+ # Rewind the stream.
93
+ def rewind
94
+ @file.rewind
95
+ @buffer = ''
96
+ nil
97
+ end
98
+
99
+ # @param length [Integer]
100
+ # @param outbuf [IO]
101
+ # @return [String]
102
+ def read(length=size, outbuf=nil)
103
+ while @buffer.length < length and not @file.eof?
104
+ @buffer += Base64.strict_encode64 @file.read(BLOCK_SIZE)
105
+ end
106
+ @buffer.empty? ? nil : @buffer.slice!(0, length)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end