chef 0.8.10 → 0.8.14

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of chef might be problematic. Click here for more details.

Files changed (105) hide show
  1. data/bin/chef-client +2 -2
  2. data/bin/chef-solo +1 -2
  3. data/bin/knife +1 -2
  4. data/bin/shef +2 -5
  5. data/lib/chef.rb +11 -7
  6. data/lib/chef/application.rb +25 -20
  7. data/lib/chef/application/client.rb +12 -7
  8. data/lib/chef/application/knife.rb +7 -1
  9. data/lib/chef/application/solo.rb +1 -1
  10. data/lib/chef/applications.rb +4 -0
  11. data/lib/chef/cache/checksum.rb +7 -6
  12. data/lib/chef/certificate.rb +2 -2
  13. data/lib/chef/client.rb +28 -4
  14. data/lib/chef/config.rb +4 -3
  15. data/lib/chef/cookbook_loader.rb +6 -1
  16. data/lib/chef/daemon.rb +2 -0
  17. data/lib/chef/data_bag_item.rb +2 -1
  18. data/lib/chef/exceptions.rb +5 -0
  19. data/lib/chef/file_cache.rb +18 -18
  20. data/lib/chef/index_queue/indexable.rb +5 -3
  21. data/lib/chef/knife.rb +45 -13
  22. data/lib/chef/knife/client_list.rb +1 -1
  23. data/lib/chef/knife/client_show.rb +1 -1
  24. data/lib/chef/knife/configure.rb +47 -36
  25. data/lib/chef/knife/cookbook_list.rb +1 -1
  26. data/lib/chef/knife/cookbook_metadata.rb +22 -27
  27. data/lib/chef/knife/cookbook_metadata_from_file.rb +40 -0
  28. data/lib/chef/knife/cookbook_show.rb +2 -2
  29. data/lib/chef/knife/cookbook_site_download.rb +57 -0
  30. data/lib/chef/knife/cookbook_site_list.rb +55 -0
  31. data/lib/chef/knife/cookbook_site_search.rb +50 -0
  32. data/lib/chef/knife/cookbook_site_show.rb +56 -0
  33. data/lib/chef/knife/cookbook_site_vendor.rb +114 -0
  34. data/lib/chef/knife/cookbook_test.rb +103 -0
  35. data/lib/chef/knife/cookbook_upload.rb +29 -38
  36. data/lib/chef/knife/data_bag_edit.rb +1 -1
  37. data/lib/chef/knife/data_bag_list.rb +2 -2
  38. data/lib/chef/knife/data_bag_show.rb +1 -1
  39. data/lib/chef/knife/ec2_instance_data.rb +1 -1
  40. data/lib/chef/knife/index_rebuild.rb +2 -2
  41. data/lib/chef/knife/node_from_file.rb +1 -1
  42. data/lib/chef/knife/node_list.rb +2 -2
  43. data/lib/chef/knife/node_run_list_add.rb +1 -1
  44. data/lib/chef/knife/node_run_list_remove.rb +1 -1
  45. data/lib/chef/knife/node_show.rb +1 -1
  46. data/lib/chef/knife/rackspace_server_create.rb +156 -0
  47. data/lib/chef/knife/rackspace_server_delete.rb +57 -0
  48. data/lib/chef/knife/rackspace_server_list.rb +59 -0
  49. data/lib/chef/knife/role_from_file.rb +1 -1
  50. data/lib/chef/knife/role_list.rb +1 -1
  51. data/lib/chef/knife/role_show.rb +1 -1
  52. data/lib/chef/knife/search.rb +1 -1
  53. data/lib/chef/knife/ssh.rb +21 -4
  54. data/lib/chef/knife/terremark_server_create.rb +152 -0
  55. data/lib/chef/knife/terremark_server_delete.rb +87 -0
  56. data/lib/chef/knife/terremark_server_list.rb +77 -0
  57. data/lib/chef/mixin/command.rb +11 -9
  58. data/lib/chef/mixin/params_validate.rb +1 -1
  59. data/lib/chef/mixin/recipe_definition_dsl_core.rb +4 -1
  60. data/lib/chef/mixin/template.rb +6 -5
  61. data/lib/chef/mixin/xml_escape.rb +3 -3
  62. data/lib/chef/mixins.rb +16 -0
  63. data/lib/chef/node.rb +53 -52
  64. data/lib/chef/openid_registration.rb +0 -1
  65. data/lib/chef/platform.rb +171 -137
  66. data/lib/chef/provider.rb +2 -2
  67. data/lib/chef/provider/cron.rb +22 -22
  68. data/lib/chef/provider/deploy/revision.rb +5 -1
  69. data/lib/chef/provider/erl_call.rb +2 -2
  70. data/lib/chef/provider/file.rb +44 -23
  71. data/lib/chef/provider/group/dscl.rb +6 -4
  72. data/lib/chef/provider/mdadm.rb +0 -4
  73. data/lib/chef/provider/mount/mount.rb +20 -8
  74. data/lib/chef/provider/package.rb +1 -1
  75. data/lib/chef/provider/package/freebsd.rb +22 -18
  76. data/lib/chef/provider/package/rubygems.rb +7 -10
  77. data/lib/chef/provider/remote_directory.rb +15 -0
  78. data/lib/chef/provider/remote_file.rb +73 -50
  79. data/lib/chef/provider/script.rb +10 -8
  80. data/lib/chef/provider/service/windows.rb +129 -0
  81. data/lib/chef/provider/subversion.rb +1 -1
  82. data/lib/chef/provider/template.rb +51 -50
  83. data/lib/chef/providers.rb +80 -0
  84. data/lib/chef/recipe.rb +2 -4
  85. data/lib/chef/resource.rb +21 -7
  86. data/lib/chef/resource/cron.rb +14 -5
  87. data/lib/chef/resource/deploy.rb +52 -45
  88. data/lib/chef/resource/execute.rb +1 -1
  89. data/lib/chef/resource/file.rb +16 -8
  90. data/lib/chef/resource/mount.rb +1 -1
  91. data/lib/chef/resource/remote_directory.rb +19 -10
  92. data/lib/chef/resource/scm.rb +23 -16
  93. data/lib/chef/resource/service.rb +10 -1
  94. data/lib/chef/resources.rb +60 -0
  95. data/lib/chef/rest.rb +234 -189
  96. data/lib/chef/rest/auth_credentials.rb +78 -0
  97. data/lib/chef/{application/server.rb → rest/cookie_jar.rb} +18 -6
  98. data/lib/chef/rest/rest_request.rb +151 -0
  99. data/lib/chef/role.rb +38 -46
  100. data/lib/chef/streaming_cookbook_uploader.rb +8 -2
  101. data/lib/chef/tasks/chef_repo.rake +14 -4
  102. data/lib/chef/util/file_edit.rb +0 -1
  103. data/lib/chef/webui_user.rb +0 -1
  104. metadata +46 -9
  105. data/distro/suse/etc/init.d/chef-client +0 -121
