hosted-chef 0.5.1 → 0.5.2

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