require 'date'
require 'open-uri'
+require 'net/ftp'
require 'tempfile'
+if RUBY_VERSION == '1.8.7'
+ # Mothers cry, puppies die and Ruby 1.8.7's open-uri needs to be
+ # monkeypatched to support passing in :ftp_passive_mode.
+ require 'puppet_x/apt_key/patch_openuri'
+ OpenURI::Options.merge!({:ftp_active_mode => false,})
+end
+
Puppet::Type.type(:apt_key).provide(:apt_key) do
KEY_LINE = {
value
else
begin
- key = open(value).read
- rescue OpenURI::HTTPError => e
+ key = open(value, :ftp_active_mode => false).read
+ rescue OpenURI::HTTPError, Net::FTPPermError => e
fail("#{e.message} for #{resource[:source]}")
rescue SocketError
fail("could not resolve #{resource[:source]}")
end
newparam(:source) do
- desc 'Location of a GPG key file, /path/to/file, http:// or https://'
- newvalues(/\Ahttps?:\/\//, /\A\/\w+/)
+ desc 'Location of a GPG key file, /path/to/file, ftp://, http:// or https://'
+ newvalues(/\Ahttps?:\/\//, /\Aftp:\/\//, /\A\/\w+/)
end
autorequire(:file) do
--- /dev/null
+require 'uri'
+require 'stringio'
+require 'time'
+
+module URI
+ class FTP
+ def buffer_open(buf, proxy, options) # :nodoc:
+ if proxy
+ OpenURI.open_http(buf, self, proxy, options)
+ return
+ end
+ require 'net/ftp'
+
+ directories = self.path.split(%r{/}, -1)
+ directories.shift if directories[0] == '' # strip a field before leading slash
+ directories.each {|d|
+ d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") }
+ }
+ unless filename = directories.pop
+ raise ArgumentError, "no filename: #{self.inspect}"
+ end
+ directories.each {|d|
+ if /[\r\n]/ =~ d
+ raise ArgumentError, "invalid directory: #{d.inspect}"
+ end
+ }
+ if /[\r\n]/ =~ filename
+ raise ArgumentError, "invalid filename: #{filename.inspect}"
+ end
+ typecode = self.typecode
+ if typecode && /\A[aid]\z/ !~ typecode
+ raise ArgumentError, "invalid typecode: #{typecode.inspect}"
+ end
+
+ # The access sequence is defined by RFC 1738
+ ftp = Net::FTP.open(self.host)
+ ftp.passive = true if !options[:ftp_active_mode]
+ # todo: extract user/passwd from .netrc.
+ user = 'anonymous'
+ passwd = nil
+ user, passwd = self.userinfo.split(/:/) if self.userinfo
+ ftp.login(user, passwd)
+ directories.each {|cwd|
+ ftp.voidcmd("CWD #{cwd}")
+ }
+ if typecode
+ # xxx: typecode D is not handled.
+ ftp.voidcmd("TYPE #{typecode.upcase}")
+ end
+ if options[:content_length_proc]
+ options[:content_length_proc].call(ftp.size(filename))
+ end
+ ftp.retrbinary("RETR #{filename}", 4096) { |str|
+ buf << str
+ options[:progress_proc].call(buf.size) if options[:progress_proc]
+ }
+ ftp.close
+ buf.io.rewind
+ end
+
+ include OpenURI::OpenRead
+ end
+end
PUPPETLABS_GPG_KEY_ID = '4BD6EC30'
PUPPETLABS_APT_URL = 'apt.puppetlabs.com'
PUPPETLABS_GPG_KEY_FILE = 'pubkey.gpg'
+CENTOS_GPG_KEY_ID = 'C105B9DE'
+CENTOS_REPO_URL = 'ftp.cvut.cz/centos'
+CENTOS_GPG_KEY_FILE = 'RPM-GPG-KEY-CentOS-6'
describe 'apt_key' do
before(:each) do
end
end
+ context 'ftp://' do
+ before(:each) do
+ shell("apt-key del #{CENTOS_GPG_KEY_ID}",
+ :acceptable_exit_codes => [0,1,2])
+ end
+
+ it 'works' do
+ pp = <<-EOS
+ apt_key { 'CentOS 6':
+ id => '#{CENTOS_GPG_KEY_ID}',
+ ensure => 'present',
+ source => 'ftp://#{CENTOS_REPO_URL}/#{CENTOS_GPG_KEY_FILE}',
+ }
+ EOS
+
+ apply_manifest(pp, :catch_failures => true)
+ expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero
+ shell("apt-key list | grep #{CENTOS_GPG_KEY_ID}")
+ end
+
+ it 'fails with a 550' do
+ pp = <<-EOS
+ apt_key { 'CentOS 6':
+ id => '#{CENTOS_GPG_KEY_ID}',
+ ensure => 'present',
+ source => 'ftp://#{CENTOS_REPO_URL}/herpderp.gpg',
+ }
+ EOS
+
+ apply_manifest(pp, :expect_failures => true) do |r|
+ expect(r.stderr).to match(/550 Failed to open/)
+ end
+ end
+
+ it 'fails with a socket error' do
+ pp = <<-EOS
+ apt_key { 'puppetlabs':
+ id => '#{PUPPETLABS_GPG_KEY_ID}',
+ ensure => 'present',
+ source => 'ftp://apt.puppetlabss.com/herpderp.gpg',
+ }
+ EOS
+
+ apply_manifest(pp, :expect_failures => true) do |r|
+ expect(r.stderr).to match(/could not resolve/)
+ end
+ end
+ end
+
context 'https://' do
it 'works' do
pp = <<-EOS
)}.to_not raise_error
end
+ it 'allows the ftp URI scheme in source' do
+ expect { Puppet::Type.type(:apt_key).new(
+ :id => '4BD6EC30',
+ :source => 'ftp://pgp.mit.edu'
+ )}.to_not raise_error
+ end
+
it 'allows an absolute path in source' do
expect { Puppet::Type.type(:apt_key).new(
:id => '4BD6EC30',