@@ -100,7 +100,7 @@ class Chef
100
100
  set_or_return(
101
101
  :returns,
102
102
  arg,
103
- :kind_of => [ Integer ]
103
+ :kind_of => [ Integer, Array ]
104
104
  )
105
105
  end
106
106
 
@@ -6,9 +6,9 @@
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
8
8
  # You may obtain a copy of the License at
9
- #
9
+ #
10
10
  # http://www.apache.org/licenses/LICENSE-2.0
11
- #
11
+ #
12
12
  # Unless required by applicable law or agreed to in writing, software
13
13
  # distributed under the License is distributed on an "AS IS" BASIS,
14
14
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -21,7 +21,7 @@ require 'chef/resource'
21
21
  class Chef
22
22
  class Resource
23
23
  class File < Chef::Resource
24
-
24
+
25
25
  def initialize(name, collection=nil, node=nil)
26
26
  super(name, collection, node)
27
27
  @resource_name = :file
@@ -31,6 +31,14 @@ class Chef
31
31
  @allowed_actions.push(:create, :delete, :touch, :create_if_missing)
32
32
  end
33
33
 
34
+ def content(arg=nil)
35
+ set_or_return(
36
+ :content,
37
+ arg,
38
+ :kind_of => String
39
+ )
40
+ end
41
+
34
42
  def backup(arg=nil)
35
43
  set_or_return(
36
44
  :backup,
@@ -38,7 +46,7 @@ class Chef
38
46
  :kind_of => [ Integer, FalseClass ]
39
47
  )
40
48
  end
41
-
49
+
42
50
  def checksum(arg=nil)
43
51
  set_or_return(
44
52
  :checksum,
@@ -46,7 +54,7 @@ class Chef
46
54
  :regex => /^[a-zA-Z0-9]{64}$/
47
55
  )
48
56
  end
49
-
57
+
50
58
  def group(arg=nil)
51
59
  set_or_return(
52
60
  :group,
@@ -54,7 +62,7 @@ class Chef
54
62
  :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ]
55
63
  )
56
64
  end
57
-
65
+
58
66
  def mode(arg=nil)
59
67
  set_or_return(
60
68
  :mode,
@@ -62,7 +70,7 @@ class Chef
62
70
  :regex => /^0?\d{3,4}$/
63
71
  )
64
72
  end
65
-
73
+
66
74
  def owner(arg=nil)
67
75
  set_or_return(
68
76
  :owner,
@@ -70,7 +78,7 @@ class Chef
70
78
  :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ]
71
79
  )
72
80
  end
73
-
81
+
74
82
  def path(arg=nil)
