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,114 @@
1
+ require 'yaml'
2
+ require 'puppetfactory'
3
+ require 'json'
4
+ require 'httparty'
5
+
6
+ class Puppetfactory
7
+ class Cli
8
+ def initialize(options = {})
9
+ if options[:server]
10
+ @server = options[:server]
11
+ else
12
+ @server = 'localhost'
13
+ end
14
+ @server = "http://#{@server}:#{options[:port]}" unless @server.start_with? 'http'
15
+ @master = options[:master]
16
+ @debug = options[:debug]
17
+ end
18
+
19
+ def list()
20
+ begin
21
+ puts ' Username Sandbox URL Certname Container | Node Group'
22
+ response = HTTParty.get("#{@server}/api/users")
23
+ raise "PuppetFactory service not responding: #{@server}" unless response.code == 200
24
+
25
+ JSON.parse(response.body).each do |user, params|
26
+ container = params['container_status']['Dead'] ? 'X' : '+' rescue '?'
27
+ nodegroup = params['node_group_url'].nil? ? 'X' : '+'
28
+ printf("%-14s https://%s%10s %-25s %1s %1s\n", user, @master, params['url'], params['certname'], container, nodegroup)
29
+ end
30
+ rescue => e
31
+ puts "API error listing users: #{e.message}"
32
+ puts e.backtrace if @debug
33
+ end
34
+ end
35
+
36
+ def create(user, password)
37
+ begin
38
+ params = {
39
+ body: {
40
+ username: user,
41
+ password: password
42
+ }
43
+ }
44
+ response = HTTParty.post("#{@server}/api/users", params)
45
+ raise "PuppetFactory error: #{response.body}" unless response.code == 200
46
+
47
+ data = JSON.parse(response.body)
48
+ raise data['message'] unless data['status'] == 'success'
49
+
50
+ puts "User #{user} created."
51
+ rescue => e
52
+ puts "API error creating user #{user}: #{e.message}"
53
+ puts e.backtrace if @debug
54
+ end
55
+ end
56
+
57
+ def delete(user)
58
+ begin
59
+ response = HTTParty.delete("#{@server}/api/users/#{user}")
60
+ raise "Puppetfactory error: #{response.body}" unless response.code == 200
61
+
62
+ data = JSON.parse(response.body)
63
+ raise data['message'] unless data['status'] == 'success'
64
+
65
+ puts "User #{user} deleted."
66
+ rescue => e
67
+ puts "API error deleting user #{user}: #{e.message}"
68
+ puts e.backtrace if @debug
69
+ end
70
+ end
71
+
72
+ def repair(user)
73
+ begin
74
+ response = HTTParty.put("#{@server}/api/users/#{user}",
75
+ { body: {
76
+ username: user,
77
+ action: "repair"}
78
+ })
79
+ raise "Puppetfactory error: #{response.body}" unless response.code == 200
80
+
81
+ data = JSON.parse(response.body)
82
+ raise data['message'] unless data['status'] == 'success'
83
+
84
+ puts "User #{user} repaired."
85
+ rescue => e
86
+ puts "API error repair user #{user}: #{e.message}"
87
+ puts e.backtrace if @debug
88
+ end
89
+ end
90
+ def redeploy(user)
91
+ begin
92
+ response = HTTParty.put("#{@server}/api/users/#{user}",
93
+ { body: {
94
+ username: user,
95
+ action: "redeploy"}
96
+ })
97
+ raise "Puppetfactory error: #{response.body}" unless response.code == 200
98
+
99
+ data = JSON.parse(response.body)
100
+ raise data['message'] unless data['status'] == 'success'
101
+
102
+ puts "User #{user} repaired."
103
+ rescue => e
104
+ puts "API error redeploying environment #{user}: #{e.message}"
105
+ puts e.backtrace if @debug
106
+ end
107
+ end
108
+
109
+ def test()
110
+ require 'pry'
111
+ binding.pry
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,69 @@
1
+ require 'json'
2
+ require 'rake'
3
+ require 'rspec/core/rake_task'
4
+
5
+ task :spec => 'spec:all_agents'
6
+ task :default => :spec
7
+
8
+ desc "List all available tests"
9
+ task :list do
10
+ Dir.glob('spec/*').sort.each do |dir|
11
+ puts File.basename(dir, '_spec.rb')
12
+ end
13
+ end
14
+
15
+ desc "Run each test and summarize their output"
16
+ task :generate => [:spec] do
17
+ output = {'timestamp' => Time.now}
18
+ Dir.glob("output/json/*.json") do |file|
19
+ name = File.basename(file, '.json')
20
+ data = JSON.parse(File.read(file)) rescue {}
21
+ output[name] ||= {}
22
+ output[name]['summary'] = data['summary']
23
+ end
24
+
25
+ Dir.glob("output/json/*/*.json") do |file|
26
+ current, name = file.chomp('.json').split('/').last(2)
27
+ data = JSON.parse(File.read(file)) rescue {}
28
+ output[name] ||= {}
29
+ output[name][current] = data['summary']
30
+ end
31
+
32
+ File.write('output/summary.json', output.to_json)
33
+ end
34
+
35
+ namespace :spec do
36
+ targets = []
37
+ Dir.glob('/etc/puppetlabs/code/environments/*').each do |dir|
38
+ next unless File.directory?(dir)
39
+ next unless dir.end_with? '_production'
40
+ target = File.basename(dir.sub('_production', ''))
41
+ targets << target
42
+ end
43
+ task :all_agents => targets
44
+
45
+ if ENV.include? 'current_test'
46
+ test = ENV['current_test']
47
+ html = "output/html/#{test}"
48
+ json = "output/json/#{test}"
49
+ pattern = "spec/#{test}_spec.rb"
50
+ else
51
+ html = "output/html"
52
+ json = "output/json"
53
+ pattern = "spec/*_spec.rb"
54
+ end
55
+
56
+ FileUtils.mkdir_p html
57
+ FileUtils.mkdir_p json
58
+
59
+ targets.each do |target|
60
+ desc "Run Puppetfactory tests for #{target}"
61
+ RSpec::Core::RakeTask.new(target.to_sym) do |t|
62
+ ENV['TARGET_HOST'] = target
63
+ t.verbose = false
64
+ t.fail_on_error = false
65
+ t.rspec_opts = "--format html --out #{html}/#{target}.html --format json --out #{json}/#{target}.json"
66
+ t.pattern = pattern
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,84 @@
1
+ require 'serverspec'
2
+ require "docker"
3
+
4
+ set :backend, :docker
5
+ username = ENV['TARGET_HOST']
6
+
7
+ set :docker_container, Docker::Container.get(username).id
8
+
9
+ # Serverspec types and matchers below. Until we decide to gemify them :)
10
+
11
+ # This defines the method used to build the test case
12
+ def puppet
13
+ Serverspec::Type::Puppet.new()
14
+ end
15
+
16
+ module Serverspec::Type
17
+ class Puppet < Base
18
+
19
+ def initialize
20
+ super
21
+ return unless @settings.nil?
22
+
23
+ @settings = {}
24
+ data = @runner.run_command('puppet agent --configprint all').stdout
25
+ data.split("\n").each do |line|
26
+ key, value = line.split(' = ')
27
+ @settings[key.to_sym] = value
28
+
29
+ self.class.send(:define_method, key) { value }
30
+ #define_method(key) { value }
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ 'Puppet managed attributes'
36
+ end
37
+
38
+ def enabled?
39
+ not disabled?
40
+ end
41
+
42
+ def disabled?
43
+ @runner.check_file_is_file(@settings[:agent_disabled_lockfile])
44
+ end
45
+
46
+ def has_signed_cert?
47
+ @runner.check_file_is_file(@settings[:hostcert])
48
+ end
49
+
50
+ def has_run_puppet?
51
+ @runner.check_file_is_file(@settings[:lastrunreport])
52
+ end
53
+
54
+ def classified_with?(klass)
55
+ #@runner.check_file_contains(@settings[:classfile], /^klass$/)
56
+ @classfile ||= @runner.get_file_content(@settings[:classfile]).stdout
57
+ @classfile =~ /^#{klass}$/
58
+ end
59
+
60
+ def has_resource?(resource)
61
+ #@runner.check_file_contains(@settings[:resourcefile], resource)
62
+ @resourcefile ||= @runner.get_file_content(@settings[:resourcefile]).stdout
63
+
64
+ case resource
65
+ when String
66
+ @resourcefile.include? resource
67
+ when Regexp
68
+ @resourcefile =~ /^#{resource}$/
69
+ else
70
+ false
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ RSpec::Matchers.define :manage_resource do |resource|
77
+ match do |subject|
78
+ if subject.class.name == 'Serverspec::Type::Puppet'
79
+ subject.has_resource?(resource)
80
+ else
81
+ raise "The 'manage_resource' matcher does not support #{subject.class.name}."
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,26 @@
1
+ require 'rspec-puppet'
2
+ # we can't use psh, because it declares things that conflict with serverspec
3
+
4
+ username = ENV['TARGET_HOST']
5
+ environmentpath = "/etc/puppetlabs/code/environments"
6
+
7
+ if File.directory? "#{environmentpath}/#{username}_production"
8
+ environment = "#{username}_production"
9
+ else
10
+ environment = username
11
+ end
12
+
13
+ RSpec.configure do |c|
14
+ c.environmentpath = environmentpath
15
+ c.module_path = "#{environmentpath}/#{environment}/site"
16
+ c.manifest = "#{environmentpath}/#{environment}/manifests"
17
+
18
+ # Adds to the built in defaults from rspec-puppet
19
+ c.default_facts = {
20
+ :ipaddress => '127.0.0.1',
21
+ :kernel => 'Linux',
22
+ :operatingsystem => 'CentOS',
23
+ :operatingsystemmajrelease => '7',
24
+ :osfamily => 'RedHat',
25
+ }
26
+ end
@@ -0,0 +1,37 @@
1
+ require 'puppetfactory'
2
+
3
+ class Puppetfactory::Helpers
4
+
5
+ def self.configure(options)
6
+ @@options = options
7
+ end
8
+
9
+ def self.environment_name(username)
10
+ case @@options[:repomodel]
11
+ when :peruser
12
+ "#{username}_production"
13
+
14
+ when :single
15
+ username
16
+
17
+ else
18
+ raise "Invalid setting for repomodel (#{repomodel})"
19
+ end
20
+ end
21
+
22
+ def self.approximate_time_difference(timestamp)
23
+ return 'never' if timestamp.nil?
24
+
25
+ start = timestamp.class == String ? Time.parse(timestamp) : timestamp
26
+ delta = (Time.now - start)
27
+
28
+ if delta > 60
29
+ # This grossity is rounding to the nearest whole minute
30
+ mins = ((delta / 600).round(1)*10).to_i
31
+ "about #{mins} minutes ago"
32
+ else
33
+ "#{delta.to_i} seconds ago"
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,30 @@
1
+ class String
2
+ if not String.method_defined? :snake_case
3
+ def snake_case!
4
+ gsub!(/(.)([A-Z])/,'\1_\2')
5
+ downcase!
6
+ end
7
+
8
+ def snake_case
9
+ dup.tap { |s| s.snake_case! }
10
+ end
11
+ end
12
+
13
+ if not String.method_defined? :trim
14
+ def trim(size)
15
+ if self.size > size
16
+ "#{self[0...(size - 1)]}…"
17
+ else
18
+ self
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ class Symbol
25
+ if not Symbol.method_defined? :snake_case
26
+ def snake_case
27
+ to_s.snake_case.to_sym
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ class Puppetfactory
2
+ class Plugins
3
+ attr_reader :weight
4
+ # just sets up the namespace for now
5
+
6
+ def initialize(options=nil)
7
+ @weight ||= 100
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,28 @@
1
+ require 'puppetfactory'
2
+ class Puppetfactory::Plugins::Certificates < Puppetfactory::Plugins
3
+
4
+ def initialize(options)
5
+ super(options)
6
+
7
+ @puppet = options[:puppet]
8
+ @suffix = options[:usersuffix]
9
+ end
10
+
11
+ def delete(username)
12
+ certname = "#{username}.#{@suffix}"
13
+
14
+ output, status = Open3.capture2e('puppet', 'cert', 'clean', certname)
15
+ unless status.success?
16
+ $logger.warn "Error cleaning certificate #{certname}: #{output}"
17
+ return false
18
+ end
19
+
20
+ $logger.info "Certificate #{certname} removed"
21
+ true
22
+ end
23
+
24
+ def repair(username)
25
+ delete(username)
26
+ end
27
+
28
+ end
@@ -0,0 +1,75 @@
1
+ require 'json'
2
+ require 'puppetfactory'
3
+ require 'puppetclassify'
4
+
5
+ class Puppetfactory::Plugins::Classification < Puppetfactory::Plugins
6
+
7
+ def initialize(options)
8
+ super(options)
9
+
10
+ @weight = 25
11
+ @puppet = options[:puppet]
12
+ @suffix = options[:usersuffix]
13
+ @master = options[:master]
14
+ classifier = options[:classifier] || "http://#{@master}:4433/classifier-api"
15
+
16
+ auth_info = options[:auth_info] || {}
17
+ auth_info['ca_certificate_path'] ||= "#{options[:confdir]}/ssl/ca/ca_crt.pem"
18
+ auth_info['certificate_path'] ||= "#{options[:confdir]}/ssl/certs/#{options[:master]}.pem"
19
+ auth_info['private_key_path'] ||= "#{options[:confdir]}/ssl/private_keys/#{options[:master]}.pem"
20
+
21
+ @puppetclassify = PuppetClassify.new(classifier, auth_info)
22
+ end
23
+
24
+ def create(username, password)
25
+ environment = Puppetfactory::Helpers.environment_name(username)
26
+ certname = "#{username}.#{@suffix}"
27
+
28
+ group_hash = {
29
+ 'name' => certname,
30
+ 'environment' => environment,
31
+ 'environment_trumps' => true,
32
+ 'parent' => '00000000-0000-4000-8000-000000000000',
33
+ 'classes' => {},
34
+ 'rule' => ['or', ['=', 'name', certname]]
35
+ }
36
+
37
+ begin
38
+ @puppetclassify.groups.create_group(group_hash)
39
+ rescue => e
40
+ $logger.error "Could not create node group #{certname}: #{e.message}"
41
+ return false
42
+ end
43
+
44
+ $logger.info "Created node group #{certname} assigned to environment #{environment}"
45
+ true
46
+ end
47
+
48
+ def delete(username)
49
+ certname = "#{username}.#{@suffix}"
50
+
51
+ begin
52
+ group_id = @puppetclassify.groups.get_group_id(certname)
53
+ @puppetclassify.groups.delete_group(group_id)
54
+ rescue => e
55
+ $logger.warn "Error removing node group #{certname}: #{e.message}"
56
+ return false
57
+ end
58
+
59
+ $logger.info "Node group #{certname} removed"
60
+ true
61
+ end
62
+
63
+ def userinfo(username, extended = false)
64
+ return unless extended
65
+
66
+ ngid = @puppetclassify.groups.get_group_id("#{username}.#{@suffix}")
67
+
68
+ {
69
+ :username => username,
70
+ :node_group_id => ngid,
71
+ :node_group_url => "https://#{@master}/#/node_groups/groups/#{ngid}",
72
+ }
73
+ end
74
+
75
+ end