esp_sdk 2.5.0 → 2.6.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.yardopts +1 -0
  4. data/CHANGELOG.md +8 -0
  5. data/Gemfile.lock +5 -3
  6. data/Guardfile +3 -1
  7. data/esp_sdk.gemspec +2 -1
  8. data/lib/esp/aws_clients.rb +2 -1
  9. data/lib/esp/commands/commands_tasks.rb +2 -1
  10. data/lib/esp/commands/console.rb +4 -0
  11. data/lib/esp/exceptions.rb +1 -0
  12. data/lib/esp/extensions/active_resource/dirty.rb +51 -0
  13. data/lib/esp/extensions/active_resource/formats/json_api_format.rb +5 -3
  14. data/lib/esp/extensions/active_resource/paginated_collection.rb +71 -38
  15. data/lib/esp/extensions/active_resource/validations.rb +4 -2
  16. data/lib/esp/external_account_creator.rb +4 -1
  17. data/lib/esp/resources/alert.rb +53 -42
  18. data/lib/esp/resources/cloud_trail_event.rb +18 -12
  19. data/lib/esp/resources/concerns/stat_totals.rb +70 -67
  20. data/lib/esp/resources/contact_request.rb +17 -14
  21. data/lib/esp/resources/custom_signature/definition.rb +46 -51
  22. data/lib/esp/resources/custom_signature/result/alert.rb +13 -5
  23. data/lib/esp/resources/custom_signature/result.rb +49 -53
  24. data/lib/esp/resources/custom_signature.rb +52 -61
  25. data/lib/esp/resources/dashboard.rb +11 -5
  26. data/lib/esp/resources/external_account.rb +59 -58
  27. data/lib/esp/resources/metadata.rb +21 -11
  28. data/lib/esp/resources/organization.rb +49 -39
  29. data/lib/esp/resources/region.rb +25 -28
  30. data/lib/esp/resources/report.rb +46 -44
  31. data/lib/esp/resources/reports/export/integration.rb +22 -13
  32. data/lib/esp/resources/resource.rb +4 -3
  33. data/lib/esp/resources/scan_interval.rb +19 -13
  34. data/lib/esp/resources/service.rb +17 -11
  35. data/lib/esp/resources/signature.rb +43 -53
  36. data/lib/esp/resources/stat.rb +72 -55
  37. data/lib/esp/resources/stat_custom_signature.rb +73 -65
  38. data/lib/esp/resources/stat_region.rb +76 -65
  39. data/lib/esp/resources/stat_service.rb +76 -65
  40. data/lib/esp/resources/stat_signature.rb +76 -65
  41. data/lib/esp/resources/sub_organization.rb +51 -60
  42. data/lib/esp/resources/suppression/region.rb +35 -30
  43. data/lib/esp/resources/suppression/signature.rb +35 -29
  44. data/lib/esp/resources/suppression/unique_identifier.rb +27 -22
  45. data/lib/esp/resources/suppression.rb +45 -34
  46. data/lib/esp/resources/tag.rb +20 -11
  47. data/lib/esp/resources/team.rb +56 -58
  48. data/lib/esp/resources/user.rb +35 -32
  49. data/lib/esp/version.rb +1 -1
  50. data/lib/esp.rb +39 -16
  51. data/lib/esp_sdk.rb +1 -0
  52. data/test/esp/extensions/active_resource/dirty_test.rb +81 -0
  53. data/test/esp/extensions/active_resource/formats/json_api_format_test.rb +8 -0
  54. data/test/esp/extensions/active_resource/paginated_collection_test.rb +7 -0
  55. data/test/esp/integration/json_api_format_integration_test.rb +5 -2
  56. data/test/esp/integration/organization_integration_test.rb +1 -1
  57. data/test/esp/resources/custom_signature_test.rb +15 -0
  58. data/test/factories/custom_signatures.rb +0 -10
  59. metadata +21 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 985f05618a6621f6fd81b455b13b532c91712638
4
- data.tar.gz: 773b7274d4889449a4264f8f19e869f4b9830798
3
+ metadata.gz: ee71a8ce107cfd0be1c552e60e32ea3637c21eaa
4
+ data.tar.gz: f4087d8db9e1affb4f96a1eceae43ba3211673bf
5
5
  SHA512:
