knife-server 0.1.0
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/.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
|