summaryrefslogtreecommitdiffstats
path: root/lib/puppet
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet')
-rw-r--r--lib/puppet/file_serving.rb7
-rw-r--r--lib/puppet/file_serving/configuration.rb261
-rw-r--r--lib/puppet/file_serving/content.rb35
-rw-r--r--lib/puppet/file_serving/metadata.rb48
-rw-r--r--lib/puppet/file_serving/mount.rb180
-rw-r--r--lib/puppet/indirector/content/file.rb10
-rw-r--r--lib/puppet/indirector/metadata/ral.rb15
-rw-r--r--lib/puppet/util/checksums.rb37
8 files changed, 593 insertions, 0 deletions
diff --git a/lib/puppet/file_serving.rb b/lib/puppet/file_serving.rb
new file mode 100644
index 000000000..e7e2b898e
--- /dev/null
+++ b/lib/puppet/file_serving.rb
@@ -0,0 +1,7 @@
+#
+# Created by Luke Kanies on 2007-10-16.
+# Copyright (c) 2007. All rights reserved.
+
+# Just a stub class.
+class Puppet::FileServing # :nodoc:
+end
diff --git a/lib/puppet/file_serving/configuration.rb b/lib/puppet/file_serving/configuration.rb
new file mode 100644
index 000000000..234f66962
--- /dev/null
+++ b/lib/puppet/file_serving/configuration.rb
@@ -0,0 +1,261 @@
+#
+# Created by Luke Kanies on 2007-10-16.
+# Copyright (c) 2007. All rights reserved.
+
+require 'puppet'
+require 'puppet/file_serving'
+
+class Puppet::FileServing::Configuration
+
+ def self.create(options = {})
+ unless defined?(@configuration)
+ @configuration = new(options)
+ end
+ @configuration
+ end
+
+ def initialize(options = {})
+ if options.include?(:Mount)
+ @passedconfig = true
+ unless options[:Mount].is_a?(Hash)
+ raise Puppet::DevError, "Invalid mount options %s" %
+ options[:Mount].inspect
+ end
+
+ options[:Mount].each { |dir, name|
+ if FileTest.exists?(dir)
+ mount(dir, name)
+ end
+ }
+ mount(nil, MODULES)
+ else
+ @passedconfig = false
+ readconfig(false) # don't check the file the first time.
+ end
+ end
+
+ private :initialize
+
+ # Mount a new directory with a name.
+ def mount(path, name)
+ if @mounts.include?(name)
+ if @mounts[name] != path
+ raise FileServerError, "%s is already mounted at %s" %
+ [@mounts[name].path, name]
+ else
+ # it's already mounted; no problem
+ return
+ end
+ end
+
+ # Let the mounts do their own error-checking.
+ @mounts[name] = Mount.new(name, path)
+ @mounts[name].info "Mounted %s" % path
+
+ return @mounts[name]
+ end
+
+ def umount(name)
+ @mounts.delete(name) if @mounts.include? name
+ end
+
+ private
+
+ def authcheck(file, mount, client, clientip)
+ # If we're local, don't bother passing in information.
+ if local?
+ client = nil
+ clientip = nil
+ end
+ unless mount.allowed?(client, clientip)
+ mount.warning "%s cannot access %s" %
+ [client, file]
+ raise Puppet::AuthorizationError, "Cannot access %s" % mount
+ end
+ end
+
+ def convert(url, client, clientip)
+ readconfig
+
+ url = URI.unescape(url)
+
+ mount, stub = splitpath(url, client)
+
+ authcheck(url, mount, client, clientip)
+
+ path = nil
+ unless path = mount.subdir(stub, client)
+ mount.notice "Could not find subdirectory %s" %
+ "//%s/%s" % [mount, stub]
+ return ""
+ end
+
+ return mount, path
+ end
+
+ # Deal with ignore parameters.
+ def handleignore(children, path, ignore)
+ ignore.each { |ignore|
+ Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match|
+ children.delete(File.basename(match))
+ }
+ }
+ return children
+ end
+
+ # Return the mount for the Puppet modules; allows file copying from
+ # the modules.
+ def modules_mount(module_name, client)
+ # Find our environment, if we have one.
+ unless hostname = (client || Facter.value("hostname"))
+ raise ArgumentError, "Could not find hostname"
+ end
+ if node = Puppet::Node.find(hostname)
+ env = node.environment
+ else
+ env = nil
+ end
+
+ # And use the environment to look up the module.
+ mod = Puppet::Module::find(module_name, env)
+ if mod
+ return @mounts[MODULES].copy(mod.name, mod.files)
+ else
+ return nil
+ end
+ end
+
+ # Read the configuration file.
+ def readconfig(check = true)
+ return if @noreadconfig
+
+ if check and ! @config.changed?
+ return
+ end
+
+ newmounts = {}
+ begin
+ File.open(@config.file) { |f|
+ mount = nil
+ count = 1
+ f.each { |line|
+ case line
+ when /^\s*#/: next # skip comments
+ when /^\s*$/: next # skip blank lines
+ when /\[([-\w]+)\]/:
+ name = $1
+ if newmounts.include?(name)
+ raise FileServerError, "%s is already mounted at %s" %
+ [newmounts[name], name], count, @config.file
+ end
+ mount = Mount.new(name)
+ newmounts[name] = mount
+ when /^\s*(\w+)\s+(.+)$/:
+ var = $1
+ value = $2
+ case var
+ when "path":
+ if mount.name == MODULES
+ Puppet.warning "The '#{MODULES}' module can not have a path. Ignoring attempt to set it"
+ else
+ begin
+ mount.path = value
+ rescue FileServerError => detail
+ Puppet.err "Removing mount %s: %s" %
+ [mount.name, detail]
+ newmounts.delete(mount.name)
+ end
+ end
+ when "allow":
+ value.split(/\s*,\s*/).each { |val|
+ begin
+ mount.info "allowing %s access" % val
+ mount.allow(val)
+ rescue AuthStoreError => detail
+ raise FileServerError.new(detail.to_s,
+ count, @config.file)
+ end
+ }
+ when "deny":
+ value.split(/\s*,\s*/).each { |val|
+ begin
+ mount.info "denying %s access" % val
+ mount.deny(val)
+ rescue AuthStoreError => detail
+ raise FileServerError.new(detail.to_s,
+ count, @config.file)
+ end
+ }
+ else
+ raise FileServerError.new("Invalid argument '%s'" % var,
+ count, @config.file)
+ end
+ else
+ raise FileServerError.new("Invalid line '%s'" % line.chomp,
+ count, @config.file)
+ end
+ count += 1
+ }
+ }
+ rescue Errno::EACCES => detail
+ Puppet.err "FileServer error: Cannot read %s; cannot serve" % @config
+ #raise Puppet::Error, "Cannot read %s" % @config
+ rescue Errno::ENOENT => detail
+ Puppet.err "FileServer error: '%s' does not exist; cannot serve" %
+ @config
+ #raise Puppet::Error, "%s does not exit" % @config
+ #rescue FileServerError => detail
+ # Puppet.err "FileServer error: %s" % detail
+ end
+
+ unless newmounts[MODULES]
+ mount = Mount.new(MODULES)
+ mount.allow("*")
+ newmounts[MODULES] = mount
+ end
+
+ # Verify each of the mounts are valid.
+ # We let the check raise an error, so that it can raise an error
+ # pointing to the specific problem.
+ newmounts.each { |name, mount|
+ unless mount.valid?
+ raise FileServerError, "No path specified for mount %s" %
+ name
+ end
+ }
+ @mounts = newmounts
+ end
+
+ # Split the path into the separate mount point and path.
+ def splitpath(dir, client)
+ # the dir is based on one of the mounts
+ # so first retrieve the mount path
+ mount = nil
+ path = nil
+ if dir =~ %r{/([-\w]+)/?}
+ # Strip off the mount name.
+ mount_name, path = dir.sub(%r{^/}, '').split(File::Separator, 2)
+
+ unless mount = modules_mount(mount_name, client)
+ unless mount = @mounts[mount_name]
+ raise FileServerError, "Fileserver module '%s' not mounted" % mount_name
+ end
+ end
+ else
+ raise FileServerError, "Fileserver error: Invalid path '%s'" % dir
+ end
+
+ if path == ""
+ path = nil
+ elsif path
+ # Remove any double slashes that might have occurred
+ path = URI.unescape(path.gsub(/\/\//, "/"))
+ end
+
+ return mount, path
+ end
+
+ def to_s
+ "fileserver"
+ end
+end
diff --git a/lib/puppet/file_serving/content.rb b/lib/puppet/file_serving/content.rb
new file mode 100644
index 000000000..773ae89a5
--- /dev/null
+++ b/lib/puppet/file_serving/content.rb
@@ -0,0 +1,35 @@
+#
+# Created by Luke Kanies on 2007-10-16.
+# Copyright (c) 2007. All rights reserved.
+
+require 'puppet/indirector'
+require 'puppet/file_serving'
+
+# A class that handles retrieving file contents.
+# It only reads the file when its content is specifically
+# asked for.
+class Puppet::FileServing::Content
+ extend Puppet::Indirector
+ indirects :content, :terminus_class => :file
+
+ attr_reader :path
+
+ def content
+ ::File.read(@path)
+ end
+
+ def initialize(path)
+ raise ArgumentError.new("Files must be fully qualified") unless path =~ /^#{::File::SEPARATOR}/
+ raise ArgumentError.new("Files must exist") unless FileTest.exists?(path)
+
+ @path = path
+ end
+
+ # Just return the file contents as the yaml. This allows us to
+ # avoid escaping or any such thing. LAK:FIXME Not really sure how
+ # this will behave if the file contains yaml... I think the far
+ # side needs to understand that it's a plain string.
+ def to_yaml
+ content
+ end
+end
diff --git a/lib/puppet/file_serving/metadata.rb b/lib/puppet/file_serving/metadata.rb
new file mode 100644
index 000000000..69211b2d0
--- /dev/null
+++ b/lib/puppet/file_serving/metadata.rb
@@ -0,0 +1,48 @@
+#
+# Created by Luke Kanies on 2007-10-16.
+# Copyright (c) 2007. All rights reserved.
+
+require 'puppet'
+require 'puppet/indirector'
+require 'puppet/file_serving'
+require 'puppet/util/checksums'
+
+# A class that handles retrieving file metadata.
+class Puppet::FileServing::Metadata
+ include Puppet::Util::Checksums
+
+ extend Puppet::Indirector
+ indirects :metadata, :terminus_class => :ral
+
+ attr_reader :path, :owner, :group, :mode, :checksum_type, :checksum
+
+ def checksum_type=(type)
+ raise(ArgumentError, "Unsupported checksum type %s" % type) unless respond_to?("%s_file" % type)
+
+ @checksum_type = type
+ end
+
+ def initialize(path, checksum_type = "md5")
+ raise ArgumentError.new("Files must be fully qualified") unless path =~ /^#{::File::SEPARATOR}/
+ raise ArgumentError.new("Files must exist") unless FileTest.exists?(path)
+
+ @path = path
+
+ stat = File.stat(path)
+ @owner = stat.uid
+ @group = stat.gid
+
+ # Set the octal mode, but as a string.
+ @mode = "%o" % (stat.mode & 007777)
+
+ @checksum_type = checksum_type
+ @checksum = get_checksum
+ end
+
+ private
+
+ # Retrieve our checksum.
+ def get_checksum
+ ("{%s}" % @checksum_type) + send("%s_file" % @checksum_type, @path)
+ end
+end
diff --git a/lib/puppet/file_serving/mount.rb b/lib/puppet/file_serving/mount.rb
new file mode 100644
index 000000000..719c30b16
--- /dev/null
+++ b/lib/puppet/file_serving/mount.rb
@@ -0,0 +1,180 @@
+#
+# Created by Luke Kanies on 2007-10-16.
+# Copyright (c) 2007. All rights reserved.
+
+require 'puppet/network/authstore'
+require 'puppet/util/logging'
+require 'puppet/file_serving'
+require 'puppet/file_serving/metadata'
+require 'puppet/file_serving/content'
+
+# Broker access to the filesystem, converting local URIs into metadata
+# or content objects.
+class Puppet::FileServing::Mount < Puppet::Network::AuthStore
+ include Puppet::Util::Logging
+
+ attr_reader :name
+
+ @@syncs = {}
+
+ @@files = {}
+
+ # Return a new mount with the same properties as +self+, except
+ # with a different name and path.
+ def copy(name, path)
+ result = self.clone
+ result.path = path
+ result.instance_variable_set(:@name, name)
+ return result
+ end
+
+ # Return a content instance for a given file.
+ def content(short_file, client = nil)
+ file_instance(Puppet::FileServing::Content, short_file, client)
+ end
+
+ # Return a fully qualified path, given a short path and
+ # possibly a client name.
+ def file_path(short, client = nil)
+ p = path(client)
+ raise ArgumentError.new("Mounts without paths are not usable") unless p
+ File.join(p, short)
+ end
+
+ # Create out object. It must have a name.
+ def initialize(name, path = nil)
+ unless name =~ %r{^[-\w]+$}
+ raise ArgumentError, "Invalid mount name format '%s'" % name
+ end
+ @name = name
+
+ if path
+ self.path = path
+ else
+ @path = nil
+ end
+
+ super()
+ end
+
+ # Return a metadata instance with the appropriate information provided.
+ def metadata(short_file, client = nil)
+ file_instance(Puppet::FileServing::Metadata, short_file, client)
+ end
+
+ # Return the path as appropriate, expanding as necessary.
+ def path(client = nil)
+ if expandable?
+ return expand(@path, client)
+ else
+ return @path
+ end
+ end
+
+ # Set the path.
+ def path=(path)
+ # FIXME: For now, just don't validate paths with replacement
+ # patterns in them.
+ if path =~ /%./
+ # Mark that we're expandable.
+ @expandable = true
+ else
+ unless FileTest.exists?(path)
+ raise ArgumentError, "%s does not exist" % path
+ end
+ unless FileTest.directory?(path)
+ raise ArgumentError, "%s is not a directory" % path
+ end
+ unless FileTest.readable?(path)
+ raise ArgumentError, "%s is not readable" % path
+ end
+ @expandable = false
+ end
+ @path = path
+ end
+
+ def sync(path)
+ @@syncs[path] ||= Sync.new
+ @@syncs[path]
+ end
+
+ def to_s
+ "mount[%s]" % @name
+ end
+
+ # Verify our configuration is valid. This should really check to
+ # make sure at least someone will be allowed, but, eh.
+ def valid?
+ if name == MODULES
+ return @path.nil?
+ else
+ return ! @path.nil?
+ end
+ end
+
+ private
+
+ # Create a map for a specific client.
+ def clientmap(client)
+ {
+ "h" => client.sub(/\..*$/, ""),
+ "H" => client,
+ "d" => client.sub(/[^.]+\./, "") # domain name
+ }
+ end
+
+ # Replace % patterns as appropriate.
+ def expand(path, client = nil)
+ # This map should probably be moved into a method.
+ map = nil
+
+ if client
+ map = clientmap(client)
+ else
+ Puppet.notice "No client; expanding '%s' with local host" %
+ path
+ # Else, use the local information
+ map = localmap()
+ end
+ path.gsub(/%(.)/) do |v|
+ key = $1
+ if key == "%"
+ "%"
+ else
+ map[key] || v
+ end
+ end
+ end
+
+ # Do we have any patterns in our path, yo?
+ def expandable?
+ if defined? @expandable
+ @expandable
+ else
+ false
+ end
+ end
+
+ # Return an instance of the appropriate class.
+ def file_instance(klass, short_file, client = nil)
+ file = file_path(short_file, client)
+
+ return nil unless FileTest.exists?(file)
+
+ return klass.new(file)
+ end
+
+ # Cache this manufactured map, since if it's used it's likely
+ # to get used a lot.
+ def localmap
+ unless defined? @@localmap
+ @@localmap = {
+ "h" => Facter.value("hostname"),
+ "H" => [Facter.value("hostname"),
+ Facter.value("domain")].join("."),
+ "d" => Facter.value("domain")
+ }
+ end
+ @@localmap
+ end
+end
diff --git a/lib/puppet/indirector/content/file.rb b/lib/puppet/indirector/content/file.rb
new file mode 100644
index 000000000..1e0af4f12
--- /dev/null
+++ b/lib/puppet/indirector/content/file.rb
@@ -0,0 +1,10 @@
+#
+# Created by Luke Kanies on 2007-10-16.
+# Copyright (c) 2007. All rights reserved.
+
+require 'puppet/file_serving/content'
+require 'puppet/indirector/file'
+
+class Puppet::FileServing::Content::File < Puppet::Indirector::File
+ desc "Retrieve file contents from disk."
+end
diff --git a/lib/puppet/indirector/metadata/ral.rb b/lib/puppet/indirector/metadata/ral.rb
new file mode 100644
index 000000000..a06253315
--- /dev/null
+++ b/lib/puppet/indirector/metadata/ral.rb
@@ -0,0 +1,15 @@
+#
+# Created by Luke Kanies on 2007-10-16.
+# Copyright (c) 2007. All rights reserved.
+
+require 'puppet/file_serving/metadata'
+require 'puppet/indirector/code'
+
+class Puppet::FileServing::Metadata::Ral < Puppet::Indirector::Code
+ desc "Retrieve file metadata using Puppet's Resource Abstraction Layer.
+ Returns everything about the file except its content."
+
+ def find(file)
+ Puppet::Node::Facts.new(key, Facter.to_hash)
+ end
+end
diff --git a/lib/puppet/util/checksums.rb b/lib/puppet/util/checksums.rb
new file mode 100644
index 000000000..6f6ea59b5
--- /dev/null
+++ b/lib/puppet/util/checksums.rb
@@ -0,0 +1,37 @@
+module Puppet::Util::Checksums
+ def md5(content)
+ require 'digest/md5'
+ Digest::MD5.hexdigest(content)
+ end
+
+ def md5_file(filename)
+ require 'digest/md5'
+
+ incr_digest = Digest::MD5.new()
+ File.open(filename, 'r') do |file|
+ file.each_line do |line|
+ incr_digest << line
+ end
+ end
+
+ return incr_digest.hexdigest
+ end
+
+ def sha1(content)
+ require 'digest/sha1'
+ Digest::SHA1.hexdigest(content)
+ end
+
+ def sha1_file(filename)
+ require 'digest/sha1'
+
+ incr_digest = Digest::SHA1.new()
+ File.open(filename, 'r') do |file|
+ file.each_line do |line|
+ incr_digest << line
+ end
+ end
+
+ return incr_digest.hexdigest
+ end
+end