Implement base aviator provider

This patch adds a dependency on the aimonb/aviator module and adds
functionality to support interactions with the OpenStack services' API
via aviator.

The patch adds a parent provider that is intended for other providers
to inherit from. The parent provider has methods to authenticate to
openstack services, create session objects, and make requests. The
authenticate method can accept credentials as an argument hash or infer
credentials from the environment.

It also adds a stub type parameter that allows types to incorporate
basic parameters they need in order to support using aviator.

Change-Id: I56b0d07ae8f4738037eda486b75a0f6e24fe80e7
Implements: blueprint use-aviator-in-module-resources
This commit is contained in:
Colleen Murphy
2014-08-25 16:32:13 -07:00
parent 1c9635c6ff
commit 612fa7e121
13 changed files with 938 additions and 3 deletions

View File

@@ -1,5 +1,6 @@
fixtures:
repositories:
aviator: git://github.com/aimonb/puppet_aviator.git
mysql: git://github.com/puppetlabs/puppetlabs-mysql.git
stdlib: git://github.com/puppetlabs/puppetlabs-stdlib.git
symlinks:

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
*.swp
spec/fixtures/
spec/fixtures/*
!spec/fixtures/vcr/
pkg
Gemfile.lock

View File

@@ -4,9 +4,11 @@ group :development, :test do
gem 'puppetlabs_spec_helper', :require => false
gem 'puppet-lint', '~> 0.3.2'
gem 'rake', '10.1.1'
gem 'rspec', '< 2.99'
gem 'rspec'
gem 'mocha'
gem 'json'
gem 'webmock'
gem 'faraday', '0.8.8', :require => false
gem 'vcr', :require => false
end
if puppetversion = ENV['PUPPET_GEM_VERSION']

View File

@@ -7,5 +7,6 @@ summary 'Puppet Labs OpenStackLib Module'
description 'Puppet module library to expose common functionality between OpenStack modules'
project_page 'https://launchpad.net/puppet-openstacklib'
dependency 'aimonb/aviator',
dependency 'puppetlabs/mysql', '>=2.2.0 <3.0.0'
dependency 'puppetlabs/stdlib', '>=3.2.0'

View File

@@ -119,6 +119,61 @@ array or string; optional; default to undef
Privileges given to the database user;
string or array of strings; optional; default to 'ALL'
### Types and Providers
#### Aviator
#####`Puppet::add_aviator_params`
The aviator type is not a real type, but it serves to simulate a mixin model,
whereby other types can call out to the Puppet::add\_aviator\_params method in
order to add aviator-specific parameters to themselves. Currently this adds the
auth parameter to the given type. The method must be called after the type is
declared, e.g.:
```puppet
require 'puppet/type/aviator'
Puppet::Type.newtype(:my_type) do
# ...
end
Puppet::add_aviator_params(:my_type)
```
#####`Puppet::Provider::Aviator`
The aviator provider is a parent provider intended to serve as a base for other
providers that need to authenticate against keystone in order to accomplish a
task.
**`Puppet::Provider::Aviator#authenticate`**
Either creates an authenticated session or sets up an unauthenticated session
with instance variables initialized with a token to inject into the next request.
It takes as arguments a set of authentication parameters as a hash and a path
to a log file. Puppet::Provider::Aviator#authencate looks for five different
possible methods of authenticating, in the following order:
1) Username and password credentials in the auth parameters
2) The path to an openrc file containing credentials to read in the auth
parameters
3) A service token in the auth parameters
4) Environment variables set for the environment in which Puppet is running
5) A service token in /etc/keystone/keystone.conf. This option provides
backwards compatibility with earlier keystone providers.
If the provider has password credentials, it can create an authenticated
session. If it only has a service token, it initializes an unauthenciated
session and a hash of session data that can be injected into a future request.
**`Puppet::Provider::Aviator#make_request`**
After creating a session, the make\_request method provides an interface that
providers can use to make requests without worrying about whether they have an
authenticated or unauthenticated session. It takes as arguments the
Aviator::Service it is making a request at (for example, keystone), a symbol for
the request (for example, :list\_tenants), and optionally a block to execute
that will set parameters for an update request.
Implementation
--------------

View File

@@ -0,0 +1,297 @@
require 'puppet'
require 'puppet/feature/aviator'
require 'puppet/util/inifile'
class Puppet::Provider::Aviator < Puppet::Provider
def session
@session ||= authenticate(resource[:auth], resource[:log_file])
end
def self.session
@session ||= authenticate(nil, nil)
end
def request(service, request, &block)
self.class.make_request(service, request, session_data, &block)
end
def self.request(service, request, &block)
self.make_request(service, request, session_data, &block)
end
# needed for tests
def session_data
@session_data
end
def self.session_data
@session_data
end
def session_data=(data)
@session_data=data
end
def self.session_data=(data)
@session_data=data
end
private
# Attempt to find credentials in this order:
# 1. username,password,tenant,host set in type parameters
# 2. openrc file path set in type parameters
# 3. service token and host set in type parameters
# 4. username,password,tenant,host set in environment variables
# 5. service token and host set in keystone.conf (backwards compatible version)
def authenticate(auth_params, log_file)
auth_params ||= {}
if password_credentials_set?(auth_params)
@session = get_authenticated_session(auth_params, log_file)
elsif openrc_set?(auth_params)
credentials = get_credentials_from_openrc(auth_params['openrc'])
@session = get_authenticated_session(credentials, log_file)
elsif service_credentials_set?(auth_params)
session_hash = get_unauthenticated_session(auth_params, log_file)
@session_data = session_hash[:data]
@session = session_hash[:session]
elsif env_vars_set?
credentials = get_credentials_from_env
@session = get_authenticated_session(credentials, log_file)
else # Last effort: try to get the token from keystone.conf
session_hash = self.class.try_auth_with_token(keystone_file, log_file)
@session_data = session_hash[:data]
@session = session_hash[:session]
end
end
def self.authenticate(auth_params, log_file)
auth_params = {} unless auth_params
if env_vars_set?
credentials = get_credentials_from_env
@session = get_authenticated_session(credentials, log_file)
else # Last effort: try to get the token from keystone.conf
session_hash = try_auth_with_token(keystone_file, log_file)
@session_data = session_hash[:data]
@session = session_hash[:session]
end
end
def self.try_auth_with_token(conf_file, log_file)
service_token = get_admin_token_from_keystone_file(conf_file)
auth_url = get_auth_url_from_keystone_file(conf_file)
session_hash = {}
if service_token
credentials = {
'service_token' => service_token,
'host_uri' => auth_url,
}
session_hash = get_unauthenticated_session(credentials, log_file)
else # All authentication efforts failed
raise(Puppet::Error, 'No credentials provided.')
end
end
def self.make_request(service, request, session_data, &block)
response = nil
if service && service.default_session_data
response = service.request(request, :endpoint_type => 'admin') do |params|
yield(params) if block
end
elsif session_data
response = service.request(request, :endpoint_type => 'admin',
:session_data => session_data) do |params|
yield(params) if block
end
else
raise(Puppet::Error, 'Cannot make a request with no session data.')
end
if response.body.hash['error']
raise(Puppet::Error, "Error making request: #{response.body.hash['error']['code']} #{response.body.hash['error']['title']}")
end
response
end
def password_credentials_set?(auth_params)
auth_params['username'] && auth_params['password'] && auth_params['tenant_name'] && auth_params['host_uri']
end
def openrc_set?(auth_params)
auth_params['openrc']
end
def service_credentials_set?(auth_params)
auth_params['service_token'] && auth_params['host_uri']
end
def self.env_vars_set?
ENV['OS_USERNAME'] && ENV['OS_PASSWORD'] && ENV['OS_TENANT_NAME'] && ENV['OS_AUTH_URL']
end
def env_vars_set?
self.class.env_vars_set?
end
def get_credentials_from_openrc(file)
creds = {}
begin
File.open(file).readlines.delete_if{|l| l=~ /^#/}.each do |line|
key, value = line.split('=')
key = key.split(' ').last
value = value.chomp.gsub(/'/, '')
creds[key] = value
end
return creds
rescue Exception => error
return {}
end
end
def self.get_credentials_from_env
ENV.to_hash.dup.delete_if { |key, _| ! (key =~ /^OS/) } # Ruby 1.8.7
end
def get_credentials_from_env
self.class.get_credentials_from_env
end
def self.keystone_file
keystone_file = Puppet::Util::IniConfig::File.new
keystone_file.read('/etc/keystone/keystone.conf')
keystone_file
end
def keystone_file
return @keystone_file if @keystone_file
@keystone_file = Puppet::Util::IniConfig::File.new
@keystone_file.read('/etc/keystone/keystone.conf')
@keystone_file
end
def self.get_admin_token_from_keystone_file(conf_file)
if conf_file and conf_file['DEFAULT'] and conf_file['DEFAULT']['admin_token']
return "#{conf_file['DEFAULT']['admin_token'].strip}"
else
return nil
end
end
def get_admin_token_from_keystone_file
conf_file = keystone_file
self.class.get_admin_token_from_keystone_file(conf_file)
end
def self.get_auth_url_from_keystone_file(conf_file)
if conf_file
if conf_file['DEFAULT']
if conf_file['DEFAULT']['admin_endpoint']
auth_url = conf_file['DEFAULT']['admin_endpoint'].strip
return versioned_endpoint(auth_url)
end
if conf_file['DEFAULT']['admin_port']
admin_port = conf_file['DEFAULT']['admin_port'].strip
else
admin_port = '35357'
end
if conf_file['DEFAULT']['admin_bind_host']
host = conf_file['DEFAULT']['admin_bind_host'].strip
if host == "0.0.0.0"
host = "127.0.0.1"
end
else
host = "127.0.0.1"
end
end
if conf_file['ssl'] && conf_file['ssl']['enable'] && conf_file['ssl']['enable'].strip.downcase == 'true'
protocol = 'https'
else
protocol = 'http'
end
end
"#{protocol}://#{host}:#{admin_port}/v2.0/"
end
def get_auth_url_from_keystone_file
self.class.get_auth_url_from_keystone_file(keystone_file)
end
def self.make_configuration(credentials)
host_uri = versioned_endpoint(credentials['host_uri'] || credentials['OS_AUTH_URL'], credentials['api_version'])
{
:provider => 'openstack',
:auth_service => {
:name => 'identity',
:host_uri => host_uri,
:request => 'create_token',
:validator => 'list_tenants',
},
:auth_credentials => {
:username => credentials['username'] || credentials['OS_USERNAME'],
:password => credentials['password'] || credentials['OS_PASSWORD'],
:tenant_name => credentials['tenant_name'] || credentials['OS_TENANT_NAME']
}
}
end
def self.get_authenticated_session(credentials, log_file)
configuration = make_configuration(credentials)
session = ::Aviator::Session.new(:config => configuration, :log_file => log_file)
session.authenticate
session
end
def get_authenticated_session(credentials, log_file)
self.class.get_authenticated_session(credentials, log_file)
end
def self.get_unauthenticated_session(credentials, log_file)
configuration = {
:provider => 'openstack',
}
session_data = {
:base_url => credentials['host_uri'],
:service_token => credentials['service_token']
}
session = ::Aviator::Session.new(:config => configuration, :log_file => log_file)
{ :session => session, :data => session_data }
end
def get_unauthenticated_session(credentials, log_file)
self.class.get_unauthenticated_session(credentials, log_file)
end
def self.versioned_endpoint(endpoint, version = 'v2.0')
version = 'v2.0' if version.nil?
if endpoint =~ /\/#{version}\/?$/ || endpoint =~ /\/v2.0\/?$/ || endpoint =~ /\/v3\/?$/
endpoint
else
"#{endpoint.chomp('/')}/#{version}"
end
end
end

View File

@@ -0,0 +1,46 @@
# Add the auth parameter to whatever type is given
module Puppet::Util::Aviator
def self.add_aviator_params(type)
type.newparam(:auth) do
desc <<EOT
Hash of authentication credentials. Credentials can be specified as
password credentials, e.g.:
auth => {
'username' => 'test',
'password' => 'passw0rd',
'tenant_name' => 'test',
'host_uri' => 'http://localhost:35357/v2.0',
}
or a path to an openrc file containing these credentials, e.g.:
auth => {
'openrc' => '/root/openrc',
}
or a service token and host, e.g.:
auth => {
'service_token' => 'ADMIN',
'host_uri' => 'http://localhost:35357/v2.0',
}
If not present, the provider will first look for environment variables
for password credentials and then to /etc/keystone/keystone.conf for a
service token.
EOT
validate do |value|
raise(Puppet::Error, 'This property must be a hash') unless value.is_a?(Hash)
end
end
type.newparam(:log_file) do
desc 'Log file. Defaults to no logging.'
defaultto('/dev/null')
end
end
end

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,36 @@
---
http_interactions:
- request:
method: get
uri: "http://192.168.11.4:35357/v2.0/tenants"
body:
encoding: US-ASCII
string: ""
headers:
Content-Type:
- application/json
User-Agent:
- "Faraday v0.8.8"
X-Auth-Token:
- sosp-kyl
response:
status:
code: 200
message:
headers:
vary:
- X-Auth-Token
content-type:
- application/json
content-length:
- "491"
date:
- "Tue, 30 Sep 2014 06:59:48 GMT"
connection:
- close
body:
encoding: UTF-8
string: "{\x22tenants_links\x22: [], \x22tenants\x22: [{\x22description\x22: \x22Test tenant\x22, \x22enabled\x22: true, \x22id\x22: \x2234e463e2bab24f78990ca864e4a28ba2\x22, \x22name\x22: \x22test2\x22}, {\x22description\x22: \x22Tenant for the openstack services\x22, \x22enabled\x22: true, \x22id\x22: \x2268c8fcf77aff4b409cc158c0f6cbff7b\x22, \x22name\x22: \x22services\x22}, {\x22description\x22: \x22Test tenant\x22, \x22enabled\x22: true, \x22id\x22: \x22c330f1bc663648df9c1e7835a1e7a955\x22, \x22name\x22: \x22test\x22}, {\x22description\x22: \x22admin tenant\x22, \x22enabled\x22: true, \x22id\x22: \x22c518b36fa220499b85ba9a71014ce2a5\x22, \x22name\x22: \x22admin\x22}]}"
http_version:
recorded_at: "Tue, 30 Sep 2014 06:59:48 GMT"
recorded_with: "VCR 2.9.3"

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,36 @@
---
http_interactions:
- request:
method: get
uri: "http://192.168.11.4:35357/v2.0/tenants"
body:
encoding: US-ASCII
string: ""
headers:
Content-Type:
- application/json
User-Agent:
- "Faraday v0.8.8"
X-Auth-Token:
- sosp-kyl
response:
status:
code: 200
message:
headers:
vary:
- X-Auth-Token
content-type:
- application/json
content-length:
- "491"
date:
- "Tue, 30 Sep 2014 06:59:48 GMT"
connection:
- close
body:
encoding: UTF-8
string: "{\x22tenants_links\x22: [], \x22tenants\x22: [{\x22description\x22: \x22Test tenant\x22, \x22enabled\x22: true, \x22id\x22: \x2234e463e2bab24f78990ca864e4a28ba2\x22, \x22name\x22: \x22test2\x22}, {\x22description\x22: \x22Tenant for the openstack services\x22, \x22enabled\x22: true, \x22id\x22: \x2268c8fcf77aff4b409cc158c0f6cbff7b\x22, \x22name\x22: \x22services\x22}, {\x22description\x22: \x22Test tenant\x22, \x22enabled\x22: true, \x22id\x22: \x22c330f1bc663648df9c1e7835a1e7a955\x22, \x22name\x22: \x22test\x22}, {\x22description\x22: \x22admin tenant\x22, \x22enabled\x22: true, \x22id\x22: \x22c518b36fa220499b85ba9a71014ce2a5\x22, \x22name\x22: \x22admin\x22}]}"
http_version:
recorded_at: "Tue, 30 Sep 2014 06:59:48 GMT"
recorded_with: "VCR 2.9.3"

View File

@@ -1 +1,7 @@
require 'puppetlabs_spec_helper/module_spec_helper'
require 'vcr'
VCR.configure do |c|
c.cassette_library_dir = 'spec/fixtures/vcr'
c.hook_into :faraday
end

View File

@@ -0,0 +1,320 @@
# Load libraries from aviator here to simulate how they live together in a real puppet run
$LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'modules', 'aviator', 'lib'))
require 'puppet'
require 'vcr'
require 'spec_helper'
require 'puppet/provider/aviator'
describe Puppet::Provider::Aviator do
before(:each) do
ENV['OS_USERNAME'] = nil
ENV['OS_PASSWORD'] = nil
ENV['OS_TENANT_NAME'] = nil
ENV['OS_AUTH_URL'] = nil
end
let(:log_file) { '/tmp/aviator_spec.log' }
let(:type) do
Puppet::Type.newtype(:test_resource) do
newparam(:name, :namevar => true)
newparam(:auth)
newparam(:log_file)
end
end
shared_examples 'creating a session using environment variables' do
it 'creates an authenticated session' do
ENV['OS_USERNAME'] = 'admin'
ENV['OS_PASSWORD'] = 'fyby-tet'
ENV['OS_TENANT_NAME'] = 'admin'
ENV['OS_AUTH_URL'] = 'http://192.168.11.4:35357/v2.0'
response = nil
VCR.use_cassette('aviator/session/with_password') do
session = provider.session
response = session.identity_service.request(:list_tenants, :session_data => provider.session_data)
end
expect(response.status).to eq(200)
end
end
shared_examples 'creating a session using a service token from keystone.conf' do
it 'creates an unauthenticated session' do
data = "[DEFAULT]\nadmin_token=sosp-kyl\nadmin_endpoint=http://192.168.11.4:35357/v2.0"
response = nil
VCR.use_cassette('aviator/session/with_token') do
# Stubbing File.read produces inconsistent results because of how IniConfig
# overrides the File class in some versions of Puppet.
# Stubbing FileType.filetype(:flat) simplifies working with IniConfig
Puppet::Util::FileType.filetype(:flat).any_instance.expects(:read).returns(StringIO.new(data).read)
session = provider.session
Puppet::Util::FileType.filetype(:flat).any_instance.unstub(:read)
response = session.identity_service.request(:list_tenants, :session_data => provider.session_data)
end
expect(response.status).to eq(200)
end
end
shared_examples 'it has no credentials' do
it 'fails to authenticate' do
expect{ provider.session }.to raise_error(Puppet::Error, /No credentials provided/)
end
end
shared_examples 'making request with an existing session' do
it 'makes a successful request' do
VCR.use_cassette('aviator/request/with_session') do
session = provider.session
response = provider.request(session.identity_service, :list_tenants)
expect(response.status).to eq(200)
end
end
end
shared_examples 'making request with injected session data' do
it 'makes a successful request' do
VCR.use_cassette('aviator/request/without_session') do
session = provider.session
response = provider.request(session.identity_service, :list_tenants)
expect(response.status).to eq(200)
end
end
end
shared_examples 'making request with no session or session data' do
it 'fails to make a request' do
expect{ provider.request(nil, :list_tenants) }.to raise_error(Puppet::Error, /Cannot make a request/)
end
end
describe '#session' do
context 'with valid password credentials in parameters' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'username' => 'admin',
'password' => 'fyby-tet',
'tenant_name' => 'admin',
'host_uri' => 'http://192.168.11.4:35357/v2.0',
}
}
end
it 'creates a session' do
provider = Puppet::Provider::Aviator.new(type.new(resource_attrs))
response = nil
VCR.use_cassette('aviator/session/with_password') do
session = provider.session
response = session.identity_service.request(:list_tenants)
end
expect(response.status).to eq(200)
end
end
context 'with valid openrc file in parameters' do
data = "export OS_USERNAME='admin'\nexport OS_PASSWORD='fyby-tet'\nexport OS_TENANT_NAME='admin'\nexport OS_AUTH_URL='http://192.168.11.4:35357/v2.0'"
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'openrc' => '/root/openrc'
}
}
end
it 'creates a session' do
provider = Puppet::Provider::Aviator.new(type.new(resource_attrs))
response = nil
VCR.use_cassette('aviator/session/with_password') do
File.expects(:open).with('/root/openrc').returns(StringIO.new(data))
session = provider.session
File.unstub(:open) # Ignore File.open calls to cassette file
response = session.identity_service.request(:list_tenants)
end
expect(response.status).to eq(200)
end
end
context 'with valid service token in parameters' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'service_token' => 'sosp-kyl',
'host_uri' => 'http://192.168.11.4:35357/v2.0'
}
}
end
subject(:session) do
provider = Puppet::Provider::Aviator.new(type.new(resource_attrs))
VCR.use_cassette('aviator/session/with_token') do
session = provider.session
response = session.identity_service.request(:list_tenants, :session_data => provider.session_data)
end
end
it 'creates a session' do
expect(session.status).to eq(200)
end
end
context 'with valid password credentials in environment variables' do
it_behaves_like 'creating a session using environment variables' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) do
Puppet::Provider::Aviator.new(type.new(resource_attrs))
end
end
end
context 'with valid service token in keystone.conf' do
it_behaves_like 'creating a session using a service token from keystone.conf' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) do
Puppet::Provider::Aviator.new(type.new(resource_attrs))
end
end
end
context 'with no valid credentials' do
it_behaves_like 'it has no credentials' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) { Puppet::Provider::Aviator.new(type.new(resource_attrs)) }
end
end
end
describe '::session' do
context 'with valid password credentials in environment variables' do
it_behaves_like 'creating a session using environment variables' do
let(:provider) { Puppet::Provider::Aviator.dup }
end
end
context 'with valid service token in keystone.conf' do
it_behaves_like 'creating a session using a service token from keystone.conf' do
let(:provider) { Puppet::Provider::Aviator.dup }
end
end
context 'with no valid credentials' do
it_behaves_like 'it has no credentials' do
let(:provider) { Puppet::Provider::Aviator.dup }
end
end
end
describe '#request' do
context 'when a session exists' do
it_behaves_like 'making request with an existing session' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'username' => 'admin',
'password' => 'fyby-tet',
'tenant_name' => 'admin',
'host_uri' => 'http://192.168.11.4:35357/v2.0',
}
}
end
let (:provider) { Puppet::Provider::Aviator.new(type.new(resource_attrs)) }
end
end
context 'when injecting session data' do
let(:resource_attrs) do
{
:name => 'stubresource',
:auth => {
'service_token' => 'sosp-kyl',
'host_uri' => 'http://192.168.11.4:35357/v2.0'
}
}
end
let(:provider) { Puppet::Provider::Aviator.new(type.new(resource_attrs)) }
it 'makes a successful request' do
provider = Puppet::Provider::Aviator.new(type.new(resource_attrs))
VCR.use_cassette('aviator/request/without_session') do
session = provider.session
response = provider.request(session.identity_service, :list_tenants)
expect(response.status).to eq(200)
end
end
end
context 'when there is no session or session data' do
it_behaves_like 'making request with no session or session data' do
let(:resource_attrs) do
{
:name => 'stubresource',
}
end
let(:provider) {Puppet::Provider::Aviator.new(type.new(resource_attrs)) }
end
end
end
describe '::request' do
context 'when a session exists' do
it_behaves_like 'making request with an existing session' do
let(:provider) { provider = Puppet::Provider::Aviator.dup }
before(:each) do
ENV['OS_USERNAME'] = 'admin'
ENV['OS_PASSWORD'] = 'fyby-tet'
ENV['OS_TENANT_NAME'] = 'admin'
ENV['OS_AUTH_URL'] = 'http://192.168.11.4:35357/v2.0'
end
end
end
context 'when injecting session data' do
let(:session_data) do
{
:base_url => 'http://192.168.11.4:35357/v2.0',
:service_token => 'sosp-kyl'
}
end
it 'makes a successful request' do
provider = Puppet::Provider::Aviator.dup
VCR.use_cassette('aviator/request/without_session') do
session = ::Aviator::Session.new(:config => { :provider => 'openstack' }, :log_file => log_file)
provider.session_data = session_data
response = provider.request(session.identity_service, :list_tenants)
expect(response.status).to eq(200)
end
end
end
context 'when there is no session or session data' do
it_behaves_like 'making request with no session or session data' do
let(:provider) { Puppet::Provider::Aviator.dup }
end
end
end
end