75
83
  set_or_return(
76
84
  :path,
@@ -28,7 +28,7 @@ class Chef
28
28
  @mount_point = name
29
29
  @device = nil
30
30
  @device_type = :device
31
- @fstype = nil
31
+ @fstype = "auto"
32
32
  @options = ["defaults"]
33
33
  @dump = 0
34
34
  @pass = 2
@@ -6,9 +6,9 @@
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
8
8
  # You may obtain a copy of the License at
9
- #
9
+ #
10
10
  # http://www.apache.org/licenses/LICENSE-2.0
11
- #
11
+ #
12
12
  # Unless required by applicable law or agreed to in writing, software
13
13
  # distributed under the License is distributed on an "AS IS" BASIS,
14
14
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -21,7 +21,7 @@ require 'chef/resource/directory'
21
21
  class Chef
22
22
  class Resource
23
23
  class RemoteDirectory < Chef::Resource::Directory
24
-
24
+
25
25
  def initialize(name, collection=nil, node=nil)
26
26
  super(name, collection, node)
27
27
  @resource_name = :remote_directory
@@ -30,6 +30,7 @@ class Chef
30
30
  @delete = false
31
31
  @action = :create
32
32
  @recursive = true
33
+ @purge = false
33
34
  @files_backup = 5
34
35
  @files_owner = nil
35
36
  @files_group = nil
@@ -37,7 +38,7 @@ class Chef
37
38
  @allowed_actions.push(:create, :delete)
38
39
  @cookbook = nil
39
40
  end
40
-
41
+
41
42
  def source(args=nil)
42
43
  set_or_return(
43
44
  :source,
@@ -45,7 +46,7 @@ class Chef
45
46
  :kind_of => String
46
47
  )
47
48
  end
48
-
49
+
49
50
  def files_backup(arg=nil)
50
51
  set_or_return(
51
52
  :files_backup,
@@ -53,7 +54,15 @@ class Chef
53
54
  :kind_of => [ Integer, FalseClass ]
54
55
  )
55
56
  end
56
-
57
+
58
+ def purge(arg=nil)
59
+ set_or_return(
60
+ :purge,
61
+ arg,
62
+ :kind_of => [ TrueClass, FalseClass ]
63
+ )
64
+ end
65
+
57
66
  def files_group(arg=nil)
58
67
  set_or_return(
59
68
  :files_group,
@@ -61,7 +70,7 @@ class Chef
61
70
  :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ]
62
71
  )
63
72
  end
64
-
73
+
65
74
  def files_mode(arg=nil)
66
75
  set_or_return(
67
76
  :files_mode,
@@ -69,7 +78,7 @@ class Chef
69
78
  :regex => /^\d{3,4}$/
70
79
  )
71
80
  end
72
-
81
+
73
82
  def files_owner(arg=nil)
74
83
  set_or_return(
75
84
  :files_owner,
@@ -77,7 +86,7 @@ class Chef
77
86
  :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ]
78
87
  )
79
88
  end
80
-
89
+
81
90
  def cookbook(args=nil)
82
91
  set_or_return(
83
92
  :cookbook,
@@ -85,7 +94,7 @@ class Chef
85
94
  :kind_of => String
86
95
  )
87
96
  end
88
-
97
+
89
98
  end
90
99
  end
91
100
  end
@@ -6,9 +6,9 @@
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
8
8
  # You may obtain a copy of the License at
9
- #
9
+ #
10
10
  # http://www.apache.org/licenses/LICENSE-2.0
11
- #
11
+ #
12
12
  # Unless required by applicable law or agreed to in writing, software
13
13
  # distributed under the License is distributed on an "AS IS" BASIS,
14
14
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,7 +22,7 @@ require 'chef/resource'
22
22
  class Chef
23
23
  class Resource
24
24
  class Scm < Chef::Resource
25
-
25
+
26
26
  def initialize(name, collection=nil, node=nil)
27
27
  super(name, collection, node)
28
28
  @destination = name
@@ -34,7 +34,7 @@ class Chef
34
34
  @depth = nil
35
35
  @allowed_actions.push(:checkout, :export, :sync, :diff, :log)
36
36
  end
37
-
37
+
38
38
  def destination(arg=nil)
39
39
  set_or_return(
40
40
  :destination,
@@ -42,7 +42,7 @@ class Chef
42
42
  :kind_of => String
43
43
  )
44
44
  end
45
-
45
+
46
46
  def repository(arg=nil)
47
47
  set_or_return(
48
48
  :repository,
@@ -50,7 +50,7 @@ class Chef
50
50
  :kind_of => String
51
51
  )
52
52
  end
53
-
53
+
54
54
  def revision(arg=nil)
55
55
  set_or_return(
56
56
  :revision,
@@ -58,7 +58,7 @@ class Chef
58
58
  :kind_of => String
59
59
  )
60
60
  end
61
-
61
+
62
62
  def user(arg=nil)
63
63
  set_or_return(
64
64
  :user,
@@ -66,7 +66,7 @@ class Chef
66
66
  :kind_of => [String, Integer]
67
67
  )
68
68
  end
69
-
69
+
70
70
  def group(arg=nil)
71
71
  set_or_return(
72
72
  :group,
@@ -74,7 +74,7 @@ class Chef
74
74
  :kind_of => [String, Integer]
75
75
  )
76
76
  end
77
-
77
+
78
78
  def svn_username(arg=nil)
79
79
  set_or_return(
80
80
  :svn_username,
@@ -82,7 +82,7 @@ class Chef
82
82
  :kind_of => String
83
83
  )
84
84
  end
85
-
85
+
86
86
  def svn_password(arg=nil)
87
87
  set_or_return(
88
88
  :svn_password,
@@ -90,7 +90,7 @@ class Chef
90
90
  :kind_of => String
91
91
  )
92
92
  end
93
-
93
+
94
94
  def svn_arguments(arg=nil)
95
95
  set_or_return(
96
96
  :svn_arguments,
@@ -98,7 +98,14 @@ class Chef
98
98
  :kind_of => String
99
99
  )
100
100
  end
101
-
101
+
102
+ def svn_info_args(arg=nil)
103
+ set_or_return(
104
+ :svn_arguments,
105
+ arg,
106
+ :kind_of => String)
107
+ end
108
+
102
109
  # Capistrano and git-deploy use ``shallow clone''
103
110
  def depth(arg=nil)
104
111
  set_or_return(
@@ -107,7 +114,7 @@ class Chef
107
114
  :kind_of => Integer
108
115
  )
