fhcap-cli 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rakeTasks +7 -0
  4. data/.rspec +1 -0
  5. data/CHANGELOG.md +15 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +150 -0
  9. data/Rakefile +2 -0
  10. data/bin/fhcap +6 -0
  11. data/fhcap-cli.gemspec +44 -0
  12. data/lib/cookbooks/provision/libraries/provision.rb +140 -0
  13. data/lib/cookbooks/provision/metadata.rb +7 -0
  14. data/lib/cookbooks/provision/recipes/aws.rb +15 -0
  15. data/lib/cookbooks/provision/recipes/aws_cluster_create.rb +59 -0
  16. data/lib/cookbooks/provision/recipes/aws_cluster_create_elb.rb +61 -0
  17. data/lib/cookbooks/provision/recipes/aws_cluster_destroy.rb +52 -0
  18. data/lib/cookbooks/provision/recipes/cluster_create.rb +2 -0
  19. data/lib/cookbooks/provision/recipes/cluster_destroy.rb +2 -0
  20. data/lib/cookbooks/provision/recipes/cluster_destroy_instances.rb +11 -0
  21. data/lib/cookbooks/provision/recipes/cluster_provision.rb +4 -0
  22. data/lib/cookbooks/provision/recipes/cluster_provision_instances.rb +55 -0
  23. data/lib/cookbooks/provision/recipes/cluster_status.rb +24 -0
  24. data/lib/cookbooks/provision/recipes/common.rb +9 -0
  25. data/lib/cookbooks/provision/recipes/default.rb +5 -0
  26. data/lib/cookbooks/provision/recipes/openstack.rb +11 -0
  27. data/lib/cookbooks/provision/recipes/openstack_cluster_create.rb +11 -0
  28. data/lib/cookbooks/provision/recipes/openstack_cluster_destroy.rb +4 -0
  29. data/lib/cookbooks/provision/recipes/reset_rabbitmq.rb +49 -0
  30. data/lib/cookbooks/provision/recipes/restart_services.rb +24 -0
  31. data/lib/extensions/chef/provisioning.rb +21 -0
  32. data/lib/extensions/chef/provisioning/aws_driver/driver.rb +46 -0
  33. data/lib/extensions/chef/provisioning/chef_run_data.rb +18 -0
  34. data/lib/extensions/cheffish/merged_config.rb +9 -0
  35. data/lib/fhcap.rb +14 -0
  36. data/lib/fhcap/chef-dk/chef_runner.rb +94 -0
  37. data/lib/fhcap/cli.rb +75 -0
  38. data/lib/fhcap/cluster.rb +112 -0
  39. data/lib/fhcap/config.rb +104 -0
  40. data/lib/fhcap/cookbook.rb +75 -0
  41. data/lib/fhcap/dummy_node.rb +80 -0
  42. data/lib/fhcap/fhcap_helper.rb +9 -0
  43. data/lib/fhcap/kitchen.rb +235 -0
  44. data/lib/fhcap/knife.rb +74 -0
  45. data/lib/fhcap/knife_helper.rb +38 -0
  46. data/lib/fhcap/misc.rb +103 -0
  47. data/lib/fhcap/provider.rb +41 -0
  48. data/lib/fhcap/providers_helper.rb +60 -0
  49. data/lib/fhcap/repo.rb +52 -0
  50. data/lib/fhcap/repos_helper.rb +217 -0
  51. data/lib/fhcap/tasks/chef/chef_task_base.rb +82 -0
  52. data/lib/fhcap/tasks/chef/cookbook/list.rb +37 -0
  53. data/lib/fhcap/tasks/chef/cookbook/update_changelog.rb +63 -0
  54. data/lib/fhcap/tasks/chef/cookbook/update_metadata.rb +57 -0
  55. data/lib/fhcap/tasks/chef/cookbook/update_readme.rb +30 -0
  56. data/lib/fhcap/tasks/chef/cookbook/update_version.rb +90 -0
  57. data/lib/fhcap/tasks/chef/environments/create.rb +115 -0
  58. data/lib/fhcap/tasks/chef/environments/destroy.rb +37 -0
  59. data/lib/fhcap/tasks/chef/environments/promote_cookbooks.rb +47 -0
  60. data/lib/fhcap/tasks/chef/provisioning/chef_provisioning_task.rb +27 -0
  61. data/lib/fhcap/tasks/chef/provisioning/chef_provisioning_task_base.rb +38 -0
  62. data/lib/fhcap/tasks/chef/provisioning/create.rb +22 -0
  63. data/lib/fhcap/tasks/chef/provisioning/destroy.rb +21 -0
  64. data/lib/fhcap/tasks/chef/provisioning/provision.rb +19 -0
  65. data/lib/fhcap/tasks/chef/server/bootstrap.rb +165 -0
  66. data/lib/fhcap/tasks/chef/server/create_user.rb +97 -0
  67. data/lib/fhcap/tasks/chef/server/info.rb +82 -0
  68. data/lib/fhcap/tasks/chef/server/provision.rb +45 -0
  69. data/lib/fhcap/tasks/clean.rb +34 -0
  70. data/lib/fhcap/tasks/cluster/cluster_task_base.rb +57 -0
  71. data/lib/fhcap/tasks/cluster/create.rb +243 -0
  72. data/lib/fhcap/tasks/cluster/create_environment.rb +171 -0
  73. data/lib/fhcap/tasks/cluster/destroy.rb +30 -0
  74. data/lib/fhcap/tasks/cluster/destroy_environment.rb +28 -0
  75. data/lib/fhcap/tasks/cluster/info.rb +67 -0
  76. data/lib/fhcap/tasks/cluster/list.rb +40 -0
  77. data/lib/fhcap/tasks/cluster/provision.rb +46 -0
  78. data/lib/fhcap/tasks/cluster/status.rb +17 -0
  79. data/lib/fhcap/tasks/cluster/test.rb +15 -0
  80. data/lib/fhcap/tasks/knife/add.rb +111 -0
  81. data/lib/fhcap/tasks/knife/list.rb +22 -0
  82. data/lib/fhcap/tasks/knife/remove.rb +39 -0
  83. data/lib/fhcap/tasks/misc/create_dns_record.rb +100 -0
  84. data/lib/fhcap/tasks/misc/create_ssl_cert.rb +82 -0
  85. data/lib/fhcap/tasks/provider/add.rb +136 -0
  86. data/lib/fhcap/tasks/provider/list.rb +31 -0
  87. data/lib/fhcap/tasks/provider/remove.rb +28 -0
  88. data/lib/fhcap/tasks/repo/add.rb +57 -0
  89. data/lib/fhcap/tasks/repo/checkout.rb +144 -0
  90. data/lib/fhcap/tasks/repo/list.rb +22 -0
  91. data/lib/fhcap/tasks/repo/remove.rb +34 -0
  92. data/lib/fhcap/tasks/setup.rb +59 -0
  93. data/lib/fhcap/tasks/task_base.rb +89 -0
  94. data/lib/fhcap/thor_base.rb +121 -0
  95. data/lib/fhcap/version.rb +3 -0
  96. data/spec/fhcap/cli_spec.rb +6 -0
  97. data/spec/fhcap/tasks/cluster/create_spec.rb +46 -0
  98. data/spec/fhcap/tasks/knife/add_spec.rb +35 -0
  99. data/spec/fhcap/tasks/knife/remove_spec.rb +25 -0
  100. data/spec/fhcap/tasks/provider/add_spec.rb +61 -0
  101. data/spec/fhcap/tasks/provider/remove_spec.rb +25 -0
  102. data/spec/fhcap/tasks/repo/add_spec.rb +32 -0
  103. data/spec/fhcap/tasks/repo/remove_spec.rb +25 -0
  104. data/spec/fhcap/tasks/task_base_spec.rb +51 -0
  105. data/spec/fhcap/thor_base_spec.rb +9 -0
  106. data/spec/spec_helper.rb +23 -0
  107. data/spec/support/dummy_config.rb +7 -0
  108. data/spec/support/dummy_thor.rb +3 -0
  109. data/templates/chef/cookbook/changelog.md.erb +12 -0
  110. data/templates/chef/cookbook/metadata.erb +45 -0
  111. data/templates/chef/environment_core.json.erb +278 -0
  112. data/templates/chef/environment_empty.json.erb +10 -0
  113. data/templates/chef/environment_mbaas.json.erb +120 -0
  114. data/templates/chef/environment_single.json.erb +300 -0
  115. data/templates/cluster/aws/common.json.erb +43 -0
  116. data/templates/cluster/aws/core-3node.json.erb +106 -0
  117. data/templates/cluster/aws/core-small-9node.json.erb +333 -0
  118. data/templates/cluster/aws/mbaas-3node.json.erb +116 -0
  119. data/templates/cluster/aws/nginx-test.json.erb +93 -0
  120. data/templates/cluster/aws/single-blank.json.erb +41 -0
  121. data/templates/cluster/aws/single.json.erb +88 -0
  122. data/templates/cluster/core-3node.json.erb +8 -0
  123. data/templates/cluster/core-mbaas-6node.json.erb +13 -0
  124. data/templates/cluster/core-small-9node.json.erb +8 -0
  125. data/templates/cluster/mbaas-3node.json.erb +9 -0
  126. data/templates/cluster/nginx-test.json.erb +8 -0
  127. data/templates/cluster/openstack/common.json.erb +7 -0
  128. data/templates/cluster/openstack/core-3node.json.erb +14 -0
  129. data/templates/cluster/openstack/core-small-9node.json.erb +32 -0
  130. data/templates/cluster/openstack/mbaas-3node.json.erb +14 -0
  131. data/templates/cluster/openstack/nginx-test.json.erb +11 -0
  132. data/templates/cluster/openstack/single-blank.json.erb +10 -0
  133. data/templates/cluster/openstack/single.json.erb +10 -0
  134. data/templates/cluster/single-blank.json.erb +8 -0
  135. data/templates/cluster/single.json.erb +8 -0
  136. data/templates/init/knife.rb.erb +13 -0
  137. data/templates/kitchen/Cheffile.erb +11 -0
  138. data/templates/kitchen/kitchen.aws.yml.erb +35 -0
  139. data/templates/kitchen/kitchen.docker.yml.erb +24 -0
  140. data/templates/kitchen/kitchen.generate.yml.erb +2 -0
  141. data/templates/kitchen/kitchen.openstack.yml.erb +31 -0
  142. metadata +506 -0
