boxen23 3.1.3a

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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +20 -0
  6. data/README.md +50 -0
  7. data/boxen.gemspec +29 -0
  8. data/lib/boxen/check.rb +72 -0
  9. data/lib/boxen/checkout.rb +25 -0
  10. data/lib/boxen/cli.rb +61 -0
  11. data/lib/boxen/config.rb +345 -0
  12. data/lib/boxen/error.rb +4 -0
  13. data/lib/boxen/flags.rb +291 -0
  14. data/lib/boxen/hook.rb +47 -0
  15. data/lib/boxen/hook/github_issue.rb +120 -0
  16. data/lib/boxen/hook/web.rb +56 -0
  17. data/lib/boxen/keychain.rb +61 -0
  18. data/lib/boxen/postflight.rb +13 -0
  19. data/lib/boxen/postflight/active.rb +16 -0
  20. data/lib/boxen/postflight/env.rb +29 -0
  21. data/lib/boxen/preflight.rb +13 -0
  22. data/lib/boxen/preflight/creds.rb +149 -0
  23. data/lib/boxen/preflight/directories.rb +32 -0
  24. data/lib/boxen/preflight/etc_my_cnf.rb +12 -0
  25. data/lib/boxen/preflight/identity.rb +16 -0
  26. data/lib/boxen/preflight/os.rb +33 -0
  27. data/lib/boxen/preflight/rbenv.rb +12 -0
  28. data/lib/boxen/preflight/rvm.rb +12 -0
  29. data/lib/boxen/project.rb +20 -0
  30. data/lib/boxen/puppeteer.rb +122 -0
  31. data/lib/boxen/runner.rb +149 -0
  32. data/lib/boxen/service.rb +58 -0
  33. data/lib/boxen/util.rb +19 -0
  34. data/lib/facter/boxen.rb +34 -0
  35. data/script/Boxen +0 -0
  36. data/script/bootstrap +7 -0
  37. data/script/build-keychain-helper +6 -0
  38. data/script/release +38 -0
  39. data/script/tests +10 -0
  40. data/src/keychain-helper.c +85 -0
  41. data/test/boxen/test.rb +7 -0
  42. data/test/boxen_check_test.rb +55 -0
  43. data/test/boxen_checkout_test.rb +42 -0
  44. data/test/boxen_cli_test.rb +39 -0
  45. data/test/boxen_config_test.rb +400 -0
  46. data/test/boxen_directories_test.rb +40 -0
  47. data/test/boxen_flags_test.rb +223 -0
  48. data/test/boxen_hook_github_issue_test.rb +294 -0
  49. data/test/boxen_hook_web_test.rb +58 -0
  50. data/test/boxen_keychain_test.rb +21 -0
  51. data/test/boxen_postflight_active_test.rb +29 -0
  52. data/test/boxen_postflight_env_test.rb +6 -0
  53. data/test/boxen_preflight_creds_test.rb +177 -0
  54. data/test/boxen_preflight_etc_my_cnf_test.rb +10 -0
  55. data/test/boxen_preflight_rvm_test.rb +10 -0
  56. data/test/boxen_project_test.rb +14 -0
  57. data/test/boxen_puppeteer_test.rb +101 -0
  58. data/test/boxen_runner_test.rb +171 -0
  59. data/test/boxen_service_test.rb +39 -0
  60. data/test/boxen_util_test.rb +21 -0
  61. data/test/fixtures/repo/modules/projects/manifests/first-project.pp +0 -0
  62. data/test/fixtures/repo/modules/projects/manifests/second-project.pp +0 -0
  63. metadata +257 -0
