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
@@ -0,0 +1,33 @@
1
+ require "boxen/command/project"
2
+
3
+ class Boxen::Command::Project::Install < Boxen::Command::Project
4
+ def self.detailed_help
5
+ <<-EOS
6
+
7
+ boxen project:install <project1> [<project2> ...]
8
+
9
+ Install one or more Boxen-managed projects.
10
+
11
+ EOS
12
+ end
13
+
14
+ def self.help
15
+ "Install one or more projects"
16
+ end
17
+
18
+ def run
19
+ File.open("#{config.repodir}/.projects", "w+") do |f|
20
+ f.truncate 0
21
+ f.write projects
22
+ end
23
+
24
+ Boxen::Command.invoke 'run', config
25
+ end
26
+
27
+ def projects
28
+ @args.join ','
29
+ end
30
+ end
31
+
32
+ Boxen::Command.register :"project:install", Boxen::Command::Project::Install
33
+ Boxen::Command.register :"projects:install", Boxen::Command::Project::Install
@@ -0,0 +1,199 @@
1
+ require "boxen/command"
2
+
3
+ class Boxen::Command::Run < Boxen::Command
4
+ preflight \
5
+ Boxen::Preflight::OS,
6
+ Boxen::Preflight::Directories,
7
+ Boxen::Preflight::EtcMyCnf,
8
+ Boxen::Preflight::Homebrew,
9
+ Boxen::Preflight::Rbenv,
10
+ Boxen::Preflight::RVM,
11
+ Boxen::Preflight::Offline,
12
+ Boxen::Preflight::Creds,
13
+ Boxen::Preflight::Identity,
14
+ Boxen::Preflight::Update,
15
+ Boxen::Preflight::Facts
16
+
17
+ postflight \
18
+ Boxen::Postflight::Active,
19
+ Boxen::Postflight::Hooks,
20
+ Boxen::Postflight::GithubIssue,
21
+ Boxen::Postflight::Env
22
+
23
+ def self.help
24
+ "run Boxen's managed puppet environment"
25
+ end
26
+
27
+ def self.detailed_help
28
+ <<-EOS
29
+
30
+ boxen run [options]
31
+
32
+ Runs Puppet via Boxen's environment.
33
+
34
+ Some options you may find useful:
35
+
36
+ --debug Be really, really verbose. Like incredibly verbose.
37
+ --no-issue Don't file an issue if the Boxen run fails.
38
+ --no-color Don't output any colored text to the tty.
39
+ --report Generate graphs and catalog data from Puppet.
40
+ --profile Display very high-level performance details from the Puppet run.
41
+ --future-parser Use Puppet's upcoming future parser
42
+ --graph Enable puppet dependency graph output
43
+
44
+ boxen run:noop [options]
45
+
46
+ The same thing as run, but acts as a dry run where no changes are made.
47
+
48
+ EOS
49
+ end
50
+
51
+ def run
52
+ info "Updating librarian-puppet modules"
53
+ create_clean_working_environment
54
+ run_librarian_puppet
55
+
56
+ warn command.join(" ") if debug?
57
+
58
+ info "Running puppet"
59
+ Boxen::Util.sudo(*command)
60
+
61
+ Boxen::CommandStatus.new($?.exitstatus, [0, 2])
62
+ end
63
+
64
+ def noop
65
+ false
66
+ end
67
+
68
+ def report?
69
+ @args.include? "--report"
70
+ end
71
+
72
+ def profile?
73
+ @args.include? "--profile"
74
+ end
75
+
76
+ def future_parser?
77
+ @args.include? "--future-parser"
78
+ end
79
+
80
+ def graph?
81
+ @args.include? "--graph"
82
+ end
83
+
84
+ def debug?
85
+ @args.include? "--debug"
86
+ end
87
+
88
+ def no_color?
89
+ @args.include? "--no-color"
90
+ end
91
+
92
+ private
93
+ def command
94
+ [
95
+ "#{config.repodir}/bin/puppet",
96
+ "apply",
97
+ puppet_flags,
98
+ "#{config.repodir}/manifests/site.pp"
99
+ ].flatten
100
+ end
101
+
102
+ def run_librarian_puppet
103
+ if File.file? "#{config.repodir}/Puppetfile"
104
+ librarian = "#{config.repodir}/bin/librarian-puppet"
105
+
106
+ unless config.enterprise?
107
+ ENV["GITHUB_API_TOKEN"] = config.token
108
+ end
109
+
110
+ librarian_command = [librarian, "install", "--path=#{config.repodir}/shared"]
111
+ librarian_command << "--verbose" if debug?
112
+
113
+ warn librarian_command.join(" ") if debug?
114
+ unless system(*librarian_command)
115
+ abort "Can't run Puppet, fetching dependencies with librarian failed."
116
+ end
117
+ end
118
+ end
119
+
120
+ def create_clean_working_environment
121
+ FileUtils.mkdir_p config.puppetdir
122
+
123
+ FileUtils.rm_f config.logfile
124
+
125
+ FileUtils.rm_rf "#{config.puppetdir}/var/reports" if report?
126
+
127
+ FileUtils.mkdir_p File.dirname config.logfile
128
+ FileUtils.touch config.logfile
129
+ end
130
+
131
+ def hiera_config
132
+ hiera_yaml = "#{config.repodir}/config/hiera.yaml"
133
+
134
+ File.exists?(hiera_yaml) ? hiera_yaml : "/dev/null"
135
+ end
136
+
137
+ def puppet_flags
138
+ _flags = []
139
+ root = File.expand_path "../../..", __FILE__
140
+
141
+ _flags << ["--group", "admin"]
142
+ _flags << ["--confdir", "#{config.puppetdir}/conf"]
143
+ _flags << ["--vardir", "#{config.puppetdir}/var"]
144
+ _flags << ["--libdir", "#{config.repodir}/lib"]#:#{root}/lib"]
145
+ _flags << ["--libdir", "#{root}/lib"]
146
+ _flags << ["--manifestdir", "#{config.repodir}/manifests"]
147
+ _flags << ["--modulepath", "#{config.repodir}/modules:#{config.repodir}/shared"]
148
+ _flags << ["--hiera_config", hiera_config]
149
+ _flags << ["--logdest", "#{config.repodir}/log/puppet.log"]
150
+ _flags << ["--logdest", "console"]
151
+ _flags << ["--pluginfactdest", "#{config.homedir}/facts.d"]
152
+
153
+ _flags << "--no-report" unless report?
154
+ _flags << "--detailed-exitcodes"
155
+
156
+ _flags << "--show_diff"
157
+
158
+ if profile?
159
+ _flags << "--evaltrace"
160
+ _flags << "--summarize"
161
+ end
162
+
163
+ _flags << "--parser=future" if future_parser?
164
+ _flags << "--graph" if graph?
165
+
166
+ _flags << "--debug" if debug?
167
+ _flags << "--noop" if noop
168
+
169
+ _flags << "--color=false" if no_color?
170
+
171
+ _flags.flatten
172
+ end
173
+ end
174
+
175
+ class Boxen::Command::Run::Noop < Boxen::Command::Run
176
+ preflight \
177
+ Boxen::Preflight::OS,
178
+ Boxen::Preflight::Directories,
179
+ Boxen::Preflight::EtcMyCnf,
180
+ Boxen::Preflight::Homebrew,
181
+ Boxen::Preflight::Rbenv,
182
+ Boxen::Preflight::RVM,
183
+ Boxen::Preflight::Offline,
184
+ Boxen::Preflight::Creds,
185
+ Boxen::Preflight::Identity,
186
+ Boxen::Preflight::Update,
187
+ Boxen::Preflight::Facts
188
+
189
+ postflight \
190
+ Boxen::Postflight::Active,
191
+ Boxen::Postflight::Env
192
+
193
+ def noop
194
+ true
195
+ end
196
+ end
197
+
198
+ Boxen::Command.register :run, Boxen::Command::Run
199
+ Boxen::Command.register :"run:noop", Boxen::Command::Run::Noop
@@ -0,0 +1,61 @@
1
+ require "boxen/command"
2
+ require "boxen/service"
3
+
4
+ class Boxen::Command::Service < Boxen::Command
5
+ def self.detailed_help
6
+ <<-EOS
7
+
8
+ boxen service
9
+
10
+ Display all services Boxen knows about.
11
+
12
+ boxen service:enable <service1> [<service2> ...]
13
+
14
+ Enable and start a Boxen-managed service. If none are given,
15
+ enables and starts all Boxen-managed services.
16
+
17
+ boxen service:disable <service1> [<service2> ...]
18
+
19
+ Disable and stop a Boxen-managed service. If none are given,
20
+ disables and stops all Boxen-managed services.
21
+
22
+ boxen service:restart [<service1> <service2> ...]
23
+
24
+ Restart a Boxen-managed service. If none are given, restarts all
25
+ Boxen-managed services.
26
+
27
+ NOTE: 'boxen service' is aliased to 'services' for convenience
28
+
29
+ EOS
30
+ end
31
+
32
+ def self.help
33
+ "Show and manage Boxen services."
34
+ end
35
+
36
+ def run
37
+ @args = [] # we don't care about args here
38
+
39
+ puts "Boxen manages the following services:\n\n"
40
+
41
+ services.each do |service|
42
+ puts " #{service.name}"
43
+ end
44
+
45
+ Boxen::CommandStatus.new(0)
46
+ end
47
+
48
+ def services
49
+ @services ||= if @args.any?
50
+ @args.map { |s| Boxen::Service.new(s) }
51
+ else
52
+ Boxen::Service.list
53
+ end
54
+ end
55
+ end
56
+
57
+ require "boxen/command/service/enable"
58
+ require "boxen/command/service/disable"
59
+ require "boxen/command/service/restart"
60
+
61
+ Boxen::Command.register :service, Boxen::Command::Service, :services
@@ -0,0 +1,15 @@
1
+ require "boxen/command/service"
2
+
3
+ class Boxen::Command::Service::Disable < Boxen::Command::Service
4
+ def run
5
+ services.each do |service|
6
+ puts "Disabling service: #{service.name}"
7
+ service.disable
8
+ end
9
+
10
+ Boxen::CommandStatus.new(0)
11
+ end
12
+ end
13
+
14
+ Boxen::Command.register :"service:disable", Boxen::Command::Service::Disable
15
+ Boxen::Command.register :"services:disable", Boxen::Command::Service::Disable
@@ -0,0 +1,15 @@
1
+ require "boxen/command/service"
2
+
3
+ class Boxen::Command::Service::Enable < Boxen::Command::Service
4
+ def run
5
+ services.each do |service|
6
+ puts "Enabling service: #{service.name}"
7
+ service.enable
8
+ end
9
+
10
+ Boxen::CommandStatus.new(0)
11
+ end
12
+ end
13
+
14
+ Boxen::Command.register :"service:enable", Boxen::Command::Service::Enable
15
+ Boxen::Command.register :"services:enable", Boxen::Command::Service::Enable
@@ -0,0 +1,24 @@
1
+ require "boxen/command/service"
2
+
3
+ class Boxen::Command::Service::Restart < Boxen::Command::Service
4
+ def run
5
+ services.each do |service|
6
+ puts "Restarting service: #{service.name}"
7
+ service.disable
8
+ service.enable
9
+ end
10
+
11
+ Boxen::CommandStatus.new(0)
12
+ end
13
+
14
+ def services
15
+ @services ||= if @args.any?
16
+ @args.map { |s| Boxen::Service.new(s) }
17
+ else
18
+ Boxen::Service.list_enabled
19
+ end
20
+ end
21
+ end
22
+
23
+ Boxen::Command.register :"service:restart", Boxen::Command::Service::Restart
24
+ Boxen::Command.register :"services:restart", Boxen::Command::Service::Restart
@@ -0,0 +1,29 @@
1
+ require "boxen/command"
2
+ require "boxen/version"
3
+
4
+ class Boxen::Command::Version < Boxen::Command
5
+ def self.help
6
+ "Displays the current version of Boxen"
7
+ end
8
+
9
+ def self.detailed_help
10
+ <<-EOS
11
+
12
+ boxen version
13
+
14
+ Display the current version of the Boxen gem.
15
+
16
+ EOS
17
+ end
18
+
19
+ def run
20
+ puts "Boxen #{version}"
21
+ Boxen::CommandStatus.new(0)
22
+ end
23
+
24
+ def version
25
+ Boxen::VERSION
26
+ end
27
+ end
28
+
29
+ Boxen::Command.register :version, Boxen::Command::Version, :"-v", :"--version"
@@ -0,0 +1,15 @@
1
+ module Boxen
2
+ class CommandStatus
3
+ attr_reader :code, :successful_on
4
+
5
+ def initialize(code, successful_on = [0])
6
+ @successful_on = successful_on
7
+ @code = code
8
+ end
9
+
10
+ def success?
11
+ successful_on.member? code
12
+ end
13
+
14
+ end
15
+ end
@@ -25,9 +25,6 @@ module Boxen
25
25
  end