109
116
  end
110
-
117
+
111
118
  def enable_submodules(arg=nil)
112
119
  set_or_return(
113
120
  :enable_submodules,
@@ -115,7 +122,7 @@ class Chef
115
122
  :kind_of => [TrueClass, FalseClass]
116
123
  )
117
124
  end
118
-
125
+
119
126
  def remote(arg=nil)
120
127
  set_or_return(
121
128
  :remote,
@@ -123,7 +130,7 @@ class Chef
123
130
  :kind_of => String
124
131
  )
125
132
  end
126
-
133
+
127
134
  def ssh_wrapper(arg=nil)
128
135
  set_or_return(
129
136
  :ssh_wrapper,
@@ -131,7 +138,7 @@ class Chef
131
138
  :kind_of => String
132
139
  )
133
140
  end
134
-
141
+
135
142
  end
136
143
  end
137
144
  end
@@ -35,6 +35,7 @@ class Chef
35
35
  @restart_command = nil
36
36
  @reload_command = nil
37
37
  @action = "nothing"
38
+ @startup_type = :automatic
38
39
  @supports = { :restart => false, :reload => false, :status => false }
39
40
  @allowed_actions.push(:enable, :disable, :start, :stop, :restart, :reload)
40
41
  end
@@ -127,7 +128,15 @@ class Chef
127
128
  @supports
128
129
  end
129
130
  end
130
-
131
+
132
+ # This attribute applies for Windows only.
133
+ def startup_type(arg=nil)
134
+ set_or_return(
135
+ :startup_type,
136
+ arg,
137
+ :equal_to => [:automatic, :mannual]
138
+ )
139
+ end
131
140
 
132
141
  end
133
142
  end
@@ -0,0 +1,60 @@
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Copyright:: Copyright (c) 2010 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/resource/apt_package'
20
+ require 'chef/resource/bash'
21
+ require 'chef/resource/breakpoint'
22
+ require 'chef/resource/cron'
23
+ require 'chef/resource/csh'
24
+ require 'chef/resource/deploy'
25
+ require 'chef/resource/deploy_revision'
26
+ require 'chef/resource/directory'
27
+ require 'chef/resource/dpkg_package'
28
+ require 'chef/resource/easy_install_package'
29
+ require 'chef/resource/erl_call'
30
+ require 'chef/resource/execute'
31
+ require 'chef/resource/file'
32
+ require 'chef/resource/freebsd_package'
33
+ require 'chef/resource/gem_package'
34
+ require 'chef/resource/git'
35
+ require 'chef/resource/group'
36
+ require 'chef/resource/http_request'
37
+ require 'chef/resource/ifconfig'
38
+ require 'chef/resource/link'
39
+ require 'chef/resource/log'
40
+ require 'chef/resource/macports_package'
41
+ require 'chef/resource/mdadm'
42
+ require 'chef/resource/mount'
43
+ require 'chef/resource/package'
44
+ require 'chef/resource/pacman_package'
45
+ require 'chef/resource/perl'
46
+ require 'chef/resource/portage_package'
47
+ require 'chef/resource/python'
48
+ require 'chef/resource/remote_directory'
49
+ require 'chef/resource/remote_file'
50
+ require 'chef/resource/route'
51
+ require 'chef/resource/ruby'
52
+ require 'chef/resource/ruby_block'
53
+ require 'chef/resource/scm'
54
+ require 'chef/resource/script'
55
+ require 'chef/resource/service'
56
+ require 'chef/resource/subversion'
57
+ require 'chef/resource/template'
58
+ require 'chef/resource/timestamped_deploy'
59
+ require 'chef/resource/user'
60
+ require 'chef/resource/yum_package'
@@ -10,9 +10,9 @@
10
10
  # Licensed under the Apache License, Version 2.0 (the "License");
11
11
  # you may not use this file except in compliance with the License.
12
12
  # You may obtain a copy of the License at
13
- #
13
+ #
14
14
  # http://www.apache.org/licenses/LICENSE-2.0
15
- #
15
+ #
16
16
  # Unless required by applicable law or agreed to in writing, software
17
17
  # distributed under the License is distributed on an "AS IS" BASIS,
18
18
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,67 +20,58 @@
20
20
  # limitations under the License.
21
21
  #
22
22
 
23
- require 'chef/mixin/params_validate'
24
23
  require 'net/https'
25
24
  require 'uri'
26
25
  require 'json'
27
26
  require 'tempfile'
28
- require 'singleton'
29
- require 'mixlib/authentication/signedheaderauth'
30
27
  require 'chef/api_client'
31
-
32
- include Mixlib::Authentication::SignedHeaderAuth
28
+ require 'chef/rest/auth_credentials'
29
+ require 'chef/rest/rest_request'
33
30
 
34
31
  class Chef
35
32
  class REST
33
+ attr_reader :auth_credentials
34
+ attr_accessor :url, :cookies, :sign_on_redirect, :redirect_limit
36
35
 
37
- class CookieJar < Hash
38
- include Singleton
39
- end
40
-
41
- attr_accessor :url, :cookies, :client_name, :signing_key, :signing_key_filename, :sign_on_redirect, :sign_request
42
-
43
36
  def initialize(url, client_name=Chef::Config[:node_name], signing_key_filename=Chef::Config[:client_key], options={})
44
37
  @url = url
