Merge branch '1.4.x'
[puppet-modules/puppetlabs-apt.git] / lib / puppet / provider / apt_key / apt_key.rb
1 require 'date'
2 require 'open-uri'
3 require 'tempfile'
4
5 Puppet::Type.type(:apt_key).provide(:apt_key) do
6
7   KEY_LINE = {
8     :date     => '[0-9]{4}-[0-9]{2}-[0-9]{2}',
9     :key_type => '(R|D)',
10     :key_size => '\d{4}',
11     :key_id   => '[0-9a-fA-F]+',
12     :expires  => 'expire(d|s)',
13   }
14
15   confine    :osfamily => :debian
16   defaultfor :osfamily => :debian
17   commands   :apt_key  => 'apt-key'
18
19   def self.instances
20     key_array = apt_key('list').split("\n").collect do |line|
21       line_hash = key_line_hash(line)
22       next unless line_hash
23       expired = false
24
25       if line_hash[:key_expiry]
26         expired = Date.today > Date.parse(line_hash[:key_expiry])
27       end
28
29       new(
30         :name    => line_hash[:key_id],
31         :id      => line_hash[:key_id],
32         :ensure  => :present,
33         :expired => expired,
34         :expiry  => line_hash[:key_expiry],
35         :size    => line_hash[:key_size],
36         :type    => line_hash[:key_type] == 'R' ? :rsa : :dsa,
37         :created => line_hash[:key_created]
38       )
39     end
40     key_array.compact!
41   end
42
43   def self.prefetch(resources)
44     apt_keys = instances
45     resources.keys.each do |name|
46       if provider = apt_keys.find{ |key| key.name == name }
47         resources[name].provider = provider
48       end
49     end
50   end
51
52   def self.key_line_hash(line)
53     line_array = line.match(key_line_regexp).to_a
54     return nil if line_array.length < 5
55
56     return_hash = {
57       :key_id      => line_array[3],
58       :key_size    => line_array[1],
59       :key_type    => line_array[2],
60       :key_created => line_array[4],
61       :key_expiry  => nil,
62     }
63
64     return_hash[:key_expiry] = line_array[7] if line_array.length == 8
65     return return_hash
66   end
67
68   def self.key_line_regexp
69     # This regexp is trying to match the following output
70     # pub   4096R/4BD6EC30 2010-07-10 [expires: 2016-07-08]
71     # pub   1024D/CD2EFD2A 2009-12-15
72     regexp = /\A
73       pub  # match only the public key, not signatures
74       \s+  # bunch of spaces after that
75       (#{KEY_LINE[:key_size]})  # size of the key, usually a multiple of 1024
76       #{KEY_LINE[:key_type]}  # type of the key, usually R or D
77       \/  # separator between key_type and key_id
78       (#{KEY_LINE[:key_id]})  # hex id of the key
79       \s+  # bunch of spaces after that
80       (#{KEY_LINE[:date]})  # date the key was added to the keyring
81       # following an optional block which indicates if the key has an expiration
82       # date and if it has expired yet
83       (
84         \s+  # again with thes paces
85         \[  # we open with a square bracket
86         #{KEY_LINE[:expires]}  # expires or expired
87         \:  # a colon
88         \s+  # more spaces
89         (#{KEY_LINE[:date]})  # date indicating key expiry
90         \]  # we close with a square bracket
91       )?  # end of the optional block
92       \Z/x
93       regexp
94   end
95
96   def source_to_file(value)
97     if URI::parse(value).scheme.nil?
98       fail("The file #{value} does not exist") unless File.exists?(value)
99       value
100     else
101       begin
102         key = open(value).read
103       rescue OpenURI::HTTPError => e
104         fail("#{e.message} for #{resource[:source]}")
105       rescue SocketError
106         fail("could not resolve #{resource[:source]}")
107       else
108         tempfile(key)
109       end
110     end
111   end
112
113   def tempfile(content)
114     file = Tempfile.new('apt_key')
115     file.write content
116     file.close
117     file.path
118   end
119
120   def exists?
121     @property_hash[:ensure] == :present
122   end
123
124   def create
125     command = []
126     if resource[:source].nil? and resource[:content].nil?
127       # Breaking up the command like this is needed because it blows up
128       # if --recv-keys isn't the last argument.
129       command.push('adv', '--keyserver', resource[:server])
130       unless resource[:keyserver_options].nil?
131         command.push('--keyserver-options', resource[:keyserver_options])
132       end
133       command.push('--recv-keys', resource[:id])
134     elsif resource[:content]
135       command.push('add', tempfile(resource[:content]))
136     elsif resource[:source]
137       command.push('add', source_to_file(resource[:source]))
138     # In case we really screwed up, better safe than sorry.
139     else
140       fail("an unexpected condition occurred while trying to add the key: #{resource[:id]}")
141     end
142     apt_key(command)
143     @property_hash[:ensure] = :present
144   end
145
146   def destroy
147     apt_key('del', resource[:id])
148     @property_hash.clear
149   end
150
151   def read_only(value)
152     fail('This is a read-only property.')
153   end
154
155   mk_resource_methods
156
157   # Needed until PUP-1470 is fixed and we can drop support for Puppet versions
158   # before that.
159   def expired
160     @property_hash[:expired]
161   end
162
163   # Alias the setters of read-only properties
164   # to the read_only function.
165   alias :created= :read_only
166   alias :expired= :read_only
167   alias :expiry=  :read_only
168   alias :size=    :read_only
169   alias :type=    :read_only
170 end