26
26
  end
27
27
 
28
- keychain = Boxen::Keychain.new config.user
29
- config.token = keychain.token
30
-
31
28
  if config.enterprise?
32
29
  # configure to talk to GitHub Enterprise
33
30
  Octokit.configure do |c|
@@ -80,10 +77,19 @@ module Boxen
80
77
  def initialize(&block)
81
78
  @fde = true
82
79
  @pull = true
80
+ @debug = false
83
81
 
84
82
  yield self if block_given?
85
83
  end
86
84
 
85
+ def keychain
86
+ @keychain ||= Boxen::Keychain.new self.user
87
+ end
88
+
89
+ def token
90
+ @token ||= keychain.token
91
+ end
92
+
87
93
  # Create an API instance using the current user creds. A new
88
94
  # instance is created any time `token` changes.
89
95
 
@@ -94,11 +100,32 @@ module Boxen
94
100
  # Spew a bunch of debug logging? Default is `false`.
95
101
 
96
102
  def debug?
103
+ # TODO: fix this
97
104
  !!@debug
98
105
  end
99
106
 
100
107
  attr_writer :debug
101
108
 
109
+ def offline?
110
+ @offline ||= false
111
+ end
112
+
113
+ attr_writer :offline
114
+
115
+ def report?
116
+ # TODO: Actually make this a thing
117
+ @report = false
118
+ end
119
+
120
+ attr_writer :report
121
+
122
+ def profile?
123
+ #TODO: Actually make this a thing
124
+ @profile = true
125
+ end
126
+
127
+ attr_writer :profile
128
+
102
129
  # A GitHub user's public email.
