ec2 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 67a8f18079f6dcaab371130b7bb5719adde84853
4
+ data.tar.gz: 3507f5c9ef4d942386a463aac6561e6d6e9ae98e
5
+ SHA512:
6
+ metadata.gz: 6aba022af5c0fd56b1cdcd944715bf30a60dfe376b4574e89494ff9bfb01d3365b77e36608d5701dd60cc17629c4f131f8b82436d637c57cd88dcff7f31254ff
7
+ data.tar.gz: 168ded50b3abd14b310a53c3f8afccaa02a5ca195a36e9cc2e61212a1e77be3d0ed9831de08a82f5b85fe7ef454179e562dcddf1a4c5c2689344928f393ba932
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ec2.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Neeraj Bhunwal
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Ec2
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'ec2'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install ec2
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/[my-github-username]/ec2/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/ec2 ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+ Signal.trap("INT") { exit 1 }
3
+
4
+ require 'optparse'
5
+ require 'ec2/version'
6
+ require 'ec2/error'
7
+ require 'ec2/logger'
8
+
9
+ logger = ::Ec2::Logger.logger
10
+ options = {}
11
+
12
+ opts_parser = OptionParser.new do |opts|
13
+
14
+ banner = []
15
+ banner << "Usage: ec2 [global options] command [options] args"
16
+ banner << "Commands:"
17
+ banner << " hosts"
18
+
19
+ banner << "Options: "
20
+ opts.banner = banner.join("\n")
21
+
22
+ opts.on("-v", "--version", "Show version") do |v|
23
+ puts ::Ec2::VERSION
24
+ exit
25
+ end
26
+
27
+ opts.on("--debug", "Show debug messages") do
28
+ options[:debug] = true
29
+ logger.level = ::Logger::DEBUG
30
+ end
31
+
32
+ opts.on("--trace", "Show debug messages and exception stack trace") do
33
+ options[:debug] = true
34
+ options[:trace] = true
35
+ logger.level = ::Logger::DEBUG
36
+ logger.trace = true
37
+ end
38
+
39
+ opts.on_tail("-h", "--help", "Show this message") do
40
+ puts opts
41
+ exit
42
+ end
43
+ end
44
+ begin
45
+ opts_parser.order!(ARGV)
46
+ command = ( ARGV.shift || '').to_sym
47
+ case command
48
+ when :hosts
49
+ require 'ec2/cli/hosts'
50
+ cli = ::Ec2::Cli::Hosts.new(ARGV)
51
+ cli.run
52
+ when :resources
53
+ require 'ec2/cli/resources'
54
+ cli = ::Ec2::Cli::Resources.new(ARGV)
55
+ cli.run
56
+ when :''
57
+ puts opts_parser
58
+ else
59
+ raise ::Ec2::Error, "no such command #{command}"
60
+ end
61
+
62
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument, Ec2::Error => e
63
+ cause = e.cause
64
+ if options[:trace]
65
+ puts cause
66
+ cause ? (raise cause) : (raise e)
67
+ else
68
+ logger.debug "#{cause.message}" if cause
69
+ logger.error "#{e.message}"
70
+ abort
71
+ end
72
+ end
data/dev_bin/ec2 ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
3
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
4
+ load "#{__dir__}/../bin/ec2"
data/ec2.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ec2/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ec2"
8
+ spec.version = Ec2::VERSION
9
+ spec.authors = ["Neeraj Bhunwal"]
10
+ spec.email = ["neeraj.bhunwal@gmail.com"]
11
+ spec.summary = %q{Integrates ec2 resources with salt}
12
+ spec.description = %q{Integrates ec2 resources with salt}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency 'aws-sdk', '2.0.23'
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ end
@@ -0,0 +1,10 @@
1
+ import "templates.rb"
2
+
3
+ base_subnet "dev"
4
+ availability_zones "lol5", "lol3"
5
+
6
+ use :app, :postgres
7
+
8
+ mprofile :postgres_small, template: "postgres" do
9
+ volume device: "/dev/xvdf", size: 100
10
+ end
@@ -0,0 +1,10 @@
1
+ base_profile "centos"
2
+
3
+ template "app" do
4
+ security_groups "ssh", "default"
5
+ end
6
+
7
+ template "postgres" do
8
+ security_groups "ssh", "default"
9
+ volume device: '/dev/xvdf', size: 150
10
+ end
data/examples/test.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'pry'
2
+ lib = File.expand_path(__dir__ + '/../lib')
3
+ $LOAD_PATH.unshift(lib)
4
+
5
+ require 'aws-sdk'
6
+ require 'ec2/config'
7
+ require 'ec2/query_api'
8
+ require 'ec2/profile_dsl'
9
+
10
+ config = Ec2::Config.new(".ec2.rb").config
11
+ credentials = Aws::Credentials.new(config[:aws_key], config[:aws_secret])
12
+ Aws.config[:credentials] = credentials
13
+ Aws.config[:region] = config[:region]
14
+
15
+ api = Ec2::QueryApi.new(vpc_id: config[:vpc_id])
16
+
17
+ x = Ec2::ProfileDsl.new("profiles.rb", api: api)
18
+ pry
@@ -0,0 +1,51 @@
1
+ require 'ec2/lock'
2
+ require 'ec2/salt_cloud'
3
+ require 'ec2/config'
4
+
5
+ module Ec2
6
+ module Cli
7
+ class Hosts
8
+
9
+ def initialize(argv)
10
+ @argv = argv
11
+ end
12
+
13
+ def run
14
+ lock.acquire
15
+ opts.parse!(@argv)
16
+ salt.config = config
17
+ salt.run
18
+ puts "ran hosts"
19
+ end
20
+
21
+ def salt
22
+ @salt ||= ::Ec2::SaltCloud.new
23
+ end
24
+
25
+ def lock
26
+ @lock ||= ::Ec2::Lock.new
27
+ end
28
+
29
+ def config
30
+ @config ||= ::Ec2::Config.new("ec2.conf").config
31
+ end
32
+
33
+ def opts
34
+ OptionParser.new do |opts|
35
+
36
+ opts.banner = "Usage: ec2 hosts [options] [target]"
37
+
38
+ opts.on("-f", "--file FILE", "Specify hosts file") do |f|
39
+ salt.hosts_file = f
40
+ end
41
+
42
+ opts.on("-h", "--help", "Help") do
43
+ puts opts
44
+ exit
45
+ end
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,56 @@
1
+ require 'ec2/lock'
2
+ require 'ec2/resources'
3
+ require 'ec2/config'
4
+
5
+ module Ec2
6
+ module Cli
7
+ class Resources
8
+
9
+ def initialize(argv)
10
+ @argv = argv
11
+ end
12
+
13
+ def run
14
+ lock.acquire
15
+ opts.parse!(@argv)
16
+ init_aws
17
+ @argv.each do |file|
18
+ resources = ::Ec2::Resources.new(file)
19
+ resources.apply
20
+ end
21
+ end
22
+
23
+ def lock
24
+ @lock ||= ::Ec2::Lock.new
25
+ end
26
+
27
+
28
+ def init_aws
29
+ credentials = Aws::Credentials.new(config[:aws_key], config[:aws_secret])
30
+ Aws.config[:credentials] = credentials
31
+ Aws.config[:region] = config[:region]
32
+ end
33
+
34
+ def config
35
+ @config ||= ::Ec2::Config.new("ec2.conf").config
36
+ end
37
+
38
+ def opts
39
+ OptionParser.new do |opts|
40
+
41
+ opts.banner = "Usage: ec2 resources [options] [target]"
42
+
43
+ opts.on("-f", "--file FILE", "Specify hosts file") do |f|
44
+ salt.hosts_file = f
45
+ end
46
+
47
+ opts.on("-h", "--help", "Help") do
48
+ puts opts
49
+ exit
50
+ end
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
data/lib/ec2/config.rb ADDED
@@ -0,0 +1,58 @@
1
+ require 'ec2/helper'
2
+ module Ec2
3
+ class Config
4
+
5
+ include Helper
6
+
7
+ def initialize(config_path)
8
+ @config_path = config_path
9
+ end
10
+
11
+ def config
12
+ return @config if @config
13
+ @config = {
14
+ region: "ap-southeast-1",
15
+ }
16
+ read_config
17
+ return @config
18
+ end
19
+
20
+ private
21
+
22
+ def region(region)
23
+ set :region, region
24
+ end
25
+
26
+ def vpc_id(vpc)
27
+ set :vpc_id, vpc
28
+ end
29
+
30
+ def aws_key(aws_key)
31
+ set :aws_key, aws_key
32
+ end
33
+
34
+ def aws_secret(aws_secret)
35
+ set :aws_secret, aws_secret
36
+ end
37
+
38
+ def set(key, value)
39
+ @config.store key, value
40
+ end
41
+
42
+ def read_config
43
+ read("#{Dir.home}/.ec2.rb", required: false)
44
+ read(@config_path, required: false)
45
+ end
46
+
47
+ def read(file, required: true)
48
+ if not File.readable? file
49
+ raise error "config: #{file} not readable" if required
50
+ return
51
+ end
52
+ instance_eval(File.read(file), file)
53
+ rescue NoMethodError => e
54
+ error "invalid option used in config: #{e.name}"
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,34 @@
1
+ require 'ec2/ext/string'
2
+ module Ec2
3
+ class CustomLogger < ::Logger
4
+
5
+ attr_writer :trace
6
+
7
+ def initialize(file)
8
+ super(file)
9
+ @level = ::Logger::INFO
10
+ end
11
+
12
+ def format_message(severity, timestamp, progname, msg)
13
+ case severity
14
+ when "INFO"
15
+ "#{msg}\n"
16
+ when "ERROR"
17
+ "#{severity.bold.red} #{msg}\n"
18
+ when "WARN"
19
+ "#{severity.downcase.bold.yellow} #{msg}\n"
20
+ else
21
+ "#{severity[0].bold.blue} #{msg}\n"
22
+ end
23
+ end
24
+
25
+ def bullet(msg)
26
+ puts "#{"\u2219".bold.blue} #{msg}"
27
+ end
28
+
29
+ def trace?
30
+ @trace
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ require 'ec2/logger'
2
+ require 'ec2/helper'
3
+ require 'erb'
4
+
5
+ module Ec2
6
+ class ErbProfile
7
+
8
+ include Logger
9
+ include Helper
10
+
11
+ attr_accessor :api
12
+
13
+ def initialize(file, api: nil)
14
+ @file = file
15
+ @api = api
16
+ end
17
+
18
+ def binding
19
+ api.instance_eval { binding }
20
+ end
21
+
22
+ def render
23
+ erb = ERB.new(File.read(@file), nil, '-')
24
+ erb.result(binding)
25
+ rescue => e
26
+ error "while rendering erb file #{@file}"
27
+ end
28
+
29
+ end
30
+ end
data/lib/ec2/error.rb ADDED
@@ -0,0 +1,4 @@
1
+ module Ec2
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,33 @@
1
+ class String
2
+ def colorize(color_code)
3
+ "\e[#{color_code}m#{self}\e[0m"
4
+ end
5
+
6
+ def bold
7
+ "\e[1m#{self}\e[0m"
8
+ end
9
+
10
+ def white
11
+ colorize(37)
12
+ end
13
+
14
+ def green
15
+ colorize(32)
16
+ end
17
+
18
+ def yellow
19
+ colorize(33)
20
+ end
21
+
22
+ def red
23
+ colorize(31)
24
+ end
25
+
26
+ def blue
27
+ colorize(34)
28
+ end
29
+
30
+ def grey
31
+ colorize("90")
32
+ end
33
+ end
data/lib/ec2/helper.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'ec2/error'
2
+
3
+ module Ec2
4
+ module Helper
5
+ def error(msg)
6
+ raise ::Ec2::Error, msg
7
+ end
8
+ end
9
+ end
data/lib/ec2/lock.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'ec2/logger'
2
+ module Ec2
3
+ class Lock
4
+
5
+ include Logger
6
+
7
+ def acquire
8
+ logger.debug "acquiring lock"
9
+ lock_acquired = lock_file.flock(File::LOCK_NB | File::LOCK_EX)
10
+ raise "exclusive lock not available" if not lock_acquired
11
+ end
12
+
13
+ def lock_file
14
+ @lock_file ||= File.open(".ec2.lock", "a+")
15
+ end
16
+
17
+ end
18
+ end
19
+
data/lib/ec2/logger.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'logger'
2
+ require 'ec2/custom_logger'
3
+ module Ec2
4
+ module Logger
5
+
6
+ def self.logger
7
+ @logger ||= CustomLogger.new(STDOUT)
8
+ end
9
+
10
+ def self.stderr
11
+ @stderr ||= ::Logger.new(STDERR)
12
+ end
13
+
14
+ def logger
15
+ ::Ec2::Logger.logger
16
+ end
17
+
18
+ def stderr
19
+ ::Ec2::Logger.stderr
20
+ end
21
+
22
+ def debug?
23
+ logger.level == ::Logger::DEBUG
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,92 @@
1
+ require 'yaml'
2
+ require 'ec2/logger'
3
+ require 'ec2/helper'
4
+ require 'json'
5
+
6
+ module Ec2
7
+ class Profile
8
+
9
+ include Logger
10
+ include Helper
11
+
12
+ attr_reader :api, :data
13
+
14
+ def initialize(api: nil, data: nil)
15
+ @data = deep_copy(data) || {}
16
+ @api = api
17
+ init_network
18
+ end
19
+
20
+ def create(transform: true, &block)
21
+ instance_eval &block
22
+ transform_name_to_id if transform
23
+ end
24
+
25
+ def init_network
26
+ return if @data['network_interfaces'].is_a? Array
27
+ @data['network_interfaces'] = [
28
+ {
29
+ "DeviceIndex" => 0,
30
+ "AssociatePublicIpAddress" => true,
31
+ "SecurityGroupId" => []
32
+ }
33
+ ]
34
+ end
35
+
36
+ def extends(value)
37
+ @data.store "extends", value
38
+ end
39
+
40
+ def size(value)
41
+ @data.store "size", value
42
+ end
43
+
44
+ def security_group(name, interface: 0)
45
+ @data["network_interfaces"][interface]["SecurityGroupId"] << name
46
+ end
47
+
48
+ def subnet(name, interface: 0)
49
+ @data["network_interfaces"][interface]["SubnetId"] = name
50
+ end
51
+
52
+ def security_groups(*names, interface: 0)
53
+ names.each { |name| security_group name, interface: interface }
54
+ end
55
+
56
+ def volume(device:, type: "gp2", size:)
57
+ @data["volumes"] ||= []
58
+ volumes = @data["volumes"]
59
+ volume = {
60
+ "device" => device,
61
+ "type" => type,
62
+ "size" => size
63
+ }
64
+ existing_volume = volumes.find { |v| v["device"] == device }
65
+ if existing_volume
66
+ existing_volume.merge! volume
67
+ else
68
+ volumes << volume
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def deep_copy(hash)
75
+ return nil if not hash.is_a? Hash
76
+ Marshal.load(Marshal.dump hash)
77
+ end
78
+
79
+
80
+ def transform_name_to_id
81
+ @data["network_interfaces"].each do |n|
82
+ n["SubnetId"] = api.subnet(n["SubnetId"])
83
+ security_groups = n["SecurityGroupId"]
84
+
85
+ security_groups.each_with_index do |name, i|
86
+ logger.debug "resolving security_group #{name}"
87
+ security_groups[i] = api.security_group(name)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,96 @@
1
+ require 'yaml'
2
+ require 'ec2/logger'
3
+ require 'ec2/helper'
4
+ require 'ec2/profile'
5
+ require 'set'
6
+
7
+ module Ec2
8
+ class ProfileDsl
9
+
10
+ include Logger
11
+ include Helper
12
+
13
+ attr_reader :api
14
+
15
+ def initialize(file, api:)
16
+ @file = file
17
+ @profiles = {}
18
+ @api = api
19
+ @templates = {}
20
+ @availability_zones = ["a", "b"]
21
+ @required = Set.new
22
+ end
23
+
24
+ def use(*templates)
25
+ templates.each do |t|
26
+ t = t.to_s
27
+ mprofile(t, template: t){}
28
+ end
29
+ end
30
+
31
+ def template(name, &block)
32
+ profile = Profile.new(api: api)
33
+ profile.extends(@base_profile) if @base_profile
34
+ profile.create(transform: false, &block)
35
+ @templates[name] = profile.data
36
+ @templates[name].freeze
37
+ end
38
+
39
+ def mprofile(name, template: nil, &block)
40
+ data = @templates.fetch template if template
41
+ @availability_zones.each do |az|
42
+ profile = Profile.new(data: data, api: api)
43
+ profile.extends(@base_profile) if @base_profile
44
+ profile.subnet("#{@base_subnet}-#{az}")
45
+ profile.create &block
46
+ profile_name = "#{name}-#{az}"
47
+ profile.data.freeze
48
+ @profiles[profile_name] = profile.data
49
+ end
50
+ end
51
+
52
+ def profile(name, template: nil, &block)
53
+ data = @templates.fetch template if template
54
+ profile = Profile.new(data: data, api: api)
55
+ profile.extends(@base_profile) if @base_profile
56
+ profile.create &block
57
+ profile.data.freeze
58
+ @profiles[name] = profile.data
59
+ end
60
+
61
+ def base_profile(name)
62
+ @base_profile = name
63
+ end
64
+
65
+ def base_subnet(name)
66
+ @base_subnet = name
67
+ end
68
+
69
+ def availability_zones(*zones)
70
+ @availability_zones = zones
71
+ end
72
+
73
+ def render
74
+ if not File.readable? @file
75
+ logger.info "#{@file} not readable"
76
+ return
77
+ end
78
+ instance_eval(File.read(@file), @file)
79
+ YAML.dump @profiles
80
+ rescue NoMethodError => e
81
+ error "invalid option used in profiles config: #{e.name}"
82
+ end
83
+
84
+ def vpc_id(vpc_id)
85
+ @vpc_id = vpc_id
86
+ end
87
+
88
+ def import(path)
89
+ abs_path = File.realpath path
90
+ return if @required.include? abs_path
91
+ instance_eval((File.read abs_path), abs_path)
92
+ @required << abs_path
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,35 @@
1
+ require 'ec2/helper'
2
+ require 'ec2/security_group'
3
+ require 'ec2/subnet'
4
+
5
+ module Ec2
6
+ class QueryApi
7
+
8
+ include Helper
9
+
10
+ def initialize(vpc_id: nil)
11
+ @vpc_id = vpc_id
12
+ end
13
+
14
+ def security_group(name)
15
+ security_group = SecurityGroup.new(name, vpc_id: @vpc_id)
16
+ sg_cache[name] ||= security_group.id!
17
+ end
18
+
19
+ def subnet(name)
20
+ subnet = Subnet.new(name, vpc_id: @vpc_id)
21
+ subnet_cache[name] ||= subnet.id!
22
+ end
23
+
24
+ private
25
+
26
+ def sg_cache
27
+ @sg_cache ||= {}
28
+ end
29
+
30
+ def subnet_cache
31
+ @subnet_cache ||= {}
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ require 'ec2/security_group'
2
+ require 'ec2/subnet'
3
+ module Ec2
4
+ class Resources
5
+
6
+
7
+ def initialize(file)
8
+ @file = file
9
+ end
10
+
11
+ def vpc_id(vpc_id)
12
+ @vpc_id = vpc_id
13
+ end
14
+
15
+ def security_group(name, &block)
16
+ sg = SecurityGroup.new(name, vpc_id: @vpc_id)
17
+ sg.created &block
18
+ end
19
+
20
+ def subnet(name, &block)
21
+ subnet = Subnet.new(name, vpc_id: @vpc_id)
22
+ subnet.created &block
23
+ end
24
+
25
+ def apply
26
+ instance_eval(File.read(@file), @file)
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,112 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'aws-sdk'
4
+
5
+ require 'ec2/query_api'
6
+ require 'ec2/profile_dsl'
7
+ require 'ec2/erb_profile'
8
+ require 'ec2/logger'
9
+
10
+ module Ec2
11
+ class SaltCloud
12
+
13
+ include Logger
14
+ attr_writer :hosts_file
15
+ attr_accessor :config
16
+
17
+ def initialize
18
+ end
19
+
20
+ def run
21
+ init_working_dir
22
+ copy_providers
23
+ copy_profiles
24
+ copy_master_config
25
+ copy_deploy_scripts
26
+ init_aws
27
+ render_global_profiles
28
+ render_local_profile
29
+ run_salt_cloud
30
+ end
31
+
32
+ private
33
+
34
+ def init_aws
35
+ credentials = Aws::Credentials.new(config[:aws_key], config[:aws_secret])
36
+ Aws.config[:credentials] = credentials
37
+ Aws.config[:region] = config[:region]
38
+ end
39
+
40
+ def api
41
+ @api ||= QueryApi.new(vpc_id: config[:vpc_id])
42
+ end
43
+
44
+ def working_dir
45
+ @working_dir ||= ".salt.tmp"
46
+ end
47
+
48
+ def hosts_file
49
+ @hosts_file ||= "hosts"
50
+ end
51
+
52
+ def init_working_dir
53
+ FileUtils.rm_r working_dir if File.directory? working_dir
54
+ FileUtils.mkdir_p "#{working_dir}/cloud.profiles.d"
55
+ end
56
+
57
+ def copy_providers
58
+ return if not File.directory? "/etc/salt/cloud.providers.d"
59
+ FileUtils.cp_r "/etc/salt/cloud.providers.d", working_dir
60
+ end
61
+
62
+ def copy_profiles
63
+ return if not File.directory? "/etc/salt/cloud.profiles.d"
64
+ Dir["/etc/salt/cloud.profiles.d/*.conf"].each do |f|
65
+ path = Pathname.new f
66
+ FileUtils.cp path, "#{working_dir}/cloud.profiles.d/_#{path.basename}"
67
+ end
68
+ end
69
+
70
+ def copy_master_config
71
+ FileUtils.cp "/etc/salt/master", working_dir
72
+ end
73
+
74
+ def copy_deploy_scripts
75
+ return if not File.directory? "/etc/salt/cloud.deploy.d"
76
+ FileUtils.cp_r "/etc/salt/cloud.deploy.d", working_dir
77
+ end
78
+
79
+ def render_global_profiles
80
+ Dir["/etc/salt/cloud.profiles.d/*.erb"].each do |f|
81
+ render(f, prefix: "_")
82
+ end
83
+ end
84
+
85
+ def render_local_profile
86
+ local_profile = ["profiles.rb", "profiles.erb"].find { |f| File.readable? f }
87
+ render(local_profile) if local_profile
88
+ end
89
+
90
+ def render(path, prefix: nil)
91
+ extname = File.extname path
92
+ name = File.basename(path, extname)
93
+ outfile = "#{working_dir}/cloud.profiles.d/#{prefix}#{name}.conf"
94
+ case extname
95
+ when ".rb"
96
+ profile_dsl = ProfileDsl.new(path, api: api)
97
+ File.open(outfile, "w") { |f| f.write profile_dsl.render }
98
+ when ".erb"
99
+ erb = ErbProfile.new(path, api: api)
100
+ File.open(outfile, "w") { |f| f.write erb.render }
101
+ end
102
+ end
103
+
104
+ def run_salt_cloud
105
+ logger.debug "running salt cloud"
106
+ command = %Q( salt-cloud -m #{hosts_file} -c #{working_dir} )
107
+ command << " -l debug" if logger.trace?
108
+ system command
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,196 @@
1
+ require 'ec2/helper'
2
+ require 'ec2/logger'
3
+ require 'aws-sdk'
4
+ require 'set'
5
+
6
+ module Ec2
7
+ class SecurityGroup
8
+
9
+ include Helper
10
+ include Logger
11
+ attr_reader :desired_permissions
12
+
13
+ def initialize(name, vpc_id: nil)
14
+ error "vpc_id not specified for security group" if not vpc_id
15
+ @vpc_id = vpc_id.to_s
16
+ @name = name
17
+ @dry_run = false
18
+ end
19
+
20
+ def id
21
+ return @id unless @id.nil?
22
+ @id = load_id
23
+ end
24
+
25
+ def description(description)
26
+ @description = description
27
+ end
28
+
29
+ def id!
30
+ error "specified security_group #{@name} doesn't exist in vpc #{@vpc_id}" if not exists?
31
+ id
32
+ end
33
+
34
+ def created(&block)
35
+ clear_vars
36
+ instance_eval &block
37
+ create if not exists?
38
+ sync
39
+ end
40
+
41
+ private
42
+
43
+ def sg
44
+ @sg ||= ::Aws::EC2::SecurityGroup.new(id)
45
+ end
46
+
47
+ def create
48
+ resp = ec2.create_security_group(
49
+ group_name: @name,
50
+ description: @description,
51
+ vpc_id: @vpc_id
52
+ )
53
+ @id = resp.group_id
54
+ logger.info "(#{@name}) created security group"
55
+ rescue Aws::Errors::ServiceError, ArgumentError
56
+ error "while creating security_group #{@name}"
57
+ end
58
+
59
+ def load_id
60
+ filters = []
61
+ filters << { name: "group-name", values: [@name.to_s] }
62
+ filters << vpc_filter
63
+ result = ec2.describe_security_groups(filters: filters)
64
+ if result.security_groups.size == 1
65
+ return result.security_groups.first.group_id
66
+ else
67
+ return false
68
+ end
69
+ end
70
+
71
+ def exists?
72
+ id
73
+ end
74
+
75
+ def vpc_filter
76
+ { name: "vpc-id", values: [@vpc_id.to_s] }
77
+ end
78
+
79
+ def outbound(&block)
80
+ @outbound = true
81
+ instance_eval &block
82
+ ensure
83
+ @outbound = false
84
+ end
85
+
86
+ def parse_ports(port)
87
+ if port.is_a? Integer
88
+ return port, port
89
+ elsif port =~ /\A([0-9]+)\z/
90
+ return $1.to_i, $1.to_i
91
+ elsif port =~ /\A([0-9]+)\-([0-9]+)\z/
92
+ return $1.to_i, $2.to_i
93
+ else
94
+ error "invalid port specified #{port}"
95
+ end
96
+ end
97
+
98
+ def tcp(port, **args)
99
+ (from_port, to_port) = parse_ports(port)
100
+ rule(ip_protocol: "tcp", from_port: from_port, to_port: to_port, **args)
101
+ end
102
+
103
+ def udp(port, **args)
104
+ (from_port, to_port) = parse_ports(port)
105
+ rule(ip_protocol: "udp", from_port: from_port, to_port: to_port, **args)
106
+ end
107
+
108
+ def icmp(**args)
109
+ rule(ip_protocol: "icmp", from_port: -1, to_port: -1, **args)
110
+ end
111
+
112
+ def rule(ip_protocol: nil, from_port: nil, to_port: nil, cidr_ip: nil)
113
+ options = {
114
+ ip_protocol: ip_protocol,
115
+ from_port: from_port,
116
+ to_port: to_port,
117
+ cidr_ip: cidr_ip
118
+ }
119
+ if not @outbound
120
+ @desired_permissions ||= Set.new
121
+ @desired_permissions << options
122
+ end
123
+ end
124
+
125
+ def current_permissions
126
+ @current_permissions ||= begin
127
+ permissions = Set.new
128
+ sg.ip_permissions.each do |permission|
129
+ rules = permission_to_rules(permission)
130
+ rules.each { |r| permissions << r }
131
+ end
132
+ permissions
133
+ end
134
+ end
135
+
136
+ def diff(current, desired)
137
+ to_add = desired - current
138
+ to_remove = current - desired
139
+ s = Struct.new(:to_add, :to_remove)
140
+ s.new(to_add, to_remove)
141
+ end
142
+
143
+ def sync
144
+ ingress_diff.to_add.each { |r| authorize_ingress r }
145
+ ingress_diff.to_remove.each { |r| revoke_ingress r }
146
+ end
147
+
148
+
149
+ def authorize_ingress(rule)
150
+ sg.authorize_ingress rule if not @dry_run
151
+ logger.info "(#{@name}) #{"+".green.bold} allow #{rule[:cidr_ip]} #{rule[:from_port]}-#{rule[:to_port]}"
152
+ rescue Aws::Errors::ServiceError
153
+ error "error authorizing rule #{rule}"
154
+ end
155
+
156
+ def revoke_ingress(rule)
157
+ sg.revoke_ingress rule if not @dry_run
158
+ logger.info "(#{@name}) #{"-".red.bold} allow #{rule[:cidr_ip]} #{rule[:from_port]}-#{rule[:to_port]}"
159
+ end
160
+
161
+ def ingress_diff
162
+ @ingress_diff ||= diff(current_permissions, desired_permissions)
163
+ end
164
+
165
+ def clear_vars
166
+ @desired_permissions = nil
167
+ @current_permissions = nil
168
+ @ingress_diff = nil
169
+ end
170
+
171
+ def permission_to_rules(permission)
172
+ rules = Set.new
173
+ permission.ip_ranges.each do |r|
174
+ rule = {
175
+ ip_protocol: permission.ip_protocol,
176
+ from_port: permission.from_port,
177
+ to_port: permission.to_port,
178
+ cidr_ip: r.cidr_ip
179
+ }
180
+ rules << rule
181
+ end
182
+ rules
183
+ end
184
+
185
+ def ec2
186
+ @ec2 ||= begin
187
+ if @region
188
+ Aws::EC2::Client.new(region: @region)
189
+ else
190
+ Aws::EC2::Client.new
191
+ end
192
+ end
193
+ end
194
+
195
+ end
196
+ end
data/lib/ec2/subnet.rb ADDED
@@ -0,0 +1,105 @@
1
+ require 'ec2/helper'
2
+ require 'ec2/logger'
3
+ module Ec2
4
+ class Subnet
5
+
6
+ include Helper
7
+ include Logger
8
+
9
+ def initialize(name, vpc_id: nil)
10
+ error "vpc_id not specified for subnet" if not vpc_id
11
+ @vpc_id = vpc_id.to_s
12
+ @name = name.to_s
13
+ end
14
+
15
+ def id!
16
+ load_id if not @id
17
+ error "specified subnet #{@name} doesn't exist in vpc #{@vpc_id}" if not exists?
18
+ @id
19
+ end
20
+
21
+ def created(&block)
22
+ instance_eval &block
23
+ load_id using_cidr: true
24
+ if exists?
25
+ tag if not tagged?
26
+ verify
27
+ else
28
+ create
29
+ tag
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def subnet
36
+ @subnet ||= ::Aws::EC2::Subnet.new(@id)
37
+ end
38
+
39
+ def create
40
+ resp = ec2.create_subnet(
41
+ vpc_id: @vpc_id,
42
+ cidr_block: @cidr,
43
+ availability_zone: @availability_zone
44
+ )
45
+ @id = resp.subnet.subnet_id
46
+ logger.info "(#{@name}) created subnet: #{@cidr}"
47
+ rescue Aws::Errors::ServiceError, ArgumentError
48
+ error "while creating subnet #{@name}"
49
+ end
50
+
51
+ def tag
52
+ subnet.create_tags(
53
+ tags: [ { key: "Name", value: @name } ]
54
+ )
55
+ subnet.load
56
+ end
57
+
58
+ def tagged?
59
+ subnet.tags.any? { |tag| tag.key == "Name"}
60
+ end
61
+
62
+ def verify
63
+ name_tag = subnet.tags.find { |tag| tag.key == "Name" }.value
64
+ error "availability zone mismatch for subnet #{@name}" if subnet.availability_zone != @availability_zone
65
+ error "subnet #{@name} already tagged with another name #{name_tag}" if @name != name_tag
66
+ end
67
+
68
+ def cidr(cidr)
69
+ @cidr = cidr
70
+ end
71
+
72
+ def availability_zone(availability_zone)
73
+ @availability_zone = availability_zone
74
+ end
75
+
76
+ alias_method :az, :availability_zone
77
+
78
+
79
+ def load_id(using_cidr: false)
80
+ filters = []
81
+
82
+ if using_cidr
83
+ filters << { name: "cidr", values: [@cidr.to_s] }
84
+ else
85
+ filters << { name: "tag:Name", values: [@name.to_s] }
86
+ end
87
+
88
+ filters << { name: "vpc-id", values: [@vpc_id.to_s] }
89
+ result = ec2.describe_subnets(filters: filters)
90
+ error "mulitple subnets found with name #{@name}" if result.subnets.size > 1
91
+ @id = result.subnets.first.subnet_id if result.subnets.size == 1
92
+ end
93
+
94
+ def exists?
95
+ @id
96
+ end
97
+
98
+ def ec2
99
+ @ec2 ||= begin
100
+ @region ? Aws::EC2::Client.new(region: @region) : Aws::EC2::Client.new
101
+ end
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,3 @@
1
+ module Ec2
2
+ VERSION = "0.0.4"
3
+ end
data/lib/ec2.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "ec2/version"
2
+
3
+ module Ec2
4
+ # Your code goes here...
5
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ec2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Neeraj Bhunwal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.23
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.23
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: Integrates ec2 resources with salt
56
+ email:
57
+ - neeraj.bhunwal@gmail.com
58
+ executables:
59
+ - ec2
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - .gitignore
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - bin/ec2
69
+ - dev_bin/ec2
70
+ - ec2.gemspec
71
+ - examples/profiles.rb
72
+ - examples/templates.rb
73
+ - examples/test.rb
74
+ - lib/ec2.rb
75
+ - lib/ec2/cli/hosts.rb
76
+ - lib/ec2/cli/resources.rb
77
+ - lib/ec2/config.rb
78
+ - lib/ec2/custom_logger.rb
79
+ - lib/ec2/erb_profile.rb
80
+ - lib/ec2/error.rb
81
+ - lib/ec2/ext/string.rb
82
+ - lib/ec2/helper.rb
83
+ - lib/ec2/lock.rb
84
+ - lib/ec2/logger.rb
85
+ - lib/ec2/profile.rb
86
+ - lib/ec2/profile_dsl.rb
87
+ - lib/ec2/query_api.rb
88
+ - lib/ec2/resources.rb
89
+ - lib/ec2/salt_cloud.rb
90
+ - lib/ec2/security_group.rb
91
+ - lib/ec2/subnet.rb
92
+ - lib/ec2/version.rb
93
+ homepage: ''
94
+ licenses:
95
+ - MIT
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.0.14
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: Integrates ec2 resources with salt
117
+ test_files: []