boxen 3.0.0.beta1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +0 -1
  3. data/README.md +1 -1
  4. data/boxen.gemspec +13 -13
  5. data/lib/boxen/check.rb +39 -8
  6. data/lib/boxen/cli.rb +45 -19
  7. data/lib/boxen/config.rb +61 -43
  8. data/lib/boxen/flags.rb +282 -0
  9. data/lib/boxen/hook.rb +8 -15
  10. data/lib/boxen/hook/github_issue.rb +120 -0
  11. data/lib/boxen/hook/web.rb +56 -0
  12. data/lib/boxen/keychain.rb +1 -1
  13. data/lib/boxen/postflight/env.rb +1 -1
  14. data/lib/boxen/preflight.rb +7 -4
  15. data/lib/boxen/preflight/creds.rb +47 -8
  16. data/lib/boxen/preflight/identity.rb +0 -2
  17. data/lib/boxen/preflight/os.rb +6 -2
  18. data/lib/boxen/puppeteer.rb +121 -0
  19. data/lib/boxen/runner.rb +149 -0
  20. data/script/bootstrap +1 -1
  21. data/script/tests +0 -1
  22. data/test/boxen/test.rb +1 -1
  23. data/test/boxen_check_test.rb +55 -0
  24. data/test/boxen_cli_test.rb +31 -8
  25. data/test/boxen_config_test.rb +31 -1
  26. data/test/boxen_directories_test.rb +4 -4
  27. data/test/boxen_flags_test.rb +217 -0
  28. data/test/{postflight/boxen_postflight_github_issue_test.rb → boxen_hook_github_issue_test.rb} +82 -72
  29. data/test/{postflight/boxen_postflight_web_hook_test.rb → boxen_hook_web_test.rb} +11 -12
  30. data/test/{postflight/boxen_postflight_active_test.rb → boxen_postflight_active_test.rb} +3 -3
  31. data/test/{postflight/boxen_postflight_env_test.rb → boxen_postflight_env_test.rb} +0 -0
  32. data/test/boxen_preflight_creds_test.rb +177 -0
  33. data/test/{preflight/boxen_preflight_etc_my_cnf_test.rb → boxen_preflight_etc_my_cnf_test.rb} +1 -1
  34. data/test/{preflight/boxen_preflight_rvm_test.rb → boxen_preflight_rvm_test.rb} +1 -1
  35. data/test/boxen_puppeteer_test.rb +101 -0
  36. data/test/boxen_runner_test.rb +171 -0
  37. metadata +172 -251
  38. data/lib/boxen/command.rb +0 -142
  39. data/lib/boxen/command/help.rb +0 -40
  40. data/lib/boxen/command/preflight.rb +0 -38
  41. data/lib/boxen/command/project.rb +0 -49
  42. data/lib/boxen/command/project/install.rb +0 -33
  43. data/lib/boxen/command/run.rb +0 -199
  44. data/lib/boxen/command/service.rb +0 -61
  45. data/lib/boxen/command/service/disable.rb +0 -15
  46. data/lib/boxen/command/service/enable.rb +0 -15
  47. data/lib/boxen/command/service/restart.rb +0 -24
  48. data/lib/boxen/command/version.rb +0 -29
  49. data/lib/boxen/command_status.rb +0 -15
  50. data/lib/boxen/postflight/github_issue.rb +0 -124
  51. data/lib/boxen/postflight/hooks.rb +0 -16
  52. data/lib/boxen/postflight/web_hook.rb +0 -63
  53. data/lib/boxen/preflight/facts.rb +0 -36
  54. data/lib/boxen/preflight/homebrew.rb +0 -13
  55. data/lib/boxen/preflight/offline.rb +0 -33
  56. data/lib/boxen/preflight/update.rb +0 -109
  57. data/lib/boxen/util/logging.rb +0 -59
  58. data/lib/boxen/version.rb +0 -3
  59. data/lib/system_timer.rb +0 -13
  60. data/test/boxen_command_test.rb +0 -93
  61. data/test/boxen_hook_test.rb +0 -25
  62. data/test/command/help_test.rb +0 -49
  63. data/test/command/project/install_test.rb +0 -34
  64. data/test/command/project_test.rb +0 -32
  65. data/test/command/run_test.rb +0 -21
  66. data/test/command/service/disable_test.rb +0 -49
  67. data/test/command/service/enable_test.rb +0 -49
  68. data/test/command/service/restart_test.rb +0 -53
  69. data/test/command/service_test.rb +0 -55
  70. data/test/command/version_test.rb +0 -15
  71. data/test/preflight/boxen_preflight_creds_test.rb +0 -82
  72. data/test/preflight/boxen_preflight_homebrew_test.rb +0 -10
  73. data/test/system_timer.rb +0 -10
