From b227c9243d74f51c3183f99e576b6db2ed91fc8d Mon Sep 17 00:00:00 2001 From: Gareth Rushgrove Date: Mon, 8 Sep 2014 13:53:06 -0700 Subject: [PATCH] initial working commit --- .rspec | 2 + .rubocop.yml | 11 ++ .travis.yml | 17 ++ Gemfile | 20 +++ Gemfile.lock | 167 ++++++++++++++++++++ README.md | 21 ++- Rakefile | 12 ++ lib/puppet/feature/aws.rb | 3 + lib/puppet/feature/retries.rb | 3 + lib/puppet/provider/ec2_instance/v2.rb | 26 +++ lib/puppet/provider/ec2_securitygroup/v2.rb | 77 +++++++++ lib/puppet/provider/elb_loadbalancer/v2.rb | 26 +++ lib/puppet/type/ec2_instance.rb | 25 +++ lib/puppet/type/ec2_securitygroup.rb | 24 +++ lib/puppet/type/elb_loadbalancer.rb | 26 +++ spec/spec_helper.rb | 1 + tests/init.pp | 63 ++++++++ 17 files changed, 521 insertions(+), 3 deletions(-) create mode 100644 .rspec create mode 100644 .rubocop.yml create mode 100644 .travis.yml create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 Rakefile create mode 100644 lib/puppet/feature/aws.rb create mode 100644 lib/puppet/feature/retries.rb create mode 100644 lib/puppet/provider/ec2_instance/v2.rb create mode 100644 lib/puppet/provider/ec2_securitygroup/v2.rb create mode 100644 lib/puppet/provider/elb_loadbalancer/v2.rb create mode 100644 lib/puppet/type/ec2_instance.rb create mode 100644 lib/puppet/type/ec2_securitygroup.rb create mode 100644 lib/puppet/type/elb_loadbalancer.rb create mode 100644 spec/spec_helper.rb create mode 100644 tests/init.pp diff --git a/.rspec b/.rspec new file mode 100644 index 00000000..8c18f1ab --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--format documentation +--color diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..76b88cf9 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,11 @@ +AllCops: + Include: + - Gemfile + - Guardfile + - Rakefile + +Documentation: + Enabled: false + +Encoding: + Enabled: false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..9b623370 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +--- +language: ruby +bundler_args: --without development +before_install: rm Gemfile.lock || true +rvm: + - 1.8.7 + - 1.9.3 + - 2.1.1 + - jruby-19mode +script: bundle exec rake test +env: + - PUPPET_VERSION="~> 3.6.0" + - PUPPET_VERSION="~> 3.7.0" +matrix: + allow_failures: + - rvm: jruby-19mode + - rvm: 1.8.7 diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..6c3f9a04 --- /dev/null +++ b/Gemfile @@ -0,0 +1,20 @@ +source 'https://rubygems.org' + +gem 'retries' +gem 'aws-sdk-core', '~> 2.0.0.rc' + +group :test do + gem 'rake' + gem 'puppet', ENV['PUPPET_VERSION'] || '~> 3.7.0' + gem 'puppetlabs_spec_helper' + gem 'webmock' +end + +group :development do + gem 'travis' + gem 'travis-lint' + gem 'puppet-blacksmith' + gem 'guard-rake' + gem 'rubocop', require: false + gem 'pry' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..801bd0c0 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,167 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (2.2.8) + addressable (2.3.6) + ast (2.0.0) + astrolabe (1.3.0) + parser (>= 2.2.0.pre.3, < 3.0) + aws-sdk-core (2.0.0.rc15) + builder (~> 3.0) + jamespath (~> 0) + multi_json (~> 1.0) + multi_xml (~> 0.5) + backports (3.6.0) + builder (3.2.2) + celluloid (0.15.2) + timers (~> 1.1.0) + coderay (1.1.0) + crack (0.4.2) + safe_yaml (~> 1.0.0) + diff-lcs (1.2.5) + ethon (0.7.1) + ffi (>= 1.3.0) + facter (2.2.0) + CFPropertyList (~> 2.2.6) + faraday (0.9.0) + multipart-post (>= 1.2, < 3) + faraday_middleware (0.9.1) + faraday (>= 0.7.4, < 0.10) + ffi (1.9.3) + formatador (0.2.5) + gh (0.13.2) + addressable + backports + faraday (~> 0.8) + multi_json (~> 1.0) + net-http-persistent (>= 2.7) + net-http-pipeline + guard (2.6.1) + formatador (>= 0.2.4) + listen (~> 2.7) + lumberjack (~> 1.0) + pry (>= 0.9.12) + thor (>= 0.18.1) + guard-rake (0.0.9) + guard + rake + hiera (1.3.4) + json_pure + highline (1.6.21) + jamespath (0.5.0) + json (1.8.1) + json_pure (1.8.1) + launchy (2.4.2) + addressable (~> 2.3) + listen (2.7.9) + celluloid (>= 0.15.2) + rb-fsevent (>= 0.9.3) + rb-inotify (>= 0.9) + lumberjack (1.0.9) + metaclass (0.0.4) + method_source (0.8.2) + mime-types (1.25.1) + mocha (1.1.0) + metaclass (~> 0.0.1) + multi_json (1.10.1) + multi_xml (0.5.5) + multipart-post (2.0.0) + net-http-persistent (2.9.4) + net-http-pipeline (1.0.1) + netrc (0.7.7) + nokogiri (1.5.11) + parser (2.2.0.pre.4) + ast (>= 1.1, < 3.0) + slop (~> 3.4, >= 3.4.5) + powerpack (0.0.9) + pry (0.9.12.6) + coderay (~> 1.0) + method_source (~> 0.8) + slop (~> 3.4) + puppet (3.7.0) + facter (> 1.6, < 3) + hiera (~> 1.0) + json_pure + puppet-blacksmith (2.3.1) + nokogiri + puppet (>= 2.7.16) + rest-client + puppet-lint (1.0.1) + puppet-syntax (1.3.0) + rake + puppetlabs_spec_helper (0.8.1) + mocha + puppet-lint + puppet-syntax + rake + rspec + rspec-puppet + pusher-client (0.6.0) + json + websocket (~> 1.0) + rainbow (2.0.0) + rake (10.3.2) + rb-fsevent (0.9.4) + rb-inotify (0.9.5) + ffi (>= 0.5.0) + rest-client (1.7.2) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) + retries (0.0.5) + rspec (2.99.0) + rspec-core (~> 2.99.0) + rspec-expectations (~> 2.99.0) + rspec-mocks (~> 2.99.0) + rspec-core (2.99.2) + rspec-expectations (2.99.2) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.99.2) + rspec-puppet (1.0.1) + rspec + rubocop (0.26.0) + astrolabe (~> 1.3) + parser (>= 2.2.0.pre.4, < 3.0) + powerpack (~> 0.0.6) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.4) + ruby-progressbar (1.5.1) + safe_yaml (1.0.3) + slop (3.6.0) + thor (0.19.1) + timers (1.1.0) + travis (1.7.1) + addressable (~> 2.3) + backports + faraday (~> 0.9) + faraday_middleware (~> 0.9, >= 0.9.1) + gh (~> 0.13) + highline (~> 1.6) + launchy (~> 2.1) + pry (~> 0.9, < 0.10) + pusher-client (~> 0.4) + typhoeus (~> 0.6, >= 0.6.8) + travis-lint (2.0.0) + json + typhoeus (0.6.9) + ethon (>= 0.7.1) + webmock (1.18.0) + addressable (>= 2.3.6) + crack (>= 0.3.2) + websocket (1.2.1) + +PLATFORMS + ruby + +DEPENDENCIES + aws-sdk-core (~> 2.0.0.rc) + guard-rake + pry + puppet (~> 3.7.0) + puppet-blacksmith + puppetlabs_spec_helper + rake + retries + rubocop + travis + travis-lint + webmock diff --git a/README.md b/README.md index 9ca8d6a0..e86f5651 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,19 @@ -puppetlabs-aws -============== - Puppet module for managing AWS resources to build out full stacks + +> Note that this repository contains a work-in-progress proof of +> concept. + +## Usage + +First, set a couple of environment variables with your AWS credentials. + +``` +export AWS_ACCESS_KEY_ID=your_access_key_id +export AWS_SECRET_ACCESS_KEY=your_secret_access_key +``` + +Running the sample code with Puppet apply: + +```bash +puppet apply tests/init.pp --modulepath ../ --test +``` diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..58362586 --- /dev/null +++ b/Rakefile @@ -0,0 +1,12 @@ +require 'puppetlabs_spec_helper/rake_tasks' + +begin + require 'puppet_blacksmith/rake_tasks' +rescue LoadError +end + +begin + require 'rubocop/rake_task' + RuboCop::RakeTask.new +rescue LoadError +end diff --git a/lib/puppet/feature/aws.rb b/lib/puppet/feature/aws.rb new file mode 100644 index 00000000..9e58c795 --- /dev/null +++ b/lib/puppet/feature/aws.rb @@ -0,0 +1,3 @@ +require 'puppet/util/feature' + +Puppet.features.add(:aws, libs: 'aws-sdk-core') diff --git a/lib/puppet/feature/retries.rb b/lib/puppet/feature/retries.rb new file mode 100644 index 00000000..1b0bf8ee --- /dev/null +++ b/lib/puppet/feature/retries.rb @@ -0,0 +1,3 @@ +require 'puppet/util/feature' + +Puppet.features.add(:retries, libs: 'retries') diff --git a/lib/puppet/provider/ec2_instance/v2.rb b/lib/puppet/provider/ec2_instance/v2.rb new file mode 100644 index 00000000..e99b88ca --- /dev/null +++ b/lib/puppet/provider/ec2_instance/v2.rb @@ -0,0 +1,26 @@ +require 'aws-sdk-core' + +module Puppet + class Provider + class Ec2Instance < Puppet::Provider + def initialize(*args) + super(*args) + end + + def exists? + Puppet.debug("Checking if instance #{resource[:name]} exists") + end + + def create + end + + def destroy + end + end + end +end + +Puppet::Type.type(:ec2_instance).provide(:v2, + parent: Puppet::Provider::Ec2Instance) do + confine feature: :aws +end diff --git a/lib/puppet/provider/ec2_securitygroup/v2.rb b/lib/puppet/provider/ec2_securitygroup/v2.rb new file mode 100644 index 00000000..9c5f3f7f --- /dev/null +++ b/lib/puppet/provider/ec2_securitygroup/v2.rb @@ -0,0 +1,77 @@ +require 'aws-sdk-core' +require 'retries' +require 'pry' + +module Puppet + class Provider + class Ec2Securitygroup < Puppet::Provider + def initialize(*args) + @client = Aws::EC2::Client.new(region: 'us-west-2') + super(*args) + end + + def exists? + begin + @client.describe_security_groups(group_names: [name]) + Puppet.info("Security group #{name} exists") + rescue Aws::EC2::Errors::InvalidGroupNotFound + Puppet.info("Security group #{name} doesn't exist") + false + end + end + + def create + Puppet.info("Creating security group #{name}") + @client.create_security_group( + group_name: name, + description: resource[:description] + ) + + rules = resource[:ingress] + rules = [rules] unless rules.is_a?(Array) + + permissions = [] + source = false + rules.each do |rule| + if rule.key? 'source' + source = rule['source'].title + else + permissions << { + ip_protocol: rule['protocol'], + to_port: rule['port'].to_i, + from_port: rule['port'].to_i, + ip_ranges: [{ + cidr_ip: rule['cidr'] + }] + } + end + end + + if source + @client.authorize_security_group_ingress( + group_name: name, + source_security_group_name: source + ) + elsif permissions + @client.authorize_security_group_ingress( + group_name: name, + ip_permissions: permissions + ) + end + end + + def destroy + Puppet.info("Deleting security group #{name}") + @client.delete_security_group( + group_name: name + ) + end + end + end +end + +Puppet::Type.type(:ec2_securitygroup).provide(:v2, + parent: Puppet::Provider::Ec2Securitygroup) do + confine feature: :aws + confine feature: :retries +end diff --git a/lib/puppet/provider/elb_loadbalancer/v2.rb b/lib/puppet/provider/elb_loadbalancer/v2.rb new file mode 100644 index 00000000..48982c95 --- /dev/null +++ b/lib/puppet/provider/elb_loadbalancer/v2.rb @@ -0,0 +1,26 @@ +require 'aws-sdk-core' + +module Puppet + class Provider + class ElbLoadbalancer < Puppet::Provider + def initialize(*args) + super(*args) + end + + def exists? + Puppet.debug("Checking if load balancer #{resource[:name]} exists") + end + + def create + end + + def destroy + end + end + end +end + +Puppet::Type.type(:elb_loadbalancer).provide(:v2, + parent: Puppet::Provider::ElbLoadbalancer) do + confine feature: :aws +end diff --git a/lib/puppet/type/ec2_instance.rb b/lib/puppet/type/ec2_instance.rb new file mode 100644 index 00000000..2e7473e0 --- /dev/null +++ b/lib/puppet/type/ec2_instance.rb @@ -0,0 +1,25 @@ +Puppet::Type.newtype(:ec2_instance) do + @doc = 'type representing an EC2 instance' + + ensurable + + newparam(:name, namevar: true) do + desc 'the name of the instance' + validate do |value| + fail Puppet::Error, 'Should not contains spaces' if value =~ /\s/ + fail Puppet::Error, 'Empty values are not allowed' if value == '' + end + end + + newparam(:security_groups) do + desc 'the security groups to associate the instance' + end + + newparam(:image_id) do + desc 'the image id to use for the instance' + validate do |value| + fail Puppet::Error, 'Should not contains spaces' if value =~ /\s/ + fail Puppet::Error, 'Empty values are not allowed' if value == '' + end + end +end diff --git a/lib/puppet/type/ec2_securitygroup.rb b/lib/puppet/type/ec2_securitygroup.rb new file mode 100644 index 00000000..34b5d73b --- /dev/null +++ b/lib/puppet/type/ec2_securitygroup.rb @@ -0,0 +1,24 @@ +Puppet::Type.newtype(:ec2_securitygroup) do + @doc = 'type representing an EC2 security group' + + ensurable + + newparam(:name, namevar: true) do + desc 'the name of the security group' + validate do |value| + fail Puppet::Error, 'Should not contains spaces' if value =~ /\s/ + fail Puppet::Error, 'Empty values are not allowed' if value == '' + end + end + + newparam(:ingress) do + desc 'rules for ingress traffic' + end + + newparam(:description) do + desc 'a short description of the group' + validate do |value| + fail Puppet::Error, 'Empty values are not allowed' if value == '' + end + end +end diff --git a/lib/puppet/type/elb_loadbalancer.rb b/lib/puppet/type/elb_loadbalancer.rb new file mode 100644 index 00000000..450f9dc7 --- /dev/null +++ b/lib/puppet/type/elb_loadbalancer.rb @@ -0,0 +1,26 @@ +Puppet::Type.newtype(:elb_loadbalancer) do + @doc = 'type representing an ELB load balancer' + + ensurable + + newparam(:name, namevar: true) do + desc 'the name of the load balancer' + validate do |value| + fail Puppet::Error, 'Should not contains spaces' if value =~ /\s/ + fail Puppet::Error, 'Empty values are not allowed' if value == '' + end + end + + newparam(:security_groups) do + desc 'the security groups to associate the load balancer' + end + + newparam(:instances) do + desc 'the instances to associate with the load balancer' + end + + newparam(:listeners) do + desc 'the ports and protocols the load balancer listens to' + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 00000000..2c6f5664 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1 @@ +require 'puppetlabs_spec_helper/module_spec_helper' diff --git a/tests/init.pp b/tests/init.pp new file mode 100644 index 00000000..d608ff3c --- /dev/null +++ b/tests/init.pp @@ -0,0 +1,63 @@ +# The baseline for module testing used by Puppet Labs is that each manifest +# should have a corresponding test manifest that declares that class or defined +# type. +# +# Tests are then run by using puppet apply --noop (to check for compilation +# errors and view a log of events) or by fully applying the test in a virtual +# environment (to compare the resulting system state to the desired state). +# +# Learn more about module testing here: +# http://docs.puppetlabs.com/guides/tests_smoke.html +# + +ec2_securitygroup { 'lb-sg': + ensure => present, + description => 'Security group for load balancer', + ingress => [{ + protocol => 'tcp', + port => 80, + cidr => '0.0.0.0/0' + }], +} + +ec2_securitygroup { 'web-sg': + ensure => present, + description => 'Security group for web servers', + ingress => [{ + source => Ec2_securitygroup['lb-sg'], + }], +} + +ec2_securitygroup { 'db-sg': + ensure => present, + description => 'Security group for database servers', + ingress => [{ + source => Ec2_securitygroup['web-sg'], + }], +} + +ec2_instance { ['web-1', 'web-2']: + ensure => present, + image_id => 'ami-2d9add1d', + security_groups => [Ec2_securitygroup['web-sg']], +} + +ec2_instance { 'db': + ensure => present, + image_id => 'ami-2d9add1d', + security_groups => [Ec2_securitygroup['db-sg']], +} + +elb_loadbalancer { 'lb-1': + ensure => present, + security_groups => [Ec2_securitygroup['lb-sg']], + instances => [ + Ec2_instance['web-1'], + Ec2_instance['web-2'], + ], + listeners => [{ + protocol => 'tcp', + port => 80, + }], +} +