@@ -0,0 +1,45 @@
1
+ require 'fhcap/tasks/chef/chef_task_base'
2
+
3
+ module Fhcap
4
+ module Tasks
5
+ module Chef
6
+ module Server
7
+ class Provision < ChefTaskBase
8
+
9
+ attr_reader :environments, :node_names, :roles
10
+
11
+ def initialize(options)
12
+ super
13
+ @environments = options[:environments]
14
+ @node_names = options[:nodes]
15
+ @roles = options[:roles]
16
+ @chef_server = options[:chef_server]
17
+ end
18
+
19
+ def run
20
+ thor.say "Chef::Server::Provision: environments = #{environments}", :yellow
21
+
22
+ chef_server_environments = {}
23
+ environments.each do |environment|
24
+ chef_server = select_chef_server(environment, @chef_server)
25
+ chef_server_environments[chef_server] = chef_server_environments[chef_server] || []
26
+ chef_server_environments[chef_server] << environment
27
+ end
28
+
29
+ chef_server_environments.each do |server, environments|
30
+ environments.each do |environment|
31
+ query = ["chef_environment:#{environment}"]
32
+ query << "AND name:#{node_names}" if node_names
33
+ query << "AND roles:#{roles}" if roles
34
+ query = query.join(' ')
35
+ cmd = "sudo chef-client"
36
+ run_knife_ssh_cmd(query, cmd, server, options)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,34 @@
1
+ require 'fhcap/tasks/task_base'
2
+
3
+ module Fhcap
4
+ module Tasks
5
+ class Clean < TaskBase
6
+
7
+ def initialize(options)
8
+ super
9
+ end
10
+
11
+ def run
12
+ if config.exists?
13
+ repos = config[:repos]
14
+ delete_files = repos.collect do |name, cfg|
15
+ repo_dir(name)
16
+ end.compact
17
+ delete_files << config.config_file
18
+
19
+ delete_files.each do |file|
20
+ thor.say_status 'removing', file, :yellow
21
+ end
22
+ if thor.yes? "Continue (y/n)"
23
+ delete_files.each do |file|
24
+ thor.remove_file file
25
+ end
26
+ end
27
+ else
28
+ thor.say_status 'error', "No config file found #{config.config_file}", :red
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,57 @@
1
+ require 'fhcap/tasks/task_base'
2
+
3
+ module Fhcap
4
+ module Tasks
5
+ module Cluster
6
+ class ClusterTaskBase < TaskBase
7
+
8
+ attr_reader :name, :cluster_config, :cluster_file
9
+
10
+ def initialize(options, exit_on_error=true)
11
+ super(options)
12
+ @name = options[:name]
13
+ @cluster_config = options[:'cluster-config']
14
+
15
+ unless @cluster_config
16
+ @cluster_file = find_cluster(@name)
17
+ if @cluster_file
18
+ @cluster_config = JSON.parse(IO.read(@cluster_file), {:symbolize_names => true})
19
+ else
20
+ exit_with_error("Unknown cluster #{@name}") if exit_on_error
21
+ @cluster_config = {}
22
+ end
23
+ end
24
+ end
25
+
26
+ def template_as_object(template_file, config)
27
+ template = File.read(template_file)
28
+ eruby = Erubis::Eruby.new(template)
29
+ result = eruby.result({config: config})
30
+ JSON.parse(result, {:symbolize_names => true})
31
+ end
32
+
33
+ def checkout_repos
34
+ repos = ['fhcap', cluster_config[:repo]].compact.uniq
35
+ repos.each do |name|
36
+ name = name.to_s
37
+ if @options[:'git-ref']
38
+ fallback_git_ref = name == 'fhcap' ? nil : 'master'
39
+ else
40
+ fallback_git_ref = nil
41
+ end
42
+ Repo::Checkout.new(@options.dup.merge({:repo => name, :'fallback-git-ref' => fallback_git_ref})).run
43
+ end if repos
44
+ end
45
+
46
+ private
47
+
48
+ def cluster_environments
49
+ @cluster_config[:environments].collect do |env_name, cfg|
50
+ [@name.to_s, env_name.to_s].uniq.join('-')
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,243 @@
1
+ require 'fhcap/tasks/cluster/cluster_task_base'
2
+ require 'fhcap/tasks/cluster/create_environment'
3
+ require 'fhcap/tasks/chef/provisioning/create'
4
+ require 'fhcap/tasks/chef/environments/promote_cookbooks'
5
+ require 'fhcap/tasks/chef/environments/create'
6
+ require 'fhcap/tasks/chef/server/bootstrap'
7
+ require 'fhcap/tasks/repo/checkout'
8
+ require "fhcap/tasks/misc/create_dns_record"
9
+
10
+ module Fhcap
11
+ module Tasks
12
+ module Cluster
13
+ class Create < ClusterTaskBase
14
+
15
+ attr_reader :name, :cluster_config
16
+
17
+ def initialize(options)
18
+ super(options, false)
19
+ @template = options[:template]
20
+ @repo = options[:'repo']
21
+ @provider_config = options[:'provider-config'] || {}
22
+ @gitref = options[:'git-ref']
23
+ @domain = options[:domain]
24
+ @ssl_cert = options[:'ssl-cert']
25
+ @dns_provider_id = options[:'dns-provider-id']
26
+ @chef_server = options[:'chef-server']
27
+ @provider_id = options[:'provider-id']
28
+ @skip_repo_checkout = options[:'skip-repo-checkout']
29
+ @skip_cookbook_promote = options[:'skip-cookbook-promote']
30
+ @skip_server_bootstrap = options[:'skip-server-bootstrap']
31
+ @skip_dns_record = options[:'skip-dns-record']
32
+ @skip_create = options[:'skip-create']
33
+
34
+ @cluster_config = {
35
+ id: @name,
36
+ template: @template,
37
+ driver: "",
38
+ domain: @domain,
39
+ repo: @repo,
40
+ chef_server: @chef_server,
41
+ provider_id: @provider_id
42
+ }.merge(@cluster_config)
43
+
44
+ @cluster_config[:provider_config] = @cluster_config[:provider_config] || {}
45
+ @cluster_config[:provider_config].merge!(@provider_config)
46
+ end
47
+
48
+ def run
49
+ thor.say "Cluster::Create: name = #{@name}", :yellow
50
+
51
+ #Checkout repos
52
+ checkout_repos unless @skip_repo_checkout
53
+
54
+ #Generate cluster config
55
+ generate_cluster_config
56
+
57
+ #Create environment files
58
+ create_cluster_environments
59
+
60
+ #Write config to disk
61
+ cluster_file = find_cluster(name) || File.join(repo_dir(cluster_config[:repo]), repo_clusters_dir(cluster_config[:repo]), "#{name}.json")
62
+ thor.create_file(cluster_file, JSON.pretty_generate(cluster_config))
63
+
64
+ #Chef Stuff
65
+ #Promote cookbooks
66
+ Chef::Environments::PromoteCookbooks.new(@options.dup.merge({:environments => cluster_environments})).run unless @skip_cookbook_promote
67
+
68
+ #Bootstrap Server
69
+ Chef::Server::Bootstrap.new(@options.dup.merge({:environments => cluster_environments, :chef_server => @cluster_config[:chef_server]})).run unless @skip_server_bootstrap
70
+
71
+ #Provisioning create
72
+ Chef::Provisioning::Create.new(options).run unless @skip_create
73
+
74
+ create_dns_record unless @skip_dns_record
75
+ end
76
+
77
+ private
78
+
79
+ def required_config
80
+ {
81
+ repo: {
82
+ msg: "In which repo should this clusters config and environment files be stored?",
83
+ options: {:limited_to => repo_names}
84
+ },
85
+ domain: {
86
+ },
87
+ chef_server: {
88
+ options: {:limited_to => chef_server_names}
89
+ },
90
+ provider_id: {
91
+ options: {:limited_to => provider_names_for('compute')}
92
+ },
93
+ template: {
94
+ options: {:limited_to => cluster_template_names}
95
+ }
96
+ }
97
+ end
98
+
99
+ def aws_required_config
100
+ aws_regions = provider_config(@cluster_config[:provider_id])[:regions]
101
+ {
102
+ region: {
103
+ options: {:limited_to => aws_regions.keys.collect { |k| k.to_s }}
104
+ },
105
+ cidr: {}
106
+ }
107
+ end
108
+
109
+ def generate_cluster_config
110
+ ask_config(required_config, cluster_config)
111
+ cluster_config[:driver] = provider_type(cluster_config[:provider_id])
112
+ send(:"generate_cluster_config_#{cluster_config[:driver]}")
113
+ driver_template_file = File.join('templates', 'cluster', cluster_config[:driver], "common.json.erb")
114
+ driver_template_config = template_as_object(driver_template_file, cluster_config)
115
+ cluster_config.merge!(driver_template_config)
116
+ end
117
+
118
+ def generate_cluster_config_aws
119
+ ask_config(aws_required_config, cluster_config[:provider_config])
120
+ aws_regions = provider_config(cluster_config[:provider_id])[:regions]
121
+ cluster_config[:default_instance_options] = cluster_config[:default_instance_options] || {}
122
+ cluster_config[:default_instance_options][:image_id] = aws_regions[cluster_config[:provider_config][:region].to_sym][:base_image]
123
+ end
124
+
125
+ def generate_cluster_config_openstack
126
+ cluster_config[:default_instance_options] = cluster_config[:default_instance_options] || {}
127
+ cluster_config[:default_instance_options][:image_ref] = provider_config(@cluster_config[:provider_id])[:image_ref]
128
+ cluster_config[:default_instance_options][:flavor_ref] = provider_config(@cluster_config[:provider_id])[:flavor_ref]
129
+ cluster_config[:default_instance_options][:floating_ip_pool] = provider_config(@cluster_config[:provider_id])[:floating_ip_pool]
130
+ cluster_config[:default_instance_options][:ssh_username] = provider_config(@cluster_config[:provider_id])[:ssh_username]
131
+ end
132
+
133
+ def create_cluster_environments
134
+ env_template_file = File.join('templates', 'cluster', "#{cluster_config[:template]}.json.erb")
135
+ template_config = template_as_object(env_template_file, cluster_config)
136
+
137
+ template_config[:environments].each do |env|
138
+ env_template_file = File.join('templates', 'cluster', cluster_config[:driver], "#{env[:template]}.json.erb")
139
+ template_config = template_as_object(env_template_file, cluster_config)
140
+
141
+ env_domain = [env[:'domain-prefix'], cluster_config[:domain]].compact.join('.')
142
+ env_cfg = {
143
+ :name => name,
144
+ :'environment-name' => env[:name],
145
+ :domain => env_domain,
146
+ :template => env[:template],
147
+ :'cluster-config' => cluster_config,
148
+ :'skip-create-cluster-file' => true
149
+ }
150
+
151
+ CreateEnvironment.new(@options.dup.merge(env_cfg)).run
152
+ end
153
+ end
154
+
155
+ def create_dns_record
156
+ send(:"create_dns_record_#{cluster_config[:driver]}")
157
+ end
158
+
159
+ def create_dns_record_aws
160
+ cluster_config[:environments].each do |env_name, env_cfg|
161
+ env_cfg[:load_balancers].each do |lb_name, lb_cfg|
162
+ if lb_cfg[:scheme] == "internet-facing"
163
+ begin
164
+ require 'aws-sdk'
165
+
166
+ cfg = provider_config(cluster_config[:provider_id])
167
+ creds = cfg[:credentials]
168
+
169
+ elb = Aws::ElasticLoadBalancing::Client.new(
170
+ region: cluster_config[:provider_config][:region],
171
+ access_key_id: creds[:'aws-access-key'],
172
+ secret_access_key: creds[:'aws-secret-key']
173
+ )
174
+
175
+ lb = load_balancer_name_for(name, env_name, lb_name)
176
+ resp = elb.describe_load_balancers({
177
+ load_balancer_names: [lb],
178
+ page_size: 1,
179
+ })
180
+
181
+ elb = resp.load_balancer_descriptions.first
182
+ thor.say "Found ELB name: #{elb.load_balancer_name}, dns_name: #{elb.dns_name}, zone_id: #{elb.canonical_hosted_zone_name_id}"
183
+
184
+ dns_record_cfg = {
185
+ domain: "*.#{env_cfg[:domain]}",
186
+ :"alias-target" => {
187
+ dns_name: elb.dns_name,
188
+ hosted_zone_id: elb.canonical_hosted_zone_name_id
189
+ }
190
+ }
191
+ Misc::CreateDNSRecord.new(@options.dup.merge(dns_record_cfg)).run
192
+ rescue Aws::ElasticLoadBalancing::Errors::LoadBalancerNotFound => e
193
+ thor.say_status 'error', "LoadBalancer #{lb} not found, unable to create DNS Entry", :red
194
+ rescue Aws::ElasticLoadBalancing::Errors::ServiceError => e
195
+ thor.say_status 'error', e.message, :red
196
+ end
197
+ end
198
+ end if env_cfg[:load_balancers]
199
+ end
200
+ end
201
+
202
+ def create_dns_record_openstack
203
+ cluster_config[:environments].each do |env_name, env_cfg|
204
+ env_name = "#{name}-#{env_name}"
205
+ knife_config_file = knife_config_file_for(cluster_config[:chef_server])
206
+ nodes = JSON.parse(`knife search "chef_environment:#{env_name} AND recipes:nginx_feedhenry\\:\\:loadbalancer" -c #{knife_config_file} -F json -a name -a cloud.public_ipv4`)
207
+
208
+ lb_node = nodes['rows'].collect do |row|
209
+ name, attrs = row.first
210
+ attrs
211
+ end.first
212
+
213
+ if lb_node
214
+ if lb_node['cloud.public_ipv4']
215
+ dns_record_cfg = {
216
+ domain: "*.#{env_cfg[:domain]}",
217
+ ipaddress: lb_node['cloud.public_ipv4']
218
+ }
219
+ Misc::CreateDNSRecord.new(@options.dup.merge(dns_record_cfg)).run
220
+ else
221
+ thor.say "Found lb node '#{lb_node['name']}', but was unable to retrieve it's IP!!}"
222
+ end
223
+ else
224
+ thor.say "Unable to locate lb node in cluster!!"
225
+ end
226
+ end
227
+ end
228
+
229
+ def load_balancer_name_for(org_name, environment, lb_name)
230
+ fh_name_for('fh-lb', org_name, environment, lb_name)
231
+ end
232
+
233
+ def fh_name_for(*args)
234
+ args.collect do |str|
235
+ str.to_s.split('-')
236
+ end.flatten.uniq.join('-')
237
+ end
238
+
239
+
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,171 @@
1
+ require 'fhcap/tasks/cluster/cluster_task_base'
2
+ require 'fhcap/tasks/chef/environments/create'
3
+ require "fhcap/tasks/misc/create_ssl_cert"
4
+
5
+ module Fhcap
6
+ module Tasks
7
+ module Cluster
8
+ class CreateEnvironment < ClusterTaskBase
9
+
10
+ attr_reader :template, :environment_name, :domain, :environment_config
11
+
12
+ def initialize(options)
13
+ super(options)
14
+ @skip_create_cluster_file = options[:'skip-create-cluster-file']
15
+ @template = options[:template]
16
+ @environment_name = options[:'environment-name']
17
+ @domain = options[:domain]
18
+
19
+ @cluster_config[:environments] = @cluster_config[:environments] || {}
20
+ @environment_config = @cluster_config[:environments][@environment_name.to_sym] || {}
21
+
22
+ template_filepath = File.join(Fhcap.source_root, 'templates', 'cluster', @cluster_config[:driver], "#{template}.json.erb")
23
+ eruby = Erubis::Eruby.new(File.read(template_filepath))
24
+ result = eruby.result(
25
+ {
26
+ config: {
27
+ domain: @domain,
28
+ environment_name: @environment_name
29
+ }
30
+ }
31
+ )
32
+ @environment_config.merge!(JSON.parse(result, {:symbolize_names => true}))
33
+
34
+ @availability_zones = provider_availability_zones(@cluster_config[:provider_id], @cluster_config[:provider_config][:region])
35
+ @cidr = @cluster_config[:provider_config][:cidr]
36
+ @subnets = {}
37
+ @allocated_ips = {}
38
+ @next_subnet = 0
39
+ end
40
+
41
+ def run
42
+ thor.say "Cluster::CreateEnvironment: cluster = #{name}, environment = #{environment_name}, domain = #{domain}", :yellow
43
+ env_cfg = {
44
+ name: "#{name}-#{environment_name}",
45
+ domain: domain,
46
+ type: get_env_type(environment_name),
47
+ repo: cluster_config[:repo]
48
+ }
49
+ generate_environment_config
50
+ create_chef_environment(env_cfg)
51
+ create_ssl_cert(env_cfg)
52
+ thor.create_file(cluster_file, JSON.pretty_generate(cluster_config)) unless @skip_create_cluster_file
53
+ end
54
+
55
+
56
+ private
57
+
58
+ def generate_environment_config
59
+ environment_config[:subnets].each do |subnet_id, subnet_cfg|
60
+ subnet_cfg[:cidr] = get_subnet_cidr(subnet_id, subnet_cfg[:cidr])
61
+ subnet_cfg[:availability_zone] = get_availability_zone(subnet_id)
62
+ end if environment_config[:subnets]
63
+
64
+ environment_config[:instances].each do |instance_id, instance_cfg|
65
+ if instance_cfg[:aws]
66
+ instance_cfg[:aws][:private_ip_address] = get_subnet_private_ip(instance_cfg[:aws][:subnet]) if instance_cfg[:aws].has_key?(:private_ip_address)
67
+ end
68
+ end if environment_config[:instances]
69
+
70
+ if environment_config[:security_groups]
71
+ cluster_config[:security_groups] = cluster_config[:security_groups] || {}
72
+
73
+ environment_config[:security_groups].each do |name, cfg|
74
+ cluster_config[:security_groups][name.to_sym] = cfg
75
+ end
76
+ environment_config.delete(:security_groups)
77
+ end
78
+
79
+ cluster_config[:environments][environment_name.to_sym] = cluster_config[:environments][environment_name.to_sym] || {}
80
+ cluster_config[:environments][environment_name.to_sym].merge!(environment_config)
81
+ end
82
+
83
+ def create_chef_environment(env_cfg)
84
+ Chef::Environments::Create.new(@options.dup.merge(env_cfg)).run
85
+ end
86
+
87
+ def create_ssl_cert(env_cfg)
88
+ if provider_type(cluster_config[:provider_id]) == 'aws'
89
+ cert_format = 'pem'
90
+ cert_directory = File.join(repo_dir(cluster_config[:repo]), repo_clusters_dir(cluster_config[:repo]), 'ssl_certs')
91
+ else
92
+ cert_format = 'json'
93
+ cert_directory = File.join(repo_dir(cluster_config[:repo]), 'data_bags', 'nginx_ssl_certs')
94
+ end
95
+ cert_name = "wildcard.#{env_cfg[:domain]}"
96
+ cert_domain = "*.#{env_cfg[:domain]}"
97
+ domain_cert_cfg = {
98
+ name: cert_name,
99
+ domain: cert_domain,
100
+ format: cert_format,
101
+ directory: cert_directory
102
+ }
103
+ cluster_config[:environments].each do |env_name, env_cfg|
104
+ env_cfg[:load_balancers].each do |lb_name, lb_cfg|
105
+ lb_cfg[:listeners].each do |listener|
106
+ if listener[:server_certificate]
107
+ listener[:server_certificate] = cert_name
108
+ end
109
+ end
110
+ end if env_cfg[:load_balancers]
111
+ end
112
+ Misc::CreateSslCert.new(@options.dup.merge(domain_cert_cfg)).run
113
+ end
114
+
115
+ def get_subnet(subnet)
116
+ subnet = subnet.to_sym
117
+ unless @subnets[subnet]
118
+ @subnets[subnet] = {}
119
+ @allocated_ips[subnet] = []
120
+ end
121
+ @subnets[subnet]
122
+ end
123
+
124
+ def get_subnet_cidr(subnet, range)
125
+ subnet = subnet.to_sym
126
+ unless get_subnet(subnet)[:cidr]
127
+ @subnets[subnet][:cidr] = "#{@cidr.split('.')[0..2].join('.')}.#{range}"
128
+ end
129
+ @subnets[subnet][:cidr]
130
+ end
131
+
132
+ def get_availability_zone(subnet)
133
+ subnet = subnet.to_sym
134
+ unless get_subnet(subnet)[:avilability_zone]
135
+ @subnets[subnet][:avilability_zone] = @availability_zones[@next_subnet]
136
+ @next_subnet = @next_subnet < (@availability_zones.length - 1) ? @next_subnet + 1 : 0
137
+ end
138
+ @subnets[subnet][:avilability_zone]
139
+ end
140
+
141
+ def get_subnet_private_ip(subnet)
142
+ subnet = subnet.to_sym
143
+ require 'ipaddr'
144
+ range = IPAddr.new(@subnets[subnet][:cidr]).to_range
145
+ private_ip = nil
146
+ range.each do |ip|
147
+ next if @allocated_ips[subnet].include? ip
148
+ private_ip = ip
149
+ @allocated_ips[subnet] << private_ip
150
+ break
151
+ end
152
+ private_ip.to_s
153
+ end
154
+
155
+ def get_env_type(name)
156
+ case name
157
+ when /core/
158
+ 'core'
159
+ when /mbaas/
160
+ 'mbaas'
161
+ when /single/
162
+ 'single'
163
+ else
164
+ 'empty'
165
+ end
166
+ end
167
+
168
+ end
169
+ end
170
+ end
171
+ end