knife-server 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +21 -0
- data/.rspec +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +9 -0
- data/Gemfile +8 -0
- data/LICENSE +201 -0
- data/README.md +267 -0
- data/Rakefile +16 -0
- data/knife-server.gemspec +25 -0
- data/lib/chef/knife/bootstrap/chef-server-debian.erb +127 -0
- data/lib/chef/knife/server_bootstrap_ec2.rb +235 -0
- data/lib/knife-server.rb +6 -0
- data/lib/knife/server/credentials.rb +64 -0
- data/lib/knife/server/ec2_security_group.rb +89 -0
- data/lib/knife/server/ssh.rb +34 -0
- data/lib/knife/server/version.rb +5 -0
- data/spec/chef/knife/server_bootstrap_ec2_spec.rb +259 -0
- data/spec/knife/server/credientials_spec.rb +104 -0
- data/spec/knife/server/ec2_security_group_spec.rb +164 -0
- data/spec/knife/server/ssh_spec.rb +62 -0
- metadata +135 -0
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
desc "Run all specs in spec directory"
|
10
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
11
|
+
t.pattern = 'spec/**/*_spec.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
rescue LoadError
|
15
|
+
STDERR.puts "\n*** RSpec not available. (sudo) gem install rspec to run unit tests. ***\n\n"
|
16
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/knife/server/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Fletcher Nichol"]
|
6
|
+
gem.email = ["fnichol@nichol.ca"]
|
7
|
+
gem.summary = %q{Chef Knife plugin to bootstrap Chef Servers}
|
8
|
+
gem.description = gem.summary
|
9
|
+
gem.homepage = "http://fnichol.github.com/knife-server"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "knife-server"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Knife::Server::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency "fog", "~> 1.3"
|
19
|
+
gem.add_dependency "net-ssh"
|
20
|
+
gem.add_dependency "chef", ">= 0.10.10"
|
21
|
+
gem.add_dependency "knife-ec2", "~> 0.5.12"
|
22
|
+
|
23
|
+
gem.add_development_dependency "rspec", "~> 2.10"
|
24
|
+
gem.add_development_dependency "fakefs", "~> 0.4.0"
|
25
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
bash -c '
|
2
|
+
<%=
|
3
|
+
if knife_config[:bootstrap_proxy]
|
4
|
+
%{export http_proxy="#{knife_config[:bootstrap_proxy]}"}
|
5
|
+
end
|
6
|
+
-%>
|
7
|
+
|
8
|
+
export hostname="<%= @config[:chef_node_name] %>"
|
9
|
+
export webui_password="<%= ENV['WEBUI_PASSWORD'] %>"
|
10
|
+
export amqp_password="<%= ENV['AMQP_PASSWORD'] %>"
|
11
|
+
export DEBIAN_FRONTEND=noninteractive
|
12
|
+
|
13
|
+
set -x
|
14
|
+
|
15
|
+
setup() {
|
16
|
+
apt-get update
|
17
|
+
apt-get install -y lsb-release
|
18
|
+
|
19
|
+
platform="$(lsb_release -is | tr [[:upper:]] [[:lower:]])"
|
20
|
+
platform_version="$(lsb_release -rs)"
|
21
|
+
}
|
22
|
+
|
23
|
+
set_hostname_for_ubuntu() {
|
24
|
+
if hostname | grep -q "$hostname" >/dev/null ; then
|
25
|
+
printf "Hostname is correct, so skipping...\n"
|
26
|
+
return
|
27
|
+
fi
|
28
|
+
|
29
|
+
local host_first="$(echo $hostname | cut -d . -f 1)"
|
30
|
+
local hostnames="${hostname} ${host_first}"
|
31
|
+
|
32
|
+
sed -i "s/^.*$/$hostname/" /etc/hostname
|
33
|
+
if egrep -q "^127.0.1.1[[:space:]]" /etc/hosts >/dev/null ; then
|
34
|
+
sed -i "s/^\(127[.]0[.]1[.]1[[:space:]]\+\)/\1${hostnames} /" \
|
35
|
+
/etc/hosts
|
36
|
+
else
|
37
|
+
sed -i "s/^\(127[.]0[.]0[.]1[[:space:]]\+.*\)$/\1\n127.0.1.1 ${hostnames} /" \
|
38
|
+
/etc/hosts
|
39
|
+
fi
|
40
|
+
service hostname start
|
41
|
+
}
|
42
|
+
|
43
|
+
set_hostname_for_debian() {
|
44
|
+
if hostname --fqdn | grep -q "^${hostname}$" || hostname --short | grep -q "^${hostname}$" ; then
|
45
|
+
printf "Hostname is correct, so skipping...\n"
|
46
|
+
return
|
47
|
+
fi
|
48
|
+
|
49
|
+
local host_first="$(echo $hostname | cut -d . -f 1)"
|
50
|
+
|
51
|
+
sed -r -i "s/^(127[.]0[.]1[.]1[[:space:]]+).*$/\\1${hostname} ${host_first}/" \
|
52
|
+
/etc/hosts
|
53
|
+
echo $host_first > /etc/hostname
|
54
|
+
hostname -F /etc/hostname
|
55
|
+
}
|
56
|
+
|
57
|
+
add_opscode_apt_repo() {
|
58
|
+
echo "deb http://apt.opscode.com/ $(lsb_release -cs)-0.10 main" > \
|
59
|
+
/etc/apt/sources.list.d/opscode.list
|
60
|
+
|
61
|
+
# add the GPG Key and Update Index
|
62
|
+
mkdir -p /etc/apt/trusted.gpg.d
|
63
|
+
apt-get update
|
64
|
+
# permanent upgradeable keyring
|
65
|
+
apt-get install -y --force-yes opscode-keyring
|
66
|
+
apt-get upgrade -y
|
67
|
+
}
|
68
|
+
|
69
|
+
preseed_chef_pkg() {
|
70
|
+
local preseed=/var/cache/local/preseeding/chef-server.seed
|
71
|
+
|
72
|
+
mkdir -p $(dirname $preseed)
|
73
|
+
cat <<PRESEED > $preseed
|
74
|
+
chef chef/chef_server_url string http://127.0.0.1:4000
|
75
|
+
chef-server-webui chef-server-webui/admin_password password $webui_password
|
76
|
+
chef-solr chef-solr/amqp_password password $amqp_password
|
77
|
+
PRESEED
|
78
|
+
|
79
|
+
debconf-set-selections $preseed
|
80
|
+
}
|
81
|
+
|
82
|
+
install_chef_server() {
|
83
|
+
preseed_chef_pkg
|
84
|
+
|
85
|
+
apt-get install -y --force-yes chef chef-server libshadow-ruby1.8
|
86
|
+
}
|
87
|
+
|
88
|
+
config_chef_solo() {
|
89
|
+
## Configure Apache2 to proxy SSL traffic, using chef-solo
|
90
|
+
local tmp_solo="$1"
|
91
|
+
|
92
|
+
mkdir -p $tmp_solo
|
93
|
+
cat > $tmp_solo/solo.rb <<SOLO_RB
|
94
|
+
file_cache_path "$tmp_solo"
|
95
|
+
cookbook_path "$tmp_solo/cookbooks"
|
96
|
+
SOLO_RB
|
97
|
+
|
98
|
+
cat <<BOOTSTRAP_JSON > $tmp_solo/bootstrap.json
|
99
|
+
{
|
100
|
+
"chef_server" : {
|
101
|
+
"webui_enabled" : true,
|
102
|
+
"ssl_req" : "/C=CA/ST=Several/L=Locality/O=Example/OU=Operations/CN=${hostname}/emailAddress=root@${hostname}"
|
103
|
+
},
|
104
|
+
"run_list" : [ "recipe[chef-server::apache-proxy]" ]
|
105
|
+
}
|
106
|
+
BOOTSTRAP_JSON
|
107
|
+
}
|
108
|
+
|
109
|
+
enable_ssl_proxy() {
|
110
|
+
local tmp_solo=/tmp/chef-solo
|
111
|
+
|
112
|
+
config_chef_solo $tmp_solo
|
113
|
+
|
114
|
+
chef-solo -c $tmp_solo/solo.rb -j $tmp_solo/bootstrap.json \
|
115
|
+
-r http://s3.amazonaws.com/chef-solo/bootstrap-latest.tar.gz
|
116
|
+
|
117
|
+
rm -rf $tmp_solo
|
118
|
+
}
|
119
|
+
|
120
|
+
setup
|
121
|
+
set_hostname_for_${platform}
|
122
|
+
add_opscode_apt_repo
|
123
|
+
install_chef_server
|
124
|
+
enable_ssl_proxy
|
125
|
+
|
126
|
+
printf -- "-----> Bootstraping Chef Server on ${hostname} is complete.\n"
|
127
|
+
'
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'chef/knife'
|
2
|
+
require 'knife/server/ec2_security_group'
|
3
|
+
require 'knife/server/ssh'
|
4
|
+
require 'knife/server/credentials'
|
5
|
+
|
6
|
+
class Chef
|
7
|
+
class Knife
|
8
|
+
class ServerBootstrapEc2 < Knife
|
9
|
+
|
10
|
+
deps do
|
11
|
+
require 'chef/knife/ec2_server_create'
|
12
|
+
require 'fog'
|
13
|
+
require 'net/ssh'
|
14
|
+
Chef::Knife::Ec2ServerCreate.load_deps
|
15
|
+
end
|
16
|
+
|
17
|
+
banner "knife server bootstrap ec2 (options)"
|
18
|
+
|
19
|
+
option :chef_node_name,
|
20
|
+
:short => "-N NAME",
|
21
|
+
:long => "--node-name NAME",
|
22
|
+
:description => "The name of your new Chef Server"
|
23
|
+
|
24
|
+
option :platform,
|
25
|
+
:short => "-P PLATFORM",
|
26
|
+
:long => "--platform PLATFORM",
|
27
|
+
:description => "The platform type that will be bootstrapped (debian)",
|
28
|
+
:default => "debian"
|
29
|
+
|
30
|
+
option :ssh_user,
|
31
|
+
:short => "-x USERNAME",
|
32
|
+
:long => "--ssh-user USERNAME",
|
33
|
+
:description => "The ssh username",
|
34
|
+
:default => "root"
|
35
|
+
|
36
|
+
option :ssh_port,
|
37
|
+
:short => "-p PORT",
|
38
|
+
:long => "--ssh-port PORT",
|
39
|
+
:description => "The ssh port",
|
40
|
+
:default => "22",
|
41
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
|
42
|
+
|
43
|
+
option :identity_file,
|
44
|
+
:short => "-i IDENTITY_FILE",
|
45
|
+
:long => "--identity-file IDENTITY_FILE",
|
46
|
+
:description => "The SSH identity file used for authentication"
|
47
|
+
|
48
|
+
option :prerelease,
|
49
|
+
:long => "--prerelease",
|
50
|
+
:description => "Install the pre-release chef gem"
|
51
|
+
|
52
|
+
option :bootstrap_version,
|
53
|
+
:long => "--bootstrap-version VERSION",
|
54
|
+
:description => "The version of Chef to install",
|
55
|
+
:proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
|
56
|
+
|
57
|
+
option :template_file,
|
58
|
+
:long => "--template-file TEMPLATE",
|
59
|
+
:description => "Full path to location of template to use",
|
60
|
+
:proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
|
61
|
+
:default => false
|
62
|
+
|
63
|
+
option :distro,
|
64
|
+
:short => "-d DISTRO",
|
65
|
+
:long => "--distro DISTRO",
|
66
|
+
:description => "Bootstrap a distro using a template; default is 'chef-server-<platform>'"
|
67
|
+
|
68
|
+
option :webui_password,
|
69
|
+
:long => "--webui-password SECRET",
|
70
|
+
:description => "Initial password for WebUI admin account, default is 'chefchef'",
|
71
|
+
:default => "chefchef"
|
72
|
+
|
73
|
+
option :amqp_password,
|
74
|
+
:long => "--amqp-password SECRET",
|
75
|
+
:description => "Initial password for AMQP, default is 'chefchef'",
|
76
|
+
:default => "chefchef"
|
77
|
+
|
78
|
+
# aws/ec2 options
|
79
|
+
|
80
|
+
option :aws_access_key_id,
|
81
|
+
:short => "-A ID",
|
82
|
+
:long => "--aws-access-key-id KEY",
|
83
|
+
:description => "Your AWS Access Key ID",
|
84
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key }
|
85
|
+
|
86
|
+
option :aws_secret_access_key,
|
87
|
+
:short => "-K SECRET",
|
88
|
+
:long => "--aws-secret-access-key SECRET",
|
89
|
+
:description => "Your AWS API Secret Access Key",
|
90
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key }
|
91
|
+
|
92
|
+
option :region,
|
93
|
+
:long => "--region REGION",
|
94
|
+
:description => "Your AWS region",
|
95
|
+
:default => "us-east-1",
|
96
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:region] = key }
|
97
|
+
|
98
|
+
option :ssh_key_name,
|
99
|
+
:short => "-S KEY",
|
100
|
+
:long => "--ssh-key KEY",
|
101
|
+
:description => "The AWS SSH key id",
|
102
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:aws_ssh_key_id] = key }
|
103
|
+
|
104
|
+
option :flavor,
|
105
|
+
:short => "-f FLAVOR",
|
106
|
+
:long => "--flavor FLAVOR",
|
107
|
+
:description => "The flavor of server (m1.small, m1.medium, etc)",
|
108
|
+
:proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f },
|
109
|
+
:default => "m1.small"
|
110
|
+
|
111
|
+
option :image,
|
112
|
+
:short => "-I IMAGE",
|
113
|
+
:long => "--image IMAGE",
|
114
|
+
:description => "The AMI for the server",
|
115
|
+
:proc => Proc.new { |i| Chef::Config[:knife][:image] = i }
|
116
|
+
|
117
|
+
option :availability_zone,
|
118
|
+
:short => "-Z ZONE",
|
119
|
+
:long => "--availability-zone ZONE",
|
120
|
+
:description => "The Availability Zone",
|
121
|
+
:default => "us-east-1b",
|
122
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:availability_zone] = key }
|
123
|
+
|
124
|
+
option :security_groups,
|
125
|
+
:short => "-G X,Y,Z",
|
126
|
+
:long => "--groups X,Y,Z",
|
127
|
+
:description => "The security groups for this server",
|
128
|
+
:default => ["infrastructure"],
|
129
|
+
:proc => Proc.new { |groups| groups.split(',') }
|
130
|
+
|
131
|
+
option :tags,
|
132
|
+
:short => "-T T=V[,T=V,...]",
|
133
|
+
:long => "--tags Tag=Value[,Tag=Value...]",
|
134
|
+
:description => "The tags for this server",
|
135
|
+
:proc => Proc.new { |tags| tags.split(',') }
|
136
|
+
|
137
|
+
option :ebs_size,
|
138
|
+
:long => "--ebs-size SIZE",
|
139
|
+
:description => "The size of the EBS volume in GB, for EBS-backed instances"
|
140
|
+
|
141
|
+
option :ebs_no_delete_on_term,
|
142
|
+
:long => "--ebs-no-delete-on-term",
|
143
|
+
:description => "Do not delete EBS volumn on instance termination"
|
144
|
+
|
145
|
+
def run
|
146
|
+
validate!
|
147
|
+
config_security_group
|
148
|
+
ec2_bootstrap.run
|
149
|
+
fetch_validation_key
|
150
|
+
create_root_client
|
151
|
+
install_client_key
|
152
|
+
end
|
153
|
+
|
154
|
+
def ec2_bootstrap
|
155
|
+
ENV['WEBUI_PASSWORD'] = config[:webui_password]
|
156
|
+
ENV['AMQP_PASSWORD'] = config[:amqp_password]
|
157
|
+
bootstrap = Chef::Knife::Ec2ServerCreate.new
|
158
|
+
[ :chef_node_name, :ssh_user, :ssh_port, :identity_file,
|
159
|
+
:security_groups, :ebs_size, :webui_password, :amqp_password
|
160
|
+
].each { |attr| bootstrap.config[attr] = config[attr] }
|
161
|
+
bootstrap.config[:tags] = bootstrap_tags
|
162
|
+
bootstrap.config[:distro] = bootstrap_distro
|
163
|
+
bootstrap
|
164
|
+
end
|
165
|
+
|
166
|
+
def ec2_connection
|
167
|
+
@ec2_connection ||= Fog::Compute.new(
|
168
|
+
:provider => 'AWS',
|
169
|
+
:aws_access_key_id => Chef::Config[:knife][:aws_access_key_id],
|
170
|
+
:aws_secret_access_key => Chef::Config[:knife][:aws_secret_access_key],
|
171
|
+
:region => Chef::Config[:knife][:region]
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
def server_dns_name
|
176
|
+
server = ec2_connection.servers.find do |s|
|
177
|
+
s.state == "running" &&
|
178
|
+
s.tags['Name'] == config[:chef_node_name] &&
|
179
|
+
s.tags['Role'] == 'chef_server'
|
180
|
+
end
|
181
|
+
|
182
|
+
server && server.dns_name
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def validate!
|
188
|
+
if config[:chef_node_name].nil?
|
189
|
+
ui.error "You did not provide a valid --node-name value."
|
190
|
+
exit 1
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def config_security_group(name = config[:security_groups].first)
|
195
|
+
::Knife::Server::Ec2SecurityGroup.new(ec2_connection, ui).
|
196
|
+
configure_chef_server_group(name, :description => "#{name} group")
|
197
|
+
end
|
198
|
+
|
199
|
+
def bootstrap_tags
|
200
|
+
Hash[Array(config[:tags]).map { |t| t.split('=') }].
|
201
|
+
merge({"Role" => "chef_server"}).map { |k,v| "#{k}=#{v}" }
|
202
|
+
end
|
203
|
+
|
204
|
+
def bootstrap_distro
|
205
|
+
config[:distro] || "chef-server-#{config[:platform]}"
|
206
|
+
end
|
207
|
+
|
208
|
+
def credentials_client
|
209
|
+
@credentials_client ||= ::Knife::Server::Credentials.new(
|
210
|
+
ssh_connection, Chef::Config[:validation_key])
|
211
|
+
end
|
212
|
+
|
213
|
+
def fetch_validation_key
|
214
|
+
credentials_client.install_validation_key
|
215
|
+
end
|
216
|
+
|
217
|
+
def install_client_key
|
218
|
+
credentials_client.install_client_key(
|
219
|
+
Chef::Config[:node_name], Chef::Config[:client_key])
|
220
|
+
end
|
221
|
+
|
222
|
+
def create_root_client
|
223
|
+
ui.msg(credentials_client.create_root_client)
|
224
|
+
end
|
225
|
+
|
226
|
+
def ssh_connection
|
227
|
+
::Knife::Server::SSH.new(
|
228
|
+
:host => server_dns_name,
|
229
|
+
:user => config[:ssh_user],
|
230
|
+
:port => config[:ssh_port]
|
231
|
+
)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
data/lib/knife-server.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Knife
|
4
|
+
module Server
|
5
|
+
class Credentials
|
6
|
+
def initialize(ssh, validation_key_path)
|
7
|
+
@ssh = ssh
|
8
|
+
@validation_key_path = validation_key_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def install_validation_key(suffix = Time.now.to_i)
|
12
|
+
if File.exists?(@validation_key_path)
|
13
|
+
FileUtils.cp(@validation_key_path,
|
14
|
+
backup_file_path(@validation_key_path, suffix))
|
15
|
+
end
|
16
|
+
|
17
|
+
File.open(@validation_key_path, "wb") do |f|
|
18
|
+
f.write(@ssh.exec!("cat /etc/chef/validation.pem"))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_root_client
|
23
|
+
@ssh.exec!([
|
24
|
+
"knife configure",
|
25
|
+
"--initial",
|
26
|
+
"--server-url http://127.0.0.1:4000",
|
27
|
+
"--user root",
|
28
|
+
'--repository ""',
|
29
|
+
"--defaults --yes"
|
30
|
+
].join(" "))
|
31
|
+
end
|
32
|
+
|
33
|
+
def install_client_key(user, client_key_path, suffix = Time.now.to_i)
|
34
|
+
create_user_client(user)
|
35
|
+
|
36
|
+
if File.exists?(client_key_path)
|
37
|
+
FileUtils.cp(client_key_path,
|
38
|
+
backup_file_path(client_key_path, suffix))
|
39
|
+
end
|
40
|
+
|
41
|
+
File.open(client_key_path, "wb") do |f|
|
42
|
+
f.write(@ssh.exec!("cat /tmp/chef-client-#{user}.pem"))
|
43
|
+
end
|
44
|
+
|
45
|
+
@ssh.exec!("rm -f /tmp/chef-client-#{user}.pem")
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def backup_file_path(file_path, suffix)
|
51
|
+
parts = file_path.rpartition(".")
|
52
|
+
"#{parts[0]}.#{suffix}.#{parts[2]}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_user_client(user)
|
56
|
+
@ssh.exec!([
|
57
|
+
"knife client create",
|
58
|
+
user,
|
59
|
+
"--admin --file /tmp/chef-client-#{user}.pem --disable-editing"
|
60
|
+
].join(" "))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|