Module: Canistor

Defined in:
lib/canistor.rb,
lib/canistor/plugin.rb,
lib/canistor/handler.rb,
lib/canistor/subject.rb,
lib/canistor/version.rb,
lib/canistor/authorization.rb,
lib/canistor/error_handler.rb,
lib/canistor/storage/bucket.rb,
lib/canistor/storage/object.rb

Overview

Replacement for the HTTP handler in the AWS SDK that mocks all interaction with S3 just above the HTTP level.

The mock implementation is turned on by removing the NetHttp handlers that comes with the library by the Canistor handler.

Aws::S3::Client.remove_plugin(Seahorse::Client::Plugins::NetHttp)
Aws::S3::Client.add_plugin(Canistor::Plugin)

The Canistor instance then needs to be configured with buckets and credentials to be useful. It can be configured using either the config method on the instance or by specifying the buckets one by one.

In the example below Canistor will have two accounts and three buckets. It also specifies which accounts can access the buckets.

Canistor.config(
  logger: Rails.logger,
  credentials: {
    'global' => {
      access_key_id: 'AKIAIXXXXXXXXXXXXXX1',
      secret_access_key: 'phRL+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1'
    },
    'accounting' => {
      access_key_id: 'AKIAIXXXXXXXXXXXXXX2',
      secret_access_key: 'phRL+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2'
    }
  },
  buckets: {
    'us-east-1' => {
      'com-procore-production-images' => ['global'],
      'com-procore-production-books' => ['global', 'accounting']
    },
    'eu-central-1' => {
      'com-procore-production-sales' => ['global']
    }
  }
)

Canistor implements basic interaction with buckets and objects. It also verifies authentication information. It does not implement control lists so all accounts have full access to the buckets and objects.

The mock can simulate a number of failures. These are triggered by setting the operation which needs to fail on the mock. For more information see [Canistor.fail].

In most cases you should configure the suite to clear the mock before running each example with [Canistor.clear].

Defined Under Namespace

Modules: Storage Classes: Authorization, ErrorHandler, Handler, Plugin, Subject

Constant Summary collapse

SUPPORTED_FAILURES =
[
  :internal_server_error,
  :reset_connection,
  :fatal,
  :store
]
VERSION =
'0.2.1'

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.credentialsObject

Returns the value of attribute credentials.



66
67
68
# File 'lib/canistor.rb', line 66

def credentials
  @credentials
end

.fail(*operations) ⇒ Object (readonly)

The mock can simulate a number of failures. These are triggered by setting the way we expect it to fail. Note that the AWS-SDK already helps you to recover from certain errors like :reset_connection. If you want these kinds of error to trigger a failure you have to call #fail more than then configured retry count.

 Canistor.fail(:reset_connection)
 Canistor.fail(
   :reset_connection,
   :reset_connection,
   :reset_connection,
   :reset_connection
 )

* reset_connection: Signals the library to handle a connection error
  (retryable)
* internal_server_error: Returns a 500 internal server error (retryable)
* fatal: Signals the library to handle a fatal error (fatal)

A less common problem is when S3 reports a successful write but fails to store the file. This means the PUT to the bucket will be successful, but GET and HEAD on the object fail, because it’s not there.

Canistor.fail(:store)


161
162
163
# File 'lib/canistor.rb', line 161

def fail
  @fail
end

.fail_mutexObject (readonly)

Returns the value of attribute fail_mutex.



68
69
70
# File 'lib/canistor.rb', line 68

def fail_mutex
  @fail_mutex
end

.loggerObject

Returns the value of attribute logger.



64
65
66
# File 'lib/canistor.rb', line 64

def logger
  @logger
end

.storeObject

Returns the value of attribute store.



65
66
67
# File 'lib/canistor.rb', line 65

def store
  @store
end

Class Method Details

.buckets=(buckets) ⇒ Object



103
104
105
106
107
108
109
110
111
# File 'lib/canistor.rb', line 103

def self.buckets=(buckets)
  buckets.each do |region, attributes|
    attributes.each do |bucket, access_key_ids|
      bucket = create_bucket(region, bucket)
      bucket.allow_access_to(access_key_ids)
      bucket
    end
  end
end

.clearObject

Clears the state of the mock. Leaves all the credentials and buckets but removes all objects and mocked responses.



191
192
193
194
195
196
197
198
# File 'lib/canistor.rb', line 191

def self.clear
  @fail = []
  @store.each do |region, buckets|
    buckets.each do |bucket_name, bucket|
      bucket.clear
    end
  end
end

.config(config) ⇒ Object



124
125
126
127
128
# File 'lib/canistor.rb', line 124

def self.config(config)
  config.each do |section, attributes|
    public_send("#{section}=", attributes)
  end
end

.create_bucket(region, bucket_name) ⇒ Object

Configures a bucket in the mock implementation. Use #allow_access_to on the Container object returned by this method to configure who may access the bucket.



116
117
118
119
120
121
122
# File 'lib/canistor.rb', line 116

def self.create_bucket(region, bucket_name)
  store[region] ||= {}
  store[region][bucket_name] = Canistor::Storage::Bucket.new(
    region: region,
    name: bucket_name
  )
end

.find_credentials(authorization) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/canistor.rb', line 76

def self.find_credentials(authorization)
  if authorization.access_key_id
    credentials.each do |attributes|
      if authorization.access_key_id == attributes[:access_key_id]
        return Aws::Credentials.new(
          attributes[:access_key_id],
          attributes[:secret_access_key]
        )
      end
    end
  end
  nil
end

.take_fail(operation, &block) ⇒ Object

Executes the block when the operation is in the failure queue and removes one instance of the operation.



177
178
179
180
181
182
183
184
185
186
187
# File 'lib/canistor.rb', line 177

def self.take_fail(operation, &block)
  fail_mutex.synchronize do
    if index = @fail.index(operation)
      begin
        block.call
      ensure
        @fail.delete_at(index)
      end
    end
  end
end