stack-kicker 0.0.11 → 0.0.12
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.
- 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
|