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.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +13 -0
  3. data/README.md +0 -0
  4. data/bin/pfsh +31 -0
  5. data/bin/puppetfactory +153 -0
  6. data/lib/puppetfactory.rb +300 -0
  7. data/lib/puppetfactory/cli.rb +114 -0
  8. data/lib/puppetfactory/dashboard/rake_tasks.rb +69 -0
  9. data/lib/puppetfactory/dashboard/serverspec_helper.rb +84 -0
  10. data/lib/puppetfactory/dashboard/spec_helper.rb +26 -0
  11. data/lib/puppetfactory/helpers.rb +37 -0
  12. data/lib/puppetfactory/monkeypatches.rb +30 -0
  13. data/lib/puppetfactory/plugins.rb +11 -0
  14. data/lib/puppetfactory/plugins/certificates.rb +28 -0
  15. data/lib/puppetfactory/plugins/classification.rb +75 -0
  16. data/lib/puppetfactory/plugins/code_manager.rb +156 -0
  17. data/lib/puppetfactory/plugins/console_user.rb +62 -0
  18. data/lib/puppetfactory/plugins/dashboard.rb +128 -0
  19. data/lib/puppetfactory/plugins/docker.rb +193 -0
  20. data/lib/puppetfactory/plugins/example.rb +88 -0
  21. data/lib/puppetfactory/plugins/github.rb +102 -0
  22. data/lib/puppetfactory/plugins/gitlab.rb +62 -0
  23. data/lib/puppetfactory/plugins/hooks.rb +46 -0
  24. data/lib/puppetfactory/plugins/login_shell.rb +10 -0
  25. data/lib/puppetfactory/plugins/logs.rb +34 -0
  26. data/lib/puppetfactory/plugins/r10k.rb +112 -0
  27. data/lib/puppetfactory/plugins/shell_user.rb +69 -0
  28. data/lib/puppetfactory/plugins/user_environment.rb +77 -0
  29. data/public/dashboard.js +100 -0
  30. data/public/font-awesome/css/font-awesome.css +2199 -0
  31. data/public/font-awesome/css/font-awesome.min.css +4 -0
  32. data/public/font-awesome/fonts/FontAwesome.otf +0 -0
  33. data/public/font-awesome/fonts/fontawesome-webfont.eot +0 -0
  34. data/public/font-awesome/fonts/fontawesome-webfont.svg +685 -0
  35. data/public/font-awesome/fonts/fontawesome-webfont.ttf +0 -0
  36. data/public/font-awesome/fonts/fontawesome-webfont.woff +0 -0
  37. data/public/font-awesome/fonts/fontawesome-webfont.woff2 +0 -0
  38. data/public/gitviz/LICENSE.md +20 -0
  39. data/public/gitviz/README.md +13 -0
  40. data/public/gitviz/css/explaingit.css +227 -0
  41. data/public/gitviz/css/vendor/1140.css +130 -0
  42. data/public/gitviz/images/forkme_right_red_aa0000.png +0 -0
  43. data/public/gitviz/images/grippy-close.png +0 -0
  44. data/public/gitviz/images/grippy.png +0 -0
  45. data/public/gitviz/images/prompt.gif +0 -0
  46. data/public/gitviz/index.html +734 -0
  47. data/public/gitviz/js/controlbox.js +459 -0
  48. data/public/gitviz/js/explaingit.js +74 -0
  49. data/public/gitviz/js/historyview.js +979 -0
  50. data/public/gitviz/js/main.js +56 -0
  51. data/public/gitviz/js/vendor/d3.min.js +4 -0
  52. data/public/gitviz/js/vendor/jquery-latest.min.js +6 -0
  53. data/public/gitviz/js/vendor/normalize.css +396 -0
  54. data/public/gitviz/js/vendor/require.min.js +35 -0
  55. data/public/gitviz/memtest.html +44 -0
  56. data/public/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  57. data/public/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  58. data/public/images/ui-bg_flat_10_000000_40x100.png +0 -0
  59. data/public/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  60. data/public/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  61. data/public/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  62. data/public/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  63. data/public/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  64. data/public/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  65. data/public/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  66. data/public/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  67. data/public/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  68. data/public/images/ui-icons_222222_256x240.png +0 -0
  69. data/public/images/ui-icons_228ef1_256x240.png +0 -0
  70. data/public/images/ui-icons_454545_256x240.png +0 -0
  71. data/public/images/ui-icons_ef8c08_256x240.png +0 -0
  72. data/public/images/ui-icons_ffd27a_256x240.png +0 -0
  73. data/public/images/ui-icons_ffffff_256x240.png +0 -0
  74. data/public/jquery-1.11.1.min.js +4 -0
  75. data/public/jquery-ui.css +464 -0
  76. data/public/jquery-ui.min.css +7 -0
  77. data/public/jquery-ui.min.js +13 -0
  78. data/public/jquery-ui.structure.min.css +5 -0
  79. data/public/jquery-ui.theme.min.css +5 -0
  80. data/public/jquery.activity-indicator-1.0.0.min.js +10 -0
  81. data/public/jquery.js +9789 -0
  82. data/public/loginscripts.js +18 -0
  83. data/public/scripts.js +36 -0
  84. data/public/style.css +193 -0
  85. data/public/usermanagement.js +133 -0
  86. data/templates/init_scripts.erb +10 -0
  87. data/templates/puppet.conf.erb +10 -0
  88. data/templates/site.pp.erb +50 -0
  89. data/views/dashboard.erb +62 -0
  90. data/views/home.erb +43 -0
  91. data/views/index.erb +29 -0
  92. data/views/logs.erb +26 -0
  93. data/views/shell.erb +35 -0
  94. data/views/users.erb +69 -0
  95. metadata +256 -0
