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
|
require 'puppet'
require 'spec_helper'
require 'puppet/provider/openstack'
describe Puppet::Provider::Openstack do
before(:each) do
ENV['OS_USERNAME'] = nil
ENV['OS_PASSWORD'] = nil
ENV['OS_PROJECT_NAME'] = nil
ENV['OS_AUTH_URL'] = nil
end
let(:type) do
Puppet::Type.newtype(:test_resource) do
newparam(:name, :namevar => true)
newparam(:log_file)
end
end
let(:credentials) do
credentials = double('credentials')
allow(credentials).to receive(:to_env).and_return({
'OS_USERNAME' => 'user',
'OS_PASSWORD' => 'password',
'OS_PROJECT_NAME' => 'project',
'OS_AUTH_URL' => 'http://url',
})
credentials
end
let(:list_data) do
<<-eos
"ID","Name","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","test","Test tenant",True
eos
end
let(:show_data) do
<<-eos
description="Test tenant"
enabled="True"
id="1cb05cfed7c24279be884ba4f6520262"
name="test"
eos
end
describe '#request' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) do
Puppet::Provider::Openstack.new(type.new(resource_attrs))
end
it 'makes a successful list request' do
expect(provider.class).to receive(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
.and_return list_data
response = Puppet::Provider::Openstack.request('project', 'list', ['--long'])
expect(response.first[:description]).to eq 'Test tenant'
end
it 'makes a successful show request' do
expect(provider.class).to receive(:openstack)
.with('project', 'show', '--format', 'shell', ['1cb05cfed7c24279be884ba4f6520262'])
.and_return show_data
response = Puppet::Provider::Openstack.request('project', 'show', ['1cb05cfed7c24279be884ba4f6520262'])
expect(response[:description]).to eq 'Test tenant'
end
it 'makes a successful set request' do
expect(provider.class).to receive(:openstack)
.with('project', 'set', ['--name', 'new name', '1cb05cfed7c24279be884ba4f6520262'])
.and_return ''
response = Puppet::Provider::Openstack.request('project', 'set', ['--name', 'new name', '1cb05cfed7c24279be884ba4f6520262'])
expect(response).to eq ''
end
it 'uses provided credentials' do
expect(Puppet::Util).to receive(:withenv).with(credentials.to_env)
Puppet::Provider::Openstack.request('project', 'list', ['--long'], credentials)
end
it 'redacts sensitive data from an exception message' do
e1 = Puppet::ExecutionFailure.new "Execution of 'openstack user create --format shell hello --password world --enable --email foo@example.com --domain Default' returned 1: command failed"
expect do
Puppet::Provider::Openstack.redact_and_raise(e1)
end.to raise_error(Puppet::ExecutionFailure, /Execution of \'openstack user create --format shell hello --password \[redacted secret\] --enable --email foo@example.com --domain Default/)
e2 = Puppet::ExecutionFailure.new "Execution of 'openstack user create --format shell hello --password world' returned 1: command failed"
expect do
Puppet::Provider::Openstack.redact_and_raise(e2)
end.to raise_error(Puppet::ExecutionFailure, /Execution of \'openstack user create --format shell hello --password \[redacted secret\]\' returned/)
end
it 'redacts password in execution output on exception' do
allow(provider.class).to receive(:execute)
.and_raise(Puppet::ExecutionFailure, "Execution of '/usr/bin/openstack user create --format shell hello --password world --enable --email foo@example.com --domain Default' returned 1: command failed")
expect do
Puppet::Provider::Openstack.request('user', 'create', ['hello', '--password', 'world', '--enable', '--email', 'foo@example.com', '--domain', 'Default'])
end.to raise_error Puppet::ExecutionFailure, "Execution of '/usr/bin/openstack user create --format shell hello --password [redacted secret] --enable --email foo@example.com --domain Default' returned 1: command failed"
end
context 'on connection errors' do
it 'retries the failed command' do
allow(provider.class).to receive(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
.and_invoke(
lambda { |*args| raise Puppet::ExecutionFailure, 'Unable to establish connection' },
lambda { |*args| return list_data }
)
expect(provider.class).to receive(:sleep).with(10).and_return(nil)
response = Puppet::Provider::Openstack.request('project', 'list', ['--long'])
expect(response.first[:description]).to eq 'Test tenant'
end
it 'fails after the timeout and redacts' do
expect(provider.class).to receive(:execute)
.and_raise(Puppet::ExecutionFailure, "Execution of 'openstack user create foo --password secret' returned 1: command failed")
.exactly(6).times
allow(provider.class).to receive(:sleep)
allow(provider.class).to receive(:current_time)
.and_return(0, 10, 20, 100, 200, 300, 400)
expect do
Puppet::Provider::Openstack.request('project', 'list', ['--long'])
end.to raise_error Puppet::ExecutionFailure, /Execution of \'openstack user create foo --password \[redacted secret\]\' returned 1/
end
it 'fails after the timeout' do
expect(provider.class).to receive(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
.and_raise(Puppet::ExecutionFailure, 'Unable to establish connection')
.exactly(6).times
allow(provider.class).to receive(:sleep)
allow(provider.class).to receive(:current_time)
.and_return(0, 10, 20, 100, 200, 300, 400)
expect do
Puppet::Provider::Openstack.request('project', 'list', ['--long'])
end.to raise_error Puppet::ExecutionFailure, /Unable to establish connection/
end
it 'does not retry non-idempotent commands' do
expect(provider.class).to receive(:openstack)
.with('project', 'create', '--format', 'shell', ['--quiet'])
.and_raise(Puppet::ExecutionFailure, 'Unable to establish connection')
.exactly(1).times
expect(provider.class).to receive(:sleep).never
expect do
Puppet::Provider::Openstack.request('project', 'create', ['--quiet'])
end.to raise_error Puppet::ExecutionFailure, /Unable to establish connection/
end
end
context 'catch unauthorized errors' do
it 'should raise an error with non-existent user' do
ENV['OS_USERNAME'] = 'test'
ENV['OS_PASSWORD'] = 'abc123'
ENV['OS_PROJECT_NAME'] = 'test'
ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000'
allow(provider.class).to receive(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
.and_raise(Puppet::ExecutionFailure, 'Could not find user: test (HTTP 401)')
expect do
Puppet::Provider::Openstack.request('project', 'list', ['--long'])
end.to raise_error(Puppet::Error::OpenstackUnauthorizedError, /Could not authenticate/)
end
it 'should raise an error with not authorized to perform' do
allow(provider.class).to receive(:openstack)
.with('role', 'list', '--quiet', '--format', 'csv', ['--long'])
.and_raise(Puppet::ExecutionFailure, 'You are not authorized to perform the requested action: identity:list_grants (HTTP 403)')
expect do
Puppet::Provider::Openstack.request('role', 'list', ['--long'])
end.to raise_error(Puppet::Error::OpenstackUnauthorizedError, /Could not authenticate/)
end
end
end
describe 'parse_csv' do
context 'with mixed stderr' do
text = "ERROR: Testing\n\"field\",\"test\",1,2,3\n"
csv = Puppet::Provider::Openstack.parse_csv(text)
it 'should ignore non-CSV text at the beginning of the input' do
expect(csv).to be_kind_of(Array)
expect(csv[0]).to match_array(%w(field test 1 2 3))
expect(csv.size).to eq(1)
end
end
context 'with \r\n line endings' do
text = "ERROR: Testing\r\n\"field\",\"test\",1,2,3\r\n"
csv = Puppet::Provider::Openstack.parse_csv(text)
it 'ignore the carriage returns' do
expect(csv).to be_kind_of(Array)
expect(csv[0]).to match_array(%w(field test 1 2 3))
expect(csv.size).to eq(1)
end
end
context 'with embedded newlines' do
text = "ERROR: Testing\n\"field\",\"te\nst\",1,2,3\n"
csv = Puppet::Provider::Openstack.parse_csv(text)
it 'should parse correctly' do
expect(csv).to be_kind_of(Array)
expect(csv[0]).to match_array(['field', "te\nst", '1', '2', '3'])
expect(csv.size).to eq(1)
end
end
end
end
|