45
38
  @cookies = CookieJar.instance
46
- @client_name = client_name
47
39
  @default_headers = options[:headers] || {}
48
- if signing_key_filename
49
- @signing_key_filename = signing_key_filename
50
- @signing_key = load_signing_key(signing_key_filename)
51
- @sign_request = true
52
- else
53
- @signing_key = nil
54
- @sign_request = false
55
- end
56
- @sign_on_redirect = true
40
+ @auth_credentials = AuthCredentials.new(client_name, signing_key_filename)
41
+ @sign_on_redirect, @sign_request = true, true
42
+ @redirects_followed = 0
43
+ @redirect_limit = 10
57
44
  end
58
45
 
59
- def load_signing_key(key)
60
- begin
61
- IO.read(key)
62
- rescue StandardError=>se
63
- Chef::Log.error "Failed to read the private key #{key}: #{se.inspect}, #{se.backtrace}"
64
- raise Chef::Exceptions::PrivateKeyMissing, "I cannot read #{key}, which you told me to use to sign requests!"
65
- end
46
+ def signing_key_filename
47
+ @auth_credentials.key_file
48
+ end
49
+
50
+ def client_name
51
+ @auth_credentials.client_name
52
+ end
53
+
54
+ def signing_key
55
+ @auth_credentials.raw_key
66
56
  end
67
-
68
- # Register the client
69
- def register(name=Chef::Config[:node_name], destination=Chef::Config[:client_key])
70
- raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination} - check permissions?" if (File.exists?(destination) && !File.writable?(destination))
71
57
 
58
+ # Register the client
59
+ def register(name=Chef::Config[:node_name], destination=Chef::Config[:client_key])
60
+ if (File.exists?(destination) && !File.writable?(destination))
61
+ raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination} - check permissions?"
62
+ end
72
63
  nc = Chef::ApiClient.new
73
64
  nc.name(name)
74
65
 
75
66
  catch(:done) do
76
- retries = Chef::Config[:client_registration_retries] || 5
67
+ retries = config[:client_registration_retries] || 5
77
68
  0.upto(retries) do |n|
78
69
  begin
79
70
  response = nc.save(true, true)
80
71
  Chef::Log.debug("Registration response: #{response.inspect}")
81
72
  raise Chef::Exceptions::CannotWritePrivateKey, "The response from the server did not include a private key!" unless response.has_key?("private_key")
82
73
  # Write out the private key
83
- file = File.open(destination, File::WRONLY|File::EXCL|File::CREAT, 0600)
74
+ file = ::File.open(destination, File::WRONLY|File::EXCL|File::CREAT, 0600)
84
75
  file.print(response["private_key"])
85
76
  file.close
86
77
  throw :done
@@ -100,27 +91,40 @@ class Chef
100
91
  #
101
92
  # === Parameters
102
93
  # path:: The path to GET
103
- # raw:: Whether you want the raw body returned, or JSON inflated. Defaults
94
+ # raw:: Whether you want the raw body returned, or JSON inflated. Defaults
104
95
  # to JSON inflated.
105
96
  def get_rest(path, raw=false, headers={})
106
- run_request(:GET, create_url(path), headers, false, 10, raw)
107
- end
108
-
97
+ if raw
98
+ streaming_request(create_url(path), headers)
99
+ else
100
+ api_request(:GET, create_url(path), headers)
101
+ end
102
+ end
103
+
109
104
  # Send an HTTP DELETE request to the path
110
- def delete_rest(path, headers={})
111
- run_request(:DELETE, create_url(path), headers)
112
- end
113
-
114
- # Send an HTTP POST request to the path
105
+ def delete_rest(path, headers={})
106
+ api_request(:DELETE, create_url(path), headers)
107
+ end
108
+
109
+ # Send an HTTP POST request to the path
115
110
  def post_rest(path, json, headers={})
116
- run_request(:POST, create_url(path), headers, json)
117
- end
118
-
111
+ api_request(:POST, create_url(path), headers, json)
112
+ end
113
+
119
114
  # Send an HTTP PUT request to the path
120
115
  def put_rest(path, json, headers={})
121
- run_request(:PUT, create_url(path), headers, json)
116
+ api_request(:PUT, create_url(path), headers, json)
122
117
  end
123
-
118
+
119
+ # Streams a download to a tempfile, then yields the tempfile to a block.
120
+ # After the download, the tempfile will be closed and unlinked.
121
+ # If you rename the tempfile, it will not be deleted.
122
+ # Beware that if the server streams infinite content, this method will
123
+ # stream it until you run out of disk space.
124
+ def fetch(path, headers={})
125
+ streaming_request(create_url(path), headers) {|tmp_file| yield tmp_file }
126
+ end
127
+
124
128
  def create_url(path)
125
129
  if path =~ /^(http|https):\/\//
126
130
  URI.parse(path)
@@ -128,150 +132,39 @@ class Chef
128
132
  URI.parse("#{@url}/#{path}")
129
133
  end
130
134
  end
131
-
132
- def sign_request(http_method, path, private_key, user_id, body = "", host="localhost")
133
- #body = "" if body == false
134
- timestamp = Time.now.utc.iso8601
135
- sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(
136
- :http_method=>http_method,
137
- :path => path,
138
- :body=>body,
139
- :user_id=>user_id,
140
- :timestamp=>timestamp)
141
- signed = sign_obj.sign(private_key).merge({:host => host})
142
- signed.inject({}){|memo, kv| memo["#{kv[0].to_s.upcase}"] = kv[1];memo}
135
+
136
+ def sign_requests?
137
+ auth_credentials.sign_requests? && @sign_request
143
138
  end
