Question

I'm trying to add the users on a system as puppet facts. I'm not a ruby programmer and the following code properly generates the users, but their uid is all the same (the uid of the last entry in the password file). If uid was out of scope, I would expect an unknown symbol error, if Facter.add was only getting called once at the end, I would expect there to be only one user, the last one, same as the uid. I don't understand how one can iterate without the other one doing so as well...

File.open("/etc/passwd", "r") do |passwords|
  while pw_entry = passwords.gets
    user, pw, uid, gid, gecos, home, shell = pw_entry.split(/:/)

    Facter.add("user_" + user) do
      setcode do
        uid
      end
    end
  end
end

In poking around, I found someone else with nearly the identical problem and this was the solution (which did work for me as well):

require 'etc'

Etc.passwd { |user|

    Facter.add("user_" + user.name) {
      setcode {
        user.uid
      }
    }
}

...however I don't understand what the difference is. It acts like calls to the Facter.add block are getting buffered and run all at once at the end of the loop, and Etc loaded up all of passwd so user.uid indexes into the array and timing is irrelevant. That would be a bizarre thing for a procedural language to be doing though...

Was it helpful?

Solution

You are right to say the following:

File.open("/etc/passwd", "r") do |passwords|
  while pw_entry = passwords.gets
      user, pw, uid, gid, gecos, home, shell = pw_entry.split(/:/)
      print uid
  end
end

is nearly equivalent to:

require 'etc'
Etc.passwd { |user|
  print uid
}

In fact these two ruby snippets will produce exactly the same result.

The only difference is that the last method uses another way of iterating through the passwd file. It uses a closure which receives a parameter user as an input. This user is the only user that exists in the scope of the anonymous function.

Now, about your problem with the difference between your two methods:

When you reference variables inside a ruby closure that don't belong to the closure itself (i.e. outer variables) the reference to the symbol of that variable within that (outer) scope is stored inside the closure's code. Since in the first method you reuse the same symbol within the same scope, uid references the last known value of uid when the closure's code is called after all facts have been added. This is known as the outer variable trap.

One way to work around this problem is by having each uid exist in its own scope.

def addUserFact(passwd_entry)
  user, pw, uid, gid, gecos, home, shell = passwd_entry.split(/:/)   
  Facter.add("user_" + user) do
    setcode do
      uid
    end
   end
end

File.open("C:/cygwin/etc/passwd", "r") do |passwords|
  while pw_entry = passwords.gets
    addUserFact(pw_entry)
  end
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top