buckler 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ module Buckler
2
+
3
+ # Returns the Ruby executable on the user’s $PATH
4
+ def self.ruby_cmd
5
+ @ruby_cmd ||= find_executable0("ruby")
6
+ end
7
+
8
+ # Returns the Heroku executable on the user’s $PATH
9
+ def self.heroku_cmd
10
+ @heroku_cmd ||= find_executable0("heroku")
11
+ end
12
+
13
+ # True if the user has a Heroku and Ruby executable
14
+ def self.heroku_available?
15
+ ruby_cmd.present? && heroku_cmd.present?
16
+ end
17
+
18
+ # Fetches the given environment `variable_name` from the user’s Heroku project
19
+ def self.heroku_config_get(variable_name)
20
+
21
+ command_output, command_intake = IO.pipe
22
+
23
+ pid = Kernel.spawn(
24
+ "#{ruby_cmd} #{heroku_cmd} config:get #{variable_name}",
25
+ STDOUT => command_intake,
26
+ STDERR => command_intake
27
+ )
28
+
29
+ command_intake.close
30
+
31
+ _, status = Process.wait2(pid)
32
+
33
+ if status.exitstatus == 0
34
+ return command_output.read.to_s
35
+ else
36
+ return false
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,19 @@
1
+ module Buckler::Logging
2
+
3
+ def log(message)
4
+ STDOUT.print("#{message}\n")
5
+ end
6
+
7
+ def alert(message)
8
+ STDERR.print("#{message.dangerize}\n")
9
+ end
10
+
11
+ def verbose(message)
12
+ STDERR.print("#{message}\n") if $buckler_verbose_mode
13
+ end
14
+
15
+ end
16
+
17
+ module Buckler
18
+ extend Buckler::Logging
19
+ end
@@ -0,0 +1,19 @@
1
+ # When you call MakeMakefile methods, they write information to ./mkmf.conf.
2
+ # Please don’t do that, MakeMakefile.
3
+ module MakeMakefile::Logging
4
+ @logfile = File::NULL
5
+ end
6
+
7
+ # The Aws gem complains on STDOUT when you’re redirected to the right region.
8
+ # Silence warnings about region redirects, they’re unavoidable with this tool.
9
+ module Aws
10
+ module Plugins
11
+ class S3RequestSigner < Seahorse::Client::Plugin
12
+ class BucketRegionErrorHandler < Handler
13
+ def log_warning(*args)
14
+ # Intentional no-op
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ module Buckler
2
+
3
+ S3_BUCKET_REGIONS = {
4
+ "us-east-1" => "🇺🇸 US East (Virginia)",
5
+ "us-west-1" => "🇺🇸 US West (California)",
6
+ "us-west-2" => "🇺🇸 US West (Oregon)",
7
+ "sa-east-1" => "🇧🇷 South America (São Paulo)",
8
+ "eu-central-1" => "🇩🇪 Europe (Frankfurt)",
9
+ "eu-west-1" => "🇮🇪 Europe (Ireland)",
10
+ "ap-northeast-1" => "🇯🇵 Asia Pacific (Tokyo)",
11
+ "ap-northeast-2" => "🇰🇷 Asia Pacific (Seoul)",
12
+ "ap-south-1" => "🇮🇳 Asia Pacific (Mumbai)",
13
+ "ap-southeast-1" => "🇸🇬 Asia Pacific (Singapore)",
14
+ "ap-southeast-2" => "🇦🇺 Asia Pacific (Sydney)",
15
+ "cn-north-1" => "🇨🇳 China (Beijing)",
16
+ }.freeze
17
+
18
+ # True if the given name is a valid AWS region.
19
+ def self.valid_region?(name)
20
+ S3_BUCKET_REGIONS.keys.include?(name)
21
+ end
22
+
23
+ end
@@ -0,0 +1,24 @@
1
+ class String
2
+
3
+ # Returns a copy of this string with terminal escapes to bold it
4
+ def bold
5
+ "\033[1m#{self}\e[0m"
6
+ end
7
+
8
+ # Returns a copy of this string with terminal escapes to make it red
9
+ def dangerize
10
+ "\e[38;5;196m#{self}\e[0m"
11
+ end
12
+
13
+ # Returns a copy of this string with terminal escapes to make it a color
14
+ # Options: `:orange` or `:pink`
15
+ def bucketize(color = :orange)
16
+ case color
17
+ when :pink
18
+ "\e[38;5;206m#{self}\e[0m"
19
+ else
20
+ "\e[38;5;208m#{self}\e[0m"
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,48 @@
1
+ module Buckler
2
+ class ThreadDispatch
3
+
4
+ include Buckler::Logging
5
+
6
+ def initialize
7
+ @lambda_pool = []
8
+ @thread_pool = []
9
+ @max_threads = Etc.nprocessors * 2 # Twice the number of CPU cores available to Ruby
10
+ end
11
+
12
+ def queue(λ)
13
+ @lambda_pool << λ
14
+ end
15
+
16
+ def any_running_threads?
17
+ @thread_pool.any?{|s| s.status == "run"}
18
+ end
19
+
20
+ def running_thread_count
21
+ @thread_pool.select{|s| s.status == "run"}.count
22
+ end
23
+
24
+ def perform_and_wait
25
+
26
+ Thread.abort_on_exception = true
27
+
28
+ start_time = Time.now
29
+
30
+ @lambda_pool.each do |λ|
31
+ while running_thread_count >= @max_threads
32
+ verbose "Sleeping due to worker limit. #{running_thread_count} currently running."
33
+ sleep 0.2
34
+ end
35
+ @thread_pool << Thread.new(&λ)
36
+ end
37
+
38
+ verbose "All workers spawned, waiting for workers to finish"
39
+ while any_running_threads? do
40
+ sleep 0.2
41
+ end
42
+
43
+ return (Time.now - start_time).round(2)
44
+
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,14 @@
1
+ module Buckler
2
+
3
+ # Returns Buckler’s version number
4
+ def self.version
5
+ Gem::Version.new("1.0.0")
6
+ end
7
+
8
+ # Contains Buckler’s version number
9
+ module VERSION
10
+ MAJOR, MINOR, TINY, PRE = Buckler.version.segments
11
+ STRING = Buckler.version.to_s
12
+ end
13
+
14
+ end
@@ -0,0 +1,80 @@
1
+ class BucklerTest < Minitest::Test
2
+
3
+ # An S3 client for checking/cleaning the results of actions
4
+ # that the bucket executable should have performed
5
+
6
+ def initialize(*args)
7
+ @s3 = Aws::S3::Client.new(
8
+ region: "us-east-1",
9
+ access_key_id: ENV.fetch("AWS_ACCESS_KEY_ID"),
10
+ secret_access_key: ENV.fetch("AWS_SECRET_ACCESS_KEY"),
11
+ )
12
+ super
13
+ end
14
+
15
+ # Create a test bucket and return it
16
+
17
+ def create_test_bucket
18
+ test_name = "buckler-#{SecureRandom.hex(6)}"
19
+ bucket = Aws::S3::Bucket.new(test_name, client:@s3)
20
+ bucket.create
21
+ bucket.wait_until_exists
22
+ return [bucket, test_name]
23
+ end
24
+
25
+ # Fills a bucket with random, but testable objects
26
+
27
+ def load_bucket_with_objects(bucket)
28
+ 50.times do
29
+ bucket.put_object({
30
+ acl: "private",
31
+ body: SecureRandom.hex,
32
+ key: "#{SecureRandom.uuid}.txt",
33
+ content_type: "text/plain",
34
+ cache_control: "no-cache",
35
+ })
36
+ end
37
+ fail unless bucket.objects.to_a.many?
38
+ return true
39
+ end
40
+
41
+ # Runs the given buckler `command`
42
+ # Returns two values: the text that the process printed to STDOUT
43
+ # and the text the process printed to STDERR
44
+
45
+ def run_buckler_command(command)
46
+
47
+ command_stdout_ouput, command_stdout_intake = IO.pipe
48
+ command_stderr_ouput, command_stderr_intake = IO.pipe
49
+
50
+ environment = {
51
+ "AWS_ACCESS_KEY_ID" => ENV.fetch("AWS_ACCESS_KEY_ID"),
52
+ "AWS_SECRET_ACCESS_KEY" => ENV.fetch("AWS_SECRET_ACCESS_KEY"),
53
+ }
54
+
55
+ pid = Kernel.spawn(
56
+ environment,
57
+ "#{BUCKLER_EXECUTABLE} #{command}",
58
+ STDOUT => command_stdout_intake,
59
+ STDERR => command_stderr_intake,
60
+ )
61
+
62
+ command_stdout_intake.close
63
+ command_stderr_intake.close
64
+
65
+ Process.wait2(pid)
66
+
67
+ stdout_results = command_stdout_ouput.read
68
+ stderr_results = command_stderr_ouput.read
69
+
70
+ command_stdout_ouput.close
71
+ command_stderr_ouput.close
72
+
73
+ return [
74
+ stdout_results,
75
+ stderr_results
76
+ ]
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,25 @@
1
+ class BadOptionsTest < BucklerTest
2
+
3
+ def test_bad_credentials
4
+
5
+ # Errors about bad credentials
6
+ stdout, stderr = run_buckler_command("list --id CUTE_CATS --secret BUT_ALSO_DOGGIES")
7
+ assert stderr.include?("Invalid AWS Access Key"), "Error message should be printed"
8
+ assert stderr.include?("CUTE_CATS"), "Should repeat Key ID"
9
+
10
+ # Errors when the ID is correct but the secret is wrong
11
+ stdout, stderr = run_buckler_command("list --id #{ENV.fetch("AWS_ACCESS_KEY_ID")} --secret BUT_ALSO_DOGGIES")
12
+ assert stderr.include?("Invalid AWS Secret Access Key"), "Error mesage should be printed"
13
+ assert stderr.include?(ENV.fetch("AWS_ACCESS_KEY_ID")), "Should repeat Key ID"
14
+
15
+ end
16
+
17
+ def test_invaid_options
18
+
19
+ # Passing invalid options should not work
20
+ stdout, stderr = run_buckler_command("list --fake FAKE --uhh")
21
+ assert stderr.include?("illegal option"), "Error message should be printed"
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,27 @@
1
+ class CreateBucketTest < BucklerTest
2
+
3
+ def test_create_bucket
4
+
5
+ test_name = "buckler-#{SecureRandom.hex(6)}"
6
+ bucket = Aws::S3::Bucket.new(test_name, client:@s3)
7
+
8
+ # Test that a bucket was indeed created
9
+
10
+ stdout, stderr = run_buckler_command("create #{test_name}")
11
+ assert stdout.include?("available for use"), "“availble for use” text should be printed"
12
+ assert stdout.include?(test_name), "Bucket name should be repeated to user"
13
+ assert bucket.exists?, "Bucket should be created"
14
+
15
+ # Can’t create a bucket that already exisits
16
+
17
+ stdout, stderr = run_buckler_command("create #{test_name}")
18
+ assert stderr.include?("already exists"), "“already exists” text not printed"
19
+ assert stderr.include?(test_name), "Bucket name not repeated to user"
20
+
21
+ # Cleanup
22
+
23
+ bucket.delete! if bucket.exists?
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,21 @@
1
+ class DestroyBucketTest < BucklerTest
2
+
3
+ def test_destroy_bucket
4
+
5
+ bucket, test_name = create_test_bucket
6
+
7
+ # Test that a bucket was indeed destroyed
8
+
9
+ stdout, stderr = run_buckler_command("destroy #{test_name} --confirm #{test_name}")
10
+ assert stdout.include?("destroyed"), "Destruction confirmation should be printed"
11
+ assert stdout.include?(test_name), "Bucket name should be repeated"
12
+
13
+ # Can’t destroy a bucket that doesn’t exist
14
+
15
+ stdout, stderr = run_buckler_command("destroy #{test_name}")
16
+ assert stderr.include?("No such bucket"), "Rejection message should be printed"
17
+ assert stderr.include?(test_name), "Bucket name should be repeated to user"
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,25 @@
1
+ class EmptyBucketTest < BucklerTest
2
+
3
+ def test_empty_bucket
4
+
5
+ # Fill a bucket and empty it
6
+
7
+ bucket, test_name = create_test_bucket
8
+ load_bucket_with_objects(bucket)
9
+ stdout, stderr = run_buckler_command("empty #{test_name} --confirm #{test_name}")
10
+ assert bucket.objects.none?, "The bucket should be emptied"
11
+
12
+ # Can’t empty a non-existant bucket
13
+
14
+ test_name = "buckler-#{SecureRandom.hex(6)}"
15
+ stdout, stderr = run_buckler_command("empty #{test_name}")
16
+ assert stderr.include?("No such"), "Error message should be printed"
17
+ assert stderr.include?(test_name), "Bucket name should be repeated"
18
+
19
+ # Cleanup
20
+
21
+ bucket.delete! if bucket.exists?
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,20 @@
1
+ class ListBucketsTest < BucklerTest
2
+
3
+ def test_list_buckets
4
+
5
+ bucket, test_name = create_test_bucket
6
+
7
+ # Test that a bucket is listed
8
+
9
+ stdout, stderr = run_buckler_command("list")
10
+ assert stdout.include?(test_name), "Bucket name should be repeated"
11
+ assert stdout.include?("us-east-1"), "Bucket region should be repeated"
12
+ assert stdout.include?("NAME"), "Header should be printed"
13
+
14
+ # Cleanup
15
+
16
+ bucket.delete! if bucket.exists?
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,15 @@
1
+ class ListRegionsTest < BucklerTest
2
+
3
+ def test_list_regions
4
+
5
+ stdout, stderr = run_buckler_command("regions")
6
+ assert stdout.include?("REGION"), "Header should be printed"
7
+ assert stdout.include?("us-west-1"), "Other rows should be printed"
8
+ assert stdout.include?("eu-central-1"), "Other rows should be printed"
9
+ assert stdout.include?("🇰🇷"), "Other rows should be printed"
10
+ assert stdout.include?("China"), "Other rows should be printed"
11
+ assert stdout.include?("Tokyo"), "Other rows should be printed"
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,30 @@
1
+ class SyncBucketsTest < BucklerTest
2
+
3
+ def test_sync_buckets
4
+
5
+ # Fill a bucket and sync it
6
+
7
+ source_bucket, source_name = create_test_bucket
8
+ target_bucket, target_name = create_test_bucket
9
+ load_bucket_with_objects(source_bucket)
10
+
11
+ stdout, stderr = run_buckler_command("sync #{source_name} #{target_name} --confirm #{target_name}")
12
+ assert target_bucket.objects.to_a.many?, "The target bucket should be filled with goodies"
13
+ assert target_bucket.objects.first.object.content_type.include?("text/plain"), "Content-Type headers should have synced"
14
+ assert target_bucket.objects.first.object.cache_control.include?("no-cache"), "Cache-Control headers should have synced"
15
+
16
+ # Can’t sync bad buckets
17
+
18
+ bad_name = "bucker-#{SecureRandom.hex(6)}"
19
+ stdout, stderr = run_buckler_command("sync #{source_name} #{bad_name} --confirm #{bad_name}")
20
+ assert stderr.include?("No such bucket"), "“No such” message should be printed"
21
+ assert stderr.include?(bad_name), "Bucket name should be repeated"
22
+
23
+ # Cleanup
24
+
25
+ source_bucket.delete! if source_bucket.exists?
26
+ target_bucket.delete! if target_bucket.exists?
27
+
28
+ end
29
+
30
+ end