Application resource implementation

Change-Id: I5627f94d4fc0d0fa1a63968cfa7ed67350fcc769
This commit is contained in:
Alexey Deryugin 2015-09-03 20:01:50 +03:00
parent 28d2663030
commit 914a2d9c69
8 changed files with 414 additions and 0 deletions

View File

@ -0,0 +1,87 @@
require 'puppet/util/inifile'
class Puppet::Provider::Murano < Puppet::Provider
def self.conf_filename
'/etc/murano/murano.conf'
end
def self.withenv(hash, &block)
saved = ENV.to_hash
hash.each do |name, val|
ENV[name.to_s] = val
end
yield
ensure
ENV.clear
saved.each do |name, val|
ENV[name] = val
end
end
def self.murano_conf
return @murano_conf if @murano_conf
@murano_conf = Puppet::Util::IniConfig::File.new
@murano_conf.read(conf_filename)
@murano_conf
end
def self.murano_credentials
@murano_credentials ||= get_murano_credentials
end
def murano_credentials
self.class.murano_credentials
end
def self.get_murano_credentials
#needed keys for authentication
auth_keys = ['auth_uri', 'admin_tenant_name', 'admin_user', 'admin_password']
conf = murano_conf
if conf and conf['keystone_authtoken'] and
auth_keys.all?{|k| !conf['keystone_authtoken'][k].nil?}
return Hash[ auth_keys.map { |k| [k, conf['keystone_authtoken'][k].strip] } ]
else
raise(Puppet::Error, "File: #{conf_filename} does not contain all " +
'required sections. Murano types will not work if murano is not ' +
'correctly configured.')
end
end
def self.auth_murano(*args)
m = murano_credentials
authenv = {
:OS_AUTH_URL => m['auth_uri'],
:OS_USERNAME => m['admin_user'],
:OS_TENANT_NAME => m['admin_tenant_name'],
:OS_PASSWORD => m['admin_password'],
:OS_ENDPOINT_TYPE => 'internalURL'
}
begin
withenv authenv do
murano(args)
end
rescue Exception => e
if (e.message =~ /\[Errno 111\] Connection refused/) or
(e.message =~ /\(HTTP 400\)/)
sleep 10
withenv authenv do
murano(args)
end
else
raise(e)
end
end
end
def auth_murano(*args)
self.class.auth_murano(args)
end
def self.reset
@murano_conf = nil
@murano_credentials = nil
end
end

View File

