stack-kicker 0.0.11 → 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +3 -3
- data/README.md +51 -1
- data/doc/examples/Gemfile +1 -1
- data/doc/examples/apache-via-cloud-init/README.md +6 -0
- data/doc/examples/apache-via-cloud-init/Stackfile +2 -2
- data/doc/examples/cloud-init-with-erb/README.md +6 -0
- data/doc/examples/cloud-init-with-erb/Stackfile +40 -0
- data/doc/examples/cloud-init-with-erb/cloud-init.sh.erb +15 -0
- data/doc/examples/cloud-init-with-erb/cloud-init.yaml.erb +33 -0
- data/lib/stack-kicker/version.rb +1 -1
- data/lib/stack.rb +71 -15
- metadata +13 -2
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
1
|
## 0.0.11
|
2
|
-
Stack.validate() was still using the role name, not role_details[:security_group]
|
3
|
-
Added doc/examples/apache-via-cloud-init, example of just using cloud-init, no chef.
|
4
|
-
Fixed some issues when only using cloud-init
|
2
|
+
* Stack.validate() was still using the role name, not role_details[:security_group]
|
3
|
+
* Added doc/examples/apache-via-cloud-init, example of just using cloud-init, no chef.
|
4
|
+
* Fixed some issues when only using cloud-init
|
data/README.md
CHANGED
@@ -61,7 +61,7 @@ This is usually only used when :chef_server = true, it stops stack-kicker from a
|
|
61
61
|
#### :security_group
|
62
62
|
security group to be assigned to this node. (set to default is you don't want to manage security groups for every role)
|
63
63
|
#### :cloud_config_yaml
|
64
|
-
defaults to a file which contains a simple template (lib/cloud-config.yaml in the github repo) that installs the http://apt.opscode.com repo &
|
64
|
+
defaults to a file which contains a simple template (lib/cloud-config.yaml in the github repo) that installs the http://apt.opscode.com repo & gpg key, as well as installing the opscode-keyring. Can be replaced with any filename that complies with cloud-init. If the filename supplied ends in '.erb', it will be processed with ERB. See [ERB Templates](#erbtemplates) for details on available data.
|
65
65
|
#### :bootstrap
|
66
66
|
Optional filename, the contents of which will get combined with :cloud_config_yaml to form the cloud-init payload (using mime encoding, supported types are #include, ) with some variable substation (chef server ip, environment, validation.pem, roles) See lib/chef-client-bootstrap-excl-validation-pem.sh as an example.
|
67
67
|
|
@@ -76,6 +76,8 @@ text/upstart-job | #upstart-job
|
|
76
76
|
text/part-handler | #part-handler
|
77
77
|
text/cloud-boothook | #cloud-boothook
|
78
78
|
|
79
|
+
Can be replaced with any filename that complies with cloud-init. If the filename supplied ends in '.erb', it will be processed with ERB. See [ERB Templates](id:erbtemplates) for details on available data.
|
80
|
+
|
79
81
|
#### :data_dir
|
80
82
|
data_dir is a hook into the optional :cloud_config_yaml template (lib/cloud-config-w-ephemeral.yaml), which formats & mounts ephemeral0 early in the boot process, allowing it to be used during the rest of the cloud-init. ephemeral0 is mounted as /mnt & then bind mounted to #{data_dir}
|
81
83
|
|
@@ -85,6 +87,54 @@ This can be an array of strings, such that node X will be assigned :floating_ips
|
|
85
87
|
#### :post_install_script, :post_install_args & :post_install_cwd
|
86
88
|
These are used to construct a command to execute, which is executed locally where you executed stack-kicker. :post_install_args can contain %PUBLIC_IP%, which will be replaced by the public IP of the just created node. :post_install_script scripts are executed as soon the the instance returns a status='ACTIVE'. They can be used delay the creation of further nodes of the same role (for example, when creating a rabbitmq cluster, you need to wait for the rabbitmq process to be running before creating the next member of the cluster, or when you are creating a chef-server, you need to wait for the packages to install & daemons to start before attempting to create Chef users & retrieve keys)
|
87
89
|
|
90
|
+
## [ERB Templates](id:erbtemplates)
|
91
|
+
Both the :cloud_config_yaml & :bootstrap role attributes can point to plain files, files with simple tokens (%HOSTNAME%, %CHEF_SERVER%, %CHEF_ENVIRONMENT%, %CHEF_VALIDATION_PEM%, %SERVER_NAME%, %ROLE% and %DATA_DIR%) or [Ruby ERB](http://www.ruby-doc.org/stdlib-2.0/libdoc/erb/rdoc/ERB.html) templates.
|
92
|
+
|
93
|
+
There are 3 key data sets exposed to the ERB templates:
|
94
|
+
|
95
|
+
* instances - subset all_instances, just the nodes referenced/managed by this Stackfile
|
96
|
+
* all_instances - hash of all instances running in this account
|
97
|
+
* config - config contains all the config data from the Stackfile, as well as info about running instances in the account used by the Stackfile
|
98
|
+
|
99
|
+
instances & all_instances hashes look like this:
|
100
|
+
|
101
|
+
```
|
102
|
+
{
|
103
|
+
"webci-az1-web0001" => {
|
104
|
+
:region => "az-1.region-a.geo-1",
|
105
|
+
:id => 1395635,
|
106
|
+
:private_ips => ["10.5.170.11"],
|
107
|
+
:public_ips => ["15.185.114.181"],
|
108
|
+
:az => "az-1.region-a.geo-1",
|
109
|
+
:role => :web},
|
110
|
+
"webci-az1-web0002" => {
|
111
|
+
:region => "az-1.region-a.geo-1",
|
112
|
+
:id => 1396181,
|
113
|
+
:private_ips => ["10.5.172.145"],
|
114
|
+
:public_ips => ["15.185.110.42"],
|
115
|
+
:az => "az-1.region-a.geo-1",
|
116
|
+
:role=>:web
|
117
|
+
}
|
118
|
+
}
|
119
|
+
```
|
120
|
+
So say you wanted to drop the private IP address of webci-az1-web0001 into the hosts file of webci-az1-web0002 via cloud-init, this fragment in your :cloud_config_yaml could be used:
|
121
|
+
|
122
|
+
```
|
123
|
+
bootcmd:
|
124
|
+
- echo <%=instances['webci-az1-web0001'][:private_ips][0]%> webci-az1-web0001 >> /etc/hosts
|
125
|
+
```
|
126
|
+
|
127
|
+
As we're using ERB for these templates, you can use the standard Ruby iterators to work through the hash:
|
128
|
+
|
129
|
+
```
|
130
|
+
bootcmd:
|
131
|
+
<% instances.each do |node_name, node_details| %>
|
132
|
+
- echo <%=node_details[:private_ips][0]%> <%=node_name%> >> /etc/hosts
|
133
|
+
<% end %>
|
134
|
+
```
|
135
|
+
|
136
|
+
|
137
|
+
|
88
138
|
|
89
139
|
## Example workflows/models
|
90
140
|
stack-kicker was built with the following workflows in mind:
|
data/doc/examples/Gemfile
CHANGED
@@ -16,8 +16,8 @@ module StackConfig
|
|
16
16
|
'az-2.region-a.geo-1' => { 'image_id' => 67074 },
|
17
17
|
|
18
18
|
# provisioning info
|
19
|
-
:key_pair => '
|
20
|
-
:key_public => '
|
19
|
+
:key_pair => 'YOURKEYPAIRNAME',
|
20
|
+
:key_public => '/path/to/your/public-key.pub',
|
21
21
|
|
22
22
|
:name_template => '%s-%s-%s%04d', # service-site-role0001
|
23
23
|
:global_service_name => 'webci',
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module StackConfig
|
2
|
+
Stacks = Hash.new
|
3
|
+
|
4
|
+
Stacks['apache-cloud-init'] = {
|
5
|
+
# (we can access environment variable via ENV['foo'] instead of hard coding u/p here)
|
6
|
+
'REGION' => ENV['OS_REGION_NAME'],
|
7
|
+
'USERNAME' => ENV['OS_USERNAME'],
|
8
|
+
'PASSWORD' => ENV['OS_PASSWORD'],
|
9
|
+
'AUTH_URL' => ENV['OS_AUTH_URL'],
|
10
|
+
'TENANT_NAME' => ENV['OS_TENANT_NAME'],
|
11
|
+
|
12
|
+
# generic instance info
|
13
|
+
'flavor_id' => 103,
|
14
|
+
'image_id' => 75845, # Ubuntu Precise 12.04 LTS Server 64-bit
|
15
|
+
# per-az image_id's
|
16
|
+
'az-2.region-a.geo-1' => { 'image_id' => 67074 },
|
17
|
+
|
18
|
+
# provisioning info
|
19
|
+
:key_pair => 'YOURKEYPAIRNAME',
|
20
|
+
:key_public => '/path/to/your/public-key.pub',
|
21
|
+
|
22
|
+
:name_template => '%s-%s-%s%04d', # service-site-role0001
|
23
|
+
:global_service_name => 'webci',
|
24
|
+
:site_template => '%s',
|
25
|
+
|
26
|
+
# role specification
|
27
|
+
# role names & chef roles should match
|
28
|
+
:roles => {
|
29
|
+
:web => { :count => 3,
|
30
|
+
# use the default security group
|
31
|
+
:security_group => 'default',
|
32
|
+
# don't use chef as the second-stage provisioner
|
33
|
+
:skip_chef_prereg => true,
|
34
|
+
:bootstrap => 'cloud-init.sh.erb',
|
35
|
+
# use the yaml template that supports configuring the ephemeral space early in the boot process
|
36
|
+
:cloud_config_yaml => 'cloud-init.yaml.erb',
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
cat > /var/www/index.html <<HEREDOC
|
4
|
+
<html><body><h1>Stack-Kicker was 'ere!</h1>
|
5
|
+
<p>This file is dropped in place by a shell script passed to the host via cloud-init</p>
|
6
|
+
<p>The web server software is running but no content has been added, yet.</p>
|
7
|
+
<h2>instances</h2>
|
8
|
+
<%=instances%>
|
9
|
+
<hr>
|
10
|
+
<h2>all_instances</h2>
|
11
|
+
<%=all_instances%>
|
12
|
+
<h2>config</h2>
|
13
|
+
<%=config%>
|
14
|
+
</body></html>
|
15
|
+
HEREDOC
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#cloud-config
|
2
|
+
|
3
|
+
output:
|
4
|
+
all: ">> /var/log/cloud-init.log"
|
5
|
+
|
6
|
+
# use the HPCS Ubuntu Mirror for security-updates too
|
7
|
+
bootcmd:
|
8
|
+
- echo 127.0.1.1 %HOSTNAME% >> /etc/hosts
|
9
|
+
- echo # force security.ubuntu.com to mirror.clouds.archive.ubuntu.com >> /etc/hosts
|
10
|
+
- echo 15.185.107.200 security.ubuntu.com >> /etc/hosts
|
11
|
+
- echo <%=instances['webci-az1-web0001'][:private_ips][0]%> webci-az1-web0001 >> /etc/hosts
|
12
|
+
<% instances.each do |node_name, node_details| %>
|
13
|
+
- echo <%=node_details[:private_ips][0]%> <%=node_name%> >> /etc/hosts
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
|
17
|
+
# use the HPCS Ubuntu Mirrors
|
18
|
+
apt_mirror: http://nova.clouds.archive.ubuntu.com/ubuntu
|
19
|
+
|
20
|
+
# Run apt-get update
|
21
|
+
package_update: true
|
22
|
+
|
23
|
+
# Run apt-get update
|
24
|
+
package_upgrade: true
|
25
|
+
|
26
|
+
# Install some packages
|
27
|
+
packages:
|
28
|
+
- apache2
|
29
|
+
- php5
|
30
|
+
|
31
|
+
# Use `sudo -i` to simulate a login shell...
|
32
|
+
runcmd:
|
33
|
+
- sudo -i touch /var/log/cloud-init.complete
|
data/lib/stack-kicker/version.rb
CHANGED
data/lib/stack.rb
CHANGED
@@ -448,9 +448,61 @@ cookbook_path [ '<%=config[:stackhome]%>/cookbooks' ]
|
|
448
448
|
end
|
449
449
|
config[:all_instances] = servers
|
450
450
|
end
|
451
|
+
Stack.categorise_instance_ips(config)
|
451
452
|
config[:all_instances]
|
452
453
|
end
|
453
454
|
|
455
|
+
# Add an instance to the :all_instances hash, instead of having to poll the whole lot again
|
456
|
+
def Stack.add_instance(config, hostname, region, id, addresses)
|
457
|
+
config[:all_instances][hostname] = { :region => region, :id => id, :addresses => addresses}
|
458
|
+
Stack.categorise_instance_ips(config)
|
459
|
+
end
|
460
|
+
|
461
|
+
def Stack.categorise_instance_ips(config)
|
462
|
+
Logger.debug { ">>> Stack.categorise_instance_ips" }
|
463
|
+
# create a private_ip & public_ip array on the server objects for easy retrieval
|
464
|
+
config[:all_instances].each do |hostname, instance_details|
|
465
|
+
Logger.debug { "Flushing categorised addresses for #{hostname}" }
|
466
|
+
config[:all_instances][hostname][:private_ips] = Array.new
|
467
|
+
config[:all_instances][hostname][:public_ips] = Array.new
|
468
|
+
|
469
|
+
Logger.debug { "Categorising addresses for #{hostname}" }
|
470
|
+
config[:all_instances][hostname][:addresses].each do |address|
|
471
|
+
case address.label
|
472
|
+
when 'public'
|
473
|
+
config[:all_instances][hostname][:public_ips] << address.address
|
474
|
+
when 'private'
|
475
|
+
config[:all_instances][hostname][:private_ips] << address.address
|
476
|
+
else
|
477
|
+
Logger.error "#{address.label} is an unhandled address type for #{hostname}"
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
Logger.debug { "config[:all_instances][= #{config[:all_instances]}" }
|
482
|
+
Logger.debug { "<<< Stack.categorise_instance_ips" }
|
483
|
+
end
|
484
|
+
|
485
|
+
def Stack.get_public_ip(config, hostname)
|
486
|
+
# get a public address from the instance
|
487
|
+
# (could be either the dynamic or one of our floating IPs
|
488
|
+
config[:all_instances][hostname][:addresses].each do |address|
|
489
|
+
if address.label == 'public'
|
490
|
+
return address.address
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
def Stack.get_private_ip(config, hostname)
|
496
|
+
# get the private address for an instance
|
497
|
+
config[:all_instances][hostname][:addresses].each do |address|
|
498
|
+
if address.label == 'private'
|
499
|
+
return address.address
|
500
|
+
end
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
|
505
|
+
|
454
506
|
def Stack.show_running(config)
|
455
507
|
# TODO: optionally show the hosts that are missing
|
456
508
|
ours = Stack.get_our_instances(config)
|
@@ -460,11 +512,6 @@ cookbook_path [ '<%=config[:stackhome]%>/cookbooks' ]
|
|
460
512
|
end
|
461
513
|
end
|
462
514
|
|
463
|
-
# Add an instance to the :all_instances hash, instead of having to poll the whole lot again
|
464
|
-
def Stack.add_instance(config, hostname, region, id, addresses)
|
465
|
-
config[:all_instances][hostname] = { :region => region, :id => id, :addresses => addresses}
|
466
|
-
end
|
467
|
-
|
468
515
|
def Stack.ssh(config, hostname = nil, user = ENV['USER'], command = nil)
|
469
516
|
# ssh to a host, or all hosts
|
470
517
|
|
@@ -486,6 +533,7 @@ cookbook_path [ '<%=config[:stackhome]%>/cookbooks' ]
|
|
486
533
|
|
487
534
|
|
488
535
|
def Stack.get_our_instances(config)
|
536
|
+
Logger.debug { ">>> Stack.get_our_instances" }
|
489
537
|
# build an hash of running instances that match our generated hostnames
|
490
538
|
node_details = Stack.populate_config(config)
|
491
539
|
|
@@ -504,6 +552,8 @@ cookbook_path [ '<%=config[:stackhome]%>/cookbooks' ]
|
|
504
552
|
end
|
505
553
|
end
|
506
554
|
|
555
|
+
Logger.debug { "#{running}" }
|
556
|
+
Logger.debug { "<<< Stack.get_our_instances" }
|
507
557
|
running
|
508
558
|
end
|
509
559
|
|
@@ -540,16 +590,6 @@ cookbook_path [ '<%=config[:stackhome]%>/cookbooks' ]
|
|
540
590
|
end
|
541
591
|
end
|
542
592
|
|
543
|
-
def Stack.get_public_ip(config, hostname)
|
544
|
-
# get a public address from the instance
|
545
|
-
# (could be either the dynamic or one of our floating IPs
|
546
|
-
config[:all_instances][hostname][:addresses].each do |address|
|
547
|
-
if address.label == 'public'
|
548
|
-
return address.address
|
549
|
-
end
|
550
|
-
end
|
551
|
-
end
|
552
|
-
|
553
593
|
def Stack.set_chef_server(config, chef_server)
|
554
594
|
# set the private & public URLs for the chef server,
|
555
595
|
# called either after we create the Chef Server, or skip over it
|
@@ -685,6 +725,7 @@ cookbook_path [ '<%=config[:stackhome]%>/cookbooks' ]
|
|
685
725
|
Logger.debug { "multipart_cmd = #{multipart_cmd}" }
|
686
726
|
multipart = `#{multipart_cmd}`
|
687
727
|
Logger.debug { "multipart = #{multipart}" }
|
728
|
+
|
688
729
|
# 2) replace the tokens (CHEF_SERVER, CHEF_ENVIRONMENT, SERVER_NAME, ROLE)
|
689
730
|
Logger.debug { "Replacing %HOSTNAME% with #{hostname} in multipart" }
|
690
731
|
multipart.gsub!(%q!%HOSTNAME%!, hostname)
|
@@ -714,6 +755,21 @@ cookbook_path [ '<%=config[:stackhome]%>/cookbooks' ]
|
|
714
755
|
multipart.gsub!(%q!%ROLE%!, role.to_s)
|
715
756
|
multipart.gsub!(%q!%DATA_DIR%!, role_details[:data_dir])
|
716
757
|
|
758
|
+
if role_details[:bootstrap].match('\.erb$') || role_details[:cloud_config_yaml].match('\.erb$')
|
759
|
+
Logger.debug { "bootstrap or cloud_config_yaml are erb files"}
|
760
|
+
instances = Hash.try_convert(Stack.get_our_instances(config))
|
761
|
+
all_instances = Hash.try_convert(Stack.get_all_instances(config))
|
762
|
+
|
763
|
+
Logger.debug { instances }
|
764
|
+
Logger.debug { all_instances }
|
765
|
+
|
766
|
+
multipart_template = ERB.new(multipart)
|
767
|
+
multipart_complete = multipart_template.result(binding)
|
768
|
+
|
769
|
+
Logger.debug { "multipart_complete after erb => #{multipart_complete} " }
|
770
|
+
multipart = multipart_complete
|
771
|
+
end
|
772
|
+
|
717
773
|
Logger.info "Creating #{hostname} in #{node_details[hostname][:az]} with role #{role}"
|
718
774
|
|
719
775
|
# this will get put in /meta.js
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stack-kicker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.12
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rdoc
|
@@ -143,9 +143,14 @@ files:
|
|
143
143
|
- doc/examples/.rvmrc
|
144
144
|
- doc/examples/Gemfile
|
145
145
|
- doc/examples/apache-via-chef-server/Stackfile
|
146
|
+
- doc/examples/apache-via-cloud-init/README.md
|
146
147
|
- doc/examples/apache-via-cloud-init/Stackfile
|
147
148
|
- doc/examples/apache-via-cloud-init/cloud-init.sh
|
148
149
|
- doc/examples/apache-via-cloud-init/cloud-init.yaml
|
150
|
+
- doc/examples/cloud-init-with-erb/README.md
|
151
|
+
- doc/examples/cloud-init-with-erb/Stackfile
|
152
|
+
- doc/examples/cloud-init-with-erb/cloud-init.sh.erb
|
153
|
+
- doc/examples/cloud-init-with-erb/cloud-init.yaml.erb
|
149
154
|
- features/stack-kicker.feature
|
150
155
|
- features/step_definitions/stack-kicker_steps.rb
|
151
156
|
- features/support/env.rb
|
@@ -167,12 +172,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
167
172
|
- - ! '>='
|
168
173
|
- !ruby/object:Gem::Version
|
169
174
|
version: '0'
|
175
|
+
segments:
|
176
|
+
- 0
|
177
|
+
hash: 503041037987723651
|
170
178
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
179
|
none: false
|
172
180
|
requirements:
|
173
181
|
- - ! '>='
|
174
182
|
- !ruby/object:Gem::Version
|
175
183
|
version: '0'
|
184
|
+
segments:
|
185
|
+
- 0
|
186
|
+
hash: 503041037987723651
|
176
187
|
requirements: []
|
177
188
|
rubyforge_project:
|
178
189
|
rubygems_version: 1.8.23
|