elastic_beans 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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