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.
@@ -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