@@ -0,0 +1,56 @@
1
+ require "boxen/hook"
2
+ require "json"
3
+ require "net/http"
4
+
5
+ module Boxen
6
+ class Hook
7
+ class Web < Hook
8
+ def perform?
9
+ enabled?
10
+ end
11
+
12
+ private
13
+ def call
14
+ payload = {
15
+ :login => config.user,
16
+ :sha => checkout.sha,
17
+ :status => result.success? ? 'success' : 'failure',
18
+ :time => "#{Time.now.utc.to_i}"
19
+ }
20
+
21
+ post_web_hook payload
22
+ end
23
+
24
+ def post_web_hook(payload)
25
+ headers = { 'Content-Type' => 'application/json' }
26
+
27
+ uri = URI.parse(URI.escape(ENV['BOXEN_WEB_HOOK_URL']))
28
+
29
+ user, pass, host, port, path = \
30
+ uri.user, uri.pass, uri.host, uri.port, uri.path
31
+
32
+ request = Net::HTTP::Post.new(path, initheader = headers)
33
+
34
+ if uri.scheme =~ /https/
35
+ http.use_ssl = true
36
+ end
37
+
38
+ if user && pass
39
+ request.basic_auth user, pass
40
+ end
41
+
42
+ request.body = payload.to_json
43
+
44
+ response = Net::HTTP.new(host, port).start do |http|
45
+ http.request(request)
46
+ end
47
+ end
48
+
49
+ def required_environment_variables
50
+ ['BOXEN_WEB_HOOK_URL']
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ Boxen::Hook.register Boxen::Hook::Web
@@ -44,7 +44,7 @@ module Boxen
44
44
  def set(service, token)
45
45
  cmd = shellescape(HELPER, service, login, token)
46
46
 
47
- unless system(*cmd)
47
+ unless system *cmd
48
48
  raise Boxen::Error, "Can't save #{service} in the keychain."
49
49
  end
50
50
 
@@ -24,6 +24,6 @@ class Boxen::Postflight::Env < Boxen::Postflight
24
24
  end
25
25
 
26
26
  def run
27
- warn "Source #{config.envfile} or restart your shell for new stuff!"
27
+ warn "Run source #{config.envfile} or restart your shell for new stuff!"
28
28
  end
29
29
  end
@@ -1,10 +1,13 @@
1
1
  require "boxen/check"
2
2
 
3
3
  module Boxen
4
+
5
+ # The superclass for preflight checks.
6
+
4
7
  class Preflight < Boxen::Check
5
- end
6
- end
7
8
 
8
- Dir["#{File.expand_path('../preflight', __FILE__)}/*"].each do |f|
9
- require "boxen/preflight/#{File.basename f}"
9
+ # Load all available preflight checks.
10
+
11
+ register File.expand_path("../preflight", __FILE__)
12
+ end
10
13
  end
@@ -1,6 +1,8 @@
1
1
  require "boxen/preflight"
2
2
  require "highline"
3
3
  require "octokit"
4
+ require "digest"
5
+ require "socket"
4
6
 
5
7
  # HACK: Unless this is `false`, HighLine has some really bizarre
6
8
  # problems with empty/expended streams at bizarre intervals.
@@ -12,8 +14,6 @@ class Boxen::Preflight::Creds < Boxen::Preflight
12
14
  attr :password
13
15
 
14
16
  def ok?
15
- return true if config.offline?
16
-
17
17
  if config.token && config.api.user
