1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
#
# Author:: John Keiser (<jkeiser@opscode.com>)
# Copyright:: Copyright (c) 2012 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'chef/chef_fs/file_system/rest_list_dir'
require 'chef/chef_fs/file_system/cookbook_dir'
require 'chef/chef_fs/file_system/operation_failed_error'
require 'chef/chef_fs/file_system/cookbook_frozen_error'
require 'chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir'
require 'chef/mixin/file_class'
require 'tmpdir'
class Chef
module ChefFS
module FileSystem
class CookbooksDir < RestListDir
include Chef::Mixin::FileClass
def initialize(parent)
super("cookbooks", parent)
end
def child(name)
result = @children.select { |child| child.name == name }.first if @children
result || super
end
def make_child_entry(name)
CookbookDir.new(name, self)
end
def children
@children ||= begin
if root.versioned_cookbooks
result = []
root.get_json("#{api_path}/?num_versions=all").each_pair do |cookbook_name, cookbooks|
cookbooks['versions'].each do |cookbook_version|
result << CookbookDir.new("#{cookbook_name}-#{cookbook_version['version']}", self, :exists => true)
end
end
else
result = root.get_json(api_path).keys.map { |cookbook_name| CookbookDir.new(cookbook_name, self, :exists => true) }
end
result.sort_by(&:name)
end
end
def create_child_from(other, options = {})
@children = nil
upload_cookbook_from(other, options)
end
def upload_cookbook_from(other, options = {})
root.versioned_cookbooks ? upload_versioned_cookbook(other, options) : upload_unversioned_cookbook(other, options)
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}"
rescue Net::HTTPServerException => e
case e.response.code
when "409"
raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e), "Cookbook #{other.name} is frozen"
else
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "HTTP error writing: #{e}"
end
rescue Chef::Exceptions::CookbookFrozen => e
raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e), "Cookbook #{other.name} is frozen"
end
# Knife currently does not understand versioned cookbooks
# Cookbook Version uploader also requires a lot of refactoring
# to make this work. So instead, we make a temporary cookbook
# symlinking back to real cookbook, and upload the proxy.
def upload_versioned_cookbook(other, options)
cookbook_name = Chef::ChefFS::FileSystem::ChefRepositoryFileSystemCookbookDir.canonical_cookbook_name(other.name)
Dir.mktmpdir do |temp_cookbooks_path|
proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}"
# Make a symlink
file_class.symlink other.file_path, proxy_cookbook_path
# Instantiate a proxy loader using the temporary symlink
proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.parent.chefignore)
proxy_loader.load_cookbooks
cookbook_to_upload = proxy_loader.cookbook_version
cookbook_to_upload.freeze_version if options[:freeze]
# Instantiate a new uploader based on the proxy loader
uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest)
with_actual_cookbooks_dir(temp_cookbooks_path) do
upload_cookbook!(uploader)
end
#
# When the temporary directory is being deleted on
# windows, the contents of the symlink under that
# directory is also deleted. So explicitly remove
# the symlink without removing the original contents if we
# are running on windows
#
if Chef::Platform.windows?
Dir.rmdir proxy_cookbook_path
end
end
end
def upload_unversioned_cookbook(other, options)
cookbook_to_upload = other.chef_object
cookbook_to_upload.freeze_version if options[:freeze]
uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest)
with_actual_cookbooks_dir(other.parent.file_path) do
upload_cookbook!(uploader)
end
end
# Work around the fact that CookbookUploader doesn't understand chef_repo_path (yet)
def with_actual_cookbooks_dir(actual_cookbook_path)
old_cookbook_path = Chef::Config.cookbook_path
Chef::Config.cookbook_path = actual_cookbook_path if !Chef::Config.cookbook_path
yield
ensure
Chef::Config.cookbook_path = old_cookbook_path
end
def upload_cookbook!(uploader, options = {})
if uploader.respond_to?(:upload_cookbook)
uploader.upload_cookbook
else
uploader.upload_cookbooks
end
end
def can_have_child?(name, is_dir)
return false if !is_dir
return false if root.versioned_cookbooks && name !~ Chef::ChefFS::FileSystem::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME
return true
end
end
end
end
end
|