summaryrefslogtreecommitdiff
path: root/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir.rb
blob: 431b9ecb223ab697ee87e1f740d507f715147516 (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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#
# Author:: John Keiser (<jkeiser@chef.io>)
# Copyright:: Copyright (c) Chef Software 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_relative "../base_fs_dir"
require_relative "acls_dir"
require_relative "clients_dir"
require_relative "cookbooks_dir"
require_relative "cookbook_artifacts_dir"
require_relative "containers_dir"
require_relative "data_bags_dir"
require_relative "environments_dir"
require_relative "groups_dir"
require_relative "nodes_dir"
require_relative "policy_groups_dir"
require_relative "roles_dir"
require_relative "users_dir"
require_relative "client_keys_dir"
require_relative "file_system_entry"
require_relative "policies_dir"
require_relative "versioned_cookbooks_dir"
require_relative "../multiplexed_dir"
require_relative "../../data_handler/client_data_handler"
require_relative "../../data_handler/client_key_data_handler"
require_relative "../../data_handler/environment_data_handler"
require_relative "../../data_handler/node_data_handler"
require_relative "../../data_handler/policy_data_handler"
require_relative "../../data_handler/policy_group_data_handler"
require_relative "../../data_handler/role_data_handler"
require_relative "../../data_handler/user_data_handler"
require_relative "../../data_handler/group_data_handler"
require_relative "../../data_handler/container_data_handler"
require_relative "../../../win32/security" if ChefUtils.windows?

class Chef
  module ChefFS
    module FileSystem
      module Repository

        #
        # Represents the root of a local Chef repository, with directories for
        # nodes, cookbooks, roles, etc. under it.
        #
        class ChefRepositoryFileSystemRootDir < BaseFSDir
          #
          # Create a new Chef Repository File System root.
          #
          # == Parameters
          # [child_paths]
          #   A hash of child paths, e.g.:
          #     "nodes" => [ '/var/nodes', '/home/jkeiser/nodes' ],
          #     "roles" => [ '/var/roles' ],
          #     ...
          # [root_paths]
          #   An array of paths representing the top level, where
          #   +org.json+, +members.json+, and +invites.json+ will be stored.
          # [chef_config] - a hash of options that looks suspiciously like the ones
          #   stored in Chef::Config, containing at least these keys:
          #   :versioned_cookbooks:: whether to include versions in cookbook names
          def initialize(child_paths, root_paths = [], chef_config = Chef::Config)
            super("", nil)
            @child_paths = child_paths
            @root_paths = root_paths
            @versioned_cookbooks = chef_config[:versioned_cookbooks]
          end

          attr_accessor :write_pretty_json

          attr_reader :root_paths
          attr_reader :child_paths
          attr_reader :versioned_cookbooks

          CHILDREN = %w{org.json invitations.json members.json}.freeze

          def children
            @children ||= begin
                            result = child_paths.keys.sort.map { |name| make_child_entry(name) }
                            result += CHILDREN.map { |name| make_child_entry(name) }
                            result.select { |c| c && c.exists? }.sort_by(&:name)
                          end
          end

          def can_have_child?(name, is_dir)
            if is_dir
              child_paths.key?(name)
            elsif root_dir
              CHILDREN.include?(name)
            else
              false
            end
          end

          def create_child(name, file_contents = nil)
            if file_contents
              child = root_dir.create_child(name, file_contents)
            else
              child_paths[name].each do |path|
                begin
                  ::FileUtils.mkdir_p(path)
                  ::FileUtils.chmod(0700, path)
                  if ChefUtils.windows?
                    all_mask = Chef::ReservedNames::Win32::API::Security::GENERIC_ALL
                    administrators = Chef::ReservedNames::Win32::Security::SID.Administrators
                    owner = Chef::ReservedNames::Win32::Security::SID.default_security_object_owner
                    dacl = Chef::ReservedNames::Win32::Security::ACL.create([
                      Chef::ReservedNames::Win32::Security::ACE.access_allowed(owner, all_mask),
                      Chef::ReservedNames::Win32::Security::ACE.access_allowed(administrators, all_mask),
                    ])
                    so = Chef::ReservedNames::Win32::Security::SecurableObject.new(path)
                    so.owner = owner
                    so.set_dacl(dacl, false)
                  end
                rescue Errno::EEXIST
                end
              end
              child = make_child_entry(name)
            end
            @children = nil
            child
          end

          def json_class
            nil
          end

          # Used to print out a human-readable file system description
          def fs_description
            repo_paths = root_paths || [ File.dirname(child_paths["cookbooks"][0]) ]
            result = "repository at #{repo_paths.join(", ")}"
            if versioned_cookbooks
              result << " (Multiple versions per cookbook)"
            else
              result << " (One version per cookbook)"
            end
            child_paths.each_pair do |name, paths|
              if paths.any? { |path| !repo_paths.include?(File.dirname(path)) }
                result << "  #{name} at #{paths.join(", ")}\n"
              end
            end
            result
          end

          private

          #
          # A FileSystemEntry representing the root path where invites.json,
          # members.json and org.json may be found.
          #
          def root_dir
            existing_paths = root_paths.select { |path| File.exists?(path) }
            if existing_paths.size > 0
              MultiplexedDir.new(existing_paths.map do |path|
                dir = FileSystemEntry.new(name, parent, path)
                dir.write_pretty_json = !!write_pretty_json
                dir
              end)
            end
          end

          #
          # Create a child entry of the appropriate type:
          # cookbooks, data_bags, acls, etc.  All will be multiplexed (i.e. if
          # you have multiple paths for cookbooks, the multiplexed dir will grab
          # cookbooks from all of them when you list or grab them).
          #
          def make_child_entry(name)
            if CHILDREN.include?(name)
              return nil unless root_dir

              return root_dir.child(name)
            end

            paths = (child_paths[name] || []).select { |path| File.exists?(path) }
            if paths.size == 0
              return NonexistentFSObject.new(name, self)
            end

            case name
            when "acls"
              dirs = paths.map { |path| AclsDir.new(name, self, path) }
            when "client_keys"
              dirs = paths.map { |path| ClientKeysDir.new(name, self, path) }
            when "clients"
              dirs = paths.map { |path| ClientsDir.new(name, self, path) }
            when "containers"
              dirs = paths.map { |path| ContainersDir.new(name, self, path) }
            when "cookbooks"
              if versioned_cookbooks
                dirs = paths.map { |path| VersionedCookbooksDir.new(name, self, path) }
              else
                dirs = paths.map { |path| CookbooksDir.new(name, self, path) }
              end
            when "cookbook_artifacts"
              dirs = paths.map { |path| CookbookArtifactsDir.new(name, self, path) }
            when "data_bags"
              dirs = paths.map { |path| DataBagsDir.new(name, self, path) }
            when "environments"
              dirs = paths.map { |path| EnvironmentsDir.new(name, self, path) }
            when "groups"
              dirs = paths.map { |path| GroupsDir.new(name, self, path) }
            when "nodes"
              dirs = paths.map { |path| NodesDir.new(name, self, path) }
            when "policy_groups"
              dirs = paths.map { |path| PolicyGroupsDir.new(name, self, path) }
            when "policies"
              dirs = paths.map { |path| PoliciesDir.new(name, self, path) }
            when "roles"
              dirs = paths.map { |path| RolesDir.new(name, self, path) }
            when "users"
              dirs = paths.map { |path| UsersDir.new(name, self, path) }
            else
              raise "Unknown top level path #{name}"
            end
            MultiplexedDir.new(dirs)
          end
        end
      end
    end
  end
end