103
130
 
104
131
  attr_accessor :email
@@ -122,7 +149,7 @@ module Boxen
122
149
  # `BOXEN_HOME` environment variable.
123
150
 
124
151
  def homedir
125
- @homedir || ENV["BOXEN_HOME"] || "/opt/boxen"
152
+ @homedir ||= (ENV["BOXEN_HOME"] || "/opt/boxen")
126
153
  end
127
154
 
128
155
  attr_writer :homedir
@@ -132,7 +159,7 @@ module Boxen
132
159
  # overwritten on every run.
133
160
 
134
161
  def logfile
135
- @logfile || ENV["BOXEN_LOG_FILE"] || "#{repodir}/log/boxen.log"
162
+ @logfile ||= (ENV["BOXEN_LOG_FILE"] || "#{repodir}/log/boxen.log")
136
163
  end
137
164
 
138
165
  attr_writer :logfile
@@ -145,46 +172,6 @@ module Boxen
145
172
 
146
173
  attr_accessor :name
147
174
 
148
- # Just go through the motions? Default is `false`.
149
-
150
- def pretend?
151
- !!@pretend
152
- end
153
-
154
- attr_writer :pretend
155
-
156
- # Run a profiler on Puppet? Default is `false`.
157
-
158
- def profile?
159
- !!@profile
160
- end
161
-
162
- attr_writer :profile
163
-
164
- # Enable the Puppet future parser? Default is `false`.
165
-
166
- def future_parser?
167
- !!@future_parser
168
- end
169
-
170
- attr_writer :future_parser
171
-
172
- # Enable puppet reports ? Default is `false`.
173
-
174
- def report?
175
- !!@report
176
- end
177
-
178
- attr_writer :report
179
-
180
- # Enable generation of dependency graphs.
181
-
182
- def graph?
183
- !!@graph
184
- end
185
-
186
- attr_writer :graph
187
-
188
175
  # An Array of Boxen::Project entries, one for each project Boxen