144
-
139
+
145
140
  # Actually run an HTTP request. First argument is the HTTP method,
146
141
  # which should be one of :GET, :PUT, :POST or :DELETE. Next is the
147
142
  # URL, then an object to include in the body (which will be converted with
148
- # .to_json) and finally, the limit of HTTP Redirects to follow (10).
143
+ # .to_json). The limit argument is unused, it is present for backwards
144
+ # compatibility. Configure the redirect limit with #redirect_limit=
145
+ # instead.
149
146
  #
150
147
  # Typically, you won't use this method -- instead, you'll use one of
151
148
  # the helper methods (get_rest, post_rest, etc.)
152
149
  #
153
150
  # Will return the body of the response on success.
154
- def run_request(method, url, headers={}, data=false, limit=10, raw=false)
155
-
156
- http_retry_delay = Chef::Config[:http_retry_delay]
157
- http_retry_count = Chef::Config[:http_retry_count]
158
-
159
- raise ArgumentError, 'HTTP redirect too deep' if limit == 0
160
-
161
- http = Net::HTTP.new(url.host, url.port)
162
- if url.scheme == "https"
163
- http.use_ssl = true
164
- if Chef::Config[:ssl_verify_mode] == :verify_none
165
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
166
- elsif Chef::Config[:ssl_verify_mode] == :verify_peer
167
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
168
- end
169
- if Chef::Config[:ssl_ca_path] and File.exists?(Chef::Config[:ssl_ca_path])
170
- http.ca_path = Chef::Config[:ssl_ca_path]
171
- elsif Chef::Config[:ssl_ca_file] and File.exists?(Chef::Config[:ssl_ca_file])
172
- http.ca_file = Chef::Config[:ssl_ca_file]
173
- end
174
- if Chef::Config[:ssl_client_cert] && File.exists?(Chef::Config[:ssl_client_cert])
175
- http.cert = OpenSSL::X509::Certificate.new(File.read(Chef::Config[:ssl_client_cert]))
176
- http.key = OpenSSL::PKey::RSA.new(File.read(Chef::Config[:ssl_client_key]))
177
- end
178
- end
179
-
180
- http.read_timeout = Chef::Config[:rest_timeout]
181
-
182
- headers = @default_headers.merge(headers)
183
-
184
- unless raw
185
- headers = headers.merge({
186
- 'Accept' => "application/json",
187
- })
188
- end
151
+ def run_request(method, url, headers={}, data=false, limit=nil, raw=false)
152
+ json_body = data ? data.to_json : nil
153
+ headers = build_headers(method, url, headers, json_body, raw)
189
154
 
190
- headers['X-Chef-Version'] = ::Chef::VERSION
191
-
192
- if @cookies.has_key?("#{url.host}:#{url.port}")
193
- headers['Cookie'] = @cookies["#{url.host}:#{url.port}"]
194
- end
195
-
196
- json_body = data ? data.to_json : nil
197
-
198
- if @sign_request
199
- raise ArgumentError, "Cannot sign the request without a client name, check that :node_name is assigned" if @client_name.nil?
200
- Chef::Log.debug("Signing the request as #{@client_name}")
201
- if json_body
202
- headers.merge!(sign_request(method, url.path, OpenSSL::PKey::RSA.new(@signing_key), @client_name, json_body, "#{url.host}:#{url.port}"))
203
- else
204
- headers.merge!(sign_request(method, url.path, OpenSSL::PKey::RSA.new(@signing_key), @client_name, "", "#{url.host}:#{url.port}"))
205
- end
206
- end
207
-
208
- req = nil
209
- case method
210
- when :GET
211
- req_path = "#{url.path}"
212
- req_path << "?#{url.query}" if url.query
213
- req = Net::HTTP::Get.new(req_path, headers)
214
- when :POST
215
- headers["Content-Type"] = 'application/json' if data
216
- req_path = "#{url.path}"
217
- req_path << "?#{url.query}" if url.query
218
- req = Net::HTTP::Post.new(req_path, headers)
219
- req.body = json_body if json_body
220
- when :PUT
221
- headers["Content-Type"] = 'application/json' if data
222
- req_path = "#{url.path}"
223
- req_path << "?#{url.query}" if url.query
224
- req = Net::HTTP::Put.new(req_path, headers)
225
- req.body = json_body if json_body
226
- when :DELETE
227
- req_path = "#{url.path}"
228
- req_path << "?#{url.query}" if url.query
229
- req = Net::HTTP::Delete.new(req_path, headers)
230
- else
231
- raise ArgumentError, "You must provide :GET, :PUT, :POST or :DELETE as the method"
232
- end
233
-
234
- Chef::Log.debug("Sending HTTP Request via #{req.method} to #{url.host}:#{url.port}#{req.path}")
235
-
236
- # Optionally handle HTTP Basic Authentication
237
- req.basic_auth(url.user, url.password) if url.user
238
-
239
- res = nil
240
155
  tf = nil
241
- http_attempts = 0
242
156
 