@ -0,0 +1,38 @@
require File.join(File.dirname(__FILE__), '..','..','..',
'puppet/provider/murano')
Puppet::Type.type(:murano_application).provide(
:murano,
:parent => Puppet::Provider::Murano
) do
desc 'Manage murano applications'
commands :murano => 'murano'
mk_resource_methods
def exists?
packages = auth_murano('package-list')
return packages.split("\n")[1..-1].detect do |n|
n =~ /^(\S+)\s+(#{resource[:name]})/
end
end
def destroy
auth_murano('package-delete', resource[:name])
end
def create
opts = [ resource[:package_path] ]
unless resource[:category].nil?
opts.push('-c').push(resource[:category])
end
opts.push('--is-public').push('--exists-action').push('u')
auth_murano('package-import', opts)
end
end

View File

@ -0,0 +1,60 @@
# murano_application type
#
# == Parameters
# [*name*]
# Name for the new application
# Required
#
# [*package_path*]
# Path to package file
# Required
#
# [*category*]
# Category for the new application
# Optional
#
require 'puppet'
Puppet::Type.newtype(:murano_application) do
@doc = 'Manage creation of Murano applications.'
ensurable
newparam(:name, :namevar => true) do
desc 'Name for the new application'
validate do |value|
unless value.is_a? String
raise ArgumentError, 'name parameter must be a String'
end
unless value =~ /^[a-z0-9\.\-_]+$/
raise ArgumentError, "#{value} is not a valid name"
end
end
end
newproperty(:package_path) do
desc 'Path to package file'
validate do |value|
unless value.is_a? String
raise ArgumentError, 'package_path parameter must be a String'
end
end
newvalues(/\S+/)
end
newproperty(:category) do
desc 'Package category'
validate do |value|
unless value.is_a? String
raise ArgumentError, 'category parameter must be a String'
end
end
end
validate do
raise ArgumentError, 'Name and package path must be set' unless self[:name] and self[:package_path]
end
end

32
manifests/application.pp Normal file
View File

@ -0,0 +1,32 @@
# == Resource: murano::application
#
# murano application importer
#
# === Parameters
#
# [*package_ensure*]
# (Optional) Ensure state for package
# Defaults to 'present'
#
# [*package_name*]
# (Optional) Application package name
# Defaults to $title
#
# [*package_category*]
# (Optional) Application category
# Defaults to 'undef'
#
define murano::application (
$package_ensure = 'present',
$package_name = $title,
$package_category = undef,
) {
$package_path="/var/cache/murano/meta/${package_name}.zip"
murano_application { $package_name:
ensure => $package_ensure,
package_path => $package_path,
category => $package_category,
}
}

View File

@ -0,0 +1,27 @@
require 'spec_helper'
describe 'murano::application' do
let(:title) { 'io.murano' }
describe 'with default parameters' do
it { is_expected.to contain_murano_application('io.murano').with(
:ensure => 'present',
:package_path => '/var/cache/murano/meta/io.murano.zip'
)}
end
describe 'with package_category override' do
let :params do {
:package_category => 'library'
}
end
it { is_expected.to contain_murano_application('io.murano').with(
:ensure => 'present',
:package_path => '/var/cache/murano/meta/io.murano.zip',
:category => 'library',
)}
end
end

View File

@ -0,0 +1,62 @@
require 'puppet'
require 'puppet/provider/murano_application/murano'
require 'tempfile'
provider_class = Puppet::Type.type(:murano_application).provider(:murano)
describe provider_class do
let :app_attrs do
{
:name => 'io.murano',
:package_path => '/tmp/io.murano.zip',
:ensure => 'present',
}
end
let :resource do
Puppet::Type::Murano_application.new(app_attrs)
end
let :provider do
provider_class.new(resource)
end
shared_examples 'murano_application' do
describe '#exists?' do
it 'should check existsing application' do
provider.class.stubs(:application_exists?).returns(true)
provider.expects(:auth_murano).with("package-list")
.returns('"+----------------------------------+--------------------+----------------------------------------+---------------+-----------+\n| ID | Name | FQN | Author | Is Public |\n+----------------------------------+--------------------+----------------------------------------+---------------+-----------+\n| 9a23e4aea548462d82b66f2aee0f196e | Core library | io.murano | murano.io | True |\n+----------------------------------+--------------------+----------------------------------------+---------------+-----------+\n"')
provider.exists?
end
it 'should check non-existsing application' do
resource[:name] = 'io.murano.qwe'
provider.class.stubs(:application_exists?).returns(false)
provider.expects(:auth_murano).with("package-list")
.returns('"+----------------------------------+--------------------+----------------------------------------+---------------+-----------+\n| ID | Name | FQN | Author | Is Public |\n+----------------------------------+--------------------+----------------------------------------+---------------+-----------+\n| 9a23e4aea548462d82b66f2aee0f196e | Core library | io.murano | murano.io | True |\n+----------------------------------+--------------------+----------------------------------------+---------------+-----------+\n"')
provider.exists?
end
end
describe '#create' do
it 'should create application' do
provider.expects(:auth_murano).with("package-import", ['/tmp/io.murano.zip', '--is-public', '--exists-action', 'u'] )
.returns('')
provider.create
end
end
describe '#destroy' do
it 'should destroy application' do
resource[:ensure] = :absent
provider.expects(:auth_murano).with("package-delete", 'io.murano')
.returns('')
provider.destroy
end
end
end
it_behaves_like('murano_application')
end

View File

@ -0,0 +1,83 @@
require 'puppet'
require 'spec_helper'
require 'puppet/provider/murano'
require 'rspec/mocks'
describe Puppet::Provider::Murano do
def klass
described_class
end
let :credential_hash do
{
'auth_uri' => 'https://192.168.56.210:35357',
'admin_tenant_name' => 'admin_tenant',
'admin_user' => 'admin',
'admin_password' => 'password',
}
end
let :credential_error do
/Murano types will not work/
end
after :each do
klass.reset
end
describe 'when determining credentials' do
it 'should fail if config is empty' do
conf = {}
klass.expects(:murano_conf).returns(conf)
expect do
klass.murano_credentials
end.to raise_error(Puppet::Error, credential_error)
end
it 'should fail if config does not have keystone_authtoken section.' do
conf = {'foo' => 'bar'}
klass.expects(:murano_conf).returns(conf)
expect do
klass.murano_credentials
end.to raise_error(Puppet::Error, credential_error)
end
it 'should fail if config does not contain all auth params' do
conf = {'keystone_authtoken' => {'invalid_value' => 'foo'}}
klass.expects(:murano_conf).returns(conf)
expect do
klass.murano_credentials
end.to raise_error(Puppet::Error, credential_error)
end
end
describe 'when invoking the murano cli' do
it 'should set auth credentials in the environment' do
authenv = {
:OS_AUTH_URL => credential_hash['auth_uri'],
:OS_USERNAME => credential_hash['admin_user'],
:OS_TENANT_NAME => credential_hash['admin_tenant_name'],
:OS_PASSWORD => credential_hash['admin_password'],
:OS_ENDPOINT_TYPE => 'internalURL',
}
klass.expects(:get_murano_credentials).with().returns(credential_hash)
klass.expects(:withenv).with(authenv)
klass.auth_murano('test_retries')
end
['[Errno 111] Connection refused',
'(HTTP 400)'].reverse.each do |valid_message|
it "should retry when murano cli returns with error #{valid_message}" do
klass.expects(:get_murano_credentials).with().returns({})
klass.expects(:sleep).with(10).returns(nil)
klass.expects(:murano).twice.with(['test_retries']).raises(
Exception, valid_message).then.returns('')
klass.auth_murano('test_retries')
end
end
end
end

View File

@ -0,0 +1,25 @@
require 'puppet'
require 'puppet/type/murano_application'
describe 'Puppet::Type.type(:murano_application)' do
it 'should fail without package path' do
expect { Puppet::Type.type(:murano_application).new(:name => 'io.murano') }.to raise_error(Puppet::Error, /Name and package path must be set/)
end
it 'should reject an invalid name' do
expect { Puppet::Type.type(:murano_application).new(:name => 8082, :package_path => '/tmp/io.zip') }.to raise_error(Puppet::Error, /name parameter must be a String/)
expect { Puppet::Type.type(:murano_application).new(:name => 'io.murano.@', :package_path => '/tmp/io.zip') }.to raise_error(Puppet::Error, /is not a valid name/)
end
it 'should reject an invalid package path' do
expect { Puppet::Type.type(:murano_application).new(:name => 'io.murano', :package_path => 8082) }.to raise_error(Puppet::Error, /package_path parameter must be a String/)
end
it 'should reject an invalid category' do
expect { Puppet::Type.type(:murano_application).new(:name => 'io.murano', :package_path => '/tmp/io.zip', :category => 8082) }.to raise_error(Puppet::Error, /category parameter must be a String/)
end
it 'should accept valid parameters' do
Puppet::Type.type(:murano_application).new(:name => 'io.murano', :package_path => '/tmp/io.zip', :category => 'library')
end
end