boxen 2.9.0 → 3.0.0.beta1

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 (69) hide show
  1. data/boxen.gemspec +12 -12
  2. data/lib/boxen/check.rb +8 -39
  3. data/lib/boxen/cli.rb +19 -45
  4. data/lib/boxen/command.rb +142 -0
  5. data/lib/boxen/command/help.rb +40 -0
  6. data/lib/boxen/command/preflight.rb +38 -0
  7. data/lib/boxen/command/project.rb +49 -0
  8. data/lib/boxen/command/project/install.rb +33 -0
  9. data/lib/boxen/command/run.rb +199 -0
  10. data/lib/boxen/command/service.rb +61 -0
  11. data/lib/boxen/command/service/disable.rb +15 -0
  12. data/lib/boxen/command/service/enable.rb +15 -0
  13. data/lib/boxen/command/service/restart.rb +24 -0
  14. data/lib/boxen/command/version.rb +29 -0
  15. data/lib/boxen/command_status.rb +15 -0
  16. data/lib/boxen/config.rb +43 -61
  17. data/lib/boxen/hook.rb +15 -8
  18. data/lib/boxen/keychain.rb +1 -1
  19. data/lib/boxen/postflight/env.rb +1 -1
  20. data/lib/boxen/postflight/github_issue.rb +124 -0
  21. data/lib/boxen/postflight/hooks.rb +16 -0
  22. data/lib/boxen/postflight/web_hook.rb +63 -0
  23. data/lib/boxen/preflight.rb +4 -7
  24. data/lib/boxen/preflight/creds.rb +8 -47
  25. data/lib/boxen/preflight/facts.rb +36 -0
  26. data/lib/boxen/preflight/homebrew.rb +13 -0
  27. data/lib/boxen/preflight/identity.rb +2 -0
  28. data/lib/boxen/preflight/offline.rb +33 -0
  29. data/lib/boxen/preflight/os.rb +1 -1
  30. data/lib/boxen/preflight/update.rb +109 -0
  31. data/lib/boxen/util/logging.rb +59 -0
  32. data/lib/boxen/version.rb +3 -0
  33. data/script/bootstrap +1 -1
  34. data/script/tests +1 -0
  35. data/test/boxen/test.rb +1 -1
  36. data/test/boxen_cli_test.rb +8 -31
  37. data/test/boxen_command_test.rb +93 -0
  38. data/test/boxen_config_test.rb +1 -31
  39. data/test/boxen_directories_test.rb +4 -4
  40. data/test/boxen_hook_test.rb +25 -0
  41. data/test/command/help_test.rb +49 -0
  42. data/test/command/project/install_test.rb +34 -0
  43. data/test/command/project_test.rb +32 -0
  44. data/test/command/run_test.rb +21 -0
  45. data/test/command/service/disable_test.rb +49 -0
  46. data/test/command/service/enable_test.rb +49 -0
  47. data/test/command/service/restart_test.rb +53 -0
  48. data/test/command/service_test.rb +55 -0
  49. data/test/command/version_test.rb +15 -0
  50. data/test/{boxen_postflight_active_test.rb → postflight/boxen_postflight_active_test.rb} +3 -3
  51. data/test/{boxen_postflight_env_test.rb → postflight/boxen_postflight_env_test.rb} +0 -0
  52. data/test/{boxen_hook_github_issue_test.rb → postflight/boxen_postflight_github_issue_test.rb} +72 -82
  53. data/test/{boxen_hook_web_test.rb → postflight/boxen_postflight_web_hook_test.rb} +12 -11
  54. data/test/preflight/boxen_preflight_creds_test.rb +82 -0
  55. data/test/{boxen_preflight_etc_my_cnf_test.rb → preflight/boxen_preflight_etc_my_cnf_test.rb} +1 -1
  56. data/test/preflight/boxen_preflight_homebrew_test.rb +10 -0
  57. data/test/{boxen_preflight_rvm_test.rb → preflight/boxen_preflight_rvm_test.rb} +1 -1
  58. metadata +247 -171
  59. checksums.yaml +0 -7
  60. data/lib/boxen/flags.rb +0 -282
  61. data/lib/boxen/hook/github_issue.rb +0 -120
  62. data/lib/boxen/hook/web.rb +0 -56
  63. data/lib/boxen/puppeteer.rb +0 -121
  64. data/lib/boxen/runner.rb +0 -149
  65. data/test/boxen_check_test.rb +0 -55
  66. data/test/boxen_flags_test.rb +0 -217
  67. data/test/boxen_preflight_creds_test.rb +0 -177
  68. data/test/boxen_puppeteer_test.rb +0 -101
  69. data/test/boxen_runner_test.rb +0 -171
