summaryrefslogtreecommitdiff
path: root/spec/unit/provider/exec/posix_spec.rb
blob: 02c338b69640aa860c0faf64f447b697961e7bdd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#! /usr/bin/env ruby
require 'spec_helper'

describe Puppet::Type.type(:exec).provider(:posix), :if => Puppet.features.posix? do
  include PuppetSpec::Files

  def make_exe
    cmdpath = tmpdir('cmdpath')
    exepath = tmpfile('my_command', cmdpath)
    FileUtils.touch(exepath)
    File.chmod(0755, exepath)
    exepath
  end

  let(:resource) { Puppet::Type.type(:exec).new(:title => '/foo', :provider => :posix) }
  let(:provider) { described_class.new(resource) }

  describe "#validatecmd" do
    it "should fail if no path is specified and the command is not fully qualified" do
      expect { provider.validatecmd("foo") }.to raise_error(
        Puppet::Error,
        "'foo' is not qualified and no path was specified. Please qualify the command or specify a path."
      )
    end

    it "should pass if a path is given" do
      provider.resource[:path] = ['/bogus/bin']
      provider.validatecmd("../foo")
    end

    it "should pass if command is fully qualifed" do
      provider.resource[:path] = ['/bogus/bin']
      provider.validatecmd("/bin/blah/foo")
    end
  end

  describe "#run" do
    describe "when the command is an absolute path" do
      let(:command) { tmpfile('foo') }

      it "should fail if the command doesn't exist" do
        expect { provider.run(command) }.to raise_error(ArgumentError, "Could not find command '#{command}'")
      end

      it "should fail if the command isn't a file" do
        FileUtils.mkdir(command)
        FileUtils.chmod(0755, command)

        expect { provider.run(command) }.to raise_error(ArgumentError, "'#{command}' is a directory, not a file")
      end

      it "should fail if the command isn't executable" do
        FileUtils.touch(command)
        File.stubs(:executable?).with(command).returns(false)

        expect { provider.run(command) }.to raise_error(ArgumentError, "'#{command}' is not executable")
      end
    end

    describe "when the command is a relative path" do
      it "should execute the command if it finds it in the path and is executable" do
        command = make_exe
        provider.resource[:path] = [File.dirname(command)]
        filename = File.basename(command)

        Puppet::Util::Execution.expects(:execute).with(filename, instance_of(Hash)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0))

        provider.run(filename)
      end

      it "should fail if the command isn't in the path" do
        resource[:path] = ["/fake/path"]

        expect { provider.run('foo') }.to raise_error(ArgumentError, "Could not find command 'foo'")
      end

      it "should fail if the command is in the path but not executable" do
        command = make_exe
        File.chmod(0644, command)
        FileTest.stubs(:executable?).with(command).returns(false)
        resource[:path] = [File.dirname(command)]
        filename = File.basename(command)

        expect { provider.run(filename) }.to raise_error(ArgumentError, "Could not find command '#{filename}'")
      end
    end

    it "should not be able to execute shell builtins" do
      provider.resource[:path] = ['/bogus/bin']
      expect { provider.run("cd ..") }.to raise_error(ArgumentError, "Could not find command 'cd'")
    end

    it "does not override the user when it is already the requested user" do
      Etc.stubs(:getpwuid).returns(Struct::Passwd.new('testing'))
      provider.resource[:user] = 'testing'
      command = make_exe

      Puppet::Util::Execution.expects(:execute).with(anything(), has_entry(:uid, nil)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0))

      provider.run(command)
    end

    it "should execute the command if the command given includes arguments or subcommands" do
      provider.resource[:path] = ['/bogus/bin']
      command = make_exe

      Puppet::Util::Execution.expects(:execute).with("#{command} bar --sillyarg=true --blah", instance_of(Hash)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0))

      provider.run("#{command} bar --sillyarg=true --blah")
    end

    it "should fail if quoted command doesn't exist" do
      provider.resource[:path] = ['/bogus/bin']
      command = "/foo bar --sillyarg=true --blah"

      expect { provider.run(%Q["#{command}"]) }.to raise_error(ArgumentError, "Could not find command '#{command}'")
    end

    it "should warn if you're overriding something in environment" do
      provider.resource[:environment] = ['WHATEVER=/something/else', 'WHATEVER=/foo']
      command = make_exe

      Puppet::Util::Execution.expects(:execute).with(command, instance_of(Hash)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0))

      provider.run(command)

      @logs.map {|l| "#{l.level}: #{l.message}" }.should == ["warning: Overriding environment setting 'WHATEVER' with '/foo'"]
    end

    it "should set umask before execution if umask parameter is in use" do
      provider.resource[:umask] = '0027'
      Puppet::Util.expects(:withumask).with(0027)
      provider.run(provider.resource[:command])
    end

    describe "posix locale settings" do
      # a sentinel value that we can use to emulate what locale environment variables might be set to on an international
      # system.
      lang_sentinel_value = "en_US.UTF-8"
      # a temporary hash that contains sentinel values for each of the locale environment variables that we override in
      # "exec"
      locale_sentinel_env = {}
      Puppet::Util::POSIX::LOCALE_ENV_VARS.each { |var| locale_sentinel_env[var] = lang_sentinel_value }

      command = "/bin/echo $%s"

      it "should not override user's locale during execution" do
        # we'll do this once without any sentinel values, to give us a little more test coverage
        orig_env = {}
        Puppet::Util::POSIX::LOCALE_ENV_VARS.each { |var| orig_env[var] = ENV[var] if ENV[var] }

        orig_env.keys.each do |var|
          output, status = provider.run(command % var)
          output.strip.should == orig_env[var]
        end

        # now, once more... but with our sentinel values
        Puppet::Util.withenv(locale_sentinel_env) do
          Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var|
            output, status = provider.run(command % var)
            output.strip.should == locale_sentinel_env[var]
          end
        end
      end

      it "should respect locale overrides in user's 'environment' configuration" do
        provider.resource[:environment] = ['LANG=C', 'LC_ALL=C']
        output, status = provider.run(command % 'LANG')
        output.strip.should == 'C'
        output, status = provider.run(command % 'LC_ALL')
        output.strip.should == 'C'
      end
    end

    describe "posix user-related environment vars" do
      # a temporary hash that contains sentinel values for each of the user-related environment variables that we
      # are expected to unset during an "exec"
      user_sentinel_env = {}
      Puppet::Util::POSIX::USER_ENV_VARS.each { |var| user_sentinel_env[var] = "Abracadabra" }

      command = "/bin/echo $%s"

      it "should unset user-related environment vars during execution" do
        # first we set up a temporary execution environment with sentinel values for the user-related environment vars
        # that we care about.
        Puppet::Util.withenv(user_sentinel_env) do
          # with this environment, we loop over the vars in question
          Puppet::Util::POSIX::USER_ENV_VARS.each do |var|
            # ensure that our temporary environment is set up as we expect
            ENV[var].should == user_sentinel_env[var]

            # run an "exec" via the provider and ensure that it unsets the vars
            output, status = provider.run(command % var)
            output.strip.should == ""

            # ensure that after the exec, our temporary env is still intact
            ENV[var].should == user_sentinel_env[var]
          end

        end
      end

      it "should respect overrides to user-related environment vars in caller's 'environment' configuration" do
        sentinel_value = "Abracadabra"
        # set the "environment" property of the resource, populating it with a hash containing sentinel values for
        # each of the user-related posix environment variables
        provider.resource[:environment] = Puppet::Util::POSIX::USER_ENV_VARS.collect { |var| "#{var}=#{sentinel_value}"}

        # loop over the posix user-related environment variables
        Puppet::Util::POSIX::USER_ENV_VARS.each do |var|
          # run an 'exec' to get the value of each variable
          output, status = provider.run(command % var)
          # ensure that it matches our expected sentinel value
          output.strip.should == sentinel_value
        end
      end
    end
  end
end