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...
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