knife-container 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +1 -0
- data/.travis.yml +4 -0
- data/CONTRIBUTING.md +152 -0
- data/Gemfile +10 -0
- data/LICENSE +201 -0
- data/README.md +59 -0
- data/Rakefile +16 -0
- data/knife-container.gemspec +31 -0
- data/lib/chef/knife/container_docker_build.rb +243 -0
- data/lib/chef/knife/container_docker_init.rb +262 -0
- data/lib/knife-container/chef_runner.rb +83 -0
- data/lib/knife-container/command.rb +45 -0
- data/lib/knife-container/generator.rb +88 -0
- data/lib/knife-container/helpers.rb +16 -0
- data/lib/knife-container/skeletons/knife_container/files/default/plugins/docker_container.rb +37 -0
- data/lib/knife-container/skeletons/knife_container/metadata.rb +7 -0
- data/lib/knife-container/skeletons/knife_container/recipes/docker_init.rb +181 -0
- data/lib/knife-container/skeletons/knife_container/templates/default/berksfile.erb +5 -0
- data/lib/knife-container/skeletons/knife_container/templates/default/config.rb.erb +16 -0
- data/lib/knife-container/skeletons/knife_container/templates/default/dockerfile.erb +9 -0
- data/lib/knife-container/skeletons/knife_container/templates/default/dockerignore.erb +0 -0
- data/lib/knife-container/skeletons/knife_container/templates/default/node_name.erb +1 -0
- data/lib/knife-container/version.rb +5 -0
- data/spec/functional/docker_container_ohai_spec.rb +20 -0
- data/spec/functional/fixtures/ohai/Dockerfile +3 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/test_helpers.rb +59 -0
- data/spec/unit/container_docker_build_spec.rb +325 -0
- data/spec/unit/container_docker_init_spec.rb +464 -0
- data/spec/unit/fixtures/.chef/encrypted_data_bag_secret +0 -0
- data/spec/unit/fixtures/.chef/trusted_certs/chef_example_com.crt +0 -0
- data/spec/unit/fixtures/.chef/validator.pem +1 -0
- data/spec/unit/fixtures/Berksfile +3 -0
- data/spec/unit/fixtures/cookbooks/dummy/metadata.rb +0 -0
- data/spec/unit/fixtures/cookbooks/nginx/metadata.rb +0 -0
- data/spec/unit/fixtures/environments/dev.json +0 -0
- data/spec/unit/fixtures/nodes/demo.json +0 -0
- data/spec/unit/fixtures/roles/base.json +0 -0
- data/spec/unit/fixtures/site-cookbooks/apt/metadata.rb +0 -0
- metadata +232 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2014 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'chef'
|
19
|
+
|
20
|
+
module KnifeContainer
|
21
|
+
# An adapter to chef's APIs to kick off a chef-client run.
|
22
|
+
class ChefRunner
|
23
|
+
|
24
|
+
attr_reader :cookbook_path
|
25
|
+
attr_reader :run_list
|
26
|
+
|
27
|
+
def initialize(cookbook_path, run_list)
|
28
|
+
@cookbook_path = cookbook_path
|
29
|
+
@run_list = run_list
|
30
|
+
@formatter = nil
|
31
|
+
@ohai = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def converge
|
35
|
+
configure
|
36
|
+
Chef::Runner.new(run_context).converge
|
37
|
+
end
|
38
|
+
|
39
|
+
def run_context
|
40
|
+
@run_context ||= policy.setup_run_context
|
41
|
+
end
|
42
|
+
|
43
|
+
def policy
|
44
|
+
return @policy_builder if @policy_builder
|
45
|
+
|
46
|
+
@policy_builder = Chef::PolicyBuilder::ExpandNodeObject.new("knife_container", ohai.data, {}, nil, formatter)
|
47
|
+
@policy_builder.load_node
|
48
|
+
@policy_builder.build_node
|
49
|
+
@policy_builder.node.run_list(*run_list)
|
50
|
+
@policy_builder.expand_run_list
|
51
|
+
@policy_builder
|
52
|
+
end
|
53
|
+
|
54
|
+
def formatter
|
55
|
+
@formatter ||= Chef::Formatters.new(:doc, stdout, stderr)
|
56
|
+
end
|
57
|
+
|
58
|
+
def configure
|
59
|
+
Chef::Config.solo = true
|
60
|
+
Chef::Config.cookbook_path = cookbook_path
|
61
|
+
Chef::Config.color = true
|
62
|
+
Chef::Config.diff_disabled = true
|
63
|
+
end
|
64
|
+
|
65
|
+
def ohai
|
66
|
+
return @ohai if @ohai
|
67
|
+
|
68
|
+
@ohai = Ohai::System.new
|
69
|
+
@ohai.all_plugins(["platform", "platform_version"])
|
70
|
+
@ohai
|
71
|
+
end
|
72
|
+
|
73
|
+
def stdout
|
74
|
+
$stdout
|
75
|
+
end
|
76
|
+
|
77
|
+
def stderr
|
78
|
+
$stderr
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2014 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'knife-container/generator'
|
19
|
+
require 'knife-container/chef_runner'
|
20
|
+
|
21
|
+
module KnifeContainer
|
22
|
+
module Command
|
23
|
+
|
24
|
+
# An instance of ChefRunner. Calling ChefRunner#converge will trigger
|
25
|
+
# convergence and generate the desired code.
|
26
|
+
def chef_runner
|
27
|
+
@chef_runner ||= ChefRunner.new(docker_cookbook_path, ["knife_container::#{recipe}"])
|
28
|
+
end
|
29
|
+
|
30
|
+
# Path to the directory where the code_generator cookbook is located.
|
31
|
+
# For now, this is hard coded to the 'skeletons' directory in this
|
32
|
+
# repo.
|
33
|
+
def docker_cookbook_path
|
34
|
+
File.expand_path("../skeletons", __FILE__)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Delegates to `Generator.context`, the singleton instance of
|
38
|
+
# Generator::Context
|
39
|
+
def generator_context
|
40
|
+
Generator.context
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2014 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
module KnifeContainer
|
19
|
+
|
20
|
+
module Generator
|
21
|
+
|
22
|
+
class Context
|
23
|
+
|
24
|
+
attr_accessor :dockerfile_name
|
25
|
+
attr_accessor :dockerfiles_path
|
26
|
+
attr_accessor :base_image
|
27
|
+
attr_accessor :chef_client_mode
|
28
|
+
attr_accessor :run_list
|
29
|
+
attr_accessor :cookbook_path
|
30
|
+
attr_accessor :role_path
|
31
|
+
attr_accessor :node_path
|
32
|
+
attr_accessor :environment_path
|
33
|
+
attr_accessor :chef_server_url
|
34
|
+
attr_accessor :validation_key
|
35
|
+
attr_accessor :validation_client_name
|
36
|
+
attr_accessor :trusted_certs_dir
|
37
|
+
attr_accessor :encrypted_data_bag_secret
|
38
|
+
attr_accessor :first_boot
|
39
|
+
attr_accessor :berksfile
|
40
|
+
attr_accessor :generate_berksfile
|
41
|
+
attr_accessor :run_berks
|
42
|
+
attr_accessor :force_build
|
43
|
+
attr_accessor :include_credentials
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.reset
|
48
|
+
@context = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.context
|
52
|
+
@context ||= Context.new
|
53
|
+
end
|
54
|
+
|
55
|
+
module TemplateHelper
|
56
|
+
|
57
|
+
def self.delegate_to_app_context(name)
|
58
|
+
define_method(name) do
|
59
|
+
KnifeContainer::Generator.context.public_send(name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# delegate all the attributes of app_config
|
64
|
+
delegate_to_app_context :dockerfile_name
|
65
|
+
delegate_to_app_context :dockerfiles_path
|
66
|
+
delegate_to_app_context :base_image
|
67
|
+
delegate_to_app_context :chef_client_mode
|
68
|
+
delegate_to_app_context :run_list
|
69
|
+
delegate_to_app_context :cookbook_path
|
70
|
+
delegate_to_app_context :role_path
|
71
|
+
delegate_to_app_context :node_path
|
72
|
+
delegate_to_app_context :environment_path
|
73
|
+
delegate_to_app_context :chef_server_url
|
74
|
+
delegate_to_app_context :validation_key
|
75
|
+
delegate_to_app_context :validation_client_name
|
76
|
+
delegate_to_app_context :trusted_certs_dir
|
77
|
+
delegate_to_app_context :encrypted_data_bag_secret
|
78
|
+
delegate_to_app_context :first_boot
|
79
|
+
delegate_to_app_context :berksfile
|
80
|
+
delegate_to_app_context :generate_berksfile
|
81
|
+
delegate_to_app_context :run_berks
|
82
|
+
delegate_to_app_context :force_build
|
83
|
+
delegate_to_app_context :include_credentials
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
module KnifeContainer
|
3
|
+
|
4
|
+
module Helpers
|
5
|
+
#
|
6
|
+
# Generates a short, but random UID for instances.
|
7
|
+
#
|
8
|
+
# @return [String]
|
9
|
+
#
|
10
|
+
def random_uid
|
11
|
+
require 'securerandom' unless defined?(SecureRandom)
|
12
|
+
SecureRandom.hex(3)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'docker' # it gets this from chef-init
|
2
|
+
|
3
|
+
Ohai.plugin(:DockerContainer) do
|
4
|
+
provides "docker_container"
|
5
|
+
|
6
|
+
def container_id
|
7
|
+
shell_out("hostname").stdout.strip
|
8
|
+
end
|
9
|
+
|
10
|
+
def looks_like_docker?
|
11
|
+
hint?('docker_container') || !!Docker.version && !!Docker::Container.get(container_id)
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# The format of the data is collection is the inspect API
|
16
|
+
# http://docs.docker.io/reference/api/docker_remote_api_v1.11/#inspect-a-container
|
17
|
+
#
|
18
|
+
collect_data do
|
19
|
+
metadata_from_hints = hint?('docker_container')
|
20
|
+
|
21
|
+
if looks_like_docker?
|
22
|
+
Ohai::Log.debug("looks_like_docker? == true")
|
23
|
+
docker_container Mash.new
|
24
|
+
|
25
|
+
if metadata_from_hints
|
26
|
+
Ohai::Log.debug("docker_container hints present")
|
27
|
+
metadata_from_hints.each { |k,v| docker_container[k] = v }
|
28
|
+
end
|
29
|
+
|
30
|
+
container = Docker::Container.get(container_id).json
|
31
|
+
container.each { |k,v| docker_container[k] = v }
|
32
|
+
else
|
33
|
+
Ohai::Log.debug("looks_like_docker? == false")
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
context = KnifeContainer::Generator.context
|
2
|
+
dockerfile_dir = File.join(context.dockerfiles_path, context.dockerfile_name)
|
3
|
+
temp_chef_repo = File.join(dockerfile_dir, "chef")
|
4
|
+
user_chef_repo = File.join(context.dockerfiles_path, "..")
|
5
|
+
|
6
|
+
##
|
7
|
+
# Initial Setup
|
8
|
+
#
|
9
|
+
|
10
|
+
# Create Dockerfile directory (REPO/NAME)
|
11
|
+
directory dockerfile_dir do
|
12
|
+
recursive true
|
13
|
+
end
|
14
|
+
|
15
|
+
# Dockerfile
|
16
|
+
template File.join(dockerfile_dir, "Dockerfile") do
|
17
|
+
source "dockerfile.erb"
|
18
|
+
helpers(KnifeContainer::Generator::TemplateHelper)
|
19
|
+
end
|
20
|
+
|
21
|
+
# .dockerfile
|
22
|
+
template File.join(dockerfile_dir, ".dockerignore") do
|
23
|
+
source "dockerignore.erb"
|
24
|
+
helpers(KnifeContainer::Generator::TemplateHelper)
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
##
|
29
|
+
# Initial Chef Setup
|
30
|
+
#
|
31
|
+
|
32
|
+
# create temp chef-repo
|
33
|
+
directory temp_chef_repo do
|
34
|
+
recursive true
|
35
|
+
end
|
36
|
+
|
37
|
+
# Client Config
|
38
|
+
template File.join(temp_chef_repo, "#{context.chef_client_mode}.rb") do
|
39
|
+
source "config.rb.erb"
|
40
|
+
helpers(KnifeContainer::Generator::TemplateHelper)
|
41
|
+
end
|
42
|
+
|
43
|
+
# First Boot JSON
|
44
|
+
file File.join(temp_chef_repo, "first-boot.json") do
|
45
|
+
content context.first_boot
|
46
|
+
end
|
47
|
+
|
48
|
+
# Node Name
|
49
|
+
template File.join(temp_chef_repo, ".node_name") do
|
50
|
+
source "node_name.erb"
|
51
|
+
helpers(KnifeContainer::Generator::TemplateHelper)
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Resolve run list
|
56
|
+
#
|
57
|
+
require 'chef/run_list/run_list_item'
|
58
|
+
run_list_items = context.run_list.map { |i| Chef::RunList::RunListItem.new(i) }
|
59
|
+
cookbooks = []
|
60
|
+
|
61
|
+
run_list_items.each do |item|
|
62
|
+
# Extract cookbook name from recipe
|
63
|
+
if item.recipe?
|
64
|
+
rmatch = item.name.match(/(.+?)::(.+)/)
|
65
|
+
if rmatch
|
66
|
+
cookbooks << rmatch[1]
|
67
|
+
else
|
68
|
+
cookbooks << item.name
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Generate Berksfile from runlist
|
74
|
+
unless context.run_list.empty?
|
75
|
+
template File.join(dockerfile_dir, "Berksfile") do
|
76
|
+
source "berksfile.erb"
|
77
|
+
variables :cookbooks => cookbooks
|
78
|
+
helpers(KnifeContainer::Generator::TemplateHelper)
|
79
|
+
only_if { context.generate_berksfile }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Copy over the necessary directories into the temp chef-repo (if local-mode)
|
84
|
+
if context.chef_client_mode == "zero"
|
85
|
+
|
86
|
+
# generate a cookbooks directory unless we are building from a Berksfile
|
87
|
+
unless context.generate_berksfile
|
88
|
+
directory "#{temp_chef_repo}/cookbooks"
|
89
|
+
end
|
90
|
+
|
91
|
+
# Copy over cookbooks that are mentioned in the runlist. There is a gap here
|
92
|
+
# that dependent cookbooks are not copied. This is a result of not having a
|
93
|
+
# depsolver in the chef-client. The solution here is to use the Berkshelf integration.
|
94
|
+
if context.cookbook_path.kind_of?(Array)
|
95
|
+
context.cookbook_path.each do |dir|
|
96
|
+
if File.exists?(File.expand_path(dir))
|
97
|
+
cookbooks.each do |cookbook|
|
98
|
+
if File.exists?("#{File.expand_path(dir)}/#{cookbook}")
|
99
|
+
execute "cp -rf #{File.expand_path(dir)}/#{cookbook} #{temp_chef_repo}/cookbooks/"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
else
|
103
|
+
log "Could not find a '#{File.expand_path(dir)}' directory in your chef-repo."
|
104
|
+
end
|
105
|
+
end
|
106
|
+
elsif File.exists?(File.expand_path(context.cookbook_path))
|
107
|
+
cookbooks.each do |cookbook|
|
108
|
+
if File.exists?("#{File.expand_path(context.cookbook_path)}/#{cookbook}")
|
109
|
+
execute "cp -rf #{File.expand_path(context.cookbook_path)}/#{cookbook} #{temp_chef_repo}/cookbooks/"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
log "Could not find a '#{File.expand_path(context.cookbook_path)}' directory in your chef-repo."
|
114
|
+
end
|
115
|
+
|
116
|
+
# Because they have a smaller footprint, we will copy over all the roles, environments
|
117
|
+
# and nodes. This behavior will likely change in a future version of knife-container.
|
118
|
+
%w(role environment node).each do |dir|
|
119
|
+
path = context.send(:"#{dir}_path")
|
120
|
+
if path.kind_of?(Array)
|
121
|
+
path.each do |p|
|
122
|
+
execute "cp -r #{File.expand_path(p)}/ #{File.join(temp_chef_repo, "#{dir}s")}" do
|
123
|
+
not_if { Dir["#{p}/*"].empty? }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
elsif path.kind_of?(String)
|
127
|
+
execute "cp -r #{path}/ #{File.join(temp_chef_repo, "#{dir}s/")}" do
|
128
|
+
not_if { Dir["#{path}/*"].empty? }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Server Only Stuff
|
136
|
+
#
|
137
|
+
if context.chef_client_mode == "client"
|
138
|
+
|
139
|
+
directory File.join(temp_chef_repo, 'secure')
|
140
|
+
|
141
|
+
# Add validation.pem
|
142
|
+
file File.join(temp_chef_repo, 'secure', "validation.pem") do
|
143
|
+
content File.read(context.validation_key)
|
144
|
+
mode '0600'
|
145
|
+
end
|
146
|
+
|
147
|
+
# Copy over trusted certs
|
148
|
+
unless Dir["#{context.trusted_certs_dir}/*"].empty?
|
149
|
+
directory File.join(temp_chef_repo, 'secure', "trusted_certs")
|
150
|
+
execute "cp -r #{context.trusted_certs_dir}/* #{File.join(temp_chef_repo, 'secure', "trusted_certs/")}"
|
151
|
+
end
|
152
|
+
|
153
|
+
# Copy over encrypted_data_bag_key
|
154
|
+
unless context.encrypted_data_bag_secret.nil?
|
155
|
+
if File.exists?(context.encrypted_data_bag_secret)
|
156
|
+
file File.join(temp_chef_repo, 'secure', "encrypted_data_bag_secret") do
|
157
|
+
content File.read(context.encrypted_data_bag_secret)
|
158
|
+
mode '0600'
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Create Ohai Plugin
|
166
|
+
#
|
167
|
+
|
168
|
+
# create Ohai folder
|
169
|
+
# directory File.join(temp_chef_repo, "ohai")
|
170
|
+
|
171
|
+
# docker hints directory
|
172
|
+
# directory File.join(temp_chef_repo, "ohai", "hints")
|
173
|
+
|
174
|
+
# docker plugins directory
|
175
|
+
# directory File.join(temp_chef_repo, "ohai_plugins")
|
176
|
+
|
177
|
+
# docker_container Ohai plugin
|
178
|
+
# cookbook_file File.join(temp_chef_repo, "ohai_plugins", "docker_container.rb") do
|
179
|
+
# source "plugins/docker_container.rb"
|
180
|
+
# mode "0755"
|
181
|
+
# end
|