elastic_beans 0.1.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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +9 -0
  5. data/LICENSE.md +21 -0
  6. data/README.md +184 -0
  7. data/Rakefile +6 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/circle.yml +20 -0
  11. data/elastic_beans.gemspec +31 -0
  12. data/exe/beans +198 -0
  13. data/lib/elastic_beans/application.rb +127 -0
  14. data/lib/elastic_beans/application_version.rb +202 -0
  15. data/lib/elastic_beans/aws/cloudformation_stack.rb +66 -0
  16. data/lib/elastic_beans/command/configure.rb +184 -0
  17. data/lib/elastic_beans/command/create.rb +150 -0
  18. data/lib/elastic_beans/command/deploy.rb +77 -0
  19. data/lib/elastic_beans/command/exec.rb +37 -0
  20. data/lib/elastic_beans/command/set_env.rb +77 -0
  21. data/lib/elastic_beans/command/talk.rb +74 -0
  22. data/lib/elastic_beans/command/version.rb +17 -0
  23. data/lib/elastic_beans/command.rb +12 -0
  24. data/lib/elastic_beans/configuration_template/base.rb +114 -0
  25. data/lib/elastic_beans/configuration_template/exec.rb +50 -0
  26. data/lib/elastic_beans/configuration_template/scheduler.rb +20 -0
  27. data/lib/elastic_beans/configuration_template/webserver.rb +49 -0
  28. data/lib/elastic_beans/configuration_template/worker.rb +27 -0
  29. data/lib/elastic_beans/configuration_template.rb +197 -0
  30. data/lib/elastic_beans/dns_entry.rb +127 -0
  31. data/lib/elastic_beans/environment/exec.rb +23 -0
  32. data/lib/elastic_beans/environment/scheduler.rb +23 -0
  33. data/lib/elastic_beans/environment/webserver.rb +23 -0
  34. data/lib/elastic_beans/environment/worker.rb +29 -0
  35. data/lib/elastic_beans/environment.rb +300 -0
  36. data/lib/elastic_beans/error/environments_not_ready.rb +15 -0
  37. data/lib/elastic_beans/error.rb +15 -0
  38. data/lib/elastic_beans/exec/ebextension.yml +10 -0
  39. data/lib/elastic_beans/exec/elastic_beans_exec.conf +15 -0
  40. data/lib/elastic_beans/exec/init.rb +50 -0
  41. data/lib/elastic_beans/exec/run_command.sh +13 -0
  42. data/lib/elastic_beans/exec/sqs_consumer.rb +75 -0
  43. data/lib/elastic_beans/network.rb +44 -0
  44. data/lib/elastic_beans/rack/exec.rb +63 -0
  45. data/lib/elastic_beans/scheduler/ebextension.yml +4 -0
  46. data/lib/elastic_beans/ui.rb +31 -0
  47. data/lib/elastic_beans/version.rb +3 -0
  48. data/lib/elastic_beans.rb +9 -0
  49. metadata +218 -0