@@ -2,7 +2,6 @@ module Boxen
2
2
  class Hook
3
3
  attr_reader :config
4
4
  attr_reader :checkout
5
- attr_reader :puppet
6
5
  attr_reader :result
7
6
 
8
7
  @hooks = []
@@ -16,13 +15,24 @@ module Boxen
16
15
  end
17
16
 
18
17
  def self.all
19
- @hooks || []
18
+ @hooks
20
19
  end
21
20
 
22
- def initialize(config, checkout, puppet, result)
21
+ def self.register(klass)
22
+ unless defined? @hooks
23
+ @hooks = []
24
+ end
25
+
26
+ @hooks << klass
27
+ end
28
+
29
+ def self.run
30
+ @hooks.each { |hook| hook.new(nil, nil, nil).run }
31
+ end
32
+
33
+ def initialize(config, checkout, result)
23
34
  @config = config
24
35
  @checkout = checkout
25
- @puppet = puppet
26
36
  @result = result
27
37
  end
28
38
 
@@ -34,7 +44,7 @@ module Boxen
34
44
  end
35
45
 
36
46
  def perform?
37
- false
47
+ enabled?
38
48
  end
39
49
 
40
50
  def run
@@ -42,6 +52,3 @@ module Boxen
42
52
  end
43
53
  end
44
54
  end
45
-
46
- require "boxen/hook/github_issue"
47
- require "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 "Run source #{config.envfile} or restart your shell for new stuff!"
27
+ warn "Source #{config.envfile} or restart your shell for new stuff!"
28
28
  end
29
29
  end
