Skip to content

Commit ce75449

Browse files
author
Kylo Ginsberg
committed
Merge pull request puppetlabs#3353 from MikaelSmith/bug/master/PUP-1073-pkg-composite-namevar
(PUP-1073) Make package use a composite namevar
2 parents 3ce0524 + 6a97df1 commit ce75449

File tree

9 files changed

+336
-5
lines changed

9 files changed

+336
-5
lines changed

acceptance/config/git/options.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
],
77
:pre_suite => [
88
'setup/git/pre-suite/000_EnvSetup.rb',
9+
'setup/common/pre-suite/001_PkgBuildSetup.rb',
910
'setup/git/pre-suite/010_TestSetup.rb',
1011
'setup/git/pre-suite/020_PuppetUserAndGroup.rb',
1112
'setup/common/pre-suite/025_StopFirewall.rb',

acceptance/config/packages/options.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
:type => 'foss-packages',
33
:pre_suite => [
4+
'setup/common/pre-suite/001_PkgBuildSetup.rb',
45
'setup/packages/pre-suite/010_Install.rb',
56
'setup/packages/pre-suite/015_PackageHostsPresets.rb',
67
'setup/common/pre-suite/025_StopFirewall.rb',
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
module Puppet
2+
module Acceptance
3+
module RpmUtils
4+
# Utilities for creating a basic rpm package and using it in tests
5+
@@defaults = {:repo => '/tmp/rpmrepo', :pkg => 'mypkg', :publisher => 'tstpub.lan', :version => '1.0'}
6+
7+
def clean_rpm(agent, o={})
8+
o = @@defaults.merge(o)
9+
on agent, "rm -rf #{o[:repo]}"
10+
on agent, "yum remove -y #{o[:pkg]}"
11+
on agent, "rm -f /etc/yum.repos.d/#{o[:publisher]}.repo"
12+
end
13+
14+
def setup_rpm(agent, o={})
15+
o = @@defaults.merge(o)
16+
on agent, "mkdir -p #{o[:repo]}/{RPMS,SRPMS,BUILD,SOURCES,SPECS}"
17+
on agent, "echo '%_topdir #{o[:repo]}' > ~/.rpmmacros"
18+
on agent, "createrepo #{o[:repo]}"
19+
on agent, "cat <<EOF > /etc/yum.repos.d/#{o[:publisher]}.repo
20+
[#{o[:publisher]}]
21+
name=#{o[:publisher]}
22+
baseurl=file://#{o[:repo]}/
23+
enabled=1
24+
gpgcheck=0
25+
EOF
26+
"
27+
end
28+
29+
def send_rpm(agent, o={})
30+
o = @@defaults.merge(o)
31+
on agent, "mkdir -p #{o[:repo]}/#{o[:pkg]}-#{o[:version]}/usr/bin"
32+
on agent, "cat <<EOF > #{o[:repo]}/#{o[:pkg]}
33+
#!/bin/bash
34+
echo Hello World
35+
EOF
36+
"
37+
pkg_name = "#{o[:pkg]}-#{o[:version]}"
38+
on agent, "install -m 755 #{o[:repo]}/#{o[:pkg]} #{o[:repo]}/#{pkg_name}/usr/bin"
39+
on agent, "tar -zcvf #{o[:repo]}/SOURCES/#{pkg_name}.tar.gz -C #{o[:repo]} #{pkg_name}"
40+
on agent, "cat <<EOF > #{o[:repo]}/SPECS/#{o[:pkg]}.spec
41+
# Don't try fancy stuff like debuginfo, which is useless on binary-only packages. Don't strip binary too
42+
# Be sure buildpolicy set to do nothing
43+
%define __spec_install_post %{nil}
44+
%define debug_package %{nil}
45+
%define __os_install_post %{_dbpath}/brp-compress
46+
47+
Summary: A very simple toy bin rpm package
48+
Name: #{o[:pkg]}
49+
Version: #{o[:version]}
50+
Release: 1
51+
License: GPL+
52+
Group: Development/Tools
53+
SOURCE0 : %{name}-%{version}.tar.gz
54+
URL: http://www.puppetlabs.com/
55+
56+
BuildRoot: %{_topdir}/BUILD/%{name}-%{version}-%{release}-root
57+
58+
%description
59+
%{summary}
60+
61+
%prep
62+
%setup -q
63+
64+
%build
65+
# Empty section.
66+
67+
%install
68+
rm -rf %{buildroot}
69+
mkdir -p %{buildroot}
70+
71+
# in builddir
72+
cp -a * %{buildroot}
73+
74+
75+
%clean
76+
rm -rf %{buildroot}
77+
78+
%files
79+
%defattr(-,root,root,-)
80+
%{_bindir}/*
81+
82+
%changelog
83+
* Mon Dec 01 2014 Michael Smith <[email protected]> #{o[:version]}-1
84+
- First Build
85+
86+
EOF
87+
"
88+
on agent, "rpmbuild -ba #{o[:repo]}/SPECS/#{o[:pkg]}.spec"
89+
on agent, "createrepo --update #{o[:repo]}"
90+
end
91+
end
92+
end
93+
end
94+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
test_name "Setup acceptance environment"
2+
3+
step "Ensure package build tools"
4+
5+
require 'puppet/acceptance/install_utils'
6+
extend Puppet::Acceptance::InstallUtils
7+
8+
PACKAGES = {
9+
:redhat => [
10+
'rpm-build',
11+
'createrepo',
12+
],
13+
}
14+
15+
install_packages_on(hosts, PACKAGES, :check_if_exists => true)
16+
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
test_name "ticket 1073: common package name in two different providers should be allowed"
2+
3+
hosts_to_test = agents.select do |agent|
4+
agent['platform'].match /(?:centos|el-|fedora)/
5+
end
6+
skip_test "No suitable hosts found" if hosts_to_test.empty?
7+
8+
require 'puppet/acceptance/rpm_util'
9+
extend Puppet::Acceptance::RpmUtils
10+
11+
rpm_options = {:pkg => 'guid', :version => '1.0'}
12+
13+
teardown do
14+
step "cleanup"
15+
agents.each do |agent|
16+
clean_rpm agent, rpm_options
17+
end
18+
end
19+
20+
def verify_state(hosts, pkg, state, match)
21+
hosts.each do |agent|
22+
# Note yum lists packages as <name>.<arch>
23+
on agent, 'yum list installed' do
24+
method(match).call(/^#{pkg}\./, stdout)
25+
end
26+
27+
on agent, 'gem list --local' do
28+
method(match).call(/^#{pkg} /, stdout)
29+
end
30+
end
31+
end
32+
33+
def verify_present(hosts, pkg)
34+
verify_state(hosts, pkg, '(?!purged|absent)[^\']+', :assert_match)
35+
end
36+
37+
def verify_absent(hosts, pkg)
38+
verify_state(hosts, pkg, '(?:purged|absent)', :assert_no_match)
39+
end
40+
41+
# Setup repo and package
42+
hosts_to_test.each do |agent|
43+
clean_rpm agent, rpm_options
44+
setup_rpm agent, rpm_options
45+
send_rpm agent, rpm_options
46+
end
47+
48+
verify_absent hosts_to_test, 'guid'
49+
50+
# Test error trying to install duplicate packages
51+
collide1_manifest = <<MANIFEST
52+
package {'guid': ensure => installed}
53+
package {'other-guid': name => 'guid', ensure => present}
54+
MANIFEST
55+
56+
apply_manifest_on(hosts_to_test, collide1_manifest, :acceptable_exit_codes => [1]).each do |result|
57+
assert_match(/Error while evaluating a Resource Statement, Cannot alias Package\[other-guid\] to \["guid", nil\]/, "#{result.host}: #{result.stderr}")
58+
end
59+
60+
verify_absent hosts_to_test, 'guid'
61+
62+
collide2_manifest = <<MANIFEST
63+
package {'guid': ensure => '0.1.0', provider => gem}
64+
package {'other-guid': name => 'guid', ensure => installed, provider => gem}
65+
MANIFEST
66+
67+
apply_manifest_on(hosts_to_test, collide2_manifest, :acceptable_exit_codes => [1]).each do |result|
68+
assert_match(/Error while evaluating a Resource Statement, Cannot alias Package\[other-guid\] to \["guid", "gem"\]/, "#{result.host}: #{result.stderr}")
69+
end
70+
71+
verify_absent hosts_to_test, 'guid'
72+
73+
# Test successful parallel installation
74+
install_manifest = <<MANIFEST
75+
package {'guid': ensure => installed}
76+
77+
package {'gem-guid':
78+
provider => gem,
79+
name => 'guid',
80+
ensure => installed,
81+
}
82+
MANIFEST
83+
84+
apply_manifest_on(hosts_to_test, install_manifest).each do |result|
85+
assert_match('Package[guid]/ensure: created', "#{result.host}: #{result.stdout}")
86+
assert_match('Package[gem-guid]/ensure: created', "#{result.host}: #{result.stdout}")
87+
end
88+
89+
verify_present hosts_to_test, 'guid'
90+
91+
# Test removal
92+
remove_manifest = <<MANIFEST
93+
package {'gem-guid':
94+
provider => gem,
95+
name => 'guid',
96+
ensure => absent,
97+
}
98+
99+
package {'guid': ensure => absent}
100+
MANIFEST
101+
102+
apply_manifest_on(hosts_to_test, remove_manifest).each do |result|
103+
assert_match('Package[guid]/ensure: removed', "#{result.host}: #{result.stdout}")
104+
assert_match('Package[gem-guid]/ensure: removed', "#{result.host}: #{result.stdout}")
105+
end
106+
107+
verify_absent hosts_to_test, 'guid'
108+

lib/puppet/resource.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,11 @@ def to_s
314314
end
315315

316316
def uniqueness_key
317-
# Temporary kludge to deal with inconsistent use patters
317+
# Temporary kludge to deal with inconsistent use patterns; ensure we don't return nil for namevar/:name
318318
h = self.to_hash
319-
h[namevar] ||= h[:name]
320-
h[:name] ||= h[namevar]
319+
name = h[namevar] || h[:name] || self.name
320+
h[namevar] ||= name
321+
h[:name] ||= name
321322
h.values_at(*key_attributes.sort_by { |k| k.to_s })
322323
end
323324

lib/puppet/resource/catalog.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,13 @@ def add_resource_to_graph(resource)
111111
private :add_resource_to_graph
112112

113113
def create_resource_aliases(resource)
114-
if resource.respond_to?(:isomorphic?) and resource.isomorphic? and resource.name != resource.title
115-
self.alias(resource, resource.uniqueness_key)
114+
# Skip creating aliases and checking collisions for non-isomorphic resources.
115+
return unless resource.respond_to?(:isomorphic?) and resource.isomorphic?
116+
117+
# Add an alias if the uniqueness key is valid and not the title, which has already been checked.
118+
ukey = resource.uniqueness_key
119+
if ukey.any? and ukey != [resource.title]
120+
self.alias(resource, ukey)
116121
end
117122
end
118123
private :create_resource_aliases

lib/puppet/type/package.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,37 @@ def should_to_s(newvalue = @should)
245245
end
246246
end
247247

248+
# We call providify here so that we can set provider as a namevar.
249+
# Normally this method is called after newtype finishes constructing this
250+
# Type class.
251+
providify
252+
paramclass(:provider).isnamevar
253+
254+
# We have more than one namevar, so we need title_patterns. However, we
255+
# cheat and set the patterns to map to name only and completely ignore
256+
# provider. So far, the logic that determines uniqueness appears to just
257+
# "Do The Right Thing™" when the provider is explicitly set by the user.
258+
#
259+
# The following resources will be seen as uniqe by puppet:
260+
#
261+
# # Uniqueness Key: ['mysql', nil]
262+
# package{'mysql': }
263+
#
264+
# # Uniqueness Key: ['mysql', 'gem']
265+
# package{'gem-mysql':
266+
# name => 'mysql,
267+
# provider => gem
268+
# }
269+
#
270+
# This does not handle the case where providers like 'yum' and 'rpm' should
271+
# clash. Also, declarations that implicitly use the default provider will
272+
# clash with those that explicitly use the default.
273+
def self.title_patterns
274+
# This is the default title pattern for all types, except hard-wired to
275+
# set only name.
276+
[ [ /(.*)/m, [ [:name] ] ] ]
277+
end
278+
248279
newproperty(:package_settings, :required_features=>:package_settings) do
249280
desc "Settings that can change the contents or configuration of a package.
250281

spec/integration/type/package_spec.rb

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,77 @@ def provider_name(os)
2626
Puppet::Type.type(:package).defaultprovider.name.should == default_provider
2727
end
2828
end
29+
30+
describe Puppet::Type.type(:package), "when packages with the same name are sourced" do
31+
before :each do
32+
@provider = stub(
33+
'provider',
34+
:class => Puppet::Type.type(:package).defaultprovider,
35+
:clear => nil,
36+
:satisfies? => true,
37+
:name => :mock,
38+
:validate_source => nil
39+
)
40+
Puppet::Type.type(:package).defaultprovider.stubs(:new).returns(@provider)
41+
Puppet::Type.type(:package).defaultprovider.stubs(:instances).returns([])
42+
@package = Puppet::Type.type(:package).new(:name => "yay", :ensure => :present)
43+
44+
@catalog = Puppet::Resource::Catalog.new
45+
@catalog.add_resource(@package)
46+
end
47+
48+
describe "with same title" do
49+
before {
50+
@alt_package = Puppet::Type.type(:package).new(:name => "yay", :ensure => :present)
51+
}
52+
it "should give an error" do
53+
expect {
54+
@catalog.add_resource(@alt_package)
55+
}.to raise_error Puppet::Resource::Catalog::DuplicateResourceError, 'Duplicate declaration: Package[yay] is already declared; cannot redeclare'
56+
end
57+
end
58+
59+
describe "with different title" do
60+
before :each do
61+
@alt_package = Puppet::Type.type(:package).new(:name => "yay", :title => "gem-yay", :ensure => :present)
62+
end
63+
64+
it "should give an error" do
65+
provider_name = Puppet::Type.type(:package).defaultprovider.name
66+
expect {
67+
@catalog.add_resource(@alt_package)
68+
}.to raise_error ArgumentError, "Cannot alias Package[gem-yay] to [\"yay\", :#{provider_name}]; resource [\"Package\", \"yay\", :#{provider_name}] already declared"
69+
end
70+
end
71+
72+
describe "from multiple providers" do
73+
provider_class = Puppet::Type.type(:package).provider(:gem)
74+
75+
before :each do
76+
@alt_provider = provider_class.new
77+
@alt_package = Puppet::Type.type(:package).new(:name => "yay", :title => "gem-yay", :provider => @alt_provider, :ensure => :present)
78+
@catalog.add_resource(@alt_package)
79+
end
80+
81+
describe "when it should be present" do
82+
[:present, :latest, "1.0"].each do |state|
83+
it "should do nothing if it is #{state.to_s}" do
84+
@provider.expects(:properties).returns(:ensure => state).at_least_once
85+
@alt_provider.expects(:properties).returns(:ensure => state).at_least_once
86+
@catalog.apply
87+
end
88+
end
89+
90+
[:purged, :absent].each do |state|
91+
it "should install if it is #{state.to_s}" do
92+
@provider.stubs(:properties).returns(:ensure => state)
93+
@provider.expects(:install)
94+
@alt_provider.stubs(:properties).returns(:ensure => state)
95+
@alt_provider.expects(:install)
96+
@catalog.apply
97+
end
98+
end
99+
end
100+
end
101+
end
102+

0 commit comments

Comments
 (0)