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/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