189
176
  # knows how to manage.
190
177
  #
@@ -208,7 +195,7 @@ module Boxen
208
195
  # `BOXEN_PUPPET_DIR` environment variable.
209
196
 
210
197
  def puppetdir
211
- @puppetdir || ENV["BOXEN_PUPPET_DIR"] || "/tmp/boxen/puppet"
198
+ @puppetdir ||= (ENV["BOXEN_PUPPET_DIR"] || "/tmp/boxen/puppet")
212
199
  end
213
200
 
214
201
  attr_writer :puppetdir
@@ -217,7 +204,7 @@ module Boxen
217
204
  # `Dir.pwd`. Respects the `BOXEN_REPO_DIR` environment variable.
218
205
 
219
206
  def repodir
220
- @repodir || ENV["BOXEN_REPO_DIR"] || Dir.pwd
207
+ @repodir ||= (ENV["BOXEN_REPO_DIR"] || Dir.pwd)
221
208
  end
222
209
 
223
210
  attr_writer :repodir
@@ -228,7 +215,7 @@ module Boxen
228
215
  # Respects the `BOXEN_REPO_NAME` environment variable.
229
216
 
230
217
  def reponame
231
- override = @reponame || ENV["BOXEN_REPO_NAME"]
218
+ override = @reponame ||= ENV["BOXEN_REPO_NAME"]
232
219
  return override unless override.nil?