@@ -0,0 +1,124 @@
1
+ require "boxen/postflight"
2
+ require "boxen/checkout"
3
+
4
+ # Checks to see if the basic environment is loaded.
5
+
6
+ class Boxen::Postflight::GithubIssue < Boxen::Postflight
7
+ def checkout
8
+ @checkout ||= Boxen::Checkout.new(@config)
9
+ end
10
+
11
+ def ok?
12
+ # Only run if we have credentials and we're on master
13
+ !enabled? || config.login.to_s.empty? || !checkout.master?
14
+ end
15
+
16
+ def run
17
+ if command.success?
18
+ close_failures
19
+ else
20
+ warn "Sorry! Creating an issue on #{config.reponame}"
21
+ record_failure
22
+ end
23
+ end
24
+
25
+ def compare_url
26
+ return unless config.reponame
27
+ "#{config.ghurl}/#{config.reponame}/compare/#{checkout.sha}...master"
28
+ end
29
+
30
+ def hostname
31
+ Socket.gethostname
32
+ end
33
+
34
+ def os
35
+ `sw_vers -productVersion`.strip
36
+ end
37
+
38
+ def shell
39
+ ENV["SHELL"]
40
+ end
41
+
42
+ def logfile
43
+ File.read config.logfile
44
+ end
45
+
46
+ def record_failure
47
+ return unless issues?
48
+
49
+ title = "Failed for #{config.user}"
50
+ config.api.create_issue config.reponame,
51
+ title,
52
+ failure_details,
53
+ :labels => [
54
+ failure_label
55
+ ]
56
+ end
57
+
58
+ def close_failures
59
+ return unless issues?
60
+
61
+ comment = "Succeeded at version #{checkout.sha}."
62
+ failures.each do |issue|
63
+ config.api.add_comment(config.reponame, issue.number, comment)
64
+ config.api.close_issue(config.reponame, issue.number)
65
+ end
66
+ end
67
+
68
+ def failures
69
+ return [] unless issues?
70
+
71
+ issues = config.api.list_issues(config.reponame, :state => 'open',
72
+ :labels => failure_label, :creator => config.login)
73
+ issues.reject! {|i| i.labels.collect(&:name).include?(ongoing_label)}
74
+ issues
75
+ end
76
+
77
+ def failure_details
78
+ body = ''
79
+ body << "Running on `#{hostname}` (OS X #{os}) under `#{shell}`, "
80
+ body << "version #{checkout.sha} ([compare to master](#{compare_url}))."
81
+ body << "\n\n"
82
+
83
+ if checkout.dirty?
84
+ body << "### Changes"
85
+ body << "\n\n"
86
+ body << "```\n#{checkout.changes}\n```"
87
+ body << "\n\n"
88
+ end
89
+
90
+ body << "### Output (from #{config.logfile})"
91
+ body << "\n\n"
92
+ body << "```\n#{logfile}\n```\n"
93
+
94
+ body
95
+ end
96
+
97
+ attr_writer :failure_label
98
+ def failure_label
99
+ @failure_label ||= 'failure'
100
+ end
101
+
102
+ attr_writer :ongoing_label
103
+ def ongoing_label
104
+ @ongoing_label ||= 'ongoing'
105
+ end
106
+
107
+ def issues?
108
+ return unless config.reponame
109
+ return if config.reponame == 'boxen/our-boxen'
110
+
111
+ config.api.repository(config.reponame).has_issues
112
+ end
113
+
114
+ def required_environment_variables
115
+ ['BOXEN_ISSUES_ENABLED']
116
+ end
117
+
118
+ def enabled?
119
+ required_vars = Array(required_environment_variables)
120
+ required_vars.any? && required_vars.all? do |var|
121
+ ENV[var] && !ENV[var].empty?
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,16 @@
1
+ require "boxen/postflight"
2
+ require "boxen/hook"
3
+
4
+ # Prints deprecation notices for all pre-3.x style hooks
5
+
6
+ class Boxen::Postflight::Hooks < Boxen::Postflight
7
+ def ok?
8
+ !Boxen::Hook.all.any?
9
+ end
10
+
11
+ def run
12
+ ::Boxen::Hook.all.each do |hook|
13
+ warn "DEPRECATION WARNING: Boxen::Hook is deprecated (#{hook})"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,63 @@
1
+ require "boxen/postflight"
2
+ require "json"
3
+ require "net/http"
4
+
5
+ class Boxen::Postflight::WebHook < Boxen::Postflight
6
+ def ok?
7
+ !enabled?
8
+ end
9
+
10
+ attr_writer :checkout
11
+ def checkout
12
+ @checkout ||= Boxen::Checkout.new(config)
13
+ end
14
+
15
+ def run
16
+ payload = {
17
+ :login => config.user,
18
+ :sha => checkout.sha,
19
+ :status => command.success? ? 'success' : 'failure',
20
+ :time => "#{Time.now.utc.to_i}"
21
+ }
22
+
23
+ post_web_hook payload
24
+ end
25
+
26
+ def post_web_hook(payload)
27
+ headers = { 'Content-Type' => 'application/json' }
28
+
29
+ uri = URI.parse(URI.escape(ENV['BOXEN_WEB_HOOK_URL']))
30
+
31
+ user, pass, host, port, path = \
32
+ uri.user, uri.pass, uri.host, uri.port, uri.path
33
+
34
+ request = Net::HTTP::Post.new(path, headers)
35
+
36
+ if uri.scheme =~ /https/
37
+ http.use_ssl = true
38
+ end
39
+
40
+ if user && pass
41
+ request.basic_auth user, pass
42
+ end
43
+
44
+ request.body = payload.to_json
45
+
46
+ response = Net::HTTP.new(host, port).start do |http|
47
+ http.request(request)
48
+ end
49
+
50
+ response
51
+ end
52
+
53
+ def required_environment_variables
54
+ ['BOXEN_WEB_HOOK_URL']
55
+ end
56
+
57
+ def enabled?
58
+ required_vars = Array(required_environment_variables)
59
+ required_vars.any? && required_vars.all? do |var|
60
+ ENV[var] && !ENV[var].empty?
61
+ end
62
+ end
63
+ end
@@ -1,13 +1,10 @@
1
1
  require "boxen/check"
