puppetfactory 0.4.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.
- checksums.yaml +7 -0
- data/LICENSE +13 -0
- data/README.md +0 -0
- data/bin/pfsh +31 -0
- data/bin/puppetfactory +153 -0
- data/lib/puppetfactory.rb +300 -0
- data/lib/puppetfactory/cli.rb +114 -0
- data/lib/puppetfactory/dashboard/rake_tasks.rb +69 -0
- data/lib/puppetfactory/dashboard/serverspec_helper.rb +84 -0
- data/lib/puppetfactory/dashboard/spec_helper.rb +26 -0
- data/lib/puppetfactory/helpers.rb +37 -0
- data/lib/puppetfactory/monkeypatches.rb +30 -0
- data/lib/puppetfactory/plugins.rb +11 -0
- data/lib/puppetfactory/plugins/certificates.rb +28 -0
- data/lib/puppetfactory/plugins/classification.rb +75 -0
- data/lib/puppetfactory/plugins/code_manager.rb +156 -0
- data/lib/puppetfactory/plugins/console_user.rb +62 -0
- data/lib/puppetfactory/plugins/dashboard.rb +128 -0
- data/lib/puppetfactory/plugins/docker.rb +193 -0
- data/lib/puppetfactory/plugins/example.rb +88 -0
- data/lib/puppetfactory/plugins/github.rb +102 -0
- data/lib/puppetfactory/plugins/gitlab.rb +62 -0
- data/lib/puppetfactory/plugins/hooks.rb +46 -0
- data/lib/puppetfactory/plugins/login_shell.rb +10 -0
- data/lib/puppetfactory/plugins/logs.rb +34 -0
- data/lib/puppetfactory/plugins/r10k.rb +112 -0
- data/lib/puppetfactory/plugins/shell_user.rb +69 -0
- data/lib/puppetfactory/plugins/user_environment.rb +77 -0
- data/public/dashboard.js +100 -0
- data/public/font-awesome/css/font-awesome.css +2199 -0
- data/public/font-awesome/css/font-awesome.min.css +4 -0
- data/public/font-awesome/fonts/FontAwesome.otf +0 -0
- data/public/font-awesome/fonts/fontawesome-webfont.eot +0 -0
- data/public/font-awesome/fonts/fontawesome-webfont.svg +685 -0
- data/public/font-awesome/fonts/fontawesome-webfont.ttf +0 -0
- data/public/font-awesome/fonts/fontawesome-webfont.woff +0 -0
- data/public/font-awesome/fonts/fontawesome-webfont.woff2 +0 -0
- data/public/gitviz/LICENSE.md +20 -0
- data/public/gitviz/README.md +13 -0
- data/public/gitviz/css/explaingit.css +227 -0
- data/public/gitviz/css/vendor/1140.css +130 -0
- data/public/gitviz/images/forkme_right_red_aa0000.png +0 -0
- data/public/gitviz/images/grippy-close.png +0 -0
- data/public/gitviz/images/grippy.png +0 -0
- data/public/gitviz/images/prompt.gif +0 -0
- data/public/gitviz/index.html +734 -0
- data/public/gitviz/js/controlbox.js +459 -0
- data/public/gitviz/js/explaingit.js +74 -0
- data/public/gitviz/js/historyview.js +979 -0
- data/public/gitviz/js/main.js +56 -0
- data/public/gitviz/js/vendor/d3.min.js +4 -0
- data/public/gitviz/js/vendor/jquery-latest.min.js +6 -0
- data/public/gitviz/js/vendor/normalize.css +396 -0
- data/public/gitviz/js/vendor/require.min.js +35 -0
- data/public/gitviz/memtest.html +44 -0
- data/public/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- data/public/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- data/public/images/ui-bg_flat_10_000000_40x100.png +0 -0
- data/public/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/public/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/public/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/public/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/public/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/public/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/public/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/public/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/public/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/public/images/ui-icons_222222_256x240.png +0 -0
- data/public/images/ui-icons_228ef1_256x240.png +0 -0
- data/public/images/ui-icons_454545_256x240.png +0 -0
- data/public/images/ui-icons_ef8c08_256x240.png +0 -0
- data/public/images/ui-icons_ffd27a_256x240.png +0 -0
- data/public/images/ui-icons_ffffff_256x240.png +0 -0
- data/public/jquery-1.11.1.min.js +4 -0
- data/public/jquery-ui.css +464 -0
- data/public/jquery-ui.min.css +7 -0
- data/public/jquery-ui.min.js +13 -0
- data/public/jquery-ui.structure.min.css +5 -0
- data/public/jquery-ui.theme.min.css +5 -0
- data/public/jquery.activity-indicator-1.0.0.min.js +10 -0
- data/public/jquery.js +9789 -0
- data/public/loginscripts.js +18 -0
- data/public/scripts.js +36 -0
- data/public/style.css +193 -0
- data/public/usermanagement.js +133 -0
- data/templates/init_scripts.erb +10 -0
- data/templates/puppet.conf.erb +10 -0
- data/templates/site.pp.erb +50 -0
- data/views/dashboard.erb +62 -0
- data/views/home.erb +43 -0
- data/views/index.erb +29 -0
- data/views/logs.erb +26 -0
- data/views/shell.erb +35 -0
- data/views/users.erb +69 -0
- metadata +256 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
require 'puppetfactory'
|
|
2
|
+
require 'hocon'
|
|
3
|
+
require 'hocon/parser/config_document_factory'
|
|
4
|
+
require 'hocon/config_value_factory'
|
|
5
|
+
|
|
6
|
+
class Puppetfactory::Plugins::CodeManager < Puppetfactory::Plugins
|
|
7
|
+
|
|
8
|
+
def initialize(options)
|
|
9
|
+
super(options)
|
|
10
|
+
|
|
11
|
+
@puppet = options[:puppet]
|
|
12
|
+
@gitserver = options[:gitserver]
|
|
13
|
+
@repomodel = options[:repomodel]
|
|
14
|
+
@controlrepo = options[:controlrepo]
|
|
15
|
+
@environments = options[:environments]
|
|
16
|
+
@sources = '/etc/puppetlabs/puppet/hieradata/sources.yaml' # we can hardcode these assumptions
|
|
17
|
+
@meep = '/etc/puppetlabs/enterprise/conf.d/common.conf' # because CM is PE only.
|
|
18
|
+
@pattern = "#{@gitserver}/%s/#{@controlrepo}"
|
|
19
|
+
|
|
20
|
+
# the rest of this method is for the big boys only
|
|
21
|
+
return unless Process.euid == 0
|
|
22
|
+
|
|
23
|
+
# in case this is the first run and these doesn't exist yet
|
|
24
|
+
unless File.exist? @sources
|
|
25
|
+
FileUtils.mkdir_p File.dirname @sources
|
|
26
|
+
File.open(@sources, 'w') { |f| f.write( { 'puppet_enterprise::master::code_manager::sources' => {} }.to_yaml) }
|
|
27
|
+
end
|
|
28
|
+
File.open(@meep, 'w') { |f| f.write('') } unless File.exist? @meep
|
|
29
|
+
|
|
30
|
+
if options[:codedir]
|
|
31
|
+
# ensure sane file permissions
|
|
32
|
+
FileUtils.chown_R('pe-puppet', 'pe-puppet', options[:codedir])
|
|
33
|
+
FileUtils.chmod(0755, options[:codedir])
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create(username, password)
|
|
38
|
+
begin
|
|
39
|
+
environment = "#{@environments}/#{Puppetfactory::Helpers.environment_name(username)}"
|
|
40
|
+
FileUtils.mkdir_p environment
|
|
41
|
+
FileUtils.chown_R(username, 'pe-puppet', environment)
|
|
42
|
+
FileUtils.chmod(0750, environment)
|
|
43
|
+
|
|
44
|
+
File.open(@sources) do |file|
|
|
45
|
+
# make sure we don't have any concurrency issues
|
|
46
|
+
file.flock(File::LOCK_EX)
|
|
47
|
+
|
|
48
|
+
# We need to duplicate the sources list in the MEEP config for recovery options
|
|
49
|
+
# I'd like to add it to code-manager.conf too and avoid the delay of running
|
|
50
|
+
# puppet, but that's a race condition that we cannot accept.
|
|
51
|
+
File.open(@meep) do |anotherfile|
|
|
52
|
+
anotherfile.flock(File::LOCK_EX)
|
|
53
|
+
|
|
54
|
+
source = {
|
|
55
|
+
'remote' => sprintf(@pattern, username),
|
|
56
|
+
'prefix' => (@repomodel == :peruser),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
sources = YAML.load_file(@sources)
|
|
60
|
+
meep = Hocon::Parser::ConfigDocumentFactory.parse_file(@meep)
|
|
61
|
+
|
|
62
|
+
sources['puppet_enterprise::master::code_manager::sources'][username] = source
|
|
63
|
+
meep = meep.set_config_value(
|
|
64
|
+
"\"puppet_enterprise::master::code_manager::sources\".#{username}",
|
|
65
|
+
Hocon::ConfigValueFactory.from_any_ref(source)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Ruby 1.8.7, why don't you just go away now
|
|
69
|
+
File.open(@sources, 'w') { |f| f.write(sources.to_yaml) }
|
|
70
|
+
File.open(@meep, 'w') { |f| f.write(meep.render) }
|
|
71
|
+
$logger.info "Created Code Manager source for #{username}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
rescue => e
|
|
75
|
+
$logger.error "Cannot create Code Manager source for #{username}"
|
|
76
|
+
$logger.error e.backtrace
|
|
77
|
+
return false
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def delete(username)
|
|
84
|
+
begin
|
|
85
|
+
environment = "#{@environments}/#{Puppetfactory::Helpers.environment_name(username)}"
|
|
86
|
+
FileUtils.rm_rf environment
|
|
87
|
+
|
|
88
|
+
# also delete any prefixed environments. Is this even a good idea?
|
|
89
|
+
FileUtils.rm_rf "#{@environments}/#{username}_*" if @repomodel == :peruser
|
|
90
|
+
|
|
91
|
+
File.open(@sources) do |file|
|
|
92
|
+
# make sure we don't have any concurrency issues
|
|
93
|
+
file.flock(File::LOCK_EX)
|
|
94
|
+
|
|
95
|
+
# We need to duplicate the sources list in the MEEP config for recovery options
|
|
96
|
+
# I'd like to add it to code-manager.conf too and avoid the delay of running
|
|
97
|
+
# puppet, but that's a race condition that we cannot accept.
|
|
98
|
+
File.open(@meep) do |anotherfile|
|
|
99
|
+
anotherfile.flock(File::LOCK_EX)
|
|
100
|
+
|
|
101
|
+
source = {
|
|
102
|
+
'remote' => sprintf(@pattern, username),
|
|
103
|
+
'prefix' => (@repomodel == :peruser),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
sources = YAML.load_file(@sources)
|
|
107
|
+
sources['puppet_enterprise::master::code_manager::sources'].delete username rescue nil
|
|
108
|
+
|
|
109
|
+
meep = Hocon::Parser::ConfigDocumentFactory.parse_file(@meep)
|
|
110
|
+
meep = meep.remove_value("\"puppet_enterprise::master::code_manager::sources\".#{username}")
|
|
111
|
+
|
|
112
|
+
# Ruby 1.8.7, why don't you just go away now
|
|
113
|
+
File.open(@sources, 'w') { |f| f.write(sources.to_yaml) }
|
|
114
|
+
File.open(@meep, 'w') { |f| f.write(meep.render) }
|
|
115
|
+
$logger.info "Removed Code Manager source for #{username}"
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
rescue => e
|
|
119
|
+
$logger.error "Cannot remove Code Manager source for #{username}"
|
|
120
|
+
$logger.error e.backtrace
|
|
121
|
+
return false
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def deploy(username)
|
|
128
|
+
environment = Puppetfactory::Helpers.environment_name(username)
|
|
129
|
+
|
|
130
|
+
output, status = Open3.capture2e(@puppet, 'code', 'deploy', environment, '--wait')
|
|
131
|
+
unless status.success?
|
|
132
|
+
$logger.error "Failed to deploy environment #{environment} for #{username}"
|
|
133
|
+
$logger.error output
|
|
134
|
+
return false
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
$logger.info "Deployed environment #{environment} for #{username}"
|
|
138
|
+
true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def redeploy(username)
|
|
142
|
+
begin
|
|
143
|
+
if username == 'production'
|
|
144
|
+
raise "Can't redeploy production environment"
|
|
145
|
+
end
|
|
146
|
+
delete(username)
|
|
147
|
+
deploy(username)
|
|
148
|
+
|
|
149
|
+
rescue => e
|
|
150
|
+
raise "Error redeploying environment #{username}: #{e.message}"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
true
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'puppetfactory'
|
|
3
|
+
|
|
4
|
+
class Puppetfactory::Plugins::ConsoleUser < Puppetfactory::Plugins
|
|
5
|
+
|
|
6
|
+
def initialize(options)
|
|
7
|
+
super(options)
|
|
8
|
+
|
|
9
|
+
@puppet = options[:puppet]
|
|
10
|
+
@suffix = options[:usersuffix]
|
|
11
|
+
auth_info = options[:auth_info] || {}
|
|
12
|
+
|
|
13
|
+
@ca_certificate_path = auth_info[:ca_certificate_path] || "#{options[:confdir]}/ssl/ca/ca_crt.pem",
|
|
14
|
+
@certificate_path = auth_info[:certificate_path] || "#{options[:confdir]}/ssl/certs/#{options[:master]}.pem",
|
|
15
|
+
@private_key_path = auth_info[:private_key_path] || "#{options[:confdir]}/ssl/private_keys/#{options[:master]}.pem"
|
|
16
|
+
@classifier_url = options[:classifier] || "http://#{options[:master]}:4433/classifier-api"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create(username, password)
|
|
20
|
+
output, status = Open3.capture2e(@puppet, 'resource', 'rbac_user', username,
|
|
21
|
+
'ensure=present',
|
|
22
|
+
"display_name=#{username}",
|
|
23
|
+
'roles=Operators',
|
|
24
|
+
"email=#{username}@#{@suffix}",
|
|
25
|
+
"password=#{password}")
|
|
26
|
+
|
|
27
|
+
unless status.success
|
|
28
|
+
$logger.error "Could not create PE Console user #{username}: #{output}"
|
|
29
|
+
return false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
$logger.info "Console user #{username} created successfully"
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def delete(username)
|
|
37
|
+
output, status = Open3.capture2e(@puppet, 'resource', 'rbac_user', username, 'ensure=absent')
|
|
38
|
+
unless status.success?
|
|
39
|
+
$logger.warn "Could not remove PE Console user #{username}: #{output}"
|
|
40
|
+
return false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
$logger.info "Console user #{username} removed successfully"
|
|
44
|
+
true
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def userinfo(username, extended = false)
|
|
48
|
+
return unless extended
|
|
49
|
+
|
|
50
|
+
output, status = Open3.capture2e(PUPPET, 'resource', 'rbac_user', username)
|
|
51
|
+
unless status.success?
|
|
52
|
+
$logger.error "Could not query Puppet user #{username}: #{output}"
|
|
53
|
+
return false
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
{
|
|
57
|
+
:username => username,
|
|
58
|
+
:console_user => output =~ /present/,
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
require 'puppetfactory'
|
|
2
|
+
|
|
3
|
+
class Puppetfactory::Plugins::Dashboard < Puppetfactory::Plugins
|
|
4
|
+
attr_accessor :current_test
|
|
5
|
+
|
|
6
|
+
def initialize(options)
|
|
7
|
+
super(options)
|
|
8
|
+
return unless options[:puppetfactory]
|
|
9
|
+
|
|
10
|
+
@server = options[:puppetfactory]
|
|
11
|
+
@path = options[:dashboard_path] || '/etc/puppetfactory/dashboard'
|
|
12
|
+
@interval = options[:dashboard_interval] || 5 * 60 # test interval in seconds
|
|
13
|
+
|
|
14
|
+
# TODO: finish a real mutex implementation and avoid the current (small) race condition
|
|
15
|
+
#set :semaphore, Mutex.new
|
|
16
|
+
@current_test = 'summary'
|
|
17
|
+
@test_running = false
|
|
18
|
+
|
|
19
|
+
start_testing()
|
|
20
|
+
|
|
21
|
+
@server.get '/dashboard' do
|
|
22
|
+
protected!
|
|
23
|
+
|
|
24
|
+
# we can't call methods directly, because this block will execute in the scope
|
|
25
|
+
# of the Puppetfactory server. Use the plugin system instead.
|
|
26
|
+
@current = plugin(:Dashboard, :current_test)
|
|
27
|
+
@available = plugin(:Dashboard, :available_tests)
|
|
28
|
+
@test_data = plugin(:Dashboard, :test_data)
|
|
29
|
+
|
|
30
|
+
return 'No testing data' unless @available and @test_data
|
|
31
|
+
|
|
32
|
+
erb :dashboard
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@server.get '/dashboard/details/:user' do |user|
|
|
36
|
+
plugin(:Dashboard, :user_test_html, user)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@server.get '/dashboard/details/:user/:result' do |user, result|
|
|
40
|
+
plugin(:Dashboard, :user_test_html, user, result)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@server.get '/dashboard/update' do
|
|
44
|
+
$logger.info "Triggering dashboard update."
|
|
45
|
+
|
|
46
|
+
if plugin(:Dashboard, :update_results)
|
|
47
|
+
{'status' => 'success'}.to_json
|
|
48
|
+
else
|
|
49
|
+
{'status' => 'fail', 'message' => 'Already running'}.to_json
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@server.get '/dashboard/set/:current' do |current|
|
|
54
|
+
$logger.info "Setting current test to #{current}."
|
|
55
|
+
|
|
56
|
+
plugin(:Dashboard, :current_test=, current)
|
|
57
|
+
|
|
58
|
+
{'status' => 'success'}.to_json
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def tabs(privileged = false)
|
|
64
|
+
return unless privileged
|
|
65
|
+
|
|
66
|
+
{ 'dashboard' => 'Testing Dashboard' }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def update_results()
|
|
70
|
+
return false if @test_running
|
|
71
|
+
@test_running = true
|
|
72
|
+
|
|
73
|
+
Dir.chdir(@path) do
|
|
74
|
+
case @current_test
|
|
75
|
+
when 'all', 'summary'
|
|
76
|
+
system('rake', 'generate')
|
|
77
|
+
else
|
|
78
|
+
system('rake', 'generate', "current_test=#{@current_test}")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
@test_running = false
|
|
83
|
+
true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def available_tests()
|
|
87
|
+
Dir.chdir(@path) { `rake list`.split } rescue []
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_data()
|
|
91
|
+
JSON.parse(File.read("#{@path}/output/summary.json")) rescue {}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def user_test_html(user, result = @current_test)
|
|
95
|
+
begin
|
|
96
|
+
if result == 'summary'
|
|
97
|
+
File.read("#{@path}/output/html/#{user}.html")
|
|
98
|
+
else
|
|
99
|
+
File.read("#{@path}/output/html/#{result}/#{user}.html")
|
|
100
|
+
end
|
|
101
|
+
rescue Errno::ENOENT
|
|
102
|
+
'No results found'
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# class method so the template can call it
|
|
107
|
+
def self.test_completion(data)
|
|
108
|
+
total = data['example_count'] rescue 0
|
|
109
|
+
failed = data['failure_count'] rescue 0
|
|
110
|
+
passed = total - failed
|
|
111
|
+
percent = passed.to_f / total * 100.0 rescue 0
|
|
112
|
+
|
|
113
|
+
[total, passed, percent]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
def start_testing()
|
|
118
|
+
Thread.new do
|
|
119
|
+
loop do
|
|
120
|
+
$logger.info "Updating dashboard after #{@interval} seconds."
|
|
121
|
+
update_results()
|
|
122
|
+
sleep(@interval)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
require 'time'
|
|
3
|
+
require 'docker'
|
|
4
|
+
require 'etc'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'puppetfactory'
|
|
7
|
+
|
|
8
|
+
class Puppetfactory::Plugins::Docker < Puppetfactory::Plugins
|
|
9
|
+
|
|
10
|
+
def initialize(options)
|
|
11
|
+
super(options)
|
|
12
|
+
|
|
13
|
+
@weight = 5
|
|
14
|
+
|
|
15
|
+
@confdir = options[:confdir]
|
|
16
|
+
@environments = options[:environments]
|
|
17
|
+
@puppetcode = options[:puppetcode]
|
|
18
|
+
@master = options[:master]
|
|
19
|
+
@modulepath = options[:modulepath]
|
|
20
|
+
@templatedir = options[:templatedir]
|
|
21
|
+
@container = options[:container_name] || 'centosagent'
|
|
22
|
+
@group = options[:docker_group] || 'docker'
|
|
23
|
+
@docker_ip = options[:docker_ip] || `facter ipaddress_docker0`.strip
|
|
24
|
+
@privileged = options[:privileged] || true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create(username, password)
|
|
28
|
+
begin
|
|
29
|
+
environment = "#{@environments}/#{Puppetfactory::Helpers.environment_name(username)}"
|
|
30
|
+
|
|
31
|
+
binds = [
|
|
32
|
+
"/var/yum:/var/yum",
|
|
33
|
+
"/home/#{username}/puppet:#{@confdir}",
|
|
34
|
+
"/sys/fs/cgroup:/sys/fs/cgroup:ro"
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
case @modulepath
|
|
38
|
+
when :readonly
|
|
39
|
+
binds.push("#{environment}:#{@puppetcode}:ro")
|
|
40
|
+
when :readwrite
|
|
41
|
+
binds.push("#{environment}:#{@puppetcode}")
|
|
42
|
+
when :none
|
|
43
|
+
#pass
|
|
44
|
+
else
|
|
45
|
+
raise "Uknown modulepath setting (#{@modulepath})"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
container = ::Docker::Container.create(
|
|
49
|
+
"Cmd" => [
|
|
50
|
+
"/usr/lib/systemd/systemd"
|
|
51
|
+
],
|
|
52
|
+
"Tty" => true,
|
|
53
|
+
"Domainname" => "puppetlabs.vm",
|
|
54
|
+
"Env" => [
|
|
55
|
+
"RUNLEVEL=3",
|
|
56
|
+
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
57
|
+
"HOME=/root/",
|
|
58
|
+
"TERM=xterm"
|
|
59
|
+
],
|
|
60
|
+
"ExposedPorts" => {
|
|
61
|
+
"80/tcp" => {
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"Hostname" => "#{username}",
|
|
65
|
+
"Image" => "#{@container}",
|
|
66
|
+
"HostConfig" => {
|
|
67
|
+
"Privileged" => @privileged,
|
|
68
|
+
"Binds" => binds,
|
|
69
|
+
"ExtraHosts" => [
|
|
70
|
+
"#{@master} puppet:#{@docker_ip}"
|
|
71
|
+
],
|
|
72
|
+
"PortBindings" => {
|
|
73
|
+
"80/tcp" => [
|
|
74
|
+
{
|
|
75
|
+
"HostPort" => "#{user_port(username)}"
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
"Name" => "#{username}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
container.rename(username) # Set container name so we can identify it
|
|
84
|
+
init_scripts(username) # Create init scripts so container restarts on boot
|
|
85
|
+
container.start
|
|
86
|
+
|
|
87
|
+
rescue => e
|
|
88
|
+
# fatal error, so we stop execution here
|
|
89
|
+
raise "Error creating container #{username}: #{e.message}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
$logger.info "Container #{username} created"
|
|
93
|
+
true
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def delete(username)
|
|
97
|
+
begin
|
|
98
|
+
remove_init_scripts(username)
|
|
99
|
+
|
|
100
|
+
container = ::Docker::Container.get(username)
|
|
101
|
+
output = container.delete(:force => true)
|
|
102
|
+
rescue => e
|
|
103
|
+
$logger.warn "Error removing container #{username}: #{e.message}"
|
|
104
|
+
return false
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
$logger.info "Container #{username} removed"
|
|
108
|
+
true
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def repair(username)
|
|
112
|
+
begin
|
|
113
|
+
container = ::Docker::Container.get(username)
|
|
114
|
+
container.delete(:force => true)
|
|
115
|
+
|
|
116
|
+
create(username, nil)
|
|
117
|
+
rescue => e
|
|
118
|
+
raise "Error reparing container #{username}: #{e.message}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
$logger.info "Container #{username} repaired"
|
|
122
|
+
true
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def userinfo(username, extended = false)
|
|
126
|
+
user = {
|
|
127
|
+
:username => username,
|
|
128
|
+
:port => user_port(username),
|
|
129
|
+
:url => sandbox_url(username),
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if extended
|
|
133
|
+
user_container = ::Docker::Container.get(username).json rescue {}
|
|
134
|
+
user[:container_status] = massage_container_state(user_container['State'])
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
user
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def login
|
|
141
|
+
require 'etc'
|
|
142
|
+
username = Etc.getpwuid(Process.euid).name
|
|
143
|
+
exec("docker exec -it #{username} su -")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
def sandbox_url(username)
|
|
148
|
+
"/port/#{user_port(username)}"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# TODO: We need a better way of doing this, since we're not guaranteed to always have system users.
|
|
152
|
+
# See plugins/shell_user.rb for the other side of this coupling.
|
|
153
|
+
def user_port(username)
|
|
154
|
+
Etc.getpwnam(username).uid + 3000
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def massage_container_state(state)
|
|
158
|
+
return {'Description' => 'No container.'} if state.nil?
|
|
159
|
+
|
|
160
|
+
if state['OOMKilled']
|
|
161
|
+
state['Description'] = 'Halted because host machine is out of memory.'
|
|
162
|
+
elsif state['Restarting']
|
|
163
|
+
state['Description'] = 'Container is restarting.'
|
|
164
|
+
elsif state['Paused']
|
|
165
|
+
state['Description'] = 'Container is paused.'
|
|
166
|
+
elsif state['Running']
|
|
167
|
+
state['Description'] = "Running since #{Time.parse(state['StartedAt']).strftime("%b %d at %I:%M%p")}"
|
|
168
|
+
else
|
|
169
|
+
state['Description'] = 'Halted.'
|
|
170
|
+
end
|
|
171
|
+
state
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def init_scripts(username)
|
|
175
|
+
service_file = "/etc/systemd/system/docker-#{username}.service"
|
|
176
|
+
File.open(service_file,"w") do |f|
|
|
177
|
+
f.write ERB.new(File.read("#{@templatedir}/init_scripts.erb")).result(binding)
|
|
178
|
+
end
|
|
179
|
+
File.chmod(0644, service_file)
|
|
180
|
+
system('chkconfig', "docker-#{username}", 'on')
|
|
181
|
+
|
|
182
|
+
$logger.info "Init scripts created and enabled for container #{username}"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def remove_init_scripts(username)
|
|
186
|
+
service_file = "/etc/systemd/system/docker-#{username}.service"
|
|
187
|
+
system('chkconfig', "docker-#{username}", 'off')
|
|
188
|
+
FileUtils.rm(service_file) if File.exist? service_file
|
|
189
|
+
|
|
190
|
+
$logger.info "Init scripts for container #{username} removed"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
end
|