puppetfactory 0.4.0

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