Giuseppe Lavagetto has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/402345 )

Change subject: wmflib: simplify the role() function, convert to the new API
......................................................................


wmflib: simplify the role() function, convert to the new API

Converting to the new API makes some things possible, namely:
- Clean access to the top scope
- Cleanly require the function is called exactly once
- Use call_function for 'include'

but on the other hand makes it impossible to verify if we're calling the
function from the node scope. Please note that this happens for a reason
- messing with the scope in function can cause undefined behaviour and
indeed I found edge cases where this can happen, for instance when
trying to access the variables before they're defined can make them null.

Change-Id: I32631c84a7f6340a0d7de2ae57dfbef87015c46b
---
A modules/wmflib/lib/puppet/functions/role.rb
D modules/wmflib/lib/puppet/parser/functions/role.rb
M modules/wmflib/spec/functions/role_spec.rb
3 files changed, 73 insertions(+), 101 deletions(-)

Approvals:
  Giuseppe Lavagetto: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/modules/wmflib/lib/puppet/functions/role.rb 
b/modules/wmflib/lib/puppet/functions/role.rb
new file mode 100644
index 0000000..877624e
--- /dev/null
+++ b/modules/wmflib/lib/puppet/functions/role.rb
@@ -0,0 +1,54 @@
+# == Function: role ( string $role_name [, string $... ] )
+#
+# Declare the _roles variable (or add keys to it), and if the role class
+# role::$role_name is in the scope, include it. This is roughly a
+# shortcut for
+#
+# $::_roles[foo] = true
+# include role::foo
+#
+# and has the notable advantage over that the syntax is shorter. Also,
+# this function  will refuse to run anywhere but at the node scope,
+# thus making any additional role added to a node explicit.
+#
+# If you have more than one role to declare, you MUST do that in one
+# single role stanza, or you would encounter unexpected behaviour. If
+# you do, an exception will be raised.
+#
+# This function is very useful with our "role" hiera backend if you
+# have global configs that are role-based
+#
+# === Example
+#
+# node /^www\d+/ {
+#     role mediawiki::appserver  # this will load the 
role::mediawiki::appserver class
+#     include ::standard  #this class will use hiera lookups defined for the 
role.
+# }
+#
+# node monitoring.local {
+#     role icinga, ganglia::collector #GOOD
+# }
+#
+# node monitoring2.local {
+#     role icinga
+#     role ganglia::collector #BAD, issues a warning
+# }
+Puppet::Functions.create_function(:role) do
+  dispatch :main do
+    param 'String', :main_role
+  end
+  def main(main_role)
+    scope = closure_scope
+    # Check if the function has already been called
+    if scope.include? '_role'
+      raise Puppet::ParseError, "role() can only be called once per node"
+    end
+    role = main_role.gsub(/^::/, '')
+    # Backwards compat
+    scope['_roles'] = { role => true }
+    # This is where we're going in the future
+    scope['_role'] = role
+    rolename = 'role::' + role
+    call_function('include', rolename)
+  end
+end
diff --git a/modules/wmflib/lib/puppet/parser/functions/role.rb 
b/modules/wmflib/lib/puppet/parser/functions/role.rb
deleted file mode 100644
index 971782b..0000000
--- a/modules/wmflib/lib/puppet/parser/functions/role.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-# == Function: role ( string $role_name [, string $... ] )
-#
-# Declare the _roles variable (or add keys to it), and if the role class
-# role::$role_name is in the scope, include it. This is roughly a
-# shortcut for
-#
-# $::_roles[foo] = true
-# include role::foo
-#
-# and has the notable advantage over that the syntax is shorter. Also,
-# this function  will refuse to run anywhere but at the node scope,
-# thus making any additional role added to a node explicit.
-#
-# If you have more than one role to declare, you MUST do that in one
-# single role stanza, or you would encounter unexpected behaviour. If
-# you do, an exception will be raised.
-#
-# This function is very useful with our "role" hiera backend if you
-# have global configs that are role-based
-#
-# === Example
-#
-# node /^www\d+/ {
-#     role mediawiki::appserver  # this will load the 
role::mediawiki::appserver class
-#     include ::standard  #this class will use hiera lookups defined for the 
role.
-# }
-#
-# node monitoring.local {
-#     role icinga, ganglia::collector #GOOD
-# }
-#
-# node monitoring2.local {
-#     role icinga
-#     role ganglia::collector #BAD, issues a warning
-# }
-
-module Puppet::Parser::Functions
-  newfunction(:role, :arity => -2) do |args|
-    # This will add to the catalog, and to the node specifically:
-    # - A global 'role' hash with the 'role::#{arg}' key set to true;
-    # if the variable is present, append to it
-    # - Include class role::#{arg} if present
-
-    # Prevent use outside of node definitions
-    unless is_nodescope?
-      raise Puppet::ParseError,
-            "role can only be used in node scope, while you are in scope 
#{self}"
-    end
-
-    # Now check if the variable is already set and issue a warning
-    container = '_roles'
-    rolevar = compiler.topscope.lookupvar(container)
-    if rolevar
-      raise Puppet::ParseError,
-            "Using 'role' multiple times might yield unexpected results, use 
'role role1, role2' instead"
-    else
-      compiler.topscope.setvar(container, {})
-      rolevar = compiler.topscope.lookupvar(container)
-    end
-
-    # sanitize args
-    args = args.map do |x|
-      if x.start_with? '::'
-        x.gsub(/^::/, '')
-      else
-        x
-      end
-    end
-
-    # Set the variables
-    args.each do |arg|
-      rolevar[arg] = true
-    end
-
-    args.each do |arg|
-      rolename = 'role::' + arg
-      role_class = compiler.topscope.find_hostclass(rolename)
-      if role_class
-        send Puppet::Parser::Functions.function(:include), [rolename]
-      else
-        raise Puppet::ParseError, "Role class #{rolename} not found"
-      end
-    end
-  end
-end
diff --git a/modules/wmflib/spec/functions/role_spec.rb 
b/modules/wmflib/spec/functions/role_spec.rb
index b350208..5bb643c 100644
--- a/modules/wmflib/spec/functions/role_spec.rb
+++ b/modules/wmflib/spec/functions/role_spec.rb
@@ -6,35 +6,38 @@
 
   let(:pre_condition) {"
 class role::test {}
-class role::test2 {}
+class role::test2 {
+      if ($::_roles['test2']) { file { '/tmp/test': ensure => present} }
+      file{ \"/tmp/${::_role}\": ensure => present}
+}
 "}
   it "should be called with one parameter" do
     should run.with_params.and_raise_error(ArgumentError)
   end
-  it "throws error if called outside of the node scope" do
-    should run.with_params('cache::text').and_raise_error(Puppet::ParseError)
-  end
 
   it "throws error if called on a non-existing role" do
-    is_expected.to 
run.with_params('foo::bar').and_raise_error(Puppet::ParseError)
+    is_expected.to run.with_params('foo::bar').and_raise_error(Puppet::Error)
   end
 
   it "includes the role class" do
     is_expected.to run.with_params('test')
+    expect(catalogue).to contain_class('role::test')
   end
 
-  it "raises an error when called more than once in a scope" do
-    scope.function_role(['test2'])
-    expect { scope.function_role(['test']) }.to raise_error(Puppet::ParseError)
+  it "adds the keys to the top-scope variables" do
+    is_expected.to run.with_params('test2')
+    expect(catalogue).to contain_file('/tmp/test')
+    expect(catalogue).to contain_file('/tmp/test2')
   end
 
-  it "adds the keys to the top-scope variable" do
-    scope.function_role(['test', 'test2'])
-    expect(scope.lookupvar('_roles')).to eq({'test' => true, 'test2' => true})
-  end
-
-  it "includes the role classes" do
-    scope.function_role(['test'])
-    expect(scope.find_hostclass('role::test')).to 
be_an_instance_of(Puppet::Resource::Type)
+  context 'function has already been called' do
+    let(:pre_condition) {"
+class role::test {}
+class role::test2 {}
+role('test')
+" }
+    it "raises an error when called more than once in a scope" do
+      is_expected.to run.with_params('test2').and_raise_error(Puppet::Error)
+    end
   end
 end

-- 
To view, visit https://gerrit.wikimedia.org/r/402345
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I32631c84a7f6340a0d7de2ae57dfbef87015c46b
Gerrit-PatchSet: 5
Gerrit-Project: operations/puppet
Gerrit-Branch: production
Gerrit-Owner: Giuseppe Lavagetto <glavage...@wikimedia.org>
Gerrit-Reviewer: Alexandros Kosiaris <akosia...@wikimedia.org>
Gerrit-Reviewer: Giuseppe Lavagetto <glavage...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to