6
- metadata.gz: 54b0c9606230136eff0b3c463467be3f9eebf1986b4e603622c1efa59e680496769b7d086ae13913be912df2c12e7ac2f1e5352e39a1ba18628ffc4c9e36828a
7
- data.tar.gz: c398c98653b66c5adac110c958580662fb1bba451fa0d8db98cc6f435e6d1ed0f5f79c594a768bee60232d9b9799eeff230c65961ab1c4cc43e2917bb344667e
6
+ metadata.gz: b3f490d1f67a732b9b85daf1036d69af6b8295238ce9eb255b41f4fd10172106663e7e3bd7c7f45c7d0c12219b1a34bf68690bd0c12285c1cd25198fcf26aacd
7
+ data.tar.gz: 9ece944bf4867f36e4df3457e12c739b15d79dd0ebc729d9b5d2d47955e941b6b3e2042ef88a0fd9eec9fc35d77f052c113342bbbd06c346d1549dfcffb89f93
data/.gitignore CHANGED
@@ -17,5 +17,7 @@ tmp
17
17
  *.so
18
18
  *.o
19
19
  *.a
20
+ *.swp
21
+ *.swo
20
22
  mkmf.log
21
23
  .idea
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-private
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 2.6.0 - 2016-10-17
4
+ ### Changed
5
+ - Only send changed attributes to API #39
6
+ - API now returns nested included relation data elements correctly. Change test to reflect corrected response. #40
7
+ - Calling `next_page` on queries without parameters (e.g. `ESP::ExternalAccount.all`) no longer errors. #35
8
+ - Silently ignore `null` entires if encountered in API responses. #69
9
+ - Switch from RDoc to Yard. #37
10
+
3
11
  ## 2.5.0 - 2016-07-20
4
12
  ### Added
5
13
  - Add custom signature definitions and results. Code for a custom signature is now created/updated under a definition. Running a definition for an on demand test creates a result record which will have errors and alerts when completed.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- esp_sdk (2.5.0)
4
+ esp_sdk (2.6.0)
5
5
  activeresource (~> 4.0.0)
6
6
  api-auth (~> 2.0.0)
7
7
  rack
@@ -107,7 +107,7 @@ GEM
107
107
  rb-fsevent (0.9.6)
108
108
  rb-inotify (0.9.5)
109
109
  ffi (>= 0.5.0)
110
- rdoc (4.0.0)
110
+ rdiscount (2.2.0.1)
111
111
  rest-client (1.7.3)
112
112
  mime-types (>= 1.16, < 3.0)
113
113
  netrc (~> 0.7)
@@ -142,6 +142,7 @@ GEM
142
142
  webmock (1.21.0)
143
143
  addressable (>= 2.3.6)
144
144
  crack (>= 0.3.2)
145
+ yard (0.9.5)
145
146
 
146
147
  PLATFORMS
147
148
  ruby
@@ -161,10 +162,11 @@ DEPENDENCIES
161
162
  minitest-reporters
162
163
  mocha
163
164
  rake
164
- rdoc
165
+ rdiscount
165
166
  rubocop
166
167
  shoulda
167
168
  webmock
169
+ yard
168
170
 
169
171
  BUNDLED WITH
170
172
  1.12.5
data/Guardfile CHANGED
@@ -15,10 +15,12 @@
15
15
  #
16
16
  # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
17
 
18
- guard :minitest do
18
+ guard :minitest, all_on_start: false do
19
19
  # with Minitest::Unit
20
20
  watch(%r{^test/(.*)\/?test_(.*)\.rb$})
21
+ watch(%r{^test/.+_test\.rb$})
21
22
  watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