@@ -0,0 +1,88 @@
1
+ require 'puppetfactory'
2
+
3
+ # inherit from Puppetfactory::Plugins
4
+ class Puppetfactory::Plugins::Example < Puppetfactory::Plugins
5
+ attr_reader :weight
6
+
7
+ def initialize(options)
8
+ super(options) # call the superclass to initialize it
9
+
10
+ @weight = 1
11
+ @example = options[:example] || '/tmp/example'
12
+ end
13
+
14
+ # include one or more of the following methods. Any method you implement
15
+ # will be called when the corresponding task is invoked.
16
+
17
+ def create(username, password)
18
+ $logger.info "User #{username} created."
19
+
20
+ # Log an error with $logger.error
21
+ # fail user creation with a fatal error by raising an exception
22
+
23
+ # return true if our action succeeded
24
+ true
25
+ end
26
+
27
+ def delete(username)
28
+ $logger.info "User #{username} deleted."
29
+
30
+ # return true if our action succeeded
31
+ true
32
+ end
33
+
34
+ def userinfo(username, extended = false)
35
+ # we can bail if we don't want to add to the basic user object.
36
+ # for example, if these are heavy operations.
37
+ return unless extended
38
+
39
+ # return a hash with the :username key
40
+ {
41
+ :username => username,
42
+ :example => username.upcase,
43
+ }
44
+ end
45
+
46
+ def deploy(username)
47
+ environment = Puppetfactory::Helpers.environment_name(username)
48
+ $logger.info "Deployed environment #{environment} for #{username}"
49
+
50
+ # return true if our action succeeded
51
+ true
52
+ end
53
+
54
+ def redeploy(username)
55
+ begin
56
+ if username == 'production'
57
+ raise "Can't redeploy production environment"
58
+ end
59
+ delete(username)
60
+ deploy(username)
61
+
62
+ rescue => e
63
+ raise "Error redeploying environment #{username}: #{e.message}"
64
+ end
65
+
66
+ # return true if our action succeeded
67
+ true
68
+ end
69
+
70
+ # used by container plugins to rebuild them
71
+ def repair(username)
72
+ $logger.info "Container #{username} repaired"
73
+ true
74
+ end
75
+
76
+ # hook called when users log in. Only one can be active at any time.
77
+ def login
78
+ $logger.info 'Logging in with the default system shell'
79
+ exec('bash --login')
80
+ end
81
+
82
+ # returns an array of all user accounts. Only one can be active at any time.
83
+ def users
84
+ usernames = Dir.glob('/home/*').map { |path| File.basename path }
85
+ usernames.reject { |username| ['centos', 'training', 'showoff'].include? username }
86
+ end
87
+
88
+ end
@@ -0,0 +1,102 @@
1
+ require 'octokit'
2
+ require 'puppetfactory'
3
+
4
+ class Puppetfactory::Plugins::Github < Puppetfactory::Plugins
5
+
6
+ def initialize(options)
7
+ super(options)
8
+
9
+ @gitserver = options[:gitserver]
10
+ @gituser = options[:gituser]
11
+ @controlrepo = options[:controlrepo]
12
+ @repomodel = options[:repomodel]
13
+ @githubtoken = options[:githubtoken]
14
+
15
+ # chomp so we can support repo names with or without the .git
16
+ @controlrepo.chomp!('.git')
17
+
18
+ if @githubtoken
19
+ @client = Octokit::Client.new(:access_token => @githubtoken)
20
+ @client.user.login
21
+ else
22
+ @client = Octokit::Client.new()
23
+ end
24
+ end
25
+
26
+ def create(username, password)
27
+ # can only do anything on our own repo, and only if we're authorized!
28
+ return true unless @githubtoken and @repomodel == :single
29
+
30
+ begin
31
+ # can only do anything on our own repo!
32
+ repo = "#{@gituser}/#{@controlrepo}"
33
+ sha = @client.branches(repo).select { |branch| branch[:name] == 'production' }.first[:commit][:sha]
34
+ @client.create_ref(repo, "heads/#{username}", sha)
35
+ $logger.info "Created Github user branch for #{username}"
36
+
37
+ @client.add_collaborator(repo, username)
38
+ $logger.info "Added #{username} as a collaborator to #{repo}."
39
+
40
+ rescue => e
41
+ $logger.error "Error creating Github user branch for #{username}"
42
+ $logger.error e.message
43
+ return false
44
+ end
45
+
46
+ true
47
+ end
48
+
49
+ def delete(username)
50
+ # can only do anything on our own repo, and only if we're authorized!
51
+ return true unless @githubtoken and @repomodel == :single
52
+
53
+ begin
54
+ @client.delete_branch("#{@gituser}/#{@controlrepo}", username)
55
+ $logger.info "Deleted Github user branch for #{username}"
56
+
57
+ @client.remove_collaborator(repo, username)
58
+ $logger.info "Removed #{username} as a collaborator on #{repo}."
59
+
60
+ rescue => e
61
+ $logger.error "Error deleting Github user branch for #{username}"
62
+ $logger.error e.message
63
+ return false
64
+ end
65
+
66
+ true
67
+ end
68
+
69
+ def userinfo(username, extended = false)
70
+ if @repomodel == :single
71
+ repo = "#{@gituser}/#{@controlrepo}"
72
+ url = "#{@gitserver}/#{@gituser}/#{@controlrepo}/tree/#{username}"
73
+ else
74
+ repo = "#{@username}/#{@controlrepo}"
75
+ url = "#{@gitserver}/#{username}/#{@controlrepo}"
76
+ end
77
+
78
+ userinfo = {
79
+ :username => username,
80
+ :controlrepo => url,
81
+ }
82
+ userinfo[:latestcommit] = latest_commit(repo, username) if extended
83
+ userinfo
84
+ end
85
+
86
+ private
87
+ def latest_commit(repo, username)
88
+ begin
89
+ commit = @client.commits(repo, :author => username).first
90
+ return if commit.nil?
91
+
92
+ {
93
+ :url => commit[:html_url],
94
+ :message => commit[:commit][:message].trim(62),
95
+ :time => Puppetfactory::Helpers.approximate_time_difference(commit[:commit][:author][:date]),
96
+ }
97
+ rescue => e
98
+ $logger.error "Cannot get commits for #{repo}."
99
+ $logger.error e.message
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,62 @@
1
+ require 'json'
2
+ require 'puppetfactory'
3
+
4
+ class Puppetfactory::Plugins::Gitlab < Puppetfactory::Plugins
5
+
6
+ def initialize(options)
7
+ super(options)
8
+
9
+ @suffix = options[:usersuffix]
10
+
11
+ begin
12
+ # Use default gitlab root password to get session token
13
+ login = {:login => 'root', :password => '5iveL!fe'}
14
+ resp = JSON.parse(RestClient.post('http://localhost:8888/api/v3/session', login))
15
+ @token = resp['private_token']
16
+ rescue => e
17
+ raise "GitLab authentication error! (#{e.message})"
18
+ end
19
+ end
20
+
21
+ def create(username, password)
22
+ begin
23
+ if password.length < 8
24
+ raise "Password must be at least 8 characters"
25
+ end
26
+
27
+ RestClient.post('http://localhost:8888/api/v3/users',
28
+ {
29
+ :email => "#{username}.#{@suffix}",
30
+ :password => password,
31
+ :username => username,
32
+ :name => username,
33
+ :confirm => false,
34
+ :private_token => @token # TODO: this invocation does not look like the invocation below?
35
+ })
36
+ end
37
+
38
+ $logger.info "Created GitLab user #{username}."
39
+ rescue => e
40
+ $logger.error "Error creating GitLab user #{username}: #{e.message}"
41
+ return false
42
+ end
43
+
44
+ true
45
+ end
46
+
47
+ def delete(username)
48
+ begin
49
+ users = JSON.parse(RestClient.get('http://localhost:8888/api/v3/users', {"PRIVATE-TOKEN" => @token}))
50
+ userid = users.select { |record| record['username'] == username }['id']
51
+ RestClient.delete("http://localhost:8888/api/v3/users/#{userid}" , {"PRIVATE-TOKEN" => @token})
52
+
53
+ $logger.info "Removed GitLab user #{username}."
54
+ rescue => e
55
+ $logger.error "Error removing GitLab user #{username}: #{e.message}"
56
+ return false
57
+ end
58
+
59
+ true
60
+ end
61
+
62
+ end
@@ -0,0 +1,46 @@
1
+ require 'json'
2
+ require 'puppetfactory'
3
+
4
+ class Puppetfactory::Plugins::Hooks < Puppetfactory::Plugins
5
+ attr_reader :weight
6
+
7
+ def initialize(options)
8
+ super(options)
9
+
10
+ @weight = 1
11
+ @path = options[:hooks_path] || '/etc/puppetfactory/hooks'
12
+ end
13
+
14
+ def create(username)
15
+ call_hooks(:create, username)
16
+ end
17
+
18
+ def delete(username)
19
+ call_hooks(:delete, username)
20
+ end
21
+
22
+ private
23
+ def call_hooks(hook_type, username)
24
+ success = true
25
+ # the .to_s allows us to accept strings or symbols
26
+ Dir.glob("#{HOOKS_PATH}/#{hook_type.to_s}/*") do |hook|
27
+ next unless File.file?(hook)
28
+ next unless File.executable?(hook)
29
+
30
+ begin
31
+ output, status = Open3.capture2e(hook, username)
32
+ raise "Execution error: #{output}" unless status.success?
33
+ $logger.info output
34
+
35
+ rescue => e
36
+ $logger.error "Error running hook: #{hook}"
37
+ $logger.error e.message
38
+ success = false
39
+ end
40
+ end
41
+
42
+ # only true if all hooks succeeded.
43
+ success
44
+ end
45
+
46
+ end
@@ -0,0 +1,10 @@
1
+ class Puppetfactory::Plugins::LoginShell < Puppetfactory::Plugins
2
+ def initialize(options)
3
+ super(options)
4
+ end
5
+
6
+ def login
7
+ $logger.info 'Logging in with the default system shell'
8
+ exec('bash --login')
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ require 'puppetfactory'
2
+
3
+ class Puppetfactory::Plugins::Logs < Puppetfactory::Plugins
4
+
5
+ def initialize(options)
6
+ super(options)
7
+ return unless options[:puppetfactory]
8
+
9
+ @logfile = options[:logfile]
10
+ server = options[:puppetfactory]
11
+
12
+ # Add a web route to the puppetfactory server. Must happen in the initializer
13
+ server.get '/logs' do
14
+ protected!
15
+ erb :logs
16
+ end
17
+
18
+ server.get '/logs/data' do
19
+ protected!
20
+ plugin(:Logs, :data)
21
+ end
22
+ end
23
+
24
+ def tabs(privileged = false)
25
+ return unless privileged # only show this tab to admin users
26
+
27
+ # url path => display title
28
+ { 'logs' => 'Logs' }
29
+ end
30
+
31
+ def data
32
+ File.read(@logfile) rescue "Cannot read logfile #{@logfile}"
33
+ end
34
+ end
@@ -0,0 +1,112 @@
1
+ require 'puppetfactory'
2
+
3
+ class Puppetfactory::Plugins::R10k < Puppetfactory::Plugins
4
+
5
+ def initialize(options)
6
+ super(options)
7
+
8
+ @gitserver = options[:gitserver]
9
+ @repomodel = options[:repomodel]
10
+ @controlrepo = options[:controlrepo]
11
+ @environments = options[:environments]
12
+ @r10k_config = '/etc/puppetlabs/r10k/r10k.yaml'
13
+ @pattern = "#{@gitserver}/%s/#{@controlrepo}"
14
+
15
+ # the rest of this method is for the big boys only
16
+ return unless Process.euid == 0
17
+
18
+ # in case this is the first run and it doesn't exist yet
19
+ unless File.exist? @r10k_config
20
+ File.open(@r10k_config, 'w') { |f| f.write({'sources' => {}}.to_yaml) }
21
+ end
22
+ end
23
+
24
+ def create(username, password)
25
+ begin
26
+ environment = "#{@environments}/#{Puppetfactory::Helpers.environment_name(username)}"
27
+ FileUtils.mkdir_p environment
28
+ FileUtils.chown_R(username, 'pe-puppet', environment)
29
+ FileUtils.chmod(0750, environment)
30
+
31
+ File.open(@r10k_config) do |file|
32
+ # make sure we don't have any concurrency issues
33
+ file.flock(File::LOCK_EX)
34
+
35
+ r10k = YAML.load_file(@r10k_config)
36
+ r10k['sources'][username] = {
37
+ 'remote' => sprintf(@pattern, username),
38
+ 'basedir' => @environments,
39
+ 'prefix' => (@repomodel == :peruser),
40
+ }
41
+
42
+ # Ruby 1.8.7, why don't you just go away now
43
+ File.open(@r10k_config, 'w') { |f| f.write(r10k.to_yaml) }
44
+ $logger.info "Created r10k source for #{username}"
45
+ end
46
+ rescue => e
47
+ $logger.error "Cannot create r10k source for #{username}"
48
+ $logger.error e.backtrace
49
+ return false
50
+ end
51
+
52
+ true
53
+ end
54
+
55
+ def delete(username)
56
+ begin
57
+ environment = "#{@environments}/#{Puppetfactory::Helpers.environment_name(username)}"
58
+ FileUtils.rm_rf environment
59
+
60
+ # also delete any prefixed environments. Is this even a good idea?
61
+ FileUtils.rm_rf "#{@environments}/#{username}_*" if @repomodel == :peruser
62
+
63
+ File.open(@r10k_config) do |file|
64
+ # make sure we don't have any concurrency issues
65
+ file.flock(File::LOCK_EX)
66
+
67
+ r10k = YAML.load_file(@r10k_config)
68
+ r10k['sources'].delete username
69
+
70
+ # Ruby 1.8.7, why don't you just go away now
71
+ File.open(@r10k_config, 'w') { |f| f.write(r10k.to_yaml) }
72
+ $logger.info "Created r10k source for #{username}"
73
+ end
74
+ rescue => e
75
+ $logger.error "Cannot remove r10k source for #{username}"
76
+ $logger.error e.backtrace
77
+ return false
78
+ end
79
+
80
+ true
81
+ end
82
+
83
+ def deploy(username)
84
+ environment = Puppetfactory::Helpers.environment_name(username)
85
+
86
+ output, status = Open3.capture2e('r10k', 'deploy', 'environment', environment)
87
+ unless status.success?
88
+ $logger.error "Failed to deploy environment #{environment} for #{username}"
89
+ $logger.error output
90
+ return false
91
+ end
92
+
93
+ $logger.info "Deployed environment #{environment} for #{username}"
94
+ true
95
+ end
96
+
97
+ def redeploy(username)
98
+ begin
99
+ if username == 'production'
100
+ raise "Can't redeploy production environment"
101
+ end
102
+ delete(username)
103
+ deploy(username)
104
+
105
+ rescue => e
106
+ raise "Error redeploying environment #{username}: #{e.message}"
107
+ end
108
+
109
+ true
110
+ end
111
+
112
+ end