18
18
  # There was a period of time when login wasn't geting set on first run.
19
19
  # This should correct that.
@@ -70,12 +70,27 @@ class Boxen::Preflight::Creds < Boxen::Preflight
70
70
  fetch_login_and_password
71
71
  tokens = get_tokens
72
72
 
73
- unless auth = tokens.detect { |a| a.note == "Boxen" }
74
- auth = tmp_api.create_authorization \
75
- :note => "Boxen",
76
- :scopes => %w(repo user),
77
- :headers => headers
78
- end
73
+ # Boxen now supports the updated GitHub Authorizations API by using a unique
74
+ # `fingerprint` for each Boxen installation for a user. We delete any older
75
+ # authorization that does not make use of `fingerprint` so that the "legacy"
76
+ # authorization doesn't persist in the user's list of personal access
77
+ # tokens.
78
+ legacy_auth = tokens.detect { |a| a.note == "Boxen" && a.fingerprint == nil }
79
+ tmp_api.delete_authorization(legacy_auth.id, :headers => headers) if legacy_auth
80
+
81
+ # The updated GitHub authorizations API, in order to improve security, no
82
+ # longer returns a plaintext `token` for existing authorizations. So, if an
83
+ # authorization already exists for this machine we need to first delete it
84
+ # so that we can create a new one.
85
+ auth = tokens.detect { |a| a.note == note && a.fingerprint == fingerprint }
86
+ tmp_api.delete_authorization(auth.id, :headers => headers) if auth
87
+
88
+ auth = tmp_api.create_authorization(
89
+ :note => note,
90
+ :scopes => %w(repo user),
91
+ :fingerprint => fingerprint,
92
+ :headers => headers
93
+ )
79
94
 
80
95
  config.token = auth.token
81
96
 
@@ -107,4 +122,28 @@ class Boxen::Preflight::Creds < Boxen::Preflight
107
122
  warn "Oh, looks like you've provided your #{thing} as environmental variable..."
108
123
  found
109
124
  end
125
+
126
+ def fingerprint
127
+ @fingerprint ||= begin
128
+ # See Apple technical note TN1103, "Uniquely Identifying a Macintosh
129
+ # Computer."
130
+ serial_number_match_data = IO.popen(
131
+ ["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"]
132
+ ).read.match(/"IOPlatformSerialNumber" = "([[:alnum:]]+)"/)
133
+ if serial_number_match_data
134
+ # The fingerprint must be unique across all personal access tokens for a
135
+ # given user. We prefix the serial number with the application name to
136
+ # differentiate between any other personal access token that uses the
137
+ # Mac serial number for the fingerprint.
138
+ Digest::SHA256.hexdigest("Boxen: #{serial_number_match_data[1]}")
139
+ else
140
+ abort "Sorry, I was unable to obtain your Mac's serial number.",
141
+ "Boxen requires access to your Mac's serial number in order to generate a unique GitHub personal access token."
142
+ end
143
+ end
144
+ end
145
+
146
+ def note
147
+ @note ||= "Boxen: #{Socket.gethostname}"
148
+ end
110
149
  end
@@ -2,8 +2,6 @@ require "boxen/preflight"
2
2
 
3
3
  class Boxen::Preflight::Identity < Boxen::Preflight
4
4
  def ok?
5
- return true if config.offline?
6
-
7
5
  !user || (config.email && config.name)
8
6
  end
9
7
 
@@ -1,10 +1,10 @@
1
1
  require "boxen/preflight"
2
2
 
3
3
  class Boxen::Preflight::OS < Boxen::Preflight
4
- SUPPORTED_RELEASES = %w(10.8 10.9)
4
+ SUPPORTED_RELEASES = %w(10.8 10.9 10.10 10.11 10.12)
5
5
 
6
6
  def ok?
7
- osx? && supported_release?
7
+ osx? && (skip_os_check? || supported_release?)
8
8
  end
9
9
 
10
10
  def run
@@ -26,4 +26,8 @@ class Boxen::Preflight::OS < Boxen::Preflight
26
26
  def current_release
27
27
  @current_release ||= `sw_vers -productVersion`
28
28
  end
