buckler 1.0.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.
@@ -0,0 +1,22 @@
1
+ module Buckler
2
+
3
+ def self.destroy_bucket!(name:, confirmation:nil)
4
+
5
+ connect_to_s3!
6
+ @bucket = get_bucket!(name)
7
+ require_confirmation!(name_required:name, confirmation:confirmation)
8
+
9
+ if @bucket.versioning.status == "Enabled"
10
+ log "The bucket #{name.bucketize} has versioning enabled, it cannot be deleted."
11
+ log "You must disable versioning in the AWS Mangement Console."
12
+ exit false
13
+ end
14
+
15
+ log "Destroying bucket #{name.bucketize}…"
16
+ @bucket.delete!(max_attempts:3)
17
+ log "Bucket #{name.bucketize} was destroyed ✔"
18
+ exit true
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,16 @@
1
+ module Buckler
2
+
3
+ def self.empty_bucket!(name:, confirmation:nil)
4
+
5
+ connect_to_s3!
6
+ @bucket = get_bucket!(name)
7
+ require_confirmation!(name_required:name, confirmation:confirmation)
8
+
9
+ log "Deleting all objects in bucket #{name.bucketize}…"
10
+ @bucket.clear!
11
+ log "Bucket #{name.bucketize} is now empty ✔"
12
+ exit true
13
+
14
+ end
15
+
16
+ end
File without changes
@@ -0,0 +1,21 @@
1
+ module Buckler
2
+
3
+ def self.list_buckets!
4
+
5
+ connect_to_s3!
6
+
7
+ verbose "Fetching buckets visible to #{@aws_access_key_id}…"
8
+ table = [["NAME", "REGION", "VERSIONING"]]
9
+
10
+ @s3.list_buckets.buckets.each do |bucket|
11
+ region = @s3.get_bucket_location(bucket:bucket.name).location_constraint.presence || "us-east-1"
12
+ versioning = @s3.get_bucket_versioning(bucket:bucket.name).status.presence || "Not Configured"
13
+ table << [bucket.name, region, versioning]
14
+ end
15
+
16
+ puts_table!(table)
17
+ exit true
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,23 @@
1
+ module Buckler
2
+
3
+ def self.list_regions!
4
+
5
+ table = [["REGION", "NAME", nil]]
6
+
7
+ S3_BUCKET_REGIONS.each do |name, human_name|
8
+ case name
9
+ when "us-east-1"
10
+ table << [name, human_name, "Default region"]
11
+ when "cn-north-1"
12
+ table << [name, human_name, "Requires chinese account"]
13
+ else
14
+ table << [name, human_name, nil]
15
+ end
16
+ end
17
+
18
+ puts_table!(table)
19
+ exit true
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,106 @@
1
+ module Buckler
2
+
3
+ def self.sync_buckets!(source_name:, target_name:, confirmation:nil)
4
+
5
+ unless source_name.present? && target_name.present?
6
+ alert "You must provide both a source bucket and a target bucket"
7
+ alert "Usage: bucket sync <source-bucket> <target-bucket>"
8
+ exit false
9
+ end
10
+
11
+ unless source_name != target_name
12
+ alert "The source bucket name and target bucket name must be different"
13
+ exit false
14
+ end
15
+
16
+ connect_to_s3!
17
+ @source_bucket = get_bucket!(source_name)
18
+ @target_bucket = get_bucket!(target_name)
19
+
20
+ source_name = @source_bucket.name.bucketize(:pink).freeze
21
+ target_name = @target_bucket.name.bucketize.freeze
22
+
23
+ require_confirmation!(name_required:@target_bucket.name, confirmation:confirmation, additional_lines:[
24
+ "The contents of #{source_name} will be synced into #{target_name}.",
25
+ "Objects in #{target_name} that aren’t in the source bucket will be removed.",
26
+ ])
27
+
28
+ log "Syncing #{source_name} into #{target_name}…"
29
+ log "Fetching bucket file lists…"
30
+
31
+ @source_bucket_keys = @source_bucket.objects.collect(&:key)
32
+ @target_bucket_keys = @target_bucket.objects.collect(&:key)
33
+
34
+ # -------------------------------------------------------------------------
35
+ # Delete bucket differences
36
+ # -------------------------------------------------------------------------
37
+
38
+ @keys_to_delete = @target_bucket_keys - @source_bucket_keys
39
+
40
+ @dispatch = Buckler::ThreadDispatch.new
41
+
42
+ log "Deleting unshared objects from target bucket…"
43
+
44
+ @keys_to_delete.lazy.each do |key|
45
+ @dispatch.queue(lambda {
46
+ log "Deleting #{target_name}/#{key}"
47
+ @target_bucket.object(key).delete
48
+ })
49
+ end
50
+
51
+ time_elapsed = @dispatch.perform_and_wait
52
+ log "Unshared objects deleted from target bucket (#{time_elapsed} seconds) ✔"
53
+
54
+ # -------------------------------------------------------------------------
55
+ # Sync files
56
+ # -------------------------------------------------------------------------
57
+
58
+ @dispatch = Buckler::ThreadDispatch.new
59
+
60
+ @source_bucket_keys.lazy.each do |object_key|
61
+
62
+ @dispatch.queue(lambda {
63
+
64
+ source_object = Aws::S3::Object.new(@source_bucket.name, object_key, client:@s3)
65
+ target_object = Aws::S3::Object.new(@target_bucket.name, object_key, client:@s3)
66
+
67
+ options = {
68
+ storage_class: source_object.storage_class,
69
+ metadata: source_object.metadata,
70
+ content_encoding: source_object.content_encoding,
71
+ content_language: source_object.content_language,
72
+ content_type: source_object.content_type,
73
+ cache_control: source_object.cache_control,
74
+ expires: source_object.expires,
75
+ }
76
+
77
+ if source_object.content_disposition.present?
78
+ options[:content_disposition] = ActiveSupport::Inflector.transliterate(source_object.content_disposition, "")
79
+ end
80
+
81
+ if source_object.content_length > 5242882 # 5 megabytes + 2 bytes
82
+ options[:multipart_copy] = true
83
+ options[:content_length] = source_object.content_length
84
+ end
85
+
86
+ target_object.copy_from(source_object, options)
87
+ target_object.acl.put({
88
+ access_control_policy: {
89
+ grants: source_object.acl.grants,
90
+ owner: source_object.acl.owner,
91
+ }
92
+ })
93
+
94
+ log "Copied #{source_name} → #{target_name}/#{object_key}"
95
+
96
+ })
97
+
98
+ end
99
+
100
+ time_elapsed = @dispatch.perform_and_wait
101
+ log "#{@source_bucket_keys.count} objects synced in #{target_name} (#{time_elapsed} seconds) ✔"
102
+ exit true
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,81 @@
1
+ module Buckler
2
+
3
+ # Returns the discovered AWS Access Key ID
4
+ # Prerequisite: `Buckler.discover_aws_credentials!`
5
+ def self.aws_access_key_id
6
+ @aws_access_key_id
7
+ end
8
+
9
+ # Returns an Aws::S3::Client in the given `region`
10
+ # Prerequisite: `Buckler.discover_aws_credentials!`
11
+ def self.connect_to_s3!(region:"us-east-1")
12
+ return @s3 if @s3.present?
13
+ @s3 = Aws::S3::Client.new(
14
+ region: region,
15
+ access_key_id: @aws_access_key_id,
16
+ secret_access_key: @aws_secret_access_key,
17
+ )
18
+ return @s3
19
+ end
20
+
21
+ # Attempts to find the AWS Access Key ID and Secret Access Key
22
+ # by searching the command line paramters, the environment, the .env, and Heroku in that order.
23
+ # The parameters are the values of --id and --secret on the command line.
24
+ # The program ends if credentials cannot be discovered.
25
+ def self.discover_aws_credentials!(key_id:nil, key:nil)
26
+
27
+ verbose "Attempting to find AWS credentials…"
28
+
29
+ # Try to find keys as command line parameters, if the invoker has set them directly
30
+ if key_id.present? && key.present?
31
+ verbose "The Access Key ID and Secret Access Key were set as command line options ✔"
32
+ @aws_access_key_id = key_id
33
+ @aws_secret_access_key = key
34
+ return true
35
+ end
36
+
37
+ # Try to find keys in the current environment, if the invoker has set them directly
38
+ key_id = ENV["AWS_ACCESS_KEY_ID"]
39
+ key = ENV["AWS_SECRET_ACCESS_KEY"]
40
+ if key_id.present? && key.present?
41
+ verbose "AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY found as environment variables ✔"
42
+ @aws_access_key_id = key_id
43
+ @aws_secret_access_key = key
44
+ return true
45
+ end
46
+
47
+ # Try to find keys in a .env file in this directory
48
+ Dotenv.load
49
+ key_id = ENV["AWS_ACCESS_KEY_ID"]
50
+ key = ENV["AWS_SECRET_ACCESS_KEY"]
51
+ if key_id.present? && key.present?
52
+ verbose "AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY found in the .env file ✔"
53
+ @aws_access_key_id = key_id
54
+ @aws_secret_access_key = key
55
+ return true
56
+ end
57
+
58
+ # Try to find keys by asking Heroku about the project in this directory
59
+ if heroku_available?
60
+ key_id = heroku_config_get("AWS_ACCESS_KEY_ID")
61
+ key = heroku_config_get("AWS_SECRET_ACCESS_KEY")
62
+ if key_id.present? && key.present?
63
+ verbose "AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY found on your Heroku application ✔"
64
+ @aws_access_key_id = key_id
65
+ @aws_secret_access_key = key
66
+ return true
67
+ end
68
+ end
69
+
70
+ alert "Could not discover any AWS credentials."
71
+ alert "Set command line options --id and --secret"
72
+ alert "Or, set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as environment variables."
73
+ alert "Or, set them in a .env file in this directory."
74
+ if heroku_available?
75
+ alert "Or, set them on a Heroku application in this directory with `heroku config:set`."
76
+ end
77
+ exit false
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,271 @@
1
+ module Buckler::Commands; end
2
+
3
+ Buckler::Commands::Root = Cri::Command.define do
4
+
5
+ name "bucket"
6
+ usage "bucket <command> [options]"
7
+ summary "peform common actions on Amazon S3 buckets"
8
+ description %{
9
+ Buckler allows you perform common actions on your Amazon S3 buckets.
10
+
11
+ Run #{"bucket help <command>".bold} for more information about the commands below.
12
+
13
+ You will need a AWS Access Key ID and AWS Secret Access Key pair with
14
+ permission to mange your S3 buckets. Do not use your root keys.
15
+ Generate a new set of keys with S3 permissions only.
16
+ https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#lock-away-credentials
17
+
18
+ When you run the bucket command, Buckler tries to automatically
19
+ discover AWS credentials around your working directory.
20
+
21
+ #{"Dotenv:".bold} If the current folder has a file named .env, Buckler will look
22
+ for variables called AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in the file.
23
+ See Heroku’s documentation on this environment file format.
24
+ https://devcenter.heroku.com/articles/heroku-local#set-up-your-local-environment-variables
25
+
26
+ #{"Heroku:".bold} If the current folder has a Git repository with a Heroku remote,
27
+ Buckler will ask your Heroku application for variables
28
+ named AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY using heroku config:get
29
+
30
+ #{"Command Line:".bold} You can set the pair directly by providing the
31
+ command line options --id and --secret:
32
+
33
+ bucket list --id YOUR_AWS_ID --secret YOUR_AWS_SECRET
34
+
35
+ #{"Environment:".bold} You can set the pair directly as environment variables.
36
+
37
+ AWS_ACCESS_KEY_ID=your-id AWS_SECRET_ACCESS_KEY=your-secret bucket list
38
+ }
39
+
40
+ flag :v, :verbose, "Output additional information for all commands"
41
+ option nil, :id, "Your AWS Access Key ID", argument: :required
42
+ option nil, :secret, "Your AWS Secret Access Key", argument: :required
43
+
44
+ run do |opts, args, cmd|
45
+ puts Buckler::Commands::Root.help
46
+ end
47
+
48
+ end
49
+
50
+ Buckler::Commands::Version = Cri::Command.define do
51
+
52
+ name "version"
53
+ usage "version"
54
+ summary "Print Buckler’s version"
55
+ description %{
56
+ Prints Buckler’s version number.
57
+ }
58
+
59
+ run do |opts, args, cmd|
60
+ log Buckler::VERSION::STRING
61
+ end
62
+
63
+ end
64
+
65
+ Buckler::Commands::Regions = Cri::Command.define do
66
+
67
+ name "regions"
68
+ usage "regions"
69
+ summary "list all Amazon S3 regions"
70
+ description %{
71
+ Prints a list of all Amazon S3 bucket region names and where they are on Earth.
72
+
73
+ When creating buckets, you should select the region that is
74
+ closest to the users or computers that will be using the bucket.
75
+
76
+ Heroku applications in Heroku’s America region should use Amazon region us-east-1.
77
+
78
+ Heroku applications in Heroku’s Europe region should use Amazon region eu-west-1.
79
+ }
80
+
81
+ run do |opts, args, cmd|
82
+ Buckler.list_regions!
83
+ end
84
+
85
+ end
86
+
87
+ Buckler::Commands::List = Cri::Command.define do
88
+
89
+ name "list"
90
+ usage "list"
91
+ summary "List all buckets on your account"
92
+ description %{
93
+ Prints a list of all Amazon S3 buckets on your account.
94
+
95
+ “All” may be releative, AWS access keys can be generated without
96
+ the power to see every bucket available to the master keys.
97
+ }
98
+
99
+ run do |opts, args, cmd|
100
+ $buckler_verbose_mode = true if opts[:verbose].present?
101
+ Buckler.discover_aws_credentials!(key_id:opts[:id], key:opts[:secret])
102
+ Buckler.list_buckets!
103
+ end
104
+
105
+ end
106
+
107
+ Buckler::Commands::Create = Cri::Command.define do
108
+
109
+ name "create"
110
+ usage "create <bucket-name>"
111
+ summary "Create a new bucket"
112
+ description %{
113
+ Creates a new bucket on your Amazon S3 account with the given name.
114
+ The bucket will be empty. The bucket is created in AWS region us-east-1 unless
115
+ you specify a different region with the --region option.
116
+ Get a list of all valid regions with `Buckler regions`.
117
+
118
+ For maximum compatability, Amazon always recomends that you use
119
+ DNS and TLS-safe bucket names,
120
+ which contain only the ASCII characters a-z, 0-9, or the hypen (-)
121
+
122
+ Bucket names with dots or uppercase letters may be unable
123
+ to use certain Amazon Web Service features.
124
+
125
+ Some example good bucket names:
126
+ your-project-name,
127
+ your-project-name-staging,
128
+ and your-project-name-dev1
129
+
130
+ For more information:
131
+ http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
132
+ }
133
+
134
+ option :r, :region, "The AWS region where the bucket should be created (default: us-east-1)", argument: :required
135
+
136
+ run do |opts, args, cmd|
137
+ $buckler_verbose_mode = true if opts[:verbose].present?
138
+ Buckler.discover_aws_credentials!(key_id:opts[:id], key:opts[:secret])
139
+ Buckler.create_bucket!(name:args.first.to_s, region:opts[:region])
140
+ end
141
+
142
+ end
143
+
144
+ Buckler::Commands::Empty = Cri::Command.define do
145
+
146
+ name "empty"
147
+ usage "empty <bucket-name>"
148
+ summary "Delete all files in a bucket"
149
+ description %{
150
+ Discards all objects in the target bucket. The result will be an empty bucket.
151
+
152
+ If you have not set up versioning for your bucket,
153
+ or you have not set up Amazon Glacier backups for your bucket,
154
+ you will be unable to recover any deleted objects.
155
+
156
+ For more information:
157
+ https://docs.aws.amazon.com/AmazonS3/latest/dev/delete-or-empty-bucket.html
158
+ }
159
+
160
+ option :c, :confirm, "Confirm destructive actions without prompting", argument: :required
161
+
162
+ run do |opts, args, cmd|
163
+ $buckler_verbose_mode = true if opts[:verbose].present?
164
+ Buckler.discover_aws_credentials!(key_id:opts[:id], key:opts[:secret])
165
+ Buckler.empty_bucket!(name:args.first.to_s, confirmation:opts[:confirm])
166
+ end
167
+
168
+ end
169
+
170
+ Buckler::Commands::Destroy = Cri::Command.define do
171
+
172
+ name "destroy"
173
+ usage "destroy <bucket-name>"
174
+ summary "Delete all files in a bucket and remove it"
175
+ description %{
176
+ Discards all objects in the target bucket and then removes
177
+ the bucket from Amazon S3.
178
+
179
+ Amazon may not immediately make the bucket name
180
+ available for use again.
181
+
182
+ You CANNOT delete a bucket that has versioning enabled.
183
+ You must first disable versioning on the AWS Mangement Console.
184
+
185
+ For more information:
186
+ https://docs.aws.amazon.com/AmazonS3/latest/dev/delete-or-empty-bucket.html
187
+ https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
188
+ }
189
+
190
+ option :c, :confirm, "Confirm destructive actions without prompting", argument: :required
191
+
192
+ run do |opts, args, cmd|
193
+ $buckler_verbose_mode = true if opts[:verbose].present?
194
+ Buckler.discover_aws_credentials!(key_id:opts[:id], key:opts[:secret])
195
+ Buckler.destroy_bucket!(name:args.first.to_s, confirmation:opts[:confirm])
196
+ end
197
+
198
+ end
199
+
200
+ Buckler::Commands::Sync = Cri::Command.define do
201
+
202
+ name "sync"
203
+ usage "sync <source-bucket> <target-bucket>"
204
+ summary "Copy the contents of one bucket into another"
205
+ description %{
206
+ Copies all objects in the source bucket into the target bucket.
207
+ Objects in the target bucket that don’t exist in the source bucket will be deleted.
208
+ The end result will be two buckets with identical objects.
209
+
210
+ The following properties on each object are also transfered:
211
+ ACLs,
212
+ Amazon S3 metadata,
213
+ Amazon S3 storage class,
214
+ Cache-Control header,
215
+ Content-Type header,
216
+ Content-Encoding header,
217
+ Content-Disposition header,
218
+ Content-Language header,
219
+ and Expires header.
220
+
221
+ The source bucket will not be changed.
222
+
223
+ If you have not set up versioning for your bucket,
224
+ or you have not set up Amazon Glacier archives for your bucket,
225
+ you will be unable to recover any objects deleted from the target bucket.
226
+ }
227
+
228
+ option :c, :confirm, "Confirm destructive actions without prompting", argument: :required
229
+
230
+ run do |opts, args, cmd|
231
+ $buckler_verbose_mode = true if opts[:verbose].present?
232
+ Buckler.discover_aws_credentials!(key_id:opts[:id], key:opts[:secret])
233
+ Buckler.sync_buckets!(source_name:args.first.to_s, target_name:args.second.to_s, confirmation:opts[:confirm])
234
+ end
235
+
236
+ end
237
+
238
+ Buckler::Commands::Help = Cri::Command.define do
239
+ name "help"
240
+ usage "buckler help"
241
+ summary "Show help for a command"
242
+ description "x"
243
+ run do |opts, args, cmd|
244
+ case args.first.to_s
245
+ when "list"
246
+ puts Buckler::Commands::List.help
247
+ when "regions"
248
+ puts Buckler::Commands::Regions.help
249
+ when "create"
250
+ puts Buckler::Commands::Create.help
251
+ when "empty"
252
+ puts Buckler::Commands::Empty.help
253
+ when "destroy"
254
+ puts Buckler::Commands::Destroy.help
255
+ when "sync"
256
+ puts Buckler::Commands::Sync.help
257
+ else
258
+ puts Buckler::Commands::Root.help
259
+ end
260
+ exit true
261
+ end
262
+ end
263
+
264
+ Buckler::Commands::Root.add_command(Buckler::Commands::Version)
265
+ Buckler::Commands::Root.add_command(Buckler::Commands::Help)
266
+ Buckler::Commands::Root.add_command(Buckler::Commands::Regions)
267
+ Buckler::Commands::Root.add_command(Buckler::Commands::List)
268
+ Buckler::Commands::Root.add_command(Buckler::Commands::Create)
269
+ Buckler::Commands::Root.add_command(Buckler::Commands::Empty)
270
+ Buckler::Commands::Root.add_command(Buckler::Commands::Destroy)
271
+ Buckler::Commands::Root.add_command(Buckler::Commands::Sync)