boxen 2.9.0 → 3.0.0.beta1

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