29
+
30
+ def skip_os_check?
31
+ ENV['SKIP_OS_CHECK'] == '1'
32
+ end
29
33
  end
@@ -0,0 +1,121 @@
1
+ require "fileutils"
2
+ require "boxen/util"
3
+
4
+ module Boxen
5
+
6
+ # Manages an invocation of puppet.
7
+
8
+ class Puppeteer
9
+
10
+ class Status < Struct.new(:code)
11
+ # Puppet's detailed exit codes reserves 2 for a successful run with changes
12
+ def success?
13
+ [0,2].include?(code)
14
+ end
15
+ end
16
+
17
+ attr_reader :config
18
+
19
+ def initialize(config)
20
+ @config = config
21
+ end
22
+
23
+ def command
24
+ manifestdir = "#{config.repodir}/manifests"
25
+ puppet = "#{config.repodir}/bin/puppet"
26
+
27
+ [puppet, "apply", flags, manifestdir].flatten
28
+ end
29
+
30
+ def hiera_config
31
+ if File.exist? "#{config.repodir}/config/hiera.yaml"
32
+ "#{config.repodir}/config/hiera.yaml"
33
+ else
34
+ "/dev/null"
35
+ end
36
+ end
37
+
38
+ def flags
39
+ flags = []
40
+ root = File.expand_path "../../..", __FILE__
41
+
42
+ flags << ["--group", "admin"]
43
+ flags << ["--confdir", "#{config.puppetdir}/conf"]
44
+ flags << ["--vardir", "#{config.puppetdir}/var"]
45
+ flags << ["--libdir", "#{config.repodir}/lib"]#:#{root}/lib"]
46
+ flags << ["--libdir", "#{root}/lib"]
47
+ flags << ["--modulepath", "#{config.repodir}/modules:#{config.repodir}/shared"]
48
+
49
+ # Don't ever complain about Hiera to me
50
+ flags << ["--hiera_config", hiera_config]
51
+
52
+ # Log to both the console and a file.
53
+
54
+ flags << ["--logdest", config.logfile]
55
+ flags << ["--logdest", "console"]
56
+
57
+ # For some reason Puppet tries to set up a bunch of rrd stuff
58
+ # (user, group) unless reports are completely disabled.
59
+
60
+ flags << "--no-report" unless config.report?
61
+ flags << "--detailed-exitcodes"
62
+
63
+ flags << "--graph" if config.graph?
64
+
65
+ flags << "--show_diff"
66
+
67
+ if config.profile?
68
+ flags << "--evaltrace"
69
+ flags << "--summarize"
70
+ end
71
+
72
+ if config.future_parser?
73
+ flags << "--parser=future"
74
+ end
75
+
76
+ flags << "--debug" if config.debug?
77
+ flags << "--noop" if config.pretend?
78
+
79
+ flags << "--color=false" unless config.color?
80
+
81
+ flags.flatten
82
+ end
83
+
84
+ def run
85
+ FileUtils.mkdir_p config.puppetdir
86
+
87
+ FileUtils.rm_f config.logfile
88
+
89
+ FileUtils.rm_rf "#{config.puppetdir}/var/reports" if config.report?
90
+
91
+ FileUtils.rm_rf "#{config.puppetdir}/var/state/graphs" if config.graph?
92
+
93
+ FileUtils.mkdir_p File.dirname config.logfile
94
+ FileUtils.touch config.logfile
95
+
96
+ if File.file? "Puppetfile"
97
+ librarian = "#{config.repodir}/bin/librarian-puppet"
98
+
99
+ unless config.enterprise?
100
+ # Set an environment variable for librarian-puppet's
101
+ # github_tarball source strategy.
102
+ ENV["GITHUB_API_TOKEN"] = config.token
103
+ end
104
+
105
+ librarian_command = [librarian, "install", "--path=#{config.repodir}/shared"]
106
+ librarian_command << "--verbose" if config.debug?
107
+
108
+ warn librarian_command.join(" ") if config.debug?
109
+ unless system *librarian_command
110
+ abort "Can't run Puppet, fetching dependencies with librarian failed."
111
+ end
112
+ end
113
+
114
+ warn command.join(" ") if config.debug?
115
+
116
+ Boxen::Util.sudo *command
117
+
118
+ Status.new($?.exitstatus)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,149 @@
1
+ require "boxen/checkout"
2
+ require "boxen/config"
3
+ require "boxen/hook"
4
+ require "boxen/flags"
5
+ require "boxen/puppeteer"
6
+ require "boxen/service"
7
+ require "boxen/util"
8
+ require "facter"
9
+
10
+ module Boxen
11
+ class Runner
12
+ attr_reader :config
13
+ attr_reader :flags
14
+ attr_reader :puppet
15
+ attr_reader :checkout
16
+ attr_reader :hooks
17
+
18
+ def initialize(config, flags)
19
+ @config = config
20
+ @flags = flags
21
+ @puppet = Boxen::Puppeteer.new(@config)
22
+ @checkout = Boxen::Checkout.new(@config)
23
+ @hooks = Boxen::Hook.all
24
+ end
25
+
26
+ def process
27
+ # --env prints out the current BOXEN_ env vars.
28
+
29
+ exec "env | grep ^BOXEN_ | sort" if flags.env?
30
+
31
+ process_flags
32
+
33
+ process_args
34
+
35
+ # Actually run Puppet and return its result
36
+
37
+ puppet.run
38
+ end
39
+
40
+ def run
41
+ report(process)
42
+ end
43
+
44
+ def report(result)
45
+ hooks.each { |hook| hook.new(config, checkout, puppet, result).run }
46
+
47
+ result
48
+ end
49
+
50
+ def process_flags
51
+
52
+ # --projects prints a list of available projects and exits.
53
+
54
+ if flags.projects?
55
+ puts "You can install any of these projects with `#{$0} <project-name>`:\n"
56
+
57
+ config.projects.each do |project|
58
+ puts " #{project.name}"
59
+ end
60
+
61
+ exit
62
+ end
63
+
64
+ # --disable-services stops all services
65
+
66
+ if flags.disable_services?
67
+ Boxen::Service.list.each do |service|
68
+ puts "Disabling #{service}..."
69
+ service.disable
70
+ end
71
+
72
+ exit
73
+ end
74
+
75
+ # --enable-services starts all services
76
+
77
+ if flags.enable_services?
78
+ Boxen::Service.list.each do |service|
79
+ puts "Enabling #{service}..."
80
+ service.enable
81
+ end
82
+
83
+ exit
84
+ end
85
+
86
+ # --disable-service [name] stops a service
87
+
88
+ if flags.disable_service?
89
+ service = Boxen::Service.new(flags.disable_service)
90
+ puts "Disabling #{service}..."
91
+ service.disable
92
+
93
+ exit
94
+ end
95
+
96
+ # --enable-service [name] starts a service
97
+
98
+ if flags.enable_service?
99
+ service = Boxen::Service.new(flags.enable_service)
100
+ puts "Enabling #{service}..."
101
+ service.enable
102
+
103
+ exit
104
+ end
105
+
106
+ # --restart-service [name] starts a service
107
+
108
+ if flags.restart_service?
109
+ service = Boxen::Service.new(flags.restart_service)
110
+ puts "Restarting #{service}..."
111
+ service.disable
112
+ service.enable
113
+
114
+ exit
115
+ end
116
+
117
+ # --list-services lists all services
118
+
119
+ if flags.list_services?
120
+ Boxen::Service.list.each do |service|
121
+ puts service
122
+ end
123
+
124
+ exit
125
+ end
126
+
127
+ # --restart-services restarts all services
128
+
129
+ if flags.restart_services?
130
+ Boxen::Service.list_enabled.each do |service|
131
+ puts "Restarting #{service}..."
132
+ service.disable
133
+ service.enable
134
+ end
135
+
136
+ exit
137
+ end
138
+
139
+ end
140
+
141
+ def process_args
142
+ projects = flags.args.join(',')
143
+ File.open("#{config.repodir}/.projects", "w+") do |f|
144
+ f.truncate 0
145
+ f.write projects
146
+ end
147
+ end
148
+ end
149
+ end