summaryrefslogtreecommitdiff
path: root/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb
blob: 28d88ea330c106f2cf4fc1ca04cf41a3ede9b4a4 (plain)
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
#
# 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/chef_server/rest_list_dir'
require 'chef/chef_fs/file_system/chef_server/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/repository/chef_repository_file_system_cookbook_dir'
require 'chef/mixin/file_class'

require 'tmpdir'

class Chef
  module ChefFS
    module FileSystem
      module ChefServer
        class CookbooksDir < RestListDir

          include Chef::Mixin::FileClass

          def make_child_entry(name)
            result = @children.select { |child| child.name == name }.first if @children
            result || 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::Repository::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::ChefServer::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME
            return true
          end
        end
      end
    end
  end
end