manifests: use modern os facts
[puppet-modules/puppetlabs-apt.git] / spec / classes / apt_spec.rb
1 # frozen_string_literal: true
2
3 require 'spec_helper'
4
5 sources_list = {  ensure: 'file',
6                   path: '/etc/apt/sources.list',
7                   owner: 'root',
8                   group: 'root',
9                   notify: 'Class[Apt::Update]' }
10
11 sources_list_d = { ensure: 'directory',
12                    path: '/etc/apt/sources.list.d',
13                    owner: 'root',
14                    group: 'root',
15                    purge: false,
16                    recurse: false,
17                    notify: 'Class[Apt::Update]' }
18
19 preferences = { ensure: 'file',
20                 path: '/etc/apt/preferences',
21                 owner: 'root',
22                 group: 'root',
23                 notify: 'Class[Apt::Update]' }
24
25 preferences_d = { ensure: 'directory',
26                   path: '/etc/apt/preferences.d',
27                   owner: 'root',
28                   group: 'root',
29                   purge: false,
30                   recurse: false,
31                   notify: 'Class[Apt::Update]' }
32
33 apt_conf_d = {    ensure: 'directory',
34                   path: '/etc/apt/apt.conf.d',
35                   owner: 'root',
36                   group: 'root',
37                   purge: false,
38                   recurse: false,
39                   notify: 'Class[Apt::Update]' }
40
41 describe 'apt' do
42   let(:facts) do
43     {
44       os: {
45         family: 'Debian',
46         name: 'Debian',
47         release: {
48           major: '8',
49           full: '8.0',
50         },
51         distro: {
52           codename: 'jessie',
53           id: 'Debian',
54         },
55       },
56     }
57   end
58
59   context 'with defaults' do
60     it {
61       is_expected.to contain_file('sources.list').that_notifies('Class[Apt::Update]').only_with(sources_list)
62     }
63
64     it {
65       is_expected.to contain_file('sources.list.d').that_notifies('Class[Apt::Update]').only_with(sources_list_d)
66     }
67
68     it {
69       is_expected.to contain_file('preferences').that_notifies('Class[Apt::Update]').only_with(preferences)
70     }
71
72     it {
73       is_expected.to contain_file('preferences.d').that_notifies('Class[Apt::Update]').only_with(preferences_d)
74     }
75
76     it {
77       is_expected.to contain_file('apt.conf.d').that_notifies('Class[Apt::Update]').only_with(apt_conf_d)
78     }
79
80     it { is_expected.to contain_file('/etc/apt/auth.conf').with_ensure('absent') }
81
82     it 'lays down /etc/apt/apt.conf.d/15update-stamp' do
83       is_expected.to contain_file('/etc/apt/apt.conf.d/15update-stamp').with(group: 'root',
84                                                                              owner: 'root').with_content(
85                                                                                %r{APT::Update::Post-Invoke-Success {"touch /var/lib/apt/periodic/update-success-stamp 2>/dev/null || true";};},
86                                                                              )
87     end
88
89     it {
90       is_expected.to contain_exec('apt_update').with(refreshonly: 'true')
91     }
92
93     it { is_expected.not_to contain_apt__setting('conf-proxy') }
94   end
95
96   describe 'proxy=' do
97     context 'when host=localhost' do
98       let(:params) { { proxy: { 'host' => 'localhost' } } }
99
100       it {
101         is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
102           %r{Acquire::http::proxy "http://localhost:8080/";},
103         ).without_content(
104           %r{Acquire::https::proxy},
105         )
106       }
107     end
108
109     context 'when host=localhost and port=8180' do
110       let(:params) { { proxy: { 'host' => 'localhost', 'port' => 8180 } } }
111
112       it {
113         is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
114           %r{Acquire::http::proxy "http://localhost:8180/";},
115         ).without_content(
116           %r{Acquire::https::proxy},
117         )
118       }
119     end
120
121     context 'when host=localhost and https=true' do
122       let(:params) { { proxy: { 'host' => 'localhost', 'https' => true } } }
123
124       it {
125         is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
126           %r{Acquire::http::proxy "http://localhost:8080/";},
127         ).with_content(
128           %r{Acquire::https::proxy "https://localhost:8080/";},
129         )
130       }
131     end
132
133     context 'when host=localhost and direct=true' do
134       let(:params) { { proxy: { 'host' => 'localhost', 'direct' => true } } }
135
136       it {
137         is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
138           %r{Acquire::http::proxy "http://localhost:8080/";},
139         ).with_content(
140           %r{Acquire::https::proxy "DIRECT";},
141         )
142       }
143     end
144
145     context 'when host=localhost and https=true and direct=true' do
146       let(:params) { { proxy: { 'host' => 'localhost', 'https' => true, 'direct' => true } } }
147
148       it {
149         is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
150           %r{Acquire::http::proxy "http://localhost:8080/";},
151         ).with_content(
152           %r{Acquire::https::proxy "https://localhost:8080/";},
153         )
154       }
155       it {
156         is_expected.to contain_apt__setting('conf-proxy').with(priority: '01').with_content(
157           %r{Acquire::http::proxy "http://localhost:8080/";},
158         ).without_content(
159           %r{Acquire::https::proxy "DIRECT";},
160         )
161       }
162     end
163
164     context 'when ensure=absent' do
165       let(:params) { { proxy: { 'ensure' => 'absent' } } }
166
167       it {
168         is_expected.to contain_apt__setting('conf-proxy').with(ensure: 'absent',
169                                                                priority: '01')
170       }
171     end
172   end
173   context 'with lots of non-defaults' do
174     let :params do
175       {
176         update: { 'frequency' => 'always', 'timeout' => 1, 'tries' => 3 },
177         purge: { 'sources.list' => false, 'sources.list.d' => false,
178                  'preferences' => false, 'preferences.d' => false,
179                  'apt.conf.d' => false },
180       }
181     end
182
183     it {
184       is_expected.to contain_file('sources.list').with(content: nil)
185     }
186
187     it {
188       is_expected.to contain_file('sources.list.d').with(purge: false,
189                                                          recurse: false)
190     }
191
192     it {
193       is_expected.to contain_file('preferences').with(ensure: 'file')
194     }
195
196     it {
197       is_expected.to contain_file('preferences.d').with(purge: false,
198                                                         recurse: false)
199     }
200
201     it {
202       is_expected.to contain_file('apt.conf.d').with(purge: false,
203                                                      recurse: false)
204     }
205
206     it {
207       is_expected.to contain_exec('apt_update').with(refreshonly: false,
208                                                      timeout: 1,
209                                                      tries: 3)
210     }
211   end
212
213   context 'with lots of non-defaults' do
214     let :params do
215       {
216         update: { 'frequency' => 'always', 'timeout' => 1, 'tries' => 3 },
217         purge: { 'sources.list' => true, 'sources.list.d' => true,
218                  'preferences' => true, 'preferences.d' => true,
219                  'apt.conf.d' => true },
220       }
221     end
222
223     it {
224       is_expected.to contain_file('sources.list').with(content: "# Repos managed by puppet.\n")
225     }
226
227     it {
228       is_expected.to contain_file('sources.list.d').with(purge: true,
229                                                          recurse: true)
230     }
231
232     it {
233       is_expected.to contain_file('preferences').with(ensure: 'absent')
234     }
235
236     it {
237       is_expected.to contain_file('preferences.d').with(purge: true,
238                                                         recurse: true)
239     }
240
241     it {
242       is_expected.to contain_file('apt.conf.d').with(purge: true,
243                                                      recurse: true)
244     }
245
246     it {
247       is_expected.to contain_exec('apt_update').with(refreshonly: false,
248                                                      timeout: 1,
249                                                      tries: 3)
250     }
251   end
252
253   context 'with defaults for sources_list_force' do
254     let :params do
255       {
256         update: { 'frequency' => 'always', 'timeout' => 1, 'tries' => 3 },
257         purge: { 'sources.list' => true },
258         sources_list_force: false,
259       }
260     end
261
262     it {
263       is_expected.to contain_file('sources.list').with(content: "# Repos managed by puppet.\n")
264     }
265   end
266
267   context 'with non defaults for sources_list_force' do
268     let :params do
269       {
270         update: { 'frequency' => 'always', 'timeout' => 1, 'tries' => 3 },
271         purge: { 'sources.list' => true },
272         sources_list_force: true,
273       }
274     end
275
276     it {
277       is_expected.to contain_file('sources.list').with(ensure: 'absent')
278     }
279   end
280
281   context 'with entries for /etc/apt/auth.conf' do
282     facts_hash = {
283       'Ubuntu 14.04' => {
284         os: {
285           family: 'Debian',
286           name: 'Ubuntu',
287           release: {
288             major: '14',
289             full: '14.04',
290           },
291           distro: {
292             codename: 'trusty',
293             id: 'Ubuntu',
294           },
295         },
296       },
297       'Ubuntu 16.04' => {
298         os: {
299           family: 'Debian',
300           name: 'Ubuntu',
301           release: {
302             major: '16',
303             full: '16.04',
304           },
305           distro: {
306             codename: 'xenial',
307             id: 'Ubuntu',
308           },
309         },
310       },
311       'Ubuntu 18.04' => {
312         os: {
313           family: 'Debian',
314           name: 'Ubuntu',
315           release: {
316             major: '18',
317             full: '18.04',
318           },
319           distro: {
320             codename: 'bionic',
321             id: 'Ubuntu',
322           },
323         },
324       },
325       'Debian 7.0' => {
326         os: {
327           family: 'Debian',
328           name: 'Debian',
329           release: {
330             major: '7',
331             full: '7.0',
332           },
333           distro: {
334             codename: 'wheezy',
335             id: 'Debian',
336           },
337         },
338       },
339       'Debian 8.0' => {
340         os: {
341           family: 'Debian',
342           name: 'Debian',
343           release: {
344             major: '8',
345             full: '8.0',
346           },
347           distro: {
348             codename: 'jessie',
349             id: 'Debian',
350           },
351         },
352       },
353       'Debian 9.0' => {
354         os: {
355           family: 'Debian',
356           name: 'Debian',
357           release: {
358             major: '9',
359             full: '9.0',
360           },
361           distro: {
362             codename: 'stretch',
363             id: 'Debian',
364           },
365         },
366       },
367       'Debian 10.0' => {
368         os: {
369           family: 'Debian',
370           name: 'Debian',
371           release: {
372             major: '10',
373             full: '10.0',
374           },
375           distro: {
376             codename: 'buster',
377             id: 'Debian',
378           },
379         },
380       },
381     }
382
383     facts_hash.each do |os, facts|
384       context "on #{os}" do
385         let(:facts) do
386           facts
387         end
388         let(:params) do
389           {
390             auth_conf_entries: [
391               {
392                 machine: 'deb.example.net',
393                 login: 'foologin',
394                 password: 'secret',
395               },
396               {
397                 machine: 'apt.example.com',
398                 login: 'aptlogin',
399                 password: 'supersecret',
400               },
401             ],
402           }
403         end
404
405         context 'with manage_auth_conf => true' do
406           let(:params) do
407             super().merge(manage_auth_conf: true)
408           end
409
410           # Going forward starting with Ubuntu 16.04 and Debian 9.0
411           # /etc/apt/auth.conf is owned by _apt. In previous versions it is
412           # root.
413           auth_conf_owner = case os
414                             when 'Ubuntu 14.04', 'Debian 7.0', 'Debian 8.0'
415                               'root'
416                             else
417                               '_apt'
418                             end
419
420           auth_conf_content = "// This file is managed by Puppet. DO NOT EDIT.
421 machine deb.example.net login foologin password secret
422 machine apt.example.com login aptlogin password supersecret
423 "
424
425           it {
426             is_expected.to contain_file('/etc/apt/auth.conf').with(ensure: 'present',
427                                                                    owner: auth_conf_owner,
428                                                                    group: 'root',
429                                                                    mode: '0600',
430                                                                    notify: 'Class[Apt::Update]',
431                                                                    content: sensitive(auth_conf_content))
432           }
433         end
434
435         context 'with manage_auth_conf => false' do
436           let(:params) do
437             super().merge(manage_auth_conf: false)
438           end
439
440           it {
441             is_expected.not_to contain_file('/etc/apt/auth.conf')
442           }
443         end
444       end
445
446       context 'with improperly specified entries for /etc/apt/auth.conf' do
447         let(:params) do
448           {
449             auth_conf_entries: [
450               {
451                 machinn: 'deb.example.net',
452                 username: 'foologin',
453                 password: 'secret',
454               },
455               {
456                 machine: 'apt.example.com',
457                 login: 'aptlogin',
458                 password: 'supersecret',
459               },
460             ],
461           }
462         end
463
464         it { is_expected.to raise_error(Puppet::Error) }
465       end
466     end
467   end
468
469   context 'with sources defined on valid os.family' do
470     let :facts do
471       {
472         os: {
473           family: 'Debian',
474           name: 'Ubuntu',
475           release: {
476             major: '16',
477             full: '16.04',
478           },
479           distro: {
480             codename: 'xenial',
481             id: 'Ubuntu',
482           },
483         },
484       }
485     end
486     let(:params) do
487       { sources: {
488         'debian_unstable' => {
489           'location'          => 'http://debian.mirror.iweb.ca/debian/',
490           'release'           => 'unstable',
491           'repos'             => 'main contrib non-free',
492           'key'               => { 'id' => '150C8614919D8446E01E83AF9AA38DCD55BE302B', 'server' => 'subkeys.pgp.net' },
493           'pin'               => '-10',
494           'include'           => { 'src' => true },
495         },
496         'puppetlabs' => {
497           'location' => 'http://apt.puppetlabs.com',
498           'repos'      => 'main',
499           'key'        => { 'id' => '6F6B15509CF8E59E6E469F327F438280EF8D349F', 'server' => 'pgp.mit.edu' },
500         },
501       } }
502     end
503
504     it {
505       is_expected.to contain_apt__setting('list-debian_unstable').with(ensure: 'present')
506     }
507
508     it { is_expected.to contain_file('/etc/apt/sources.list.d/debian_unstable.list').with_content(%r{^deb http://debian.mirror.iweb.ca/debian/ unstable main contrib non-free$}) }
509     it { is_expected.to contain_file('/etc/apt/sources.list.d/debian_unstable.list').with_content(%r{^deb-src http://debian.mirror.iweb.ca/debian/ unstable main contrib non-free$}) }
510
511     it {
512       is_expected.to contain_apt__setting('list-puppetlabs').with(ensure: 'present')
513     }
514
515     it { is_expected.to contain_file('/etc/apt/sources.list.d/puppetlabs.list').with_content(%r{^deb http://apt.puppetlabs.com xenial main$}) }
516   end
517
518   context 'with confs defined on valid os.family' do
519     let :facts do
520       {
521         os: {
522           family: 'Debian',
523           name: 'Ubuntu',
524           release: {
525             major: '16',
526             full: '16.04',
527           },
528           distro: {
529             codename: 'xenial',
530             id: 'Ubuntu',
531           },
532         },
533       }
534     end
535     let(:params) do
536       { confs: {
537         'foo' => {
538           'content' => 'foo',
539         },
540         'bar' => {
541           'content' => 'bar',
542         },
543       } }
544     end
545
546     it {
547       is_expected.to contain_apt__conf('foo').with(content: 'foo')
548     }
549
550     it {
551       is_expected.to contain_apt__conf('bar').with(content: 'bar')
552     }
553   end
554
555   context 'with keys defined on valid os.family' do
556     let :facts do
557       {
558         os: {
559           family: 'Debian',
560           name: 'Ubuntu',
561           release: {
562             major: '16',
563             full: '16.04',
564           },
565           distro: {
566             codename: 'xenial',
567             id: 'Ubuntu',
568           },
569         },
570       }
571     end
572     let(:params) do
573       { keys: {
574         '55BE302B' => {
575           'server' => 'subkeys.pgp.net',
576         },
577         'EF8D349F' => {
578           'server' => 'pgp.mit.edu',
579         },
580       } }
581     end
582
583     it {
584       is_expected.to contain_apt__key('55BE302B').with(server: 'subkeys.pgp.net')
585     }
586
587     it {
588       is_expected.to contain_apt__key('EF8D349F').with(server: 'pgp.mit.edu')
589     }
590   end
591
592   context 'with ppas defined on valid os.family' do
593     let :facts do
594       {
595         os: {
596           family: 'Debian',
597           name: 'Ubuntu',
598           release: {
599             major: '16',
600             full: '16.04',
601           },
602           distro: {
603             codename: 'xenial',
604             id: 'Ubuntu',
605           },
606         },
607       }
608     end
609     let(:params) do
610       { ppas: {
611         'ppa:drizzle-developers/ppa' => {},
612         'ppa:nginx/stable' => {},
613       } }
614     end
615
616     it { is_expected.to contain_apt__ppa('ppa:drizzle-developers/ppa') }
617     it { is_expected.to contain_apt__ppa('ppa:nginx/stable') }
618   end
619
620   context 'with settings defined on valid os.family' do
621     let :facts do
622       {
623         os: {
624           family: 'Debian',
625           name: 'Ubuntu',
626           release: {
627             major: '16',
628             full: '16.04',
629           },
630           distro: {
631             codename: 'xenial',
632             id: 'Ubuntu',
633           },
634         },
635       }
636     end
637     let(:params) do
638       { settings: {
639         'conf-banana' => { 'content' => 'banana' },
640         'pref-banana' => { 'content' => 'banana' },
641       } }
642     end
643
644     it { is_expected.to contain_apt__setting('conf-banana') }
645     it { is_expected.to contain_apt__setting('pref-banana') }
646   end
647
648   context 'with pins defined on valid os.family' do
649     let :facts do
650       {
651         os: {
652           family: 'Debian',
653           name: 'Ubuntu',
654           release: {
655             major: '16',
656             full: '16.04',
657           },
658           distro: {
659             codename: 'xenial',
660             id: 'Ubuntu',
661           },
662         },
663       }
664     end
665     let(:params) do
666       { pins: {
667         'stable' => { 'priority' => 600, 'order' => 50 },
668         'testing' =>  { 'priority' => 700, 'order' => 100 },
669       } }
670     end
671
672     it { is_expected.to contain_apt__pin('stable') }
673     it { is_expected.to contain_apt__pin('testing') }
674   end
675
676   describe 'failing tests' do
677     context "with purge['sources.list']=>'banana'" do
678       let(:params) { { purge: { 'sources.list' => 'banana' } } }
679
680       it do
681         is_expected.to raise_error(Puppet::Error)
682       end
683     end
684
685     context "with purge['sources.list.d']=>'banana'" do
686       let(:params) { { purge: { 'sources.list.d' => 'banana' } } }
687
688       it do
689         is_expected.to raise_error(Puppet::Error)
690       end
691     end
692
693     context "with purge['preferences']=>'banana'" do
694       let(:params) { { purge: { 'preferences' => 'banana' } } }
695
696       it do
697         is_expected.to raise_error(Puppet::Error)
698       end
699     end
700
701     context "with purge['preferences.d']=>'banana'" do
702       let(:params) { { purge: { 'preferences.d' => 'banana' } } }
703
704       it do
705         is_expected.to raise_error(Puppet::Error)
706       end
707     end
708
709     context "with purge['apt.conf.d']=>'banana'" do
710       let(:params) { { purge: { 'apt.conf.d' => 'banana' } } }
711
712       it do
713         is_expected.to raise_error(Puppet::Error)
714       end
715     end
716   end
717 end