hosted-chef 0.5.1 → 0.5.2

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.
data/README.md CHANGED
@@ -9,7 +9,7 @@ This script currently resets both your user API key and your
9
9
  Organization's validation key, so it's only recommended for new users
10
10
  with freshly created organizations. If that's you, run:
11
11
 
12
- gem install hosted chef
12
+ gem install hosted-chef
13
13
  hosted-chef -u USERNAME -o ORGANIZATION
14
14
  # enter your password at the prompt.
15
15
 
@@ -1,11 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- begin
4
- require 'hosted-chef'
5
- rescue LoadError
6
- $:.unshift(File.expand_path("../../lib", __FILE__))
7
- require 'hosted-chef'
8
- end
3
+ $:.unshift(File.expand_path("../../lib", __FILE__))
4
+ require 'hosted-chef'
9
5
 
10
6
  HostedChef::Controller.new.run
11
7
 
@@ -4,376 +4,30 @@ require 'optparse'
4
4
  require 'rexml/document'
5
5
  require 'fileutils'
6
6
 
7
- require 'rubygems'
7
+ begin
8
+ require 'rubygems'
9
+ rescue LoadError
10
+ end
11
+
8
12
  require 'restclient'
9
13
  require 'highline'
10
14
 
11
-
15
+ require 'hosted-chef/version'
16
+ require 'hosted-chef/api_client'
17
+ require 'hosted-chef/cli'
18
+ require 'hosted-chef/config_installer'
19
+ require 'hosted-chef/controller'
20
+
21
+
22
+ #== HostedChef
23
+ # A command line application for interacting with Opscode's Hosted Chef using
24
+ # password authentication. The goal of this program is simply to automate tasks
25
+ # for which the normal API authentication mechanism cannot be (easily) used.
26
+ #
27
+ # Developers interested in this code should note that the code is not intended
28
+ # for library use. In particular, classes contain methods that call exit or
29
+ # request user input.
12
30
  module HostedChef
