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.
- data/bin/chef-client +2 -2
- data/bin/chef-solo +1 -2
- data/bin/knife +1 -2
- data/bin/shef +2 -5
- data/lib/chef.rb +11 -7
- data/lib/chef/application.rb +25 -20
- data/lib/chef/application/client.rb +12 -7
- data/lib/chef/application/knife.rb +7 -1
- data/lib/chef/application/solo.rb +1 -1
- data/lib/chef/applications.rb +4 -0
- data/lib/chef/cache/checksum.rb +7 -6
- data/lib/chef/certificate.rb +2 -2
- data/lib/chef/client.rb +28 -4
- data/lib/chef/config.rb +4 -3
- data/lib/chef/cookbook_loader.rb +6 -1
- data/lib/chef/daemon.rb +2 -0
- data/lib/chef/data_bag_item.rb +2 -1
- data/lib/chef/exceptions.rb +5 -0
- data/lib/chef/file_cache.rb +18 -18
- data/lib/chef/index_queue/indexable.rb +5 -3
- data/lib/chef/knife.rb +45 -13
- data/lib/chef/knife/client_list.rb +1 -1
- data/lib/chef/knife/client_show.rb +1 -1
- data/lib/chef/knife/configure.rb +47 -36
- data/lib/chef/knife/cookbook_list.rb +1 -1
- data/lib/chef/knife/cookbook_metadata.rb +22 -27
- data/lib/chef/knife/cookbook_metadata_from_file.rb +40 -0
- data/lib/chef/knife/cookbook_show.rb +2 -2
- data/lib/chef/knife/cookbook_site_download.rb +57 -0
- data/lib/chef/knife/cookbook_site_list.rb +55 -0
- data/lib/chef/knife/cookbook_site_search.rb +50 -0
- data/lib/chef/knife/cookbook_site_show.rb +56 -0
- data/lib/chef/knife/cookbook_site_vendor.rb +114 -0
- data/lib/chef/knife/cookbook_test.rb +103 -0
- data/lib/chef/knife/cookbook_upload.rb +29 -38
- data/lib/chef/knife/data_bag_edit.rb +1 -1
- data/lib/chef/knife/data_bag_list.rb +2 -2
- data/lib/chef/knife/data_bag_show.rb +1 -1
- data/lib/chef/knife/ec2_instance_data.rb +1 -1
- data/lib/chef/knife/index_rebuild.rb +2 -2
- data/lib/chef/knife/node_from_file.rb +1 -1
- data/lib/chef/knife/node_list.rb +2 -2
- data/lib/chef/knife/node_run_list_add.rb +1 -1
- data/lib/chef/knife/node_run_list_remove.rb +1 -1
- data/lib/chef/knife/node_show.rb +1 -1
- data/lib/chef/knife/rackspace_server_create.rb +156 -0
- data/lib/chef/knife/rackspace_server_delete.rb +57 -0
- data/lib/chef/knife/rackspace_server_list.rb +59 -0
- data/lib/chef/knife/role_from_file.rb +1 -1
- data/lib/chef/knife/role_list.rb +1 -1
- data/lib/chef/knife/role_show.rb +1 -1
- data/lib/chef/knife/search.rb +1 -1
- data/lib/chef/knife/ssh.rb +21 -4
- data/lib/chef/knife/terremark_server_create.rb +152 -0
- data/lib/chef/knife/terremark_server_delete.rb +87 -0
- data/lib/chef/knife/terremark_server_list.rb +77 -0
- data/lib/chef/mixin/command.rb +11 -9
- data/lib/chef/mixin/params_validate.rb +1 -1
- data/lib/chef/mixin/recipe_definition_dsl_core.rb +4 -1
- data/lib/chef/mixin/template.rb +6 -5
- data/lib/chef/mixin/xml_escape.rb +3 -3
- data/lib/chef/mixins.rb +16 -0
- data/lib/chef/node.rb +53 -52
- data/lib/chef/openid_registration.rb +0 -1
- data/lib/chef/platform.rb +171 -137
- data/lib/chef/provider.rb +2 -2
- data/lib/chef/provider/cron.rb +22 -22
- data/lib/chef/provider/deploy/revision.rb +5 -1
- data/lib/chef/provider/erl_call.rb +2 -2
- data/lib/chef/provider/file.rb +44 -23
- data/lib/chef/provider/group/dscl.rb +6 -4
- data/lib/chef/provider/mdadm.rb +0 -4
- data/lib/chef/provider/mount/mount.rb +20 -8
- data/lib/chef/provider/package.rb +1 -1
- data/lib/chef/provider/package/freebsd.rb +22 -18
- data/lib/chef/provider/package/rubygems.rb +7 -10
- data/lib/chef/provider/remote_directory.rb +15 -0
- data/lib/chef/provider/remote_file.rb +73 -50
- data/lib/chef/provider/script.rb +10 -8
- data/lib/chef/provider/service/windows.rb +129 -0
- data/lib/chef/provider/subversion.rb +1 -1
- data/lib/chef/provider/template.rb +51 -50
- data/lib/chef/providers.rb +80 -0
- data/lib/chef/recipe.rb +2 -4
- data/lib/chef/resource.rb +21 -7
- data/lib/chef/resource/cron.rb +14 -5
- data/lib/chef/resource/deploy.rb +52 -45
- data/lib/chef/resource/execute.rb +1 -1
- data/lib/chef/resource/file.rb +16 -8
- data/lib/chef/resource/mount.rb +1 -1
- data/lib/chef/resource/remote_directory.rb +19 -10
- data/lib/chef/resource/scm.rb +23 -16
- data/lib/chef/resource/service.rb +10 -1
- data/lib/chef/resources.rb +60 -0
- data/lib/chef/rest.rb +234 -189
- data/lib/chef/rest/auth_credentials.rb +78 -0
- data/lib/chef/{application/server.rb → rest/cookie_jar.rb} +18 -6
- data/lib/chef/rest/rest_request.rb +151 -0
- data/lib/chef/role.rb +38 -46
- data/lib/chef/streaming_cookbook_uploader.rb +8 -2
- data/lib/chef/tasks/chef_repo.rake +14 -4
- data/lib/chef/util/file_edit.rb +0 -1
- data/lib/chef/webui_user.rb +0 -1
- metadata +46 -9
- data/distro/suse/etc/init.d/chef-client +0 -121
data/lib/chef/resource/file.rb
CHANGED
@@ -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,
|
data/lib/chef/resource/mount.rb
CHANGED
@@ -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
|
data/lib/chef/resource/scm.rb
CHANGED
@@ -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'
|
data/lib/chef/rest.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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 =
|
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
|
-
|
107
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
133
|
-
|
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)
|
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=
|
155
|
-
|
156
|
-
|
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
|
-
|
244
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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 #{
|
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 #{
|
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 #{
|
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 #{
|
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 #{
|
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
|