knife-server 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,6 @@
1
+ require "knife/server/version"
2
+
3
+ module Knife
4
+ module Server
5
+ end
6
+ end
@@ -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