13
-
14
- class InvalidPassword < RuntimeError
15
- end
16
-
17
- class Options
18
- attr_writer :password
19
-
20
- def password
21
- @password ||= HighLine.new.ask("Your Hosted Chef password: ") {|q| q.echo = "*"}
22
- end
23
-
24
- attr_writer :username
25
-
26
- def username
27
- @username ||= HighLine.new.ask("Your Hosted Chef username? ")
28
- end
29
-
30
- attr_writer :orgname
31
-
32
- def orgname
33
- @orgname ||= HighLine.new.ask("Your Hosted Chef organization? ")
34
- end
35
-
36
- # force evaluation of options
37
- def ask_for_missing_opts
38
- username
39
- password
40
- orgname
41
- end
42
-
43
- end
44
-
45
- class ConfigValidator
46
-
47
- extend Forwardable
48
-
49
- def_delegators :@options, :username, :password, :orgname
50
-
51
- def initialize(api_client, options)
52
- @api_client, @options = api_client, options
53
- end
54
-
55
- def validate!
56
- puts "Validating your account information..."
57
- validate_username
58
- validate_password
59
- validate_orgname
60
- end
61
-
62
- def validate_username
63
- RestClient.head("http://community.opscode.com/users/#{username}")
64
- puts "* Username '#{username}' is valid."
65
- rescue RestClient::ResourceNotFound
66
- STDERR.puts "The user '#{username}' does not exist. Check your spelling, or visit http://www.opscode.com/hosted-chef/ to sign up."
67
- exit 1
68
- end
69
-
70
- def validate_password
71
- @api_client.login_cookies
72
- puts "* Password is correct"
73
- rescue InvalidPassword
74
- STDERR.puts "Could not login with the password you gave. Check your "\
75
- "typing, or visit http://community.opscode.com/password_reset_requests/new"\
76
- " to reset your password."
77
- exit 1
78
- end
79
-
80
- def validate_orgname
81
- page = RestClient.get("https://manage.opscode.com/organizations/#{orgname}", :cookies => @api_client.login_cookies)
82
- # TODO: use actual HTTP response codes.
83
- if page =~ /not found/i
84
- STDERR.puts "The organization '#{orgname}' does not exist. Check your"\
85
- " spelling, or visit https://manage.opscode.com/organizations to create your organization"
86
- exit 1
87
- elsif page =~ /permission denied/i
88
- STDERR.puts "You are not associated with the organization '#{orgname}'"\
89
- " or you do not have sufficient privileges to download its validator"\
90
- " key. Ask an administrator of this org to invite you."
91
- exit 1
92
- end
93
- puts "* Organization name '#{orgname}' is correct"
94
- end
95
- end
96
-
97
- class ArgvParser
98
-
99
- attr_reader :options
100
-
101
- def initialize(argv)
102
- @options = Options.new
103
- @argv = argv
104
- end
105
-
106
- def parse
107
- parser.parse!
108
- options
109
- end
110
-
111
- def parser
112
- OptionParser.new do |o|
113
- o.on("-u", "--user USERNAME", "Your Hosted Chef username") do |username|
114
- options.username = username
115
- end
116
-
117
- o.on("-p", "--password PASSWORD", "Your Hosted Chef password") do |passwd|
118
- options.password = passwd
119
- end
120
-
121
- o.on('-o', "--organization ORGANIZATION", "Your Hosted Chef Organization") do |orgname|
122
- options.orgname = orgname
123
- end
124
-
125
- o.on('-h', "--help") do
126
- puts o
127
- exit 1
128
- end
129
- end
130
- end
131
-
132
- end
133
-
134
- #--
135
- # Someday we'll return JSON when you ask for it, and then the naming of this
136
- # class won't be a passive aggressive joke.
137
- class ApiClient
138
-
139
- extend Forwardable
140
-
141
- def_delegators :@options, :username, :password, :orgname
142
-
143
- def initialize(options)
144
- @options = options
145
- end
146
-
147
- def login_authenticity_token
148
- @auth_token ||= begin
149
- login_page = RestClient.get('https://manage.opscode.com')
150
- extract_csrf_token_from(login_page)
151
- end
152
- end
153
-
154
- def user_key_authenticity_token
155
- @user_key_authenticity_token ||= begin
156
- new_key_page = RestClient.get("https://community.opscode.com/users/#{username}/user_key/new", :cookies => login_cookies)
157
- @login_cookies = new_key_page.cookies
158
- extract_csrf_token_from(new_key_page)
159
- end
160
- end
161
-
162
- def validator_key_authenticity_token
163
- @validator_key_authenticity_token ||= begin
164
- org_list_page = RestClient.get("https://manage.opscode.com/organizations", :cookies => login_cookies)
165
- @login_cookies = org_list_page.cookies
166
- extract_csrf_token_from(org_list_page)
167
- end
168
- end
169
-
170
- def login_cookies
171
- @login_cookies ||= begin
172
- post_options = {:name => username,
173
- :password => password,
174
- :authenticity_token => login_authenticity_token,
175
- :multipart => true}
176
-
177
- RestClient.post("https://manage.opscode.com/login_exec", post_options) do |response, req, result, &block|
178
- if response.headers[:location] == "https://manage.opscode.com/login" # bad passwd
179
- raise InvalidPassword
180
- else
181
- response.cookies
182
- end
183
- end
184
- end
185
- end
186
-
187
- def knife_config
188
- RestClient.get("https://manage.opscode.com/organizations/#{orgname}/_knife_config", :cookies => login_cookies)
189
- end
190
-
191
- def user_key
192
- post_options = {
193
- :authenticity_token => user_key_authenticity_token,
194
- :multipart => true
195
- }
196
- RestClient.post("https://community.opscode.com/users/#{username}/user_key", post_options, {:cookies => login_cookies})
197
- end
198
-
199
- def validator_key
200
- #https://manage.opscode.com/organizations/prodbench2/_regenerate_key
201
- post_options = {
202
- #:multipart => true,
203
- :authenticity_token => validator_key_authenticity_token,
204
- :_method => "put"
205
- }
206
- headers = {
207
- :Referer => "https://manage.opscode.com/organizations",
208
- :cookies => login_cookies
209
- }
210
- uri = "https://manage.opscode.com/organizations/#{orgname}/_regenerate_key"
211
- RestClient.post(uri, post_options,headers)
212
- end
213
- private
214
-
215
- def extract_csrf_token_from(page)
216
- # TODO: serve the authenticity token over JSON so we don't have to
217
- # do this.
218
- if md = page.match(/(\<meta content=\"[^\"]+\" name=\"csrf-token\"[\s]*\/\>)/)
219
- page_element = md[1]
220
- xml = REXML::Document.new(page_element)
221
- xml.elements.first.attributes["content"]
222
- elsif md = page.match(/(<input[^\>]*name=\"authenticity_token\"[^\>]+\>)/)
223
- page_element = md[1]
224
- xml = REXML::Document.new(page_element)
225
- xml.elements.first.attributes["value"]
226
- else
227
- raise "Can't find CSRF token on the page:\n#{page}"
228
- end
229
- end
230
- end
231
-
232
- class ConfigInstaller
233
- attr_reader :install_type
234
-
235
- def initialize(api_client, options)
236
- @api_client, @options = api_client, options
237
- @install_dir, @install_type = nil, nil
238
- end
239
-
240
- def validate!
241
- install_dir
242
- end
243
-
244
- def install_dir
245
- unless @install_dir
246
- @install_dir, @install_type = select_install_dir
247
- end
248
- @install_dir
249
- end
250
-
251
- def install_all
252
- puts "\nInstalling..."
253
-
254
- puts "> mkdir -p #{install_dir}"
255
- FileUtils.mkdir_p(install_dir)
256
-
257
- knife_rb = "#{@install_dir}/knife.rb"
258
- puts "> create #{knife_rb} 0644"
259
- File.open(knife_rb, File::RDWR|File::CREAT, 0644) {|f|
260
- f << @api_client.knife_config
261
- }
262
-
263
- user_pem = "#{@install_dir}/#{@options.username}.pem"
264
- puts "> create #{user_pem} 0600"
265
- File.open(user_pem, File::RDWR|File::CREAT, 0600) {|f|
266
- f << @api_client.user_key
267
- }
268
-
269
- validator_pem = "#{@install_dir}/#{@options.orgname}-validator.pem"
270
- puts "> create #{validator_pem} 0600"
271
- File.open(validator_pem, File::RDWR|File::CREAT, 0600) {|f|
272
- f << @api_client.validator_key
273
- }
274
- end
275
-
276
- private
277
-
278
- def select_install_dir
279
- home_dot_chef = File.expand_path("~/.chef")
280
- cwd_dot_chef = File.expand_path(".chef")
281
-
282
- if ENV['USER'] && !File.exist?(home_dot_chef)
283
- [home_dot_chef, :default]
284
- elsif !File.exist?(cwd_dot_chef)
285
- [cwd_dot_chef, :non_default]
286
- else
287
- install_locations = [home_dot_chef, cwd_dot_chef].uniq
288
- if install_locations.size == 1
289
- STDERR.puts(<<-NOWHERE_TO_GO)
290
- ERROR: You already have a Chef configuration in your home directory
291
- (#{home_dot_chef})
292
-
293
- If you really want to replace this configuration, you can remove it,
294
- otherwise cd to a different directory to generate a new config.
295
- NOWHERE_TO_GO
296
- else
297
- STDERR.puts(<<-NOWHERE_TO_GO)
298
- ERROR: You already have a Chef configuration in your home directory and
299
- your current working directory.
300
- (#{home_dot_chef} and #{cwd_dot_chef})
301
-
302
- If you wish to replace one of these you can remove it, otherwise you can
303
- cd to a different directory to create a new config.
304
- NOWHERE_TO_GO
305
- exit 1
306
- end
307
- end
308
- end
309
- end
310
-
311
- class Controller
312
-
313
- attr_reader :argv
314
-
315
- def initialize(argv=ARGV)
316
- @argv = argv
317
- @options = ArgvParser.new(argv).parse
318
- @api_client = ApiClient.new(@options)
319
- @validator = ConfigValidator.new(@api_client, @options)
320
- @config_installer = ConfigInstaller.new(@api_client, @options)
321
- end
322
-
323
- def greeting
324
- puts(<<-WELCOME)
325
- Welcome to Opscode Hosted Chef. We're going to download your API keys
326
- and knife config from Opscode and install them on your system.
327
-
328
- Before you continue, please be aware that this program will update your API key
329
- and your organization's validation key. The existing keys for these accounts
330
- will be expired and no longer valid.
331
-
332
- Your config will be created in #{@config_installer.install_dir}
333
-
334
- WELCOME
335
-
336
- unless HighLine.new.agree("Ready to get started? (yes/no): ")
337
- STDERR.puts "goodbye."
338
- exit 1
339
- end
340
- end
341
-
342
- def setup_and_validate
343
- @validator.validate!
344
- end
345
-
346
- def fetch_and_install
347
- @config_installer.install_all
348
- end
349
-
350
- def goodbye
351
- puts(<<-RESOURCES)
352
-
353
- You're ready to go! To verify your configuration, try running
354
- `knife client list`
355
-
356
- If you should run into trouble check the following resources:
357
- * Knife built-in manuals: run `knife help`
358
- * Documentation at http://wiki.opscode.com
359
- * Get support from Opscode: http://help.opscode.com/
360
- RESOURCES
361
- end
362
-
363
-
364
- def run
365
- @config_installer.validate!
366
- greeting
367
- @options.ask_for_missing_opts
368
- setup_and_validate
369
- fetch_and_install
370
- goodbye
371
- rescue Interrupt, EOFError
372
- puts "\nexiting..."
373
- exit! 1
374
- end
375
-
376
- end
377
31
  end
378
32
 
379
33
 
@@ -0,0 +1,120 @@
1
+ module HostedChef
2
+
3
+ # Talks to Hosted Chef over HTTP. Right now, uses screen scraping to fetch
4
+ # CSRF tokens from HTML pages, but I'm optimistic that we'll at least send
5
+ # these as JSON in the future.
6
+ class ApiClient
7
+
8
+ extend Forwardable
9
+
10
+ def_delegators :@options, :username, :password, :orgname
11
+
12
+ def initialize(options)
13
+ @options = options
14
+ end
15
+
16
+ # Rails csrf token for the login page.
17
+ def login_authenticity_token
18
+ @auth_token ||= begin
19
+ login_page = RestClient.get('https://manage.opscode.com')
20
+ extract_csrf_token_from(login_page)
21
+ end
22
+ end
23
+
24
+ # Rails csrf token for the user profile page on community.opscode.com
25
+ def user_key_authenticity_token
26
+ @user_key_authenticity_token ||= begin
27
+ new_key_page = RestClient.get("https://community.opscode.com/users/#{username}/user_key/new", :cookies => login_cookies)
28
+ @login_cookies = new_key_page.cookies
29
+ extract_csrf_token_from(new_key_page)
30
+ end
31
+ end
32
+
33
+ # Rails csrf token for the organization list page on manage.opscode.com
34
+ def validator_key_authenticity_token
35
+ @validator_key_authenticity_token ||= begin
36
+ org_list_page = RestClient.get("https://manage.opscode.com/organizations", :cookies => login_cookies)
37
+ @login_cookies = org_list_page.cookies
38
+ extract_csrf_token_from(org_list_page)
39
+ end
40
+ end
41
+
42
+ # Memoized cookies for authN with manage and community sites.
43
+ def login_cookies
44
+ @login_cookies ||= begin
45
+ post_options = {:name => username,
46
+ :password => password,
47
+ :authenticity_token => login_authenticity_token,
48
+ :multipart => true}
49
+
50
+ RestClient.post("https://manage.opscode.com/login_exec", post_options) do |response, req, result, &block|
51
+ if response.headers[:location] == "https://manage.opscode.com/login" # bad passwd
52
+ raise InvalidPassword
53
+ else
54
+ response.cookies
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ # Fetches the knife.rb for the given user/organization pair from manage.
61
+ def knife_config
62
+ RestClient.get("https://manage.opscode.com/organizations/#{orgname}/_knife_config", :cookies => login_cookies)
63
+ end
64
+
65
+ # Fetches the user's RSA API key from community.opscode.com.
66
+ #
67
+ # NB: Updates state on the server such that any existing key becomes
68
+ # invalid.
69
+ def user_key
70
+ post_options = {
71
+ :authenticity_token => user_key_authenticity_token,
72
+ :multipart => true
73
+ }
74
+ RestClient.post("https://community.opscode.com/users/#{username}/user_key", post_options, {:cookies => login_cookies})
75
+ end
76
+
77
+ # Fetches the organization's validator client's key from manage.
78
+ #
79
+ # NB: This updates state on the server such that any existing key for this
80
+ # client is no longer valid.
81
+ def validator_key
82
+ #https://manage.opscode.com/organizations/prodbench2/_regenerate_key
83
+ post_options = {
84
+ #:multipart => true,
85
+ :authenticity_token => validator_key_authenticity_token,
86
+ :_method => "put"
87
+ }
88
+ headers = {
89
+ :Referer => "https://manage.opscode.com/organizations",
90
+ :cookies => login_cookies
91
+ }
92
+ uri = "https://manage.opscode.com/organizations/#{orgname}/_regenerate_key"
93
+ RestClient.post(uri, post_options,headers)
94
+ end
95
+
96
+ private
97
+
98
+ # Screen scrape the HTML in +page+ and find the csrf token. Obviously this
99
+ # is suboptimal, but REXML can't parse HTML (AFAICT) and we don't want to
100
+ # depend on libxml2, so really nice solutions (like nokogiri+mechanize) are
101
+ # out. Hopefully we (opscode) will have time soon to build a saner
102
+ # password-based auth mechanism and no one will know that I parsed HTML
103
+ # with regexes :P
104
+ def extract_csrf_token_from(page)
105
+ # TODO: serve the authenticity token over JSON so we don't have to
106
+ # do this.
107
+ if md = page.match(/(\<meta content=\"[^\"]+\" name=\"csrf-token\"[\s]*\/\>)/)
108
+ page_element = md[1]
109
+ xml = REXML::Document.new(page_element)
110
+ xml.elements.first.attributes["content"]
111
+ elsif md = page.match(/(<input[^\>]*name=\"authenticity_token\"[^\>]+\>)/)
112
+ page_element = md[1]
113
+ xml = REXML::Document.new(page_element)
114
+ xml.elements.first.attributes["value"]
115
+ else
116
+ raise "Can't find CSRF token on the page:\n#{page}"
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,117 @@
1
+ module HostedChef
2
+ class InvalidPassword < RuntimeError
3
+ end
4
+
5
+ class Options
6
+ attr_writer :password
7
+
8
+ def password
9
+ @password ||= HighLine.new.ask("Your Hosted Chef password: ") {|q| q.echo = "*"}
10
+ end
11
+
12
+ attr_writer :username
13
+
14
+ def username
15
+ @username ||= HighLine.new.ask("Your Hosted Chef username? ")
16
+ end
17
+
18
+ attr_writer :orgname
19
+
20
+ def orgname
21
+ @orgname ||= HighLine.new.ask("Your Hosted Chef organization? ")
22
+ end
23
+
24
+ # force evaluation of options
25
+ def ask_for_missing_opts
26
+ username
27
+ password
28
+ orgname
29
+ end
30
+
31
+ end
32
+
33
+ class ConfigValidator
34
+
35
+ extend Forwardable
36
+
37
+ def_delegators :@options, :username, :password, :orgname
38
+
39
+ def initialize(api_client, options)
40
+ @api_client, @options = api_client, options
41
+ end
42
+
43
+ def validate!
44
+ puts "Validating your account information..."
45
+ validate_username
46
+ validate_password
47
+ validate_orgname
48
+ end
49
+
50
+ def validate_username
51
+ RestClient.head("http://community.opscode.com/users/#{username}")
52
+ puts "* Username '#{username}' is valid."
53
+ rescue RestClient::ResourceNotFound
54
+ STDERR.puts "The user '#{username}' does not exist. Check your spelling, or visit http://www.opscode.com/hosted-chef/ to sign up."
55
+ exit 1
56
+ end
57
+
58
+ def validate_password
59
+ @api_client.login_cookies
60
+ puts "* Password is correct"
61
+ rescue InvalidPassword
62
+ STDERR.puts "Could not login with the password you gave. Check your "\
63
+ "typing, or visit http://community.opscode.com/password_reset_requests/new"\
64
+ " to reset your password."
65
+ exit 1
66
+ end
67
+
68
+ def validate_orgname
69
+ page = RestClient.get("https://www.opscode.com/account/organizations/#{orgname}/plan", :cookies => @api_client.login_cookies)
70
+ # TODO: use actual HTTP response codes.
71
+ puts "* Organization name '#{orgname}' is correct"
72
+ rescue RestClient::ResourceNotFound
73
+ STDERR.puts "The organization '#{orgname}' does not exist or you dont "\
74
+ "have permission to access it. Check your spelling, or visit "\
75
+ "https://manage.opscode.com/organizations to create your organization"
76
+ exit 1
77
+ end
78
+ end
79
+
80
+ class ArgvParser
81
+
82
+ attr_reader :options
83
+
84
+ def initialize(argv)
85
+ @options = Options.new
86
+ @argv = argv
87
+ end
88
+
89
+ def parse
90
+ parser.parse!
91
+ options
92
+ end
93
+
94
+ def parser
95
+ OptionParser.new do |o|
96
+ o.on("-u", "--user USERNAME", "Your Hosted Chef username") do |username|
97
+ options.username = username
98
+ end
99
+
100
+ o.on("-p", "--password PASSWORD", "Your Hosted Chef password") do |passwd|
101
+ options.password = passwd
102
+ end
103
+
104
+ o.on('-o', "--organization ORGANIZATION", "Your Hosted Chef Organization") do |orgname|
105
+ options.orgname = orgname
106
+ end
107
+
108
+ o.on('-h', "--help") do
109
+ puts o
110
+ exit 1
111
+ end
112
+ end
113
+ end
114
+
115
+ end
116
+ end
117
+
@@ -0,0 +1,85 @@
1
+ module HostedChef
2
+
3
+ # Handles the business of selecting the install location and writing out the
4
+ # API keys and knife config.
5
+ class ConfigInstaller
6
+ attr_reader :install_type
7
+
8
+ def initialize(api_client, options)
9
+ @api_client, @options = api_client, options
10
+ @install_dir, @install_type = nil, nil
11
+ end
12
+
13
+ def validate!
14
+ install_dir
15
+ end
16
+
17
+ def install_dir
18
+ unless @install_dir
19
+ @install_dir, @install_type = select_install_dir
20
+ end
21
+ @install_dir
22
+ end
23
+
24
+ def install_all
25
+ puts "\nInstalling..."
26
+
27
+ puts "> mkdir -p #{install_dir}"
28
+ FileUtils.mkdir_p(install_dir)
29
+
30
+ knife_rb = "#{@install_dir}/knife.rb"
31
+ puts "> create #{knife_rb} 0644"
32
+ File.open(knife_rb, File::RDWR|File::CREAT, 0644) {|f|
33
+ f << @api_client.knife_config
34
+ }
35
+
36
+ user_pem = "#{@install_dir}/#{@options.username}.pem"
37
+ puts "> create #{user_pem} 0600"
38
+ File.open(user_pem, File::RDWR|File::CREAT, 0600) {|f|
39
+ f << @api_client.user_key
40
+ }
41
+
42
+ validator_pem = "#{@install_dir}/#{@options.orgname}-validator.pem"
43
+ puts "> create #{validator_pem} 0600"
44
+ File.open(validator_pem, File::RDWR|File::CREAT, 0600) {|f|
45
+ f << @api_client.validator_key
46
+ }
47
+ end
48
+
49
+ private
50
+
51
+ def select_install_dir
52
+ home_dot_chef = File.expand_path("~/.chef")
53
+ cwd_dot_chef = File.expand_path(".chef")
54
+
55
+ if ENV['USER'] && !File.exist?(home_dot_chef)
56
+ [home_dot_chef, :default]
57
+ elsif !File.exist?(cwd_dot_chef)
58
+ [cwd_dot_chef, :non_default]
59
+ else
60
+ install_locations = [home_dot_chef, cwd_dot_chef].uniq
61
+ if install_locations.size == 1
62
+ STDERR.puts(<<-NOWHERE_TO_GO)
63
+ ERROR: You already have a Chef configuration in your home directory
64
+ (#{home_dot_chef})
65
+
66
+ If you really want to replace this configuration, you can remove it,
67
+ otherwise cd to a different directory to generate a new config.
68
+ NOWHERE_TO_GO
69
+ else
70
+ STDERR.puts(<<-NOWHERE_TO_GO)
71
+ ERROR: You already have a Chef configuration in your home directory and
72
+ your current working directory.
73
+ (#{home_dot_chef} and #{cwd_dot_chef})
74
+
75
+ If you wish to replace one of these you can remove it, otherwise you can
76
+ cd to a different directory to create a new config.
77
+ NOWHERE_TO_GO
78
+ exit 1
79
+ end
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+
@@ -0,0 +1,71 @@
1
+ module HostedChef
2
+
3
+ # Controller and Presentation (yes, I know) for the Hosted Chef user setup
4
+ # task (currently the only task).
5
+ class Controller
6
+
7
+ attr_reader :argv
8
+
9
+ def initialize(argv=ARGV)
10
+ @argv = argv
11
+ @options = ArgvParser.new(argv).parse
12
+ @api_client = ApiClient.new(@options)
13
+ @validator = ConfigValidator.new(@api_client, @options)
14
+ @config_installer = ConfigInstaller.new(@api_client, @options)
15
+ end
16
+
17
+ def greeting
18
+ puts(<<-WELCOME)
19
+ Welcome to Opscode Hosted Chef. We're going to download your API keys
20
+ and knife config from Opscode and install them on your system.
21
+
22
+ Before you continue, please be aware that this program will update your API key
23
+ and your organization's validation key. The existing keys for these accounts
24
+ will be expired and no longer valid.
25
+
26
+ Your config will be created in #{@config_installer.install_dir}
27
+
28
+ WELCOME
29
+
30
+ unless HighLine.new.agree("Ready to get started? (yes/no): ")
31
+ STDERR.puts "goodbye."
32
+ exit 1
33
+ end
34
+ end
35
+
36
+ def setup_and_validate
37
+ @validator.validate!
38
+ end
39
+
40
+ def fetch_and_install
41
+ @config_installer.install_all
42
+ end
43
+
44
+ def goodbye
45
+ puts(<<-RESOURCES)
46
+
47
+ You're ready to go! To verify your configuration, try running
48
+ `knife client list`
49
+
50
+ If you should run into trouble check the following resources:
51
+ * Knife built-in manuals: run `knife help`
52
+ * Documentation at http://wiki.opscode.com
53
+ * Get support from Opscode: http://help.opscode.com/
54
+ RESOURCES
55
+ end
56
+
57
+
58
+ def run
59
+ @config_installer.validate!
60
+ greeting
61
+ @options.ask_for_missing_opts
62
+ setup_and_validate
63
+ fetch_and_install
64
+ goodbye
65
+ rescue Interrupt, EOFError
66
+ puts "\nexiting..."
67
+ exit! 1
68
+ end
69
+
70
+ end
71
+ end
@@ -1,3 +1,3 @@
1
1
  module HostedChef
2
- VERSION = "0.5.1"
2
+ VERSION = "0.5.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hosted-chef
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-16 00:00:00.000000000 Z
12
+ date: 2012-01-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rest-client
16
- requirement: &70274078954760 !ruby/object:Gem::Requirement
16
+ requirement: &70232472699880 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70274078954760
24
+ version_requirements: *70232472699880
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: highline
27
- requirement: &70274078952020 !ruby/object:Gem::Requirement
27
+ requirement: &70232472699460 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70274078952020
35
+ version_requirements: *70232472699460
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70274078948280 !ruby/object:Gem::Requirement
38
+ requirement: &70232472699020 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70274078948280
46
+ version_requirements: *70232472699020
47
47
  description: Configures your workstation for Hosted Chef
48
48
  email: info@opscode.com
49
49
  executables:
@@ -55,6 +55,10 @@ extra_rdoc_files:
55
55
  files:
56
56
  - LICENSE
57
57
  - README.md
58
+ - lib/hosted-chef/api_client.rb
59
+ - lib/hosted-chef/cli.rb
60
+ - lib/hosted-chef/config_installer.rb
61
+ - lib/hosted-chef/controller.rb
58
62
  - lib/hosted-chef/version.rb
59
63
  - lib/hosted-chef.rb
60
64
  - bin/hosted-chef