2
2
 
3
3
  module Boxen
4
-
5
- # The superclass for preflight checks.
6
-
7
4
  class Preflight < Boxen::Check
8
-
9
- # Load all available preflight checks.
10
-
11
- register File.expand_path("../preflight", __FILE__)
12
5
  end
13
6
  end
7
+
8
+ Dir["#{File.expand_path('../preflight', __FILE__)}/*"].each do |f|
9
+ require "boxen/preflight/#{File.basename f}"
10
+ end
@@ -1,8 +1,6 @@
1
1
  require "boxen/preflight"
2
2
  require "highline"
3
3
  require "octokit"
4
- require "digest"
5
- require "socket"
6
4
 
7
5
  # HACK: Unless this is `false`, HighLine has some really bizarre
8
6
  # problems with empty/expended streams at bizarre intervals.
@@ -14,6 +12,8 @@ class Boxen::Preflight::Creds < Boxen::Preflight
14
12
  attr :password
15
13
 
16
14
  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,27 +70,12 @@ class Boxen::Preflight::Creds < Boxen::Preflight
70
70
  fetch_login_and_password
71
71
  tokens = get_tokens
72
72
 
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
- )
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
94
79
 
95
80
  config.token = auth.token
96
81
 
@@ -122,28 +107,4 @@ class Boxen::Preflight::Creds < Boxen::Preflight
122
107
  warn "Oh, looks like you've provided your #{thing} as environmental variable..."
123
108
  found
124
109
  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
149
110
  end
@@ -0,0 +1,36 @@
1
+ require "fileutils"
2
+ require "pathname"
3
+
4
+ require "boxen/preflight"
5
+
6
+ class Boxen::Preflight::Facts < Boxen::Preflight
7
+ def ok?
8
+ write_facts
9
+
10
+ true
11
+ end
12
+
13
+ def run
14
+ end
15
+
16
+ private
17
+
18
+ def facter_d
19
+ Pathname.new("#{config.homedir}/facts.d")
20
+ end
21
+
22
+ def write_facts
23
+ FileUtils.mkdir_p facter_d
24
+
25
+ write_fact "offline", config.offline?
26
+ end
27
+
28
+ def write_fact(name, value)
29
+ File.open("#{facter_d}/#{name}.txt", "w") do |f|
30
+ f.write "#{name}=#{value}\n"
31
+ end
32
+
33
+ debug "Setting global fact `#{name}` to `#{value}`"
34
+ end
35
+
36
+ end
@@ -0,0 +1,13 @@
1
+ require "boxen/preflight"
2
+
3
+ class Boxen::Preflight::Homebrew < Boxen::Preflight
4
+ def run
5
+ warn "You have an existing Homebrew install in /usr/local",
6
+ "The Boxen provides its own Homebrew, so consider deleting yours.",
7
+ "Keeping both will confuse many projects."
8
+ end
9
+
10
+ def ok?
11
+ !File.exist? "/usr/local/Library/Homebrew"
12
+ end
13
+ end
@@ -2,6 +2,8 @@ require "boxen/preflight"
2
2
 
3
3
  class Boxen::Preflight::Identity < Boxen::Preflight
4
4
  def ok?
5
+ return true if config.offline?
6
+
5
7
  !user || (config.email && config.name)
6
8
  end
7
9
 
@@ -0,0 +1,33 @@
1
+ require "socket"
2
+ require "timeout"
3
+
4
+ require "boxen/preflight"
5
+
6
+ class Boxen::Preflight::Offline < Boxen::Preflight
7
+ def ok?
8
+ config.offline = !google_reachable?
9
+
10
+ info "Running boxen in offline mode as we couldn't reach google." if config.offline?
11
+
12
+ true
13
+ end
14
+
15
+ def run
16
+ end
17
+
18
+ private
19
+
20
+ def google_reachable?
21
+ @online = begin
22
+ timeout(3) do
23
+ s = TCPSocket.new('google.com', 80)
24
+ s.close
25
+ end
26
+ true
27
+ rescue Errno::ECONNREFUSED
28
+ true
29
+ rescue Timeout::Error, StandardError
30
+ false
31
+ end
32
+ end
33
+ end