boxen 3.0.0.beta1 → 3.1.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 (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