@@ -0,0 +1,150 @@
1
+ require "timeout"
2
+ require "ruby-progressbar"
3
+ require "elastic_beans/dns_entry"
4
+ require "elastic_beans/error"
5
+ require "elastic_beans/environment/webserver"
6
+
7
+ module ElasticBeans
8
+ module Command
9
+ class Create
10
+ USAGE = "create -a APPLICATION [-t TYPE] [-q QUEUE] [-d PRETTY_DNS] ENVIRONMENT"
11
+ DESC = "Create a new environment in Elastic Beanstalk and attach it to relevant resources"
12
+ LONG_DESC = <<-LONG_DESC
13
+ Create a new environment in Elastic Beanstalk and attach it to relevant resources
14
+ The environment type must be one of the recognized types: webserver, worker, or scheduled.
15
+
16
+ Requires the Elastic Beanstalk application name.
17
+ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
18
+ LONG_DESC
19
+
20
+ def initialize(
21
+ environment_name:,
22
+ dns: nil,
23
+ queue: nil,
24
+ tags:,
25
+ application:,
26
+ ui:,
27
+ elastic_beanstalk:,
28
+ route53:,
29
+ s3:
30
+ )
31
+ @environment_name = environment_name
32
+ @environment_type = environment_name
33
+ @dns = dns
34
+ if @environment_type == "worker"
35
+ @queue = queue || "default"
36
+ @environment_name = "#{@environment_name}-#{@queue}"
37
+ end
38
+ @tags = tags
39
+ @application = application
40
+ @ui = ui
41
+ @elastic_beanstalk = elastic_beanstalk
42
+ @route53 = route53
43
+ @s3 = s3
44
+ end
45
+
46
+ def run
47
+ ui.debug { "Creating application version from code at #{Dir.pwd}..." }
48
+ version = application.deployed_version || ElasticBeans::ApplicationVersion.create(
49
+ working_directory: Dir.pwd,
50
+ application: application,
51
+ elastic_beanstalk: elastic_beanstalk,
52
+ s3: s3,
53
+ )
54
+ environment = ElasticBeans::Environment.new_by_type(
55
+ environment_type,
56
+ suffix: environment_name,
57
+ queue: queue,
58
+ application: application,
59
+ elastic_beanstalk: elastic_beanstalk,
60
+ )
61
+
62
+ dns_thread = Thread.new do
63
+ if environment_type == "webserver" && dns_entry
64
+ ui.debug { "Updating DNS entry `#{dns_entry.name}'..." }
65
+ dns_entry.update(dns_value(environment))
66
+ end
67
+ end
68
+
69
+ ui.info("Creating environment `#{environment.name}'...")
70
+ progressbar = ProgressBar.create(title: "Creating", total: nil, output: ui.stdout)
71
+ environment_thread = Thread.new do
72
+ environment.create(
73
+ tags: tags,
74
+ version: version.version_label,
75
+ )
76
+ end
77
+
78
+ loop do
79
+ sleep 1
80
+ progressbar.increment
81
+ if !environment_thread.alive? && !dns_thread.alive?
82
+ progressbar.total = progressbar.progress
83
+ break
84
+ end
85
+ end
86
+
87
+ environment_thread.join
88
+ dns_thread.join
89
+ ui.info("Created!")
90
+ if environment_type == "webserver"
91
+ if dns_entry
92
+ host = dns_entry.name
93
+ else
94
+ host = environment.cname
95
+ end
96
+ ui.info("#{application.name} reachable at https://#{host}/")
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ attr_reader(
103
+ :application,
104
+ :dns,
105
+ :environment_name,
106
+ :environment_type,
107
+ :queue,
108
+ :tags,
109
+ :elastic_beanstalk,
110
+ :route53,
111
+ :s3,
112
+ :ui,
113
+ )
114
+
115
+ def dns_entry
116
+ if dns.nil?
117
+ return nil
118
+ end
119
+
120
+ DnsEntry.new(dns, route53: route53)
121
+ end
122
+
123
+ def dns_value(environment)
124
+ Timeout.timeout(1200) do
125
+ loop do
126
+ sleep 1
127
+ begin
128
+ value = environment.cname
129
+ return value if value
130
+ rescue ElasticBeans::Environment::Webserver::MissingEnvironmentError
131
+ # keep trying
132
+ end
133
+ end
134
+ end
135
+ rescue Timeout::Error
136
+ raise UnhealthyEnvironmentError.new(environment_name: environment.name)
137
+ end
138
+
139
+ class UnhealthyEnvironmentError < ElasticBeans::Error
140
+ def initialize(environment_name:)
141
+ @environment_name = environment_name
142
+ end
143
+
144
+ def message
145
+ "Environment `#{@environment_name}' is still not available"
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,77 @@
1
+ require "elastic_beans/error/environments_not_ready"
2
+
3
+ module ElasticBeans
4
+ module Command
5
+ class Deploy
6
+ USAGE = "deploy -a APPLICATION"
7
+ DESC = "Deploy the HEAD git commit to all environments in Elastic Beanstalk"
8
+ LONG_DESC = <<-LONG_DESC
9
+ Deploy the HEAD git commit to all environments in Elastic Beanstalk.
10
+
11
+ Requires the application name.
12
+ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
13
+ LONG_DESC
14
+
15
+ def initialize(application:, elastic_beanstalk:, s3:, ui:)
16
+ @application = application
17
+ @elastic_beanstalk = elastic_beanstalk
18
+ @s3 = s3
19
+ @ui = ui
20
+ end
21
+
22
+ def run
23
+ environments = application.environments
24
+ unready_environments = environments.select { |environment| environment.status != "Ready" }
25
+ if unready_environments.any?
26
+ raise EnvironmentsNotReady.new(environments: unready_environments)
27
+ end
28
+
29
+ ui.debug { "Creating application version from code at #{Dir.pwd}..." }
30
+ progressbar = ProgressBar.create(title: "Deploying", total: nil, output: ui.stdout)
31
+ version = nil
32
+ version_thread = Thread.new do
33
+ version = ElasticBeans::ApplicationVersion.create(
34
+ working_directory: Dir.pwd,
35
+ application: application,
36
+ elastic_beanstalk: elastic_beanstalk,
37
+ s3: s3,
38
+ ).version_label
39
+ end
40
+
41
+ loop do
42
+ sleep 1
43
+ progressbar.increment
44
+ if !version_thread.alive?
45
+ break
46
+ end
47
+ end
48
+ version_thread.join
49
+
50
+ progressbar.log("Deploying version #{version} to #{application.name}...")
51
+ deploy_threads = environments.map { |environment|
52
+ progressbar.log("Deploying version '#{version}' to environment `#{environment.name}'...")
53
+ thread = Thread.new do
54
+ environment.deploy_version(version)
55
+ end
56
+ progressbar.increment
57
+ thread
58
+ }
59
+
60
+ loop do
61
+ sleep 1
62
+ progressbar.increment
63
+ if deploy_threads.none?(&:alive?)
64
+ progressbar.total = progressbar.progress
65
+ break
66
+ end
67
+ end
68
+ deploy_threads.each(&:join)
69
+ ui.info("Deployed!")
70
+ end
71
+
72
+ private
73
+
74
+ attr_reader :application, :elastic_beanstalk, :s3, :ui
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,37 @@
1
+ require "shellwords"
2
+
3
+ module ElasticBeans
4
+ module Command
5
+ class Exec
6
+ USAGE = "exec -a APPLICATION rake db:migrate"
7
+ DESC = "Run an arbitrary command in the context of your application"
8
+ LONG_DESC = <<-LONG_DESC
9
+ Run an arbitrary command in the context of your application.
10
+ The command is run in an "exec" environment, separate from your webserver or worker environments.
11
+ Upload gzipped output from the command to an HTTP endpoint.
12
+
13
+ Requires the Elastic Beanstalk application name.
14
+ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
15
+ LONG_DESC
16
+
17
+ def initialize(application:, sqs:, ui:)
18
+ @application = application
19
+ @sqs = sqs
20
+ @ui = ui
21
+ end
22
+
23
+ def run(*command_parts)
24
+ ui.info("Running `#{command_parts.join(" ")}' on #{application.name}...")
25
+ application.enqueue_command(command(command_parts), sqs: sqs)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :application, :sqs, :ui
31
+
32
+ def command(command_parts)
33
+ command_parts.map { |word| Shellwords.escape(word) }.join(" ")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,77 @@
1
+ require "ruby-progressbar"
2
+ require "elastic_beans/error/environments_not_ready"
3
+
4
+ module ElasticBeans
5
+ module Command
6
+ class SetEnv
7
+ USAGE = "setenv -a APPLICATION KEY=value [KEY=value]..."
8
+ DESC = "Update environment variables in the Elastic Beanstalk application"
9
+ LONG_DESC = <<-LONG_DESC
10
+ Update environment variables in the Elastic Beanstalk application.
11
+ Updates all running environments and configuration templates resulting from `configure`.
12
+
13
+ Requires the application name.
14
+ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
15
+ LONG_DESC
16
+
17
+ def initialize(application:, ui:)
18
+ @application = application
19
+ @ui = ui
20
+ end
21
+
22
+ def run(*env_pairs)
23
+ environments = application.environments
24
+ unready_environments = environments.select { |environment| environment.status != "Ready" }
25
+ if unready_environments.any?
26
+ raise EnvironmentsNotReady.new(environments: unready_environments)
27
+ end
28
+
29
+ env_vars = pairs_as_hash(env_pairs)
30
+ threads = []
31
+
32
+ progressbar = ProgressBar.create(title: "Updating", total: nil, output: ui.stdout)
33
+
34
+ progressbar.log("Updating configuration templates in #{application.name}...")
35
+ threads += application.configuration_templates.map { |config|
36
+ thread = Thread.new do
37
+ config.update_environment(env_vars)
38
+ end
39
+ progressbar.increment
40
+ thread
41
+ }
42
+
43
+ threads += environments.map { |environment|
44
+ progressbar.log("Updating `#{environment.name}'...")
45
+ thread = Thread.new do
46
+ environment.update_environment(env_vars)
47
+ end
48
+ progressbar.increment
49
+ thread
50
+ }
51
+
52
+ loop do
53
+ sleep 1
54
+ progressbar.increment
55
+ if threads.none?(&:alive?)
56
+ progressbar.total = progressbar.progress
57
+ break
58
+ end
59
+ end
60
+
61
+ threads.each(&:join)
62
+ end
63
+
64
+ private
65
+
66
+ attr_reader :application, :ui
67
+
68
+ def pairs_as_hash(pairs)
69
+ pairs.reduce({}) { |acc, pair|
70
+ key, value = pair.split('=', 2)
71
+ acc[key] = value
72
+ acc
73
+ }
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,74 @@
1
+ module ElasticBeans
2
+ module Command
3
+ class Talk
4
+ BEANSTALK = <<-BEANSTALK
5
+ ...........................,:~~:............................
6
+ ........................:~~~~~++++=~........................
7
+ .....................,:~~~~~~~++++++~:......................
8
+ ...................,~~~~~~~~~~++++++++++=~..................
9
+ ...................,~~~~~~~~~~++++++++++++~:................
10
+ ...................,~~~~~~~~~~+++++++++++++=:...............
11
+ ...................,~~~~~~~~~~+++++++++++++=:...............
12
+ ...................,~~~~~~~~~~+++++++++++++=:...............
13
+ ...................,~~~~~~~~~~+++++++++++++=:......,::~~:...
14
+ ...................,~~~~~~~~~~+++++++++++++=~::~~~~~~~=+++=:
15
+ ...................,~~~~~~~~~~++++++=~~~~~~~~~~~~~~~~~=+++=~
16
+ ...................,~~~~~~~~~~++++==~~~~~~~~~~~~~~~~~~=+++=~
17
+ ...................,~~~~~~~~~~++++==~~~~~~~~~~~~~~~~~~=+++=~
18
+ ...................,~~~~~~~~~~++++==~~~~~~~~~~~~~~~~~~=+++=~
19
+ ...................,~~~~~~~~~~++++==~~~~~~~~~~~~~~~~~~=+++=~
20
+ ...................,~~~~~~~~~~++++==~~~~~~~~~~~~~~~~~~=+++=~
21
+ ...................,~~~~~~~~~~++++==~~~~~~~~~~~~~~~~~~=+++=~
22
+ ...................,~~~~~~~~~~++++==~~~~~~~~~~~~~~~~~~=+++=~
23
+ ...................,~~~~~~~~~~++++==~~~~~~~~~~~~~~~~~~=+++=~
24
+ ...................,~~~~~~~~~~++++==~~~~~~~:::::::::::::::::
25
+ ...................,~~~~~~~~~~++++==~::::::::,,,,,,.........
26
+ ...................,~~~~~~~~~~+++++++++++++=:...............
27
+ ...................,~~~~~~~~~~+++++++++++++=:...............
28
+ ...................,~~~~~~~~~~+++++++++++++=:...............
29
+ ...................,~~~~~~~~~~+++++++++++++=:...............
30
+ ...................,~~~~~~~~~~+++++++++++++=:...............
31
+ ...................,~~~~~~~~~~+++++++++++++=:...............
32
+ ...............:~~~~~~~~~~~~~~+++++++++:....................
33
+ ...............:~~~~~~~~~~~~~~+++++++++:....................
34
+ ...............:~~~~~~~~~~~~~~+++++++++:....................
35
+ ...............:~~~~~~~~~~~~~~+++++++++:....................
36
+ ...............:~~~~~~~~~~~~~~+++++++++:....................
37
+ ......,:~~=++????????+++~~~~~~+++++++++:....................
38
+ ~==II??????????+++++++++~~~~~~+++++++++:....................
39
+ :~~~~=++++++++++++++++++~~~~~~+++++++++:....................
40
+ :~~~~=++++++++++++++++++~~~~~~+++++++++:....................
41
+ :~~~~=++++++++++++++++++~~~~~~+++++++++:....................
42
+ :~~~~=++++++++++++++++++~~~~~~+++++++++:....................
43
+ :~~~~=++++++++++++++++++~~~~~~+++++++++:....................
44
+ :~~~~=++++++++++++++++++~~~~~~+++++++++:....................
45
+ :~~~~=++++++++++++++++++~~~~~~+++++++++:....................
46
+ :~~~~=++++++++++++++++++~~~~~~+++++++++:....................
47
+ :~~~~=+++++++++++++++=~~~~~~~~+++++++++:....................
48
+ :~~~~=++++++=~~~~~~~~~~~~~~~~~+++++++++:....................
49
+ ...:~=~~:......:~~~~~~~~~~~~~~+++++++++:....................
50
+ ...............:~~~~~~~~~~~~~~+++++++++:....................
51
+ ...............:~~~~~~~~~~~~~~+++++++++:....................
52
+ ...............:~~~~~~~~~~~~~~+++++++++:....................
53
+ ...............,:~~~~~~~~~~~~~+++++++++:....................
54
+ ..................:~~~~~~~~~~~+++++++++:....................
55
+ .....................,:~~~~~~~++++++~:......................
56
+ ........................:~~~~~++++=~........................
57
+ ...........................,:~~:............................
58
+ ............................................................
59
+ BEANSTALK
60
+
61
+ def initialize(ui:)
62
+ @ui = ui
63
+ end
64
+
65
+ def run
66
+ ui.info BEANSTALK
67
+ end
68
+
69
+ private
70
+
71
+ attr_reader :ui
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,17 @@
1
+ module ElasticBeans
2
+ module Command
3
+ class Version
4
+ def initialize(ui:)
5
+ @ui = ui
6
+ end
7
+
8
+ def run
9
+ ui.info VERSION
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :ui
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ require "elastic_beans/command/configure"
2
+ require "elastic_beans/command/create"
3
+ require "elastic_beans/command/deploy"
4
+ require "elastic_beans/command/exec"
5
+ require "elastic_beans/command/set_env"
6
+ require "elastic_beans/command/talk"
7
+ require "elastic_beans/command/version"
8
+
9
+ module ElasticBeans
10
+ module Command
11
+ end
12
+ end