From 215103f7888d9b1bd01511e4e5016a853af44104 Mon Sep 17 00:00:00 2001 From: Alexey Deryugin Date: Thu, 17 Mar 2016 15:37:57 +0300 Subject: [PATCH] Add ability to create security groups from puppet Change-Id: I1835f121726e2b99bd6cc62e67849a0bc248ea4f --- lib/puppet/provider/nova.rb | 1 - .../provider/nova_security_group/nova.rb | 37 +++++ .../provider/nova_security_rule/nova.rb | 51 +++++++ lib/puppet/type/nova_security_group.rb | 66 +++++++++ lib/puppet/type/nova_security_rule.rb | 137 ++++++++++++++++++ spec/type/nova_security_group_spec.rb | 38 +++++ spec/type/nova_security_rule_spec.rb | 35 +++++ .../provider/nova_security_group/nova_spec.rb | 91 ++++++++++++ .../provider/nova_security_rule/nova_spec.rb | 48 ++++++ spec/unit/type/nova_security_group_spec.rb | 20 +++ spec/unit/type/nova_security_rule_spec.rb | 83 +++++++++++ 11 files changed, 606 insertions(+), 1 deletion(-) create mode 100644 lib/puppet/provider/nova_security_group/nova.rb create mode 100644 lib/puppet/provider/nova_security_rule/nova.rb create mode 100644 lib/puppet/type/nova_security_group.rb create mode 100644 lib/puppet/type/nova_security_rule.rb create mode 100644 spec/type/nova_security_group_spec.rb create mode 100644 spec/type/nova_security_rule_spec.rb create mode 100644 spec/unit/provider/nova_security_group/nova_spec.rb create mode 100644 spec/unit/provider/nova_security_rule/nova_spec.rb create mode 100644 spec/unit/type/nova_security_group_spec.rb create mode 100644 spec/unit/type/nova_security_rule_spec.rb diff --git a/lib/puppet/provider/nova.rb b/lib/puppet/provider/nova.rb index afe50e7c0..9eaa63742 100644 --- a/lib/puppet/provider/nova.rb +++ b/lib/puppet/provider/nova.rb @@ -241,5 +241,4 @@ class Puppet::Provider::Nova < Puppet::Provider end return list end - end diff --git a/lib/puppet/provider/nova_security_group/nova.rb b/lib/puppet/provider/nova_security_group/nova.rb new file mode 100644 index 000000000..8e70d804b --- /dev/null +++ b/lib/puppet/provider/nova_security_group/nova.rb @@ -0,0 +1,37 @@ +require File.join(File.dirname(__FILE__), '..','..','..', + 'puppet/provider/nova') + +Puppet::Type.type(:nova_security_group).provide( + :nova, + :parent => Puppet::Provider::Nova +) do + + desc "Manage nova security groups" + + commands :nova => 'nova' + + mk_resource_methods + + def exists? + sec_groups = self.class.cliout2list(auth_nova('secgroup-list')) + return sec_groups.detect do |n| + n['Name'] == resource['name'] + end + end + + def destroy + auth_nova("secgroup-delete", name) + @property_hash[:ensure] = :absent + end + + def create + result = self.class.cliout2list(auth_nova("secgroup-create", resource[:name], resource[:description])) + + @property_hash = { + :ensure => :present, + :name => resource[:name], + :id => result[0]['Id'], + :description => resource[:description] + } + end +end diff --git a/lib/puppet/provider/nova_security_rule/nova.rb b/lib/puppet/provider/nova_security_rule/nova.rb new file mode 100644 index 000000000..cca71b3b3 --- /dev/null +++ b/lib/puppet/provider/nova_security_rule/nova.rb @@ -0,0 +1,51 @@ +require File.join(File.dirname(__FILE__), '..','..','..', + 'puppet/provider/nova') + +Puppet::Type.type(:nova_security_rule).provide( + :nova, + :parent => Puppet::Provider::Nova +) do + + desc "Manage nova security rules" + + commands :nova => 'nova' + + mk_resource_methods + + def exists? + @property_hash[:ensure] == :present + end + + def destroy + args = Array.new + + args << "#{@resource[:security_group]}" + args << "#{@resource[:ip_protocol]}" + args << "#{@resource[:from_port]}" + args << "#{@resource[:to_port]}" + if not @resource[:ip_range].nil? + args << "#{@resource[:ip_range]}" + else + args << "#{@resource[:source_group]}" + end + + auth_nova("secgroup-delete-rule", args) + @property_hash[:ensure] = :absent + end + + def create + args = Array.new + + args << "#{@resource[:security_group]}" + args << "#{@resource[:ip_protocol]}" + args << "#{@resource[:from_port]}" + args << "#{@resource[:to_port]}" + if not @resource[:ip_range].nil? + args << "#{@resource[:ip_range]}" + else + args << "#{@resource[:source_group]}" + end + + result = auth_nova("secgroup-add-rule", args) + end +end diff --git a/lib/puppet/type/nova_security_group.rb b/lib/puppet/type/nova_security_group.rb new file mode 100644 index 000000000..4cc340b42 --- /dev/null +++ b/lib/puppet/type/nova_security_group.rb @@ -0,0 +1,66 @@ +# Copyright (C) 2016 Mirantis Inc. +# +# Author: Alexey Deryugin +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# nova_security_group type +# +# == Parameters +# [*name*] +# Name for the new security group +# Required +# +# [*description*] +# Description for the new security group +# Optional +# + + +require 'puppet' + +Puppet::Type.newtype(:nova_security_group) do + + @doc = "Manage creation of nova security groups." + + ensurable + + newparam(:name, :namevar => true) do + desc 'Name for the new security group' + validate do |value| + if not value.is_a? String + raise ArgumentError, "name parameter must be a String" + end + unless value =~ /^[a-zA-Z0-9\-_]+$/ + raise ArgumentError, "#{value} is not a valid name" + end + end + end + + newproperty(:id) do + desc 'The unique Id of the security group' + validate do |v| + raise ArgumentError, 'This is a read only property' + end + end + + newproperty(:description) do + desc "Description of the security group" + defaultto '' + end + + validate do + raise ArgumentError, 'Name type must be set' unless self[:name] + end + +end diff --git a/lib/puppet/type/nova_security_rule.rb b/lib/puppet/type/nova_security_rule.rb new file mode 100644 index 000000000..5c37afc73 --- /dev/null +++ b/lib/puppet/type/nova_security_rule.rb @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2016 Mirantis Inc. +# +# Author: Alexey Deryugin +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# nova_security_group type +# +# == Parameters +# [*ip_protocol*] +# IP protocol from new security rule +# Required +# +# [*from_port*] +# Port range begin for security rule +# Required +# +# [*to_port*] +# Port range end for security rule +# Required +# +# [*ip_range*] +# IP range for security rule +# Optional +# +# [*source_group*] +# Source group for security rule +# Optional +# +# [*security_group*] +# Target security group for security rule +# Required +# + + +require 'puppet' + +Puppet::Type.newtype(:nova_security_rule) do + + desc "Manage nova security rules" + + ensurable + + newparam(:name) do + isnamevar + end + + newparam(:ip_protocol) do + defaultto do + raise Puppet::Error, 'You should give protocol!' + end + newvalues 'tcp', 'udp', 'icmp' + end + + newparam(:from_port) do + defaultto do + raise Puppet::Error, 'You should give the source port!' + end + validate do |value| + if value !~ /\d+/ or value.to_i <= 0 or value.to_i >= 65536 + raise Puppet::Error, 'Incorrect from port!' + end + end + end + + newparam(:to_port) do + defaultto do + raise Puppet::Error, 'You should give the destination port!' + end + validate do |value| + if value !~ /\d+/ or value.to_i <= 0 or value.to_i >= 65536 + raise Puppet::Error, 'Incorrect to port!' + end + end + end + + newparam(:ip_range) do + + validate do |value| + def is_cidr_net?(value) + begin + address, mask = value.split('/') + return false unless address and mask + octets = address.split('.') + return false unless octets.length == 4 + + cidr = true + octets.each do |octet| + n = octet.to_i + cidr = false unless n <= 255 + cidr = false unless n >= 0 + break unless cidr + end + + cidr = false unless mask.to_i <= 32 + cidr = false unless mask.to_i >= 0 + cidr + rescue + false + end + end + + raise Puppet::Error, 'Incorrect ip_range!' unless is_cidr_net? value + end + end + + newparam(:source_group) do + end + + newparam(:security_group) do + defaultto do + raise Puppet::Error, 'You should provide the security group to add this rule to!' + end + end + + validate do + unless !!self[:ip_range] ^ !!self[:source_group] + raise Puppet::Error, 'You should give either ip_range or source_group. Not none or both!' + end + unless self[:from_port].to_i <= self[:to_port].to_i + raise Puppet::Error, 'From_port should be lesser or equal to to_port!' + end + end + +end diff --git a/spec/type/nova_security_group_spec.rb b/spec/type/nova_security_group_spec.rb new file mode 100644 index 000000000..764e5f4bc --- /dev/null +++ b/spec/type/nova_security_group_spec.rb @@ -0,0 +1,38 @@ +# run with: rspec spec/type/nova_security_group_spec.rb + +require 'spec_helper' + + +describe Puppet::Type.type(:nova_security_group) do + before :each do + @provider_class = described_class.provide(:simple) do + mk_resource_methods + def create; end + def delete; end + def exists?; get(:ensure) != :absent; end + def flush; end + end + end + + it "should be able to create an instance" do + expect(described_class.new(:name => 'scg0')).not_to be_nil + end + + it "should be able to create a more complex instance" do + expect(described_class.new(:name => 'scg0', + :description => "Security Group")).to_not be_nil + end + + it "should be able to create a instance and have the default values" do + c = described_class.new(:name => 'scg0') + expect(c[:name]).to eq("scg0") + expect(c[:description]).to eq('') + end + + it "should return the given values" do + c = described_class.new(:name => 'scg0', + :description => 'Security Group') + expect(c[:name]).to eq("agg0") + expect(c[:description]).to eq('Security Group') + end +end diff --git a/spec/type/nova_security_rule_spec.rb b/spec/type/nova_security_rule_spec.rb new file mode 100644 index 000000000..8319b82f5 --- /dev/null +++ b/spec/type/nova_security_rule_spec.rb @@ -0,0 +1,35 @@ +# run with: rspec spec/type/nova_security_rule_spec.rb + +require 'spec_helper' + + +describe Puppet::Type.type(:nova_security_rule) do + before :each do + @provider_class = described_class.provide(:simple) do + mk_resource_methods + def create; end + def delete; end + def exists?; get(:ensure) != :absent; end + def flush; end + end + end + + it "should be able to create an instance with ip range" do + expect(described_class.new(:name => 'scr0', + :ip_protocol => 'tcp', + :from_port => 22, + :to_port => 23, + :ip_range => "0.0.0.0/0", + :security_group => "scg0")).not_to be_nil + end + + it "should be able to create an instance with source group" do + expect(described_class.new(:name => 'scr0', + :ip_protocol => 'tcp', + :from_port => 22, + :to_port => 23, + :source_group => "tenant", + :security_group => "scg0")).not_to be_nil + end + +end diff --git a/spec/unit/provider/nova_security_group/nova_spec.rb b/spec/unit/provider/nova_security_group/nova_spec.rb new file mode 100644 index 000000000..b0b1bac6c --- /dev/null +++ b/spec/unit/provider/nova_security_group/nova_spec.rb @@ -0,0 +1,91 @@ +require 'puppet' +require 'puppet/provider/nova_security_group/nova' +require 'tempfile' + +provider_class = Puppet::Type.type(:nova_security_group).provider(:nova) + +describe provider_class do + + let :secgroup_attrs do + { + :name => "scg0", + :description => "Security Group", + } + end + + let :resource do + Puppet::Type::Nova_security_group.new(secgroup_attrs) + end + + let :provider do + provider_class.new(resource) + end + + shared_examples "nova_security_group" do + describe "#exists?" do + it 'should check non-existing security group' do + output = <<-EOT ++--------------------------------------+---------+------------------------+ +| Id | Name | Description | ++--------------------------------------+---------+------------------------+ +| f630dd92-3ff7-49bc-b012-b211451aa418 | default | Default security group | ++--------------------------------------+---------+------------------------+ +EOT + + provider.expects(:auth_nova).with('secgroup-list').returns(output) + + expect(provider.exists?).to be_falsey + end + + it 'should check existing security group' do + output = <<-EOT ++--------------------------------------+------+----------------+ +| Id | Name | Description | ++--------------------------------------+------+----------------+ +| f630dd92-3ff7-49bc-b012-b211451aa419 | scg0 | Security Group | ++--------------------------------------+------+----------------+ +EOT + + provider.expects(:auth_nova).with('secgroup-list').returns(output) + + expect(provider.exists?).to be_truthy + end + end + + + describe "#create" do + it 'should create security group' do + output = <<-EOT ++--------------------------------------+------+----------------+ +| Id | Name | Description | ++--------------------------------------+------+----------------+ +| f630dd92-3ff7-49bc-b012-b211451aa419 | scg0 | Security Group | ++--------------------------------------+------+----------------+ +EOT + + provider.expects(:auth_nova).with('secgroup-create', 'scg0', 'Security Group').returns(output) + + expect(provider.create).to be_truthy + end + end + + + describe "#destroy" do + it 'should destroy security group' do + output = <<-EOT ++--------------------------------------+------+----------------+ +| Id | Name | Description | ++--------------------------------------+------+----------------+ +| f630dd92-3ff7-49bc-b012-b211451aa419 | scg0 | Security Group | ++--------------------------------------+------+----------------+ +EOT + + provider.expects(:auth_nova).with('secgroup-delete', 'scg0').returns(output) + + expect(provider.destroy).to be_truthy + end + end + end + + it_behaves_like('nova_security_group') +end diff --git a/spec/unit/provider/nova_security_rule/nova_spec.rb b/spec/unit/provider/nova_security_rule/nova_spec.rb new file mode 100644 index 000000000..253094476 --- /dev/null +++ b/spec/unit/provider/nova_security_rule/nova_spec.rb @@ -0,0 +1,48 @@ +require 'puppet' +require 'puppet/provider/nova_security_rule/nova' +require 'tempfile' + +provider_class = Puppet::Type.type(:nova_security_rule).provider(:nova) + +describe provider_class do + + let :secrule_attrs do + { + :name => "scr0", + :ip_protocol => "tcp", + :from_port => '22', + :to_port => '23', + :ip_range => '0.0.0.0/0', + :security_group => 'scg0' + } + end + + let :resource do + Puppet::Type::Nova_security_rule.new(secrule_attrs) + end + + let :provider do + provider_class.new(resource) + end + + shared_examples "nova_security_rule" do + describe "#create" do + it 'should create security rule' do + provider.expects(:auth_nova).with('secgroup-add-rule', ['scg0', 'tcp', '22', '23', '0.0.0.0/0']).returns('+-------------+-----------+---------+-----------+--------------+\n| IP Protocol | From Port | To Port | IP Range | Source Group |\n+-------------+-----------+---------+-----------+--------------+\n| tcp | 22 | 23 | 0.0.0.0/0 | |\n+-------------+-----------+---------+-----------+--------------+') + + provider.create + end + end + + + describe "#destroy" do + it 'should destroy security rule' do + provider.expects(:auth_nova).with('secgroup-delete-rule', ['scg0', 'tcp', '22', '23', '0.0.0.0/0']).returns('+-------------+-----------+---------+-----------+--------------+\n| IP Protocol | From Port | To Port | IP Range | Source Group |\n+-------------+-----------+---------+-----------+--------------+\n| tcp | 22 | 23 | 0.0.0.0/0 | |\n+-------------+-----------+---------+-----------+--------------+') + + provider.destroy + end + end + end + + it_behaves_like('nova_security_rule') +end diff --git a/spec/unit/type/nova_security_group_spec.rb b/spec/unit/type/nova_security_group_spec.rb new file mode 100644 index 000000000..e8125fd6e --- /dev/null +++ b/spec/unit/type/nova_security_group_spec.rb @@ -0,0 +1,20 @@ +require 'puppet' +require 'puppet/type/nova_security_group' + +describe 'Puppet::Type.type(:nova_security_group)' do + + it 'should reject invalid name value' do + expect { Puppet::Type.type(:nova_security_group).new(:name => 65535) }.to raise_error(Puppet::Error, /name parameter must be a String/) + expect { Puppet::Type.type(:nova_security_group).new(:name => 'sc g0') }.to raise_error(Puppet::Error, /is not a valid name/) + end + + it 'should accept a valid name value' do + Puppet::Type.type(:nova_security_group).new(:name => 'scg0') + end + + it 'should accept description' do + Puppet::Type.type(:nova_security_group).new(:name => 'scg0', + :description => 'Security Group') + end + +end diff --git a/spec/unit/type/nova_security_rule_spec.rb b/spec/unit/type/nova_security_rule_spec.rb new file mode 100644 index 000000000..638282953 --- /dev/null +++ b/spec/unit/type/nova_security_rule_spec.rb @@ -0,0 +1,83 @@ +require 'puppet' +require 'puppet/type/nova_security_group' +describe 'Puppet::Type.type(:nova_security_group)' do + + it 'should reject an invalid ipv4 CIDR value' do + expect { Puppet::Type.type(:nova_security_rule).new(:name => 'scr0', + :ip_protocol => 'tcp', + :from_port => '22', + :to_port => '22', + :ip_range => '192.168.1.0', + :security_group => 'scg0') }.to raise_error(Puppet::Error, /Incorrect ip_range!/) + expect { Puppet::Type.type(:nova_security_rule).new(:name => 'scr0', + :ip_protocol => 'tcp', + :from_port => '22', + :to_port => '22', + :ip_range => '::1/24', + :security_group => 'scg0') }.to raise_error(Puppet::Error, /Incorrect ip_range!/) + end + + it 'should reject an invalid from port value' do + expect { Puppet::Type.type(:nova_security_rule).new(:name => 'scr0', + :ip_protocol => 'tcp', + :from_port => '-22', + :to_port => '22', + :ip_range => '192.168.1.0/24', + :security_group => 'scg0') }.to raise_error(Puppet::Error, /Incorrect from port!/) + expect { Puppet::Type.type(:nova_security_rule).new(:name => 'scr0', + :ip_protocol => 'tcp', + :to_port => '22', + :ip_range => '192.168.1.0/24', + :security_group => 'scg0') }.to raise_error(Puppet::Error, /You should give the source port/) + end + + it 'should reject an invalid from port value' do + expect { Puppet::Type.type(:nova_security_rule).new(:name => 'scr0', + :ip_protocol => 'tcp', + :from_port => '22', + :to_port => '-22', + :ip_range => '192.168.1.0/24', + :security_group => 'scg0') }.to raise_error(Puppet::Error, /Incorrect to port!/) + expect { Puppet::Type.type(:nova_security_rule).new(:name => 'scr0', + :ip_protocol => 'tcp', + :from_port => '22', + :ip_range => '192.168.1.0/24', + :security_group => 'scg0') }.to raise_error(Puppet::Error, /You should give the destination port/) + end + + it 'should fails with security group not specified' do + expect { Puppet::Type.type(:nova_security_rule).new(:name => 'scr0', + :ip_protocol => 'tcp', + :from_port => '22', + :to_port => '22', + :ip_range => '192.168.1.0/24') }.to raise_error(Puppet::Error, /You should provide the security group/) + end + + it 'should fails with none of ip_range and source_group specified' do + expect { Puppet::Type.type(:nova_security_rule).new(:name => 'scr0', + :ip_protocol => 'tcp', + :from_port => '22', + :to_port => '22', + :security_group => 'scg0') }.to raise_error(Puppet::Error, /You should give either ip_range or source_group/) + end + + it 'should fails with both ip_range and source group specified' do + expect { Puppet::Type.type(:nova_security_rule).new(:name => 'scr0', + :ip_protocol => 'tcp', + :from_port => '22', + :to_port => '22', + :ip_range => '192.168.1.0/24', + :source_group => 'tenant', + :security_group => 'scg0') }.to raise_error(Puppet::Error, /You should give either ip_range or source_group/) + end + + + it 'should accept a valid parameters' do + Puppet::Type.type(:nova_security_rule).new(:name => 'scr0', + :ip_protocol => 'tcp', + :from_port => '22', + :to_port => '22', + :ip_range => '192.168.1.0/24', + :security_group => 'scg0') + end +end