233
220
 
234
221
  if File.directory? repodir
@@ -238,7 +225,7 @@ module Boxen
238
225
  # find the path and strip off the .git suffix
239
226
  repo_exp = Regexp.new Regexp.escape(ghuri.host) + "[/:]([^/]+/[^/]+)"
240
227
  if $?.success? && repo_exp.match(url)
241
- @reponame = $1.sub /\.git$/, ""
228
+ @reponame = $1.sub(/\.git$/, "")
242
229
  end
243
230
  end
244
231
  end
@@ -248,7 +235,7 @@ module Boxen
248
235
  # GitHub location (public or GitHub Enterprise)
249
236
 
250
237
  def ghurl
251
- @ghurl || ENV["BOXEN_GITHUB_ENTERPRISE_URL"] || "https://github.com"
238
+ @ghurl ||= (ENV["BOXEN_GITHUB_ENTERPRISE_URL"] || "https://github.com")
252
239
  end
253
240
 
254
241
  attr_writer :ghurl
@@ -256,8 +243,7 @@ module Boxen
256
243
  # Repository URL template (required for GitHub Enterprise)
257
244
 
258
245
  def repotemplate
259
- default = 'https://github.com/%s'
260
- @repotemplate || ENV["BOXEN_REPO_URL_TEMPLATE"] || default
246
+ @repotemplate ||= (ENV["BOXEN_REPO_URL_TEMPLATE"] || "https://github.com/%s")
261
247
  end
262
248
 
263
249
  attr_writer :repotemplate
@@ -272,7 +258,7 @@ module Boxen
272
258
  # `"/Users/#{user}/src"`.
273
259
 
274
260
  def srcdir
275
- @srcdir || ENV["BOXEN_SRC_DIR"] || "/Users/#{user}/src"
261
+ @srcdir ||= (ENV["BOXEN_SRC_DIR"] || "/Users/#{user}/src")
276
262
  end
277
263
 
278
264
  attr_writer :srcdir
@@ -281,15 +267,11 @@ module Boxen
281
267
  # Respects the `BOXEN_NO_ISSUE` environment variable.
282
268
 
283
269
  def stealth?
284
- !!ENV["BOXEN_NO_ISSUE"] || @stealth
270
+ @stealth ||= !!ENV["BOXEN_NO_ISSUE"]
285
271
  end
286
272
 
287
273
  attr_writer :stealth
288
274
 
289
- # A GitHub OAuth token. Default is `nil`.
290
-
291
- attr_reader :token
292
-
293
275
  def token=(token)
294
276
  @token = token
295
277
  @api = nil
@@ -298,7 +280,7 @@ module Boxen
298
280
  # A local user login. Default is the `USER` environment variable.
299
281
 
300
282
  def user
301
- @user || ENV["USER"]
283
+ @user ||= ENV["USER"]
302
284
  end
303
285
 
304
286
  attr_writer :user
@@ -313,7 +295,7 @@ module Boxen
313
295
  # Respects the `BOXEN_S3_HOST` environment variable.
314
296
 
315
297
  def s3host
316
- @s3host || ENV["BOXEN_S3_HOST"] || "s3.amazonaws.com"
298
+ @s3host ||= (ENV["BOXEN_S3_HOST"] || "s3.amazonaws.com")
317
299
  end
318
300
 
319
301
  attr_writer :s3host
@@ -322,7 +304,7 @@ module Boxen
322
304
  # Respects the `BOXEN_S3_BUCKET` environment variable.
323
305
 
324
306
  def s3bucket
325
- @s3bucket || ENV["BOXEN_S3_BUCKET"] || "boxen-downloads"
307
+ @s3bucket ||= (ENV["BOXEN_S3_BUCKET"] || "boxen-downloads")
326
308
  end
327
309
 
328
310
  attr_writer :s3bucket