Explorar o código

apt_key: Support fetching keys over FTP.

Daniele Sluijters %!s(int64=10) %!d(string=hai) anos
pai
achega
c3b3f5bb42

+ 10 - 2
lib/puppet/provider/apt_key/apt_key.rb

@@ -1,7 +1,15 @@
 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 = {
@@ -99,8 +107,8 @@ Puppet::Type.type(:apt_key).provide(:apt_key) do
       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]}")

+ 2 - 2
lib/puppet/type/apt_key.rb

@@ -49,8 +49,8 @@ Puppet::Type.newtype(:apt_key) do
   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

+ 63 - 0
lib/puppet_x/apt_key/patch_openuri.rb

@@ -0,0 +1,63 @@
+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

+ 52 - 0
spec/acceptance/apt_key_provider_spec.rb

@@ -3,6 +3,9 @@ require 'spec_helper_acceptance'
 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
@@ -251,6 +254,55 @@ ugVIB2pi+8u84f+an4Hml4xlyijgYu05pqNvnLRyJDLd61hviLC8GYU=
       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

+ 7 - 0
spec/unit/puppet/type/apt_key_spec.rb

@@ -143,6 +143,13 @@ describe Puppet::Type::type(:apt_key) do
       )}.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',