@@ -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
@@ -0,0 +1,61 @@
1
+ require "boxen/error"
2
+ require "shellwords"
3
+
4
+ module Boxen
5
+ class Keychain
6
+
7
+ # The keychain proxy we use to provide isolation and a friendly
8
+ # message in security prompts.
9
+
10
+ HELPER = File.expand_path "../../../script/Boxen", __FILE__
11
+
12
+ # The service name to use when loading/saving passwords.
13
+
14
+ PASSWORD_SERVICE = "GitHub Password"
15
+
16
+ # The service name to use when loading/saving API keys.
17
+
18
+ TOKEN_SERVICE = "GitHub API Token"
19
+
20
+ def initialize(login)
21
+ @login = login
22
+ # Clear the password. We're storing tokens now.
23
+ set PASSWORD_SERVICE, ""
24
+ end
25
+
26
+ def token
27
+ get TOKEN_SERVICE
28
+ end
29
+
30
+ def token=(token)
31
+ set TOKEN_SERVICE, token
32
+ end
33
+
34
+ protected
35
+
36
+ attr_reader :login
37
+
38
+ def get(service)
39
+ return nil if RUBY_PLATFORM !~ /darwin/
40
+ cmd = shellescape(HELPER, service, login)
41
+
42
+ result = `#{cmd}`.strip
43
+ $?.success? ? result : nil
44
+ end
45
+
46
+ def set(service, token)
47
+ return nil if RUBY_PLATFORM !~ /darwin/
48
+ cmd = shellescape(HELPER, service, login, token)
49
+
50
+ unless system *cmd
51
+ raise Boxen::Error, "Can't save #{service} in the keychain."
52
+ end
53
+
54
+ token
55
+ end
56
+
57
+ def shellescape(*args)
58
+ args.map { |s| Shellwords.shellescape s }.join " "
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,13 @@
1
+ require "boxen/check"
2
+
3
+ module Boxen
4
+
5
+ # The superclass for postflight checks.
6
+
7
+ class Postflight < Boxen::Check
8
+
9
+ # Load all available postflight checks.
10
+
11
+ register File.expand_path("../postflight", __FILE__)
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ require "boxen/postflight"
2
+ require "boxen/util"
3
+
4
+ # Checks to see if the basic environment is loaded.
5
+
6
+ class Boxen::Postflight::Active < Boxen::Postflight
7
+ def ok?
8
+ Boxen::Util.active?
9
+ end
10
+
11
+ def run
12
+ warn "You haven't loaded Boxen's environment yet!",
13
+ "To permanently fix this, source #{config.envfile} at the end",
14
+ "of your shell's startup file."
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ require "boxen/postflight"
2
+
3
+ class Boxen::Postflight::Env < Boxen::Postflight
4
+
5
+ # Calculate an MD5 checksum for the current environment.
6
+
7
+ def self.checksum
8
+
9
+ # We can't get this from config 'cause it's static (gotta happen
10
+ # on load), and BOXEN_HOME might not be set.
11
+
12
+ home = ENV["BOXEN_HOME"] || "/opt/boxen"
13
+ return unless File.file? "#{home}/env.sh"
14
+
15
+ `find #{home}/env* -type f 2>&1 | sort | xargs /sbin/md5 | /sbin/md5 -q`.strip
16
+ end
17
+
18
+ # The checksum when this file was loaded.
19
+
20
+ CHECKSUM = self.checksum
21
+
22
+ def ok?
23
+ self.class.checksum == CHECKSUM
24
+ end
25
+
26
+ def run
27
+ warn "Run source #{config.envfile} or restart your shell for new stuff!"
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ require "boxen/check"
2
+
3
+ module Boxen
4
+
5
+ # The superclass for preflight checks.
6
+
7
+ class Preflight < Boxen::Check
8
+
9
+ # Load all available preflight checks.
10
+
11
+ register File.expand_path("../preflight", __FILE__)
12
+ end
13
+ end
@@ -0,0 +1,149 @@
1
+ require "boxen/preflight"
2
+ require "highline"
3
+ require "octokit"
4
+ require "digest"
5
+ require "socket"
6
+
7
+ # HACK: Unless this is `false`, HighLine has some really bizarre
8
+ # problems with empty/expended streams at bizarre intervals.
9
+
10
+ HighLine.track_eof = false
11
+
12
+ class Boxen::Preflight::Creds < Boxen::Preflight
13
+ attr :otp
14
+ attr :password
15
+
16
+ def ok?
17
+ if config.token && config.api.user
18
+ # There was a period of time when login wasn't geting set on first run.
19
+ # This should correct that.
20
+ config.login = config.api.user.login
21
+ true
22
+ end
23
+ rescue
24
+ nil
25
+ end
26
+
27
+ def tmp_api
28
+ @tmp_api ||= Octokit::Client.new :login => config.login, :password => password, :auto_paginate => true
29
+ end
30
+
31
+ def headers
32
+ otp.nil? ? {} : {"X-GitHub-OTP" => otp}
33
+ end
34
+
35
+ def get_otp
36
+ console = HighLine.new
37
+
38
+ # junk API call to send OTP until we implement PUT
39
+ tmp_api.create_authorization rescue nil
40
+
41
+ @otp = console.ask "One time password (via SMS or device):" do |q|
42
+ q.echo = '*'
43
+ end
44
+ end
45
+
46
+ # Attempt to use the username+password to get a list of the user's OAuth
47
+ # authorizations from the API. If it fails because of 2FA, ask the user for
48
+ # her OTP and try again.
49
+ #
50
+ # Returns a list of authorizations
51
+ def get_tokens
52
+ begin
53
+ tmp_api.authorizations(:headers => headers)
54
+ rescue Octokit::Unauthorized
55
+ abort "Sorry, I can't auth you on GitHub.",
56
+ "Please check your credentials and teams and give it another try."
57
+ rescue Octokit::OneTimePasswordRequired
58
+ puts
59
+ if otp.nil?
60
+ warn "It looks like you have two-factor auth enabled."
61
+ else
62
+ warn "That one time password didn't work. Let's try again."
63
+ end
64
+ get_otp
65
+ get_tokens
66
+ end
67
+ end
68
+
69
+ def run
70
+ fetch_login_and_password
71
+ tokens = get_tokens
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
+ )
94
+
95
+ config.token = auth.token
96
+
97
+ unless ok?
98
+ puts
99
+ abort "Something went terribly wrong.",
100
+ "I was able to get your OAuth token, but was unable to use it."
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def fetch_login_and_password
107
+ console = HighLine.new
108
+
109
+ config.login = fetch_from_env("login") || console.ask("GitHub login: ") do |q|
110
+ q.default = config.login || config.user
111
+ q.validate = /\A[^@]+\Z/
112
+ end
113
+
114
+ @password = fetch_from_env("password") || console.ask("GitHub password: ") do |q|
115
+ q.echo = "*"
116
+ end
117
+ end
118
+
119
+ def fetch_from_env(thing)
120
+ key = "BOXEN_GITHUB_#{thing.upcase}"
121
+ return unless found = ENV[key]
122
+ warn "Oh, looks like you've provided your #{thing} as environmental variable..."
123
+ found
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
149
+ end
@@ -0,0 +1,32 @@
1
+ require "boxen/preflight"
2
+ require "boxen/util"
3
+
4
+ class Boxen::Preflight::Directories < Boxen::Preflight
5
+ def ok?
6
+ homedir_directory_exists? &&
7
+ homedir_owner == config.user &&
8
+ homedir_group == 'staff'
9
+ end
10
+
11
+ def run
12
+ Boxen::Util.sudo("/bin/mkdir", "-p", config.homedir) &&
13
+ Boxen::Util.sudo("/usr/sbin/chown", "#{config.user}:staff", config.homedir)
14
+ end
15
+
16
+ private
17
+ def homedir_directory_exists?
18
+ File.directory?(config.homedir)
19
+ end
20
+
21
+ def homedir_owner
22
+ Etc.getpwuid(homedir_stat.uid).name
23
+ end
24
+
25
+ def homedir_group
26
+ Etc.getgrgid(homedir_stat.gid).name
27
+ end
28
+
29
+ def homedir_stat
30
+ @homedir_stat ||= File.stat(config.homedir)
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ require "boxen/preflight"
2
+
3
+ class Boxen::Preflight::EtcMyCnf < Boxen::Preflight
4
+ def run
5
+ abort "You have an /etc/my.cnf file.",
6
+ "This will confuse Boxen's MySQL a lot. Please remove it."
7
+ end
8
+
9
+ def ok?
10
+ !File.file? "/etc/my.cnf"
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ require "boxen/preflight"
2
+
3
+ class Boxen::Preflight::Identity < Boxen::Preflight
4
+ def ok?
5
+ !user || (config.email && config.name)
6
+ end
7
+
8
+ def run
9
+ config.email = user.email
10
+ config.name = user.name
11
+ end
12
+
13
+ def user
14
+ @user ||= config.api.user rescue nil
15
+ end
16
+ end
@@ -0,0 +1,33 @@
1
+ require "boxen/preflight"
2
+
3
+ class Boxen::Preflight::OS < Boxen::Preflight
4
+ SUPPORTED_RELEASES = %w(10.8 10.9 10.10 10.11 10.12 10.13)
5
+
6
+ def ok?
7
+ osx? && (skip_os_check? || supported_release?)
8
+ end
9
+
10
+ def run
11
+ abort "You must be running one of the following OS X versions: #{SUPPORTED_RELEASES.join(' ')}."
12
+ end
13
+
14
+ private
15
+
16
+ def osx?
17
+ `uname -s`.chomp == "Darwin"
18
+ end
19
+
20
+ def supported_release?
21
+ SUPPORTED_RELEASES.any? do |r|
22
+ current_release.start_with? r
23
+ end
24
+ end
25
+
26
+ def current_release
27
+ @current_release ||= `sw_vers -productVersion`
28
+ end
29
+
30
+ def skip_os_check?
31
+ ENV['SKIP_OS_CHECK'] == '1'
32
+ end
33
+ end