243
- begin
244
- http_attempts += 1
245
-
246
- res = http.request(req) do |response|
157
+ retriable_rest_request(method, url, json_body, headers) do |rest_request|
158
+
159
+ res = rest_request.call do |response|
247
160
  if raw
248
- tf = Tempfile.new("chef-rest")
249
- # Stolen from http://www.ruby-forum.com/topic/166423
250
- # Kudos to _why!
251
- size, total = 0, response.header['Content-Length'].to_i
252
- response.read_body do |chunk|
253
- tf.write(chunk)
254
- size += chunk.size
255
- if size == 0
256
- Chef::Log.debug("#{req.path} done (0 length file)")
257
- elsif total == 0
258
- Chef::Log.debug("#{req.path} (zero content length)")
259
- else
260
- Chef::Log.debug("#{req.path}" + " %d%% done (%d of %d)" % [(size * 100) / total, size, total])
261
- end
262
- end
263
- tf.close
264
- tf
161
+ tf = stream_to_tempfile(url, response)
265
162
  else
266
163
  response.read_body
267
164
  end
268
- response
269
165
  end
270
-
166
+
271
167
  if res.kind_of?(Net::HTTPSuccess)
272
- if res['set-cookie']
273
- @cookies["#{url.host}:#{url.port}"] = res['set-cookie']
274
- end
275
168
  if res['content-type'] =~ /json/
276
169
  response_body = res.body.chomp
277
170
  JSON.parse(response_body)
@@ -283,37 +176,114 @@ class Chef
283
176
  end
284
177
  end
285
178
  elsif res.kind_of?(Net::HTTPFound) or res.kind_of?(Net::HTTPMovedPermanently)
286
- if res['set-cookie']
287
- @cookies["#{url.host}:#{url.port}"] = res['set-cookie']
288
- end
289
- @sign_request = false if @sign_on_redirect == false
290
- run_request(:GET, create_url(res['location']), {}, false, limit - 1, raw)
179
+ follow_redirect {run_request(:GET, create_url(res['location']), {}, false, nil, raw)}
291
180
  else
292
181
  if res['content-type'] =~ /json/
293
182
  exception = JSON.parse(res.body)
294
- Chef::Log.debug("HTTP Request Returned #{res.code} #{res.message}: #{exception["error"].respond_to?(:join) ? exception["error"].join(", ") : exception["error"]}")
183
+ msg = "HTTP Request Returned #{res.code} #{res.message}: "
184
+ msg << (exception["error"].respond_to?(:join) ? exception["error"].join(", ") : exception["error"].to_s)
185
+ Chef::Log.warn(msg)
295
186
  end
296
187
  res.error!
297
188
  end
298
-
189
+ end
190
+ end
191
+
192
+ # Similar to #run_request but only supports JSON APIs. File Download not supported.
193
+ def api_request(method, url, headers={}, data=false)
194
+ json_body = data ? data.to_json : nil
195
+ headers = build_headers(method, url, headers, json_body)
196
+
197
+ retriable_rest_request(method, url, json_body, headers) do |rest_request|
198
+ response = rest_request.call {|r| r.read_body}
199
+
200
+ if response.kind_of?(Net::HTTPSuccess)
201
+ if response['content-type'] =~ /json/
202
+ JSON.parse(response.body.chomp)
203
+ else
204
+ Chef::Log.warn("Expected JSON response, but got content-type '#{response['content-type']}'")
205
+ response.body
206
+ end
207
+ elsif redirect_location = redirected_to(response)
208
+ follow_redirect {api_request(:GET, create_url(redirect_location))}
209
+ else
210
+ if response['content-type'] =~ /json/
211
+ exception = JSON.parse(response.body)
212
+ msg = "HTTP Request Returned #{response.code} #{response.message}: "
213
+ msg << (exception["error"].respond_to?(:join) ? exception["error"].join(", ") : exception["error"].to_s)
214
+ Chef::Log.warn(msg)
215
+ end
216
+ response.error!
217
+ end
218
+ end
219
+ end
220
+
221
+ # similar to #run_request but only supports streaming downloads.
222
+ # Only supports GET, doesn't speak JSON
223
+ # Streams the response body to a tempfile. If a block is given, it's
224
+ # passed to the tempfile, which means that the tempfile will automatically
225
+ # be unlinked after the block is executed.
226
+ # If no block is given, the tempfile is returned, which means it's up to
227
+ # you to unlink the tempfile when you're done with it.
228
+ def streaming_request(url, headers, &block)
229
+ headers = build_headers(:GET, url, headers, nil, true)
230
+ retriable_rest_request(:GET, url, nil, headers) do |rest_request|
231
+ tempfile = nil
232
+ response = rest_request.call do |r|
233
+ if block_given? && r.kind_of?(Net::HTTPSuccess)
234
+ begin
235
+ tempfile = stream_to_tempfile(url, r, &block)
236
+ yield tempfile
237
+ ensure
238
+ tempfile.close!
239
+ end
240
+ else
241
+ tempfile = stream_to_tempfile(url, r)
242
+ end
243
+ end
244
+ if response.kind_of?(Net::HTTPSuccess)
245
+ tempfile
246
+ elsif redirect_location = redirected_to(response)
247
+ # TODO: test tempfile unlinked when following redirects.
248
+ tempfile && tempfile.close!
249
+ follow_redirect {streaming_request(create_url(redirect_location), {}, &block)}
250
+ else
251
+ tempfile && tempfile.close!
252
+ response.error!
253
+ end
254
+ end
255
+ end
256
+
257
+ def retriable_rest_request(method, url, req_body, headers)
258
+ rest_request = Chef::REST::RESTRequest.new(method, url, req_body, headers)
259
+
260
+ Chef::Log.debug("Sending HTTP Request via #{method} to #{url.host}:#{url.port}#{rest_request.path}")
261
+
262
+ http_attempts = 0
263
+
264
+ begin
265
+ http_attempts += 1
266
+
267
+ res = yield rest_request
268
+
299
269
  rescue Errno::ECONNREFUSED
