From 914a2d9c6962170a20db372301034ddcfc5ff310 Mon Sep 17 00:00:00 2001 From: Alexey Deryugin Date: Thu, 3 Sep 2015 20:01:50 +0300 Subject: [PATCH] Application resource implementation Change-Id: I5627f94d4fc0d0fa1a63968cfa7ed67350fcc769 --- lib/puppet/provider/murano.rb | 87 +++++++++++++++++++ .../provider/murano_application/murano.rb | 38 ++++++++ lib/puppet/type/murano_application.rb | 60 +++++++++++++ manifests/application.pp | 32 +++++++ spec/defines/murano_application_spec.rb | 27 ++++++ .../murano_application/murano_spec.rb | 62 +++++++++++++ spec/unit/provider/murano_spec.rb | 83 ++++++++++++++++++ spec/unit/type/murano_application_spec.rb | 25 ++++++ 8 files changed, 414 insertions(+) create mode 100644 lib/puppet/provider/murano.rb create mode 100644 lib/puppet/provider/murano_application/murano.rb create mode 100644 lib/puppet/type/murano_application.rb create mode 100644 manifests/application.pp create mode 100644 spec/defines/murano_application_spec.rb create mode 100644 spec/unit/provider/murano_application/murano_spec.rb create mode 100644 spec/unit/provider/murano_spec.rb create mode 100644 spec/unit/type/murano_application_spec.rb diff --git a/lib/puppet/provider/murano.rb b/lib/puppet/provider/murano.rb new file mode 100644 index 0000000..7e9582a --- /dev/null +++ b/lib/puppet/provider/murano.rb @@ -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 diff --git a/lib/puppet/provider/murano_application/murano.rb b/lib/puppet/provider/murano_application/murano.rb new file mode 100644 index 0000000..c0cda82 --- /dev/null +++ b/lib/puppet/provider/murano_application/murano.rb @@ -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 diff --git a/lib/puppet/type/murano_application.rb b/lib/puppet/type/murano_application.rb new file mode 100644 index 0000000..57356a1 --- /dev/null +++ b/lib/puppet/type/murano_application.rb @@ -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 \ No newline at end of file diff --git a/manifests/application.pp b/manifests/application.pp new file mode 100644 index 0000000..46421c4 --- /dev/null +++ b/manifests/application.pp @@ -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, + } +} diff --git a/spec/defines/murano_application_spec.rb b/spec/defines/murano_application_spec.rb new file mode 100644 index 0000000..9728064 --- /dev/null +++ b/spec/defines/murano_application_spec.rb @@ -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 diff --git a/spec/unit/provider/murano_application/murano_spec.rb b/spec/unit/provider/murano_application/murano_spec.rb new file mode 100644 index 0000000..799bf7f --- /dev/null +++ b/spec/unit/provider/murano_application/murano_spec.rb @@ -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 diff --git a/spec/unit/provider/murano_spec.rb b/spec/unit/provider/murano_spec.rb new file mode 100644 index 0000000..588f82d --- /dev/null +++ b/spec/unit/provider/murano_spec.rb @@ -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 diff --git a/spec/unit/type/murano_application_spec.rb b/spec/unit/type/murano_application_spec.rb new file mode 100644 index 0000000..4d43b3a --- /dev/null +++ b/spec/unit/type/murano_application_spec.rb @@ -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