diff --git a/lib/puppet/type/k5login.rb b/lib/puppet/type/k5login.rb index 09114e977..abe667724 100644 --- a/lib/puppet/type/k5login.rb +++ b/lib/puppet/type/k5login.rb @@ -1,87 +1,88 @@ # Plug-in type for handling k5login files +require 'puppet/util' Puppet::Type.newtype(:k5login) do @doc = "Manage the `.k5login` file for a user. Specify the full path to the `.k5login` file as the name, and an array of principals as the `principals` attribute." ensurable # Principals that should exist in the file newproperty(:principals, :array_matching => :all) do desc "The principals present in the `.k5login` file. This should be specified as an array." end # The path/name of the k5login file newparam(:path) do isnamevar desc "The path to the `.k5login` file to manage. Must be fully qualified." validate do |value| unless value =~ /^#{File::SEPARATOR}/ raise Puppet::Error, "File paths must be fully qualified." end end end # To manage the mode of the file newproperty(:mode) do desc "The desired permissions mode of the `.k5login` file. Defaults to `644`." defaultto { "644" } end provide(:k5login) do desc "The k5login provider is the only provider for the k5login type." # Does this file exist? def exists? File.exists?(@resource[:name]) end # create the file def create write(@resource.should(:principals)) should_mode = @resource.should(:mode) unless self.mode == should_mode self.mode = should_mode end end # remove the file def destroy File.unlink(@resource[:name]) end # Return the principals def principals(dummy_argument=:work_arround_for_ruby_GC_bug) if File.exists?(@resource[:name]) File.readlines(@resource[:name]).collect { |line| line.chomp } else :absent end end # Write the principals out to the k5login file def principals=(value) write(value) end # Return the mode as an octal string, not as an integer def mode "%o" % (File.stat(@resource[:name]).mode & 007777) end # Set the file mode, converting from a string to an integer. def mode=(value) File.chmod(Integer("0#{value}"), @resource[:name]) end private def write(value) - Puppet::Util.secure_open(@resource[:name], "w") do |f| - f.puts value.join("\n") + Puppet::Util.replace_file(@resource[:name], 0644) do |f| + f.puts value end end end end diff --git a/spec/unit/type/k5login_spec.rb b/spec/unit/type/k5login_spec.rb new file mode 100755 index 000000000..be9aae2ce --- /dev/null +++ b/spec/unit/type/k5login_spec.rb @@ -0,0 +1,115 @@ +#!/usr/bin/env ruby +require 'spec_helper' +require 'fileutils' +require 'puppet/type' + +describe Puppet::Type.type(:k5login) do + include PuppetSpec::Files + + context "the type class" do + subject { described_class } + it { should be_validattr :ensure } + it { should be_validattr :path } + it { should be_validattr :principals } + it { should be_validattr :mode } + # We have one, inline provider implemented. + it { should be_validattr :provider } + end + + let(:path) { tmpfile('k5login') } + + def resource(attrs = {}) + attrs = { + :ensure => 'present', + :path => path, + :principals => 'fred@EXAMPLE.COM' + }.merge(attrs) + + if content = attrs.delete(:content) + File.open(path, 'w') { |f| f.print(content) } + end + + resource = described_class.new(attrs) + resource + end + + before :each do + FileUtils.touch(path) + end + + context "the provider" do + context "when the file is missing" do + it "should initially be absent" do + File.delete(path) + resource.retrieve[:ensure].must == :absent + end + + it "should create the file when synced" do + resource(:ensure => 'present').parameter(:ensure).sync + File.should be_exist path + end + end + + context "when the file is present" do + context "retrieved initial state" do + subject { resource.retrieve } + + it "should retrieve its properties correctly with zero principals" do + subject[:ensure].should == :present + subject[:principals].should == [] + # We don't really care what the mode is, just that it got it + subject[:mode].should_not be_nil + end + + context "with one principal" do + subject { resource(:content => "daniel@EXAMPLE.COM\n").retrieve } + + it "should retrieve its principals correctly" do + subject[:principals].should == ["daniel@EXAMPLE.COM"] + end + end + + context "with two principals" do + subject do + content = ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"].join("\n") + resource(:content => content).retrieve + end + + it "should retrieve its principals correctly" do + subject[:principals].should == ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"] + end + end + end + + it "should remove the file ensure is absent" do + resource(:ensure => 'absent').property(:ensure).sync + File.should_not be_exist path + end + + it "should write one principal to the file" do + File.read(path).should == "" + resource(:principals => ["daniel@EXAMPLE.COM"]).property(:principals).sync + File.read(path).should == "daniel@EXAMPLE.COM\n" + end + + it "should write multiple principals to the file" do + content = ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"] + + File.read(path).should == "" + resource(:principals => content).property(:principals).sync + File.read(path).should == content.join("\n") + "\n" + end + + describe "when setting the mode", :unless => Puppet.features.microsoft_windows? do + # The defined input type is "mode, as an octal string" + ["400", "600", "700", "644", "664"].each do |mode| + it "should update the mode to #{mode}" do + resource(:mode => mode).property(:mode).sync + + (File.stat(path).mode & 07777).to_s(8).should == mode + end + end + end + end + end +end