300
270
  if http_retry_count - http_attempts + 1 > 0
301
- Chef::Log.error("Connection refused connecting to #{url.host}:#{url.port} for #{req.path}, retry #{http_attempts}/#{http_retry_count}")
271
+ Chef::Log.error("Connection refused connecting to #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}")
302
272
  sleep(http_retry_delay)
303
273
  retry
304
274
  end
305
- raise Errno::ECONNREFUSED, "Connection refused connecting to #{url.host}:#{url.port} for #{req.path}, giving up"
275
+ raise Errno::ECONNREFUSED, "Connection refused connecting to #{url.host}:#{url.port} for #{rest_request.path}, giving up"
306
276
  rescue Timeout::Error
307
277
  if http_retry_count - http_attempts + 1 > 0
308
- Chef::Log.error("Timeout connecting to #{url.host}:#{url.port} for #{req.path}, retry #{http_attempts}/#{http_retry_count}")
278
+ Chef::Log.error("Timeout connecting to #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}")
309
279
  sleep(http_retry_delay)
310
280
  retry
311
281
  end
312
- raise Timeout::Error, "Timeout connecting to #{url.host}:#{url.port} for #{req.path}, giving up"
282
+ raise Timeout::Error, "Timeout connecting to #{url.host}:#{url.port} for #{rest_request.path}, giving up"
313
283
  rescue Net::HTTPServerException
314
284
  if res.kind_of?(Net::HTTPForbidden)
315
285
  if http_retry_count - http_attempts + 1 > 0
316
- Chef::Log.error("Received 403 Forbidden against #{url.host}:#{url.port} for #{req.path}, retry #{http_attempts}/#{http_retry_count}")
286
+ Chef::Log.error("Received 403 Forbidden against #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}")
317
287
  sleep(http_retry_delay)
318
288
  retry
319
289
  end
@@ -321,6 +291,81 @@ class Chef
321
291
  raise
322
292
  end
323
293
  end
324
-
294
+
295
+ def authentication_headers(method, url, json_body=nil)
296
+ request_params = {:http_method => method, :path => url.path, :body => json_body, :host => "#{url.host}:#{url.port}"}
297
+ request_params[:body] ||= ""
298
+ auth_credentials.signature_headers(request_params)
299
+ end
300
+
301
+ def http_retry_delay
302
+ config[:http_retry_delay]
303
+ end
304
+
305
+ def http_retry_count
306
+ config[:http_retry_count]
307
+ end
308
+
309
+ def config
310
+ Chef::Config
311
+ end
312
+
313
+ def follow_redirect
314
+ raise Chef::Exceptions::RedirectLimitExceeded if @redirects_followed >= redirect_limit
315
+ @redirects_followed += 1
316
+ Chef::Log.debug("Following redirect #{@redirects_followed}/#{redirect_limit}")
317
+ if @sign_on_redirect
318
+ yield
319
+ else
320
+ @sign_request = false
321
+ yield
322
+ end
323
+ ensure
324
+ @redirects_followed = 0
325
+ @sign_request = true
326
+ end
327
+
328
+ private
329
+
330
+ def redirected_to(response)
331
+ if response.kind_of?(Net::HTTPFound) || response.kind_of?(Net::HTTPMovedPermanently)
332
+ response['location']
333
+ else
334
+ nil
335
+ end
336
+ end
337
+
338
+ def build_headers(method, url, headers={}, json_body=false, raw=false)
339
+ headers = @default_headers.merge(headers)
340
+ headers['Accept'] = "application/json" unless raw
341
+ headers["Content-Type"] = 'application/json' if json_body
342
+ headers.merge!(authentication_headers(method, url, json_body)) if sign_requests?
343
+ headers
344
+ end
345
+
346
+ def stream_to_tempfile(url, response)
347
+ tf = Tempfile.open("chef-rest")
348
+ Chef::Log.debug("Streaming download from #{url.to_s} to tempfile #{tf.path}")
349
+ # Stolen from http://www.ruby-forum.com/topic/166423
350
+ # Kudos to _why!
351
+ size, total = 0, response.header['Content-Length'].to_i
352
+ response.read_body do |chunk|
353
+ tf.write(chunk)
354
+ size += chunk.size
355
+ if size == 0
356
+ Chef::Log.debug("#{url.path} done (0 length file)")
357
+ elsif total == 0
358
+ Chef::Log.debug("#{url.path} (zero content length or no Content-Length header)")
359
+ else
360
+ Chef::Log.debug("#{url.path}" + " %d%% done (%d of %d)" % [(size * 100) / total, size, total])
361
+ end
362
+ end
363
+ tf.close
364
+ tf
365
+ rescue Exception
366
+ tf.close!
367
+ raise
368
+ end
369
+
325
370
  end
326
371
  end