Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FTP support #32

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ subsequent runs, so long as that file still exists, the resource will be
considered in sync.

The `remote_file` type supports tighter synchronization tolerances either
through the specification of a checksum or by checking a remote HTTP
server's Last-Modified header. For example, the following resource specifies a
checksum:
through the specification of a checksum, or by checking a remote HTTP server's
Last-Modified header or using the FTP `MDTM` command. For example, the following
resource specifies a checksum:

```puppet
remote_file { '/path/to/your/file':
Expand Down Expand Up @@ -125,6 +125,8 @@ If a username and/or password are required to authenticate to your proxy, you
can specify these either as part of the `proxy` URI, or separately using the
`proxy_username` and `proxy_password` parameters.

Note that `proxy` is not supported for files fetched using FTP.

## Reference

### Type: remote_file
Expand All @@ -137,7 +139,7 @@ can specify these either as part of the `proxy` URI, or separately using the
* `source`: The source location of the file, or where to get it from if it is
needed. This should be a URI.
* `checksum`: Checksum of this file. Hash function used is specified by the `checksum_type`
parameter. A new copy of the file will not be downloaded if the local file's
parameter. A new copy of the file will not be downloaded if the local file's
checksum matches this value.
* `checksum_type`: Hash algorithm to use for checksumming. Supports the same arguments
as [the checksum parameter of the File type](https://docs.puppetlabs.com/references/latest/type.html#file-attribute-checksum).
Expand Down Expand Up @@ -169,7 +171,7 @@ using Net::HTTP from Ruby's standard library.
## Limitations

Currently only http, https, and file URI sources are supported by the default
ruby provider.
ruby provider.

## License

Expand Down
53 changes: 53 additions & 0 deletions lib/puppet/provider/remote_file/ruby.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'fileutils'
require 'net/ftp'
require 'net/http'
require 'net/https'
require 'uri'
Expand Down Expand Up @@ -38,6 +39,8 @@ def remote_mtime
raise Puppet::Error, "#{src} does not provide last-modified header"
end
@remote_mtime = Time.parse(response.header['last-modified'])
when 'ftp'
@remote_mtime = ftp_mtime(src)
else
raise Puppet::Error, "Unable to ensure latest on #{src}"
end
Expand Down Expand Up @@ -65,11 +68,61 @@ def get(url)
case p.scheme
when %r{https?}
http_get p, @resource[:path]
when 'ftp'
ftp_get p, @resource[:path]
when 'file'
FileUtils.copy p.path, @resource[:path]
end
end

def ftp_mtime(uri)
Puppet.debug "checking mtime for '#{uri}'"
ftp = Net::FTP.new(uri.host)
ftp.login
dir, file = File.split(uri.path)
ftp.chdir(dir)

ftp.mtime(file)
end

def ftp_get(uri, download_path)
Puppet.debug "downloading '#{uri}' to '#{download_path}'"
tempfile = Tempfile.new('remote_file')
tempfile.binmode

ftp = Net::FTP.new(uri.host)
ftp.login
dir, file = File.split(uri.path)
ftp.chdir(dir)
mtime = ftp.mtime(file)
ftp.getbinaryfile(file, tempfile)

tempfile.flush

# Try to move the file from the temp location to the final destination.
# If the move operation fails due to permission denied, try a copy
# before giving up. On some platforms (Windows) file locking or weird
# permissions may cause the mv operation to fail but will still allow
# the copy operation to succeed.
begin
FileUtils.mv(tempfile.path, download_path)
rescue Errno::EACCES
FileUtils.cp(tempfile.path, download_path)
end

# If the fileserver supports the last-modified header, make sure the
# file saved has a matching timestamp. This may be used later to do a
# very rough ensure=latest kind of check.
if mtime
File.utime(mtime, mtime, download_path)
end
ensure
if tempfile
tempfile.close
tempfile.unlink
end
end

# Perform an HTTP HEAD request and return the response.
#
def http_head(uri)
Expand Down
14 changes: 13 additions & 1 deletion lib/puppet/type/remote_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def insync?(is)
newparam(:source) do
desc 'Location of the source file.'
validate do |value|
unless value =~ URI.regexp(%w[http https file])
unless value =~ URI.regexp(%w[http https file ftp])
raise ArgumentError, '%s is not a valid URL' % value
end
end
Expand Down Expand Up @@ -137,6 +137,10 @@ def insync?(is)

validate do |url|
URI.parse(url).is_a?(URI::HTTP)

if @resource[:source] =~ /^ftp/
raise ArgumentError, "proxy cannot be used with FTP sources"
end
end

munge do |url|
Expand All @@ -151,6 +155,14 @@ def insync?(is)
if @resource[:proxy] && @resource[:proxy].host != value
raise 'Conflict between proxy and proxy_host parameters.'
end

if @resource[:proxy] && @resource[:proxy].host != value
raise 'Conflict between proxy and proxy_host parameters.'
end

if @resource[:source] =~ /^ftp/
raise ArgumentError, "proxy cannot be used with FTP sources"
end
end

defaultto { (@resource[:proxy]) ? @resource[:proxy].host : nil }
Expand Down