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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +6 -0
- data/Gemfile +2 -0
- data/LICENSE +20 -0
- data/README.md +50 -0
- data/boxen.gemspec +29 -0
- data/lib/boxen/check.rb +72 -0
- data/lib/boxen/checkout.rb +25 -0
- data/lib/boxen/cli.rb +61 -0
- data/lib/boxen/config.rb +345 -0
- data/lib/boxen/error.rb +4 -0
- data/lib/boxen/flags.rb +291 -0
- data/lib/boxen/hook.rb +47 -0
- data/lib/boxen/hook/github_issue.rb +120 -0
- data/lib/boxen/hook/web.rb +56 -0
- data/lib/boxen/keychain.rb +61 -0
- data/lib/boxen/postflight.rb +13 -0
- data/lib/boxen/postflight/active.rb +16 -0
- data/lib/boxen/postflight/env.rb +29 -0
- data/lib/boxen/preflight.rb +13 -0
- data/lib/boxen/preflight/creds.rb +149 -0
- data/lib/boxen/preflight/directories.rb +32 -0
- data/lib/boxen/preflight/etc_my_cnf.rb +12 -0
- data/lib/boxen/preflight/identity.rb +16 -0
- data/lib/boxen/preflight/os.rb +33 -0
- data/lib/boxen/preflight/rbenv.rb +12 -0
- data/lib/boxen/preflight/rvm.rb +12 -0
- data/lib/boxen/project.rb +20 -0
- data/lib/boxen/puppeteer.rb +122 -0
- data/lib/boxen/runner.rb +149 -0
- data/lib/boxen/service.rb +58 -0
- data/lib/boxen/util.rb +19 -0
- data/lib/facter/boxen.rb +34 -0
- data/script/Boxen +0 -0
- data/script/bootstrap +7 -0
- data/script/build-keychain-helper +6 -0
- data/script/release +38 -0
- data/script/tests +10 -0
- data/src/keychain-helper.c +85 -0
- data/test/boxen/test.rb +7 -0
- data/test/boxen_check_test.rb +55 -0
- data/test/boxen_checkout_test.rb +42 -0
- data/test/boxen_cli_test.rb +39 -0
- data/test/boxen_config_test.rb +400 -0
- data/test/boxen_directories_test.rb +40 -0
- data/test/boxen_flags_test.rb +223 -0
- data/test/boxen_hook_github_issue_test.rb +294 -0
- data/test/boxen_hook_web_test.rb +58 -0
- data/test/boxen_keychain_test.rb +21 -0
- data/test/boxen_postflight_active_test.rb +29 -0
- data/test/boxen_postflight_env_test.rb +6 -0
- data/test/boxen_preflight_creds_test.rb +177 -0
- data/test/boxen_preflight_etc_my_cnf_test.rb +10 -0
- data/test/boxen_preflight_rvm_test.rb +10 -0
- data/test/boxen_project_test.rb +14 -0
- data/test/boxen_puppeteer_test.rb +101 -0
- data/test/boxen_runner_test.rb +171 -0
- data/test/boxen_service_test.rb +39 -0
- data/test/boxen_util_test.rb +21 -0
- data/test/fixtures/repo/modules/projects/manifests/first-project.pp +0 -0
- data/test/fixtures/repo/modules/projects/manifests/second-project.pp +0 -0
- 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,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,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,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
|