piculet 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +124 -0
- data/bin/piculet +124 -0
- data/lib/piculet.rb +3 -0
- data/lib/piculet/client.rb +148 -0
- data/lib/piculet/dsl.rb +44 -0
- data/lib/piculet/dsl/converter.rb +120 -0
- data/lib/piculet/dsl/ec2.rb +30 -0
- data/lib/piculet/dsl/permission.rb +64 -0
- data/lib/piculet/dsl/permissions.rb +47 -0
- data/lib/piculet/dsl/security-group.rb +48 -0
- data/lib/piculet/exporter.rb +58 -0
- data/lib/piculet/ext/ec2-owner-id-ext.rb +88 -0
- data/lib/piculet/ext/ip-permission-collection-ext.rb +45 -0
- data/lib/piculet/ext/string-ext.rb +27 -0
- data/lib/piculet/logger.rb +33 -0
- data/lib/piculet/version.rb +5 -0
- data/lib/piculet/wrapper/ec2-wrapper.rb +18 -0
- data/lib/piculet/wrapper/permission-collection.rb +134 -0
- data/lib/piculet/wrapper/permission.rb +92 -0
- data/lib/piculet/wrapper/security-group-collection.rb +36 -0
- data/lib/piculet/wrapper/security-group.rb +56 -0
- metadata +149 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'piculet/dsl/security-group'
|
3
|
+
|
4
|
+
module Piculet
|
5
|
+
class DSL
|
6
|
+
class EC2
|
7
|
+
attr_reader :result
|
8
|
+
|
9
|
+
def initialize(vpc, &block)
|
10
|
+
@names = Set.new
|
11
|
+
@result = OpenStruct.new({
|
12
|
+
:vpc => vpc,
|
13
|
+
:security_groups => [],
|
14
|
+
})
|
15
|
+
|
16
|
+
instance_eval(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def security_group(name, &block)
|
21
|
+
if @names.include?(name)
|
22
|
+
raise "EC2 `#{@result.vpc || :classic}`: `#{name}` is already defined"
|
23
|
+
end
|
24
|
+
|
25
|
+
@result.security_groups << SecurityGroup.new(name, @result.vpc, &block).result
|
26
|
+
@names << name
|
27
|
+
end
|
28
|
+
end # EC2
|
29
|
+
end # DSL
|
30
|
+
end # Piculet
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Piculet
|
4
|
+
class DSL
|
5
|
+
class EC2
|
6
|
+
class SecurityGroup
|
7
|
+
class Permissions
|
8
|
+
class Permission
|
9
|
+
def initialize(security_group, direction, protocol_prot_range, &block)
|
10
|
+
@security_group = security_group
|
11
|
+
@direction = direction
|
12
|
+
@protocol_prot_range = protocol_prot_range
|
13
|
+
@result = OpenStruct.new
|
14
|
+
instance_eval(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def result
|
18
|
+
unless @result.ip_ranges or @result.groups
|
19
|
+
raise "SecurityGroup `#{@security_group}`: #{@direction}: #{@protocol_prot_range}: `ip_ranges` or `groups` is required"
|
20
|
+
end
|
21
|
+
|
22
|
+
@result
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def ip_ranges(*values)
|
27
|
+
if values.empty?
|
28
|
+
raise ArgumentError, "SecurityGroup `#{@security_group}`: #{@direction}: #{@protocol_prot_range}: `ip_ranges`: wrong number of arguments (0 for 1..)"
|
29
|
+
end
|
30
|
+
|
31
|
+
values.each do |ip_range|
|
32
|
+
unless ip_range =~ %r|\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}|
|
33
|
+
raise "SecurityGroup `#{@security_group}`: #{@direction}: #{@protocol_prot_range}: `ip_ranges`: invalid ip range: #{ip_range}"
|
34
|
+
end
|
35
|
+
|
36
|
+
ip, range = ip_range.split('/', 2)
|
37
|
+
|
38
|
+
unless ip.split('.').all? {|i| (0..255).include?(i.to_i) } and (0..32).include?(range.to_i)
|
39
|
+
raise "SecurityGroup `#{@security_group}`: #{@direction}: #{@protocol_prot_range}: `ip_ranges`: invalid ip range: #{ip_range}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@result.ip_ranges = values
|
44
|
+
end
|
45
|
+
|
46
|
+
def groups(*values)
|
47
|
+
if values.empty?
|
48
|
+
raise ArgumentError, "SecurityGroup `#{@security_group}`: #{@direction}: #{@protocol_prot_range}: `groups`: wrong number of arguments (0 for 1..)"
|
49
|
+
end
|
50
|
+
|
51
|
+
values.each do |group|
|
52
|
+
unless [String, Array].any? {|i| group.kind_of?(i) }
|
53
|
+
raise "SecurityGroup `#{@security_group}`: #{@direction}: #{@protocol_prot_range}: `groups`: invalid type: #{group}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
@result.groups = values
|
58
|
+
end
|
59
|
+
end # Permission
|
60
|
+
end # Permissions
|
61
|
+
end # SecurityGroup
|
62
|
+
end # EC2
|
63
|
+
end # DSL
|
64
|
+
end # Piculet
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'piculet/dsl/permission'
|
3
|
+
|
4
|
+
module Piculet
|
5
|
+
class DSL
|
6
|
+
class EC2
|
7
|
+
class SecurityGroup
|
8
|
+
class Permissions
|
9
|
+
def initialize(security_group, direction, &block)
|
10
|
+
@security_group = security_group
|
11
|
+
@direction = direction
|
12
|
+
@result = {}
|
13
|
+
instance_eval(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def result
|
17
|
+
@result.map do |key, perm|
|
18
|
+
protocol, port_range = key
|
19
|
+
|
20
|
+
OpenStruct.new({
|
21
|
+
:protocol => protocol,
|
22
|
+
:port_range => port_range,
|
23
|
+
:ip_ranges => perm.ip_ranges,
|
24
|
+
:groups => perm.groups,
|
25
|
+
})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def permission(protocol, port_range = nil, &block)
|
31
|
+
if port_range and not port_range.kind_of?(Range)
|
32
|
+
raise TypeError, "SecurityGroup `#{@security_group}`: #{@direction}: can't convert #{port_range} into Range"
|
33
|
+
end
|
34
|
+
|
35
|
+
key = [protocol, port_range]
|
36
|
+
|
37
|
+
if @result.has_key?(key)
|
38
|
+
raise "SecurityGroup `#{@security_group}`: #{@direction}: #{key} is already defined"
|
39
|
+
end
|
40
|
+
|
41
|
+
@result[key] = Permission.new(@security_group, @direction, key, &block).result
|
42
|
+
end
|
43
|
+
end # Permissions
|
44
|
+
end # SecurityGroup
|
45
|
+
end # EC2
|
46
|
+
end # DSL
|
47
|
+
end # Piculet
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'piculet/dsl/permissions'
|
3
|
+
|
4
|
+
module Piculet
|
5
|
+
class DSL
|
6
|
+
class EC2
|
7
|
+
class SecurityGroup
|
8
|
+
def initialize(name, vpc, &block)
|
9
|
+
@name = name
|
10
|
+
@vpc = vpc
|
11
|
+
|
12
|
+
@result = OpenStruct.new({
|
13
|
+
:name => name,
|
14
|
+
:ingress => [],
|
15
|
+
:egress => [],
|
16
|
+
})
|
17
|
+
|
18
|
+
instance_eval(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def result
|
22
|
+
unless @result.description
|
23
|
+
raise "SecurityGroup `#{@name}`: `description` is required"
|
24
|
+
end
|
25
|
+
|
26
|
+
@result
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def description(value)
|
31
|
+
@result.description = value
|
32
|
+
end
|
33
|
+
|
34
|
+
def ingress(&block)
|
35
|
+
@result.ingress = Permissions.new(@name, :ingress, &block).result
|
36
|
+
end
|
37
|
+
|
38
|
+
def egress(&block)
|
39
|
+
unless @vpc
|
40
|
+
raise "SecurityGroup `#{@name}`: Cannot define `egress` in classic"
|
41
|
+
end
|
42
|
+
|
43
|
+
@result.egress = Permissions.new(@name, :egress, &block).result
|
44
|
+
end
|
45
|
+
end # SecurityGroup
|
46
|
+
end # EC2
|
47
|
+
end # DSL
|
48
|
+
end # Piculet
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'piculet/ext/ip-permission-collection-ext'
|
2
|
+
|
3
|
+
module Piculet
|
4
|
+
class Exporter
|
5
|
+
class << self
|
6
|
+
def export(ec2)
|
7
|
+
self.new(ec2).export
|
8
|
+
end
|
9
|
+
end # of class methods
|
10
|
+
|
11
|
+
def initialize(ec2)
|
12
|
+
@ec2 = ec2
|
13
|
+
end
|
14
|
+
|
15
|
+
def export
|
16
|
+
result = {}
|
17
|
+
|
18
|
+
@ec2.security_groups.each do |sg|
|
19
|
+
vpc = sg.vpc
|
20
|
+
vpc = vpc.id if vpc
|
21
|
+
result[vpc] ||= {}
|
22
|
+
result[vpc][sg.id] = export_security_group(sg)
|
23
|
+
end
|
24
|
+
|
25
|
+
return result
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def export_security_group(security_group)
|
30
|
+
{
|
31
|
+
:name => security_group.name,
|
32
|
+
:description => security_group.description,
|
33
|
+
:owner_id => security_group.owner_id,
|
34
|
+
:ingress => export_ip_permissions(security_group.ingress_ip_permissions),
|
35
|
+
:egress => export_ip_permissions(security_group.egress_ip_permissions),
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def export_ip_permissions(ip_permissions)
|
40
|
+
ip_permissions = ip_permissions ? ip_permissions.aggregate : []
|
41
|
+
|
42
|
+
ip_permissions.map do |ip_perm|
|
43
|
+
{
|
44
|
+
:protocol => ip_perm.protocol,
|
45
|
+
:port_range => ip_perm.port_range,
|
46
|
+
:ip_ranges => ip_perm.ip_ranges,
|
47
|
+
:groups => ip_perm.groups.map {|group|
|
48
|
+
{
|
49
|
+
:id => group.id,
|
50
|
+
:name => group.name,
|
51
|
+
:owner_id => group.owner_id,
|
52
|
+
}
|
53
|
+
},
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end # Exporter
|
58
|
+
end # Piculet
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module AWS
|
4
|
+
class EC2
|
5
|
+
DESC_OWNER_ID_RETRY_TIMES = 3
|
6
|
+
DESC_OWNER_ID_RETRY_WAIT = 3
|
7
|
+
SECURITY_GROUP_NAME_MAX_LEN = 255
|
8
|
+
|
9
|
+
def owner_id
|
10
|
+
return ENV['AWS_OWNER_ID'] if ENV['AWS_OWNER_ID']
|
11
|
+
|
12
|
+
unless @owner_id
|
13
|
+
security_group = create_random_security_group
|
14
|
+
return nil unless security_group
|
15
|
+
@owner_id = random_security_group_owner_id(security_group)
|
16
|
+
delete_random_security_group(security_group)
|
17
|
+
end
|
18
|
+
|
19
|
+
return @owner_id
|
20
|
+
end
|
21
|
+
|
22
|
+
def own?(other)
|
23
|
+
other == owner_id
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def create_random_security_group
|
28
|
+
security_group = nil
|
29
|
+
|
30
|
+
DESC_OWNER_ID_RETRY_TIMES.times do
|
31
|
+
name = random_security_group_name
|
32
|
+
security_group = self.security_groups.create(name) rescue nil
|
33
|
+
break if security_group
|
34
|
+
sleep DESC_OWNER_ID_RETRY_WAIT
|
35
|
+
end
|
36
|
+
|
37
|
+
return security_group
|
38
|
+
end
|
39
|
+
|
40
|
+
def random_security_group_owner_id(security_group)
|
41
|
+
owner_id = nil
|
42
|
+
exception = nil
|
43
|
+
|
44
|
+
DESC_OWNER_ID_RETRY_TIMES.times do
|
45
|
+
begin
|
46
|
+
owner_id = security_group.owner_id
|
47
|
+
break
|
48
|
+
rescue => e
|
49
|
+
exception = e
|
50
|
+
end
|
51
|
+
|
52
|
+
sleep DESC_OWNER_ID_RETRY_WAIT
|
53
|
+
end
|
54
|
+
|
55
|
+
raise exception if exception
|
56
|
+
|
57
|
+
return owner_id
|
58
|
+
end
|
59
|
+
|
60
|
+
def delete_random_security_group(security_group)
|
61
|
+
exception = nil
|
62
|
+
|
63
|
+
DESC_OWNER_ID_RETRY_TIMES.times do
|
64
|
+
begin
|
65
|
+
security_group.delete
|
66
|
+
break
|
67
|
+
rescue => e
|
68
|
+
exception = e
|
69
|
+
end
|
70
|
+
|
71
|
+
sleep DESC_OWNER_ID_RETRY_WAIT
|
72
|
+
end
|
73
|
+
|
74
|
+
raise exception if exception
|
75
|
+
end
|
76
|
+
|
77
|
+
def random_security_group_name
|
78
|
+
name = []
|
79
|
+
len = SECURITY_GROUP_NAME_MAX_LEN
|
80
|
+
|
81
|
+
while name.length < len
|
82
|
+
name.concat(('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a)
|
83
|
+
end
|
84
|
+
|
85
|
+
name.shuffle[0...len].join
|
86
|
+
end
|
87
|
+
end # EC2
|
88
|
+
end # AWS
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module AWS
|
5
|
+
class EC2
|
6
|
+
class SecurityGroup
|
7
|
+
DESC_SECURITY_GROUP_RETRY_TIMES = 3
|
8
|
+
DESC_SECURITY_GROUP_RETRY_WAIT = 3
|
9
|
+
|
10
|
+
class IpPermissionCollection
|
11
|
+
def aggregate
|
12
|
+
aggregated = nil
|
13
|
+
|
14
|
+
(1..DESC_SECURITY_GROUP_RETRY_TIMES).each do |i|
|
15
|
+
begin
|
16
|
+
aggregated = {}
|
17
|
+
|
18
|
+
self.each do |perm|
|
19
|
+
key = [perm.protocol, perm.port_range]
|
20
|
+
aggregated[key] ||= {:ip_ranges => [], :groups => []}
|
21
|
+
aggregated[key][:ip_ranges].concat(perm.ip_ranges || [])
|
22
|
+
aggregated[key][:groups].concat(perm.groups || [])
|
23
|
+
end
|
24
|
+
|
25
|
+
break
|
26
|
+
rescue AWS::EC2::Errors::InvalidGroup::NotFound => e
|
27
|
+
raise e unless i < DESC_SECURITY_GROUP_RETRY_TIMES
|
28
|
+
sleep DESC_SECURITY_GROUP_RETRY_WAIT
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
aggregated.map do |key, attrs|
|
34
|
+
protocol, port_range = key
|
35
|
+
|
36
|
+
OpenStruct.new({
|
37
|
+
:protocol => protocol,
|
38
|
+
:port_range => port_range,
|
39
|
+
}.merge(attrs))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end # IpPermissionCollection
|
43
|
+
end # SecurityGroup
|
44
|
+
end # EC2
|
45
|
+
end # AWS
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'term/ansicolor'
|
2
|
+
|
3
|
+
class String
|
4
|
+
@@colorize = false
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def colorize=(value)
|
8
|
+
@@colorize = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def colorize
|
12
|
+
@@colorize
|
13
|
+
end
|
14
|
+
end # of class methods
|
15
|
+
|
16
|
+
Term::ANSIColor::Attribute.named_attributes.map do |attr|
|
17
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
18
|
+
def #{attr.name}
|
19
|
+
if @@colorize
|
20
|
+
Term::ANSIColor.send(#{attr.name.inspect}, self)
|
21
|
+
else
|
22
|
+
self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
EOS
|
26
|
+
end
|
27
|
+
end # String
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'singleton'
|
3
|
+
require 'piculet/ext/string-ext'
|
4
|
+
|
5
|
+
module Piculet
|
6
|
+
class Logger < ::Logger
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
super($stdout)
|
11
|
+
|
12
|
+
self.formatter = proc do |severity, datetime, progname, msg|
|
13
|
+
"#{msg}\n"
|
14
|
+
end
|
15
|
+
|
16
|
+
self.level = Logger::INFO
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_debug(value)
|
20
|
+
self.level = value ? Logger::DEBUG : Logger::INFO
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClientHelper
|
24
|
+
def log(level, message, color, log_id = nil)
|
25
|
+
message = "[#{level.to_s.upcase}] #{message}" unless level == :info
|
26
|
+
message << ": #{log_id}" if log_id
|
27
|
+
message << ' (dry-run)' if @options && @options.dry_run
|
28
|
+
logger = Piculet::Logger.instance
|
29
|
+
logger.send(level, message.send(color))
|
30
|
+
end
|
31
|
+
end # ClientHelper
|
32
|
+
end # Logger
|
33
|
+
end # Piculet
|