23
+ watch(%r{^lib/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
22
24
  watch(%r{^test/test_helper\.rb$}) { 'test' }
23
25
 
24
26
  # with Minitest::Spec
data/esp_sdk.gemspec CHANGED
@@ -33,9 +33,10 @@ Gem::Specification.new do |spec|
33
33
  spec.add_development_dependency 'webmock'
34
34
  spec.add_development_dependency 'coveralls'
35
35
  spec.add_development_dependency 'factory_girl'
36
- spec.add_development_dependency 'rdoc'
36
+ spec.add_development_dependency 'yard'
37
37
  spec.add_development_dependency 'awesome_print'
38
38
  spec.add_development_dependency 'aws-sdk'
39
+ spec.add_development_dependency 'rdiscount'
39
40
 
40
41
  spec.add_dependency 'activeresource', '~> 4.0.0'
41
42
  spec.add_dependency 'api-auth', '~> 2.0.0'
@@ -1,6 +1,7 @@
1
1
  require 'aws-sdk'
2
2
 
3
- module ESP # :nodoc: all
3
+ module ESP
4
+ # @private
4
5
  class AWSClients
5
6
  include ActiveModel::Validations
6
7
 
@@ -4,7 +4,8 @@ module ESP
4
4
  #
5
5
  # Warning: This class mutates ARGV because some commands require manipulating
6
6
  # it before they are run.
7
- class CommandsTasks # :nodoc:
7
+ # @private
8
+ class CommandsTasks
8
9
  attr_reader :argv
9
10
 
10
11
  HELP_MESSAGE = <<-EOT
@@ -21,7 +21,11 @@ ARGV.clone.options do |opts|
21
21
  end
22
22
 
23
23
  module ESP
24
+ # @private
24
25
  class Console
26
+ # Start a console
27
+ #
28
+ # @return [void]
25
29
  def start # rubocop:disable Metrics/MethodLength
26
30
  ARGV.clear
27
31
  IRB.setup nil
@@ -1,3 +1,4 @@
1
1
  module ESP
2
+ # @private
2
3
  class NotImplementedError < StandardError; end
3
4
  end
@@ -0,0 +1,51 @@
1
+ module Dirty
2
+ def original_attributes
3
+ @original_attributes ||= {}.with_indifferent_access
4
+ end
5
+
6
+ def original_attributes=(attributes = {})
7
+ @original_attributes = attributes.dup
8
+ end
9
+
10
+ def changed_attributes
11
+ attributes.select do |key, value|
12
+ next if value == original_attributes[key]
13
+ true
14
+ end
15
+ end
16
+ end
17
+
18
+ # Set the original attributes every time we instantiate an object from the api
19
+ # This happens on GET requests
20
+ module InstantiateWithOriginalAttributes
21
+ private
22
+
23
+ def instantiate_record(record, _prefix_options = {})
24
+ super(record, _prefix_options = {}).tap do |object|
25
+ object.original_attributes = object.attributes
26
+ end
27
+ end
28
+ end
29
+
30
+ module LoadWithOriginalAttributes
31
+ # After sending to the API the object is reloaded with its attributes
32
+ # The persisted flag tells us it has been saved
33
+ def load(attributes, _remove_root = false, persisted = false)
34
+ if persisted
35
+ super.tap do |object|
36
+ object.original_attributes = object.attributes
37
+ end
38
+ else
39
+ super
40
+ end
41
+ end
42
+ end
43
+
44
+ class ActiveResource::Base
45
+ include Dirty
46
+ prepend LoadWithOriginalAttributes
47
+
48
+ class << self
49
+ prepend InstantiateWithOriginalAttributes
50
+ end
51
+ end
@@ -1,6 +1,7 @@
1
1
  require 'active_support/json'
2
2
 
3
- module ActiveResource # :nodoc: all
3
+ module ActiveResource
4
+ # @private
4
5
  class ConnectionError
5
6
  def initialize(response)
6
7
  @response = if response.respond_to?(:response)
@@ -20,7 +21,8 @@ module ActiveResource # :nodoc: all
20
21
  end
21
22
  end
22
23
 
23
- module Formats # :nodoc: all
24
+ # @private
25
+ module Formats
24
26
  module JsonAPIFormat
25
27
  module_function
26
28
 
@@ -121,7 +123,7 @@ module ActiveResource # :nodoc: all
121
123
  end
122
124
 
123
125
  def self.merge_nested_included_objects(object, data, included)
124
- assocs = included.select { |i| data.include?((i.slice('type', 'id'))) }
126
+ assocs = included.compact.select { |i| data.include?(i.slice('type', 'id')) }
125
127
  # Remove the object from the included array to prevent an infinite loop if one of it's associations relates back to itself.
126
128
  assoc_included = included.dup
127
129
  assoc_included.delete(object)
@@ -1,12 +1,18 @@
1
1
  require 'rack'
2
2
 
3
3
  module ActiveResource
4
- # Provides a mean to call the Evident.io API to easily retrieve paginated data
5
4
  class PaginatedCollection < ActiveResource::Collection
6
- attr_reader :next_page_params, :previous_page_params, :last_page_params #:nodoc:
7
- attr_accessor :from #:nodoc:
8
-
9
- def initialize(elements = []) #:nodoc:
5
+ # Internal variable used to construct queries.
6
+ # @return [Hash]
7
+ # @private
8
+ attr_reader :next_page_params, :previous_page_params, :last_page_params
9
+ # Internal variable used to construct queries.
10
+ # @return [Integer]
11
+ # @private
12
+ attr_accessor :from
13
+
14
+ # @private
15
+ def initialize(elements = [])
10
16
  # If a collection is sent without the pagination links, then elements will just be an array.
11
17
  if elements.is_a? Hash
12
18
  super(elements['data'])
@@ -16,22 +22,24 @@ module ActiveResource
16
22
  end
17
23
  end
18
24
 
19
- # Returns a new PaginatedCollection with the first page of results.
25
+ # Returns the first page of results.
20
26
  #
21
- # Returns self when on the first page and no API call is made.
27
+ # Returns +self+ (and no API call is made) when already on the first page.
22
28
  #
23
- # ==== Example
29
+ # @return [PaginatedCollection, self]
30
+ # @example
24
31
  # alerts.current_page_number # => 5
25
32
  # first_page = alerts.first_page
26
33
  # alerts.current_page_number # => 5
27
34
  # first_page.current_page_number # => 1
28
35
  def first_page
29
- previous_page? ? resource_class.where(original_params.merge(from: from, page: { number: 1 })) : self
36
+ previous_page? ? updated_collection(from: from, page: { number: 1 }) : self
30
37
  end
31
38
 
32
39
  # Updates the existing PaginatedCollection object with the first page of data when not on the first page.
33
40
  #
34
- # ==== Example
41
+ # @return (see #first_page)
42
+ # @example
35
43
  # alerts.current_page_number # => 5
36
44
  # alerts.first_page!
37
45
  # alerts.current_page_number # => 1
@@ -39,22 +47,24 @@ module ActiveResource
39
47
  first_page.tap { |page| update_self(page) }
40
48
  end
41
49
 
42
- # Returns a new PaginatedCollection with the previous page of results.
50
+ # Returns the previous page of results.
43
51
  #
44
- # Returns self when on the first page and no API call is made.
52
+ # Returns +self+ (and no API call is made) when already on the first page.
45
53
  #
46
- # ==== Example
54
+ # @return [PaginatedCollection, self]
55
+ # @example
47
56
  # alerts.current_page_number # => 5
48
57
  # previous_page = alerts.previous_page
49
58
  # alerts.current_page_number # => 5
50
59
  # previous_page.current_page_number # => 4
51
60
  def previous_page
52
- previous_page? ? resource_class.where(original_params.merge(previous_page_params.merge(from: from))) : self
61
+ previous_page? ? updated_collection(previous_page_params.merge(from: from)) : self
53
62
  end
54
63
 
55
64
  # Updates the existing PaginatedCollection object with the previous page of data when not on the first page.
56
65
  #
57
- # ==== Example
66
+ # @return (see #previous_page)
67
+ # @example
58
68
  # alerts.current_page_number # => 5
59
69
  # alerts.previous_page!
60
70
  # alerts.current_page_number # => 4
@@ -62,22 +72,24 @@ module ActiveResource
62
72
  previous_page.tap { |page| update_self(page) }
63
73
  end
64
74
 
65
- # Returns a new PaginatedCollection with the next page of results.
75
+ # Returns the next page of results.
66
76
  #
67
- # Returns self when on the last page and no API call is made.
77
+ # Returns +self+ (and no API call is made) when already on the last page.
68
78
  #
69
- # ==== Example
79
+ # @return [PaginatedCollection, self]
80
+ # @example
70
81
  # alerts.current_page_number # => 5
71
82
  # next_page = alerts.next_page
72
83
  # alerts.current_page_number # => 5
73
84
  # next_page.current_page_number # => 6
74
85
  def next_page
75
- next_page? ? resource_class.where(original_params.merge(next_page_params.merge(from: from))) : self
86
+ next_page? ? updated_collection(next_page_params.merge(from: from)) : self
76
87
  end
77
88
 
78
89
  # Updates the existing PaginatedCollection object with the last page of data when not on the last page.
79
90
  #
80
- # ==== Example
91
+ # @return (see #next_page)
92
+ # @example
81
93
  # alerts.current_page_number # => 5
82
94
  # alerts.next_page!
83
95
  # alerts.current_page_number # => 6
@@ -85,22 +97,24 @@ module ActiveResource
85
97
  next_page.tap { |page| update_self(page) }
86
98
  end
87
99
 
88
- # Returns a new PaginatedCollection with the last page of results.
100
+ # Returns the last page of results.
89
101
  #
90
- # Returns self when on the last page and no API call is made.
102
+ # Returns +self+ (and no API call is made) when already on the last page.
91
103
  #
92
- # ==== Example
104
+ # @return [PaginatedCollection, self]
105
+ # @example
93
106
  # alerts.current_page_number # => 5
94
107
  # last_page = alerts.last_page
95
108
  # alerts.current_page_number # => 5
96
109
  # last_page.current_page_number # => 25
97
110
  def last_page
98
- !last_page? ? resource_class.where(original_params.merge(last_page_params.merge(from: from))) : self
111
+ !last_page? ? updated_collection(last_page_params.merge(from: from)) : self
99
112
  end
100
113
 
101
114
  # Updates the existing PaginatedCollection object with the last page of data when not on the last page.
102
115
  #
103
- # ==== Example
116
+ # @return (see #last_page)
117
+ # @example
104
118
  # alerts.current_page_number # => 5
105
119
  # alerts.last_page!
106
120
  # alerts.current_page_number # => 25
@@ -108,15 +122,14 @@ module ActiveResource
108
122
  last_page.tap { |page| update_self(page) }
109
123
  end
110
124
 
111
- # Returns a new PaginatedCollection with the +page_number+ page of data.
112
- #
113
- # Returns self when +page_number+ == #current_page_number
125
+ # Returns the +page_number+ page of data.
114
126
  #
115
- # ==== Attribute
127
+ # Returns +self+ when +page_number+ == +#current_page_number+
116
128
  #
117
- # +page_number+ - The page number of the data wanted. +page_number+ must be between 1 and #last_page_number.
118
- #
119
- # ==== Example
129
+ # @param page_number [Integer] The page number of the data wanted. Must be between 1 and +#last_page_number+.
130
+ # @return [PaginatedCollection, self]
131
+ # @raise [ArgumentError] if no page number or an out-of-bounds page number is supplied.
132
+ # @example
120
133
  # alerts.current_page_number # => 5
121
134
  # page = alerts.page(2)
122
135
  # alerts.current_page_number # => 5
@@ -125,16 +138,14 @@ module ActiveResource
125
138
  fail ArgumentError, "You must supply a page number." unless page_number.present?
126
139
  fail ArgumentError, "Page number cannot be less than 1." if page_number.to_i < 1
127
140
  fail ArgumentError, "Page number cannot be greater than the last page number." if page_number.to_i > last_page_number.to_i
128
- page_number.to_i != current_page_number.to_i ? resource_class.where(original_params.merge(from: from, page: { number: page_number, size: (next_page_params || previous_page_params)['page']['size'] })) : self
141
+ page_number.to_i != current_page_number.to_i ? updated_collection(from: from, page: { number: page_number, size: (next_page_params || previous_page_params)['page']['size'] }) : self
129
142
  end
130
143
 
131
144
  # Returns a new PaginatedCollection with the +page_number+ page of data when not already on page +page_number+.
132
145
  #
133
- # ==== Attribute
134
- #
135
- # +page_number+ - The page number of the data wanted. +page_number+ must be between 1 and #last_page_number.
136
- #
137
- # ==== Example
146
+ # @param (see #page)
147
+ # @return (see #page)
148
+ # @example
138
149
  # alerts.current_page_number # => 5
139
150
  # alerts.page!(2)
140
151
  # alerts.current_page_number # => 2
@@ -143,42 +154,64 @@ module ActiveResource
143
154
  end
144
155
 
145
156
  # The current page number of data.
157
+ #
158
+ # @return [String]
146
159
  def current_page_number
147
160
  (previous_page_number.to_i + 1).to_s
148
161
  end
149
162
 
150
163
  # The previous page number of data.
164
+ #
165
+ # @return [String, nil]
151
166
  def previous_page_number
152
167
  Hash(previous_page_params).fetch('page', {}).fetch('number', nil)
153
168
  end
154
169
 
155
170
  # The next page number of data.
171
+ #
172
+ # @return [String, nil]
156
173
  def next_page_number
157
174
  Hash(next_page_params).fetch('page', {}).fetch('number', nil)
158
175
  end
159
176
 
160
177
  # The last page number of data.
178
+ #
179
+ # @return [String, nil]
161
180
  def last_page_number
162
181
  Hash(last_page_params).fetch('page', {}).fetch('number', nil)
163
182
  end
164
183
 
165
184
  # Returns whether or not there is a previous page of data in the collection.
185
+ #
186
+ # @return [Boolean]
166
187
  def previous_page?
167
188
  !previous_page_number.nil?
168
189
  end
169
190
 
170
191
  # Returns whether or not there is a next page of data in the collection.
192
+ #
193
+ # @return [Boolean]
171
194
  def next_page?
172
195
  !next_page_number.nil?
173
196
  end
174
197
 
175
198
  # Returns whether or not the collection is on the last page.
199
+ #
200
+ # @return [Boolean]
176
201
  def last_page?
177
202
  last_page_number.nil?
178
203
  end
179
204
 
180
205
  private
181
206
 
207
+ # Start a new collection.
208
+ #
209
+ # @param params [Hash]
210
+ # @return [PaginatedCollection]
211
+ def updated_collection(params)
212
+ resource_class.where(original_params.merge(params))
213
+ end
214
+
182
215
  def update_self(page)
183
216
  @elements = page.elements
184
217
  @next_page_params = page.next_page_params
@@ -1,8 +1,9 @@
1
- module ActiveResource # :nodoc: all
1
+ module ActiveResource
2
+ # @private
2
3
  module Validations
3
4
  # Loads the set of remote errors into the object's Errors based on the
4
5
  # content-type of the error-block received.
5
- def load_remote_errors(remote_errors, save_cache = false) #:nodoc:
6
+ def load_remote_errors(remote_errors, save_cache = false)
6
7
  if self.class.format == ActiveResource::Formats::JsonAPIFormat
7
8
  errors.from_json_api(remote_errors.response.body, save_cache)
8
9
  elsif self.class.format == ActiveResource::Formats[:json]
@@ -11,6 +12,7 @@ module ActiveResource # :nodoc: all
11
12
  end
12
13
  end
13
14
 
15
+ # @private
14
16
  class Errors
15
17
  def from_json_api(json, save_cache = false)
16
18
  raw_errors = decoded_errors(json)
@@ -1,4 +1,5 @@
1
- module ESP # :nodoc: all
1
+ module ESP
2
+ # @private
2
3
  class AddExternalAccountError < StandardError
3
4
  EXIT_CODES = {
4
5
  '12 characters' => 98,
@@ -20,6 +21,7 @@ module ESP # :nodoc: all
20
21
  end
21
22
  end
22
23
 
24
+ # @private
23
25
  class ExternalAccountCreator
24
26
  attr_reader :aws
25
27
 
@@ -27,6 +29,7 @@ module ESP # :nodoc: all
27
29
  @aws = AWSClients.new
28
30
  end
29
31
 
32
+ # @return [ESP::ExternalAccount]
30
33
  def create
31
34
  fail ESP::AddExternalAccountError, aws.errors.full_messages.join(', ') unless aws.valid?
32
35