puppetfactory 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|