demeter-cli 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ require 'demeter/aws/manage_security_groups'
2
+ require 'terminal-table'
3
+ require 'logger'
4
+ require_relative 'base'
5
+
6
+ module Demeter
7
+ module Commands
8
+ class Apply < Base
9
+ def start
10
+ sgs_manager = Demeter::Aws::ManageSecurityGroups.new(ec2:@ec2, options:@options)
11
+ diff = sgs_manager.apply
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ require 'logger'
2
+
3
+ module Demeter
4
+ module Commands
5
+ class Base
6
+ def initialize(options)
7
+ check_path
8
+ ENV['DEMETER_ENV'] = options['environment']
9
+ Dotenv.load(".env.#{options['environment']}")
10
+ ::Aws.config.update(:logger => Logger.new(STDOUT))
11
+ @ec2 = ::Aws::EC2::Client.new()
12
+ @options = options
13
+ end
14
+
15
+ def check_path
16
+ fail "configs directory not found!" if !File.directory?('./configs')
17
+ fail "variables directory not found!" if !File.directory?('./variables')
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,109 @@
1
+ require 'colorize'
2
+ require 'demeter/aws/manage_security_groups'
3
+ require_relative 'base'
4
+
5
+ module Demeter
6
+ module Commands
7
+ class Generate < Base
8
+ def initialize(options)
9
+ super
10
+ @ids = options[:ids]
11
+ end
12
+
13
+ def project_key name
14
+ name
15
+ .gsub('::', '_')
16
+ .gsub('/', '_')
17
+ .gsub('-', '_')
18
+ .gsub(' ', '_')
19
+ .downcase
20
+ end
21
+
22
+ def start
23
+ # collect vars
24
+ res = @ec2.describe_security_groups
25
+ res[:security_groups].each do |object|
26
+ name_tag = object['tags'].detect{|tag| tag['key'].downcase == 'name'}
27
+ sg_key = name_tag ? project_key(name_tag['value']) : project_key(object.group_name)
28
+ Demeter::set_var("security_group.#{sg_key}.id", object.group_id)
29
+ Demeter::set_var(object.group_id, "<% security_group.#{sg_key}.id %>")
30
+ end
31
+
32
+ resp = @ec2.describe_security_groups({group_ids: @ids})
33
+
34
+ template = {
35
+ 'environments' => [@options['environment']],
36
+ 'security_groups' => []
37
+ }
38
+
39
+ resp[:security_groups].each do |_sg|
40
+ name_tag = _sg['tags'].detect{|tag| tag['key'].downcase == 'name'}
41
+ sg_key = name_tag ? project_key(name_tag['value']) : project_key(_sg.group_name)
42
+
43
+ sg_template = {
44
+ 'name' => (name_tag ? name_tag['value'] : _sg.group_name),
45
+ 'vpc_id' => '<% env.vpc_id %>',
46
+ 'ingress' => [],
47
+ 'egress' => []
48
+ }
49
+
50
+ # Ingress
51
+ _sg['ip_permissions'].each do |_rule|
52
+ rule = {
53
+ 'protocol' => _rule.ip_protocol,
54
+ 'from_port' => _rule.from_port.to_i,
55
+ 'to_port' => _rule.to_port.to_i,
56
+ }
57
+
58
+ if !_rule['user_id_group_pairs'].empty?
59
+ rule['source_security_groups'] = []
60
+ _rule['user_id_group_pairs'].each do |_group|
61
+ group_key = Demeter::vars[_group['group_id']] ? Demeter::vars[_group['group_id']] : _group['group_id']
62
+ rule['source_security_groups'] << group_key
63
+ end
64
+ end
65
+
66
+ if !_rule['ip_ranges'].empty?
67
+ rule['cidr_blocks'] = []
68
+ _rule['ip_ranges'].each do |_range|
69
+ rule['cidr_blocks'] << _range['cidr_ip']
70
+ end
71
+ end
72
+
73
+ sg_template['ingress'] << rule
74
+ end
75
+
76
+ # Egress
77
+ _sg['ip_permissions_egress'].each do |_rule|
78
+ rule = {
79
+ 'protocol' => _rule.ip_protocol,
80
+ 'from_port' => _rule.from_port.to_i,
81
+ 'to_port' => _rule.to_port.to_i,
82
+ }
83
+
84
+ if !_rule['user_id_group_pairs'].empty?
85
+ rule['source_security_groups'] = []
86
+ _rule['user_id_group_pairs'].each do |_group|
87
+ group_key = Demeter::vars[_group['group_id']] ? Demeter::vars[_group['group_id']] : _group['group_id']
88
+ rule['source_security_groups'] << group_key
89
+ end
90
+ end
91
+
92
+ if !_rule['ip_ranges'].empty?
93
+ rule['cidr_blocks'] = []
94
+ _rule['ip_ranges'].each do |_range|
95
+ rule['cidr_blocks'] << _range['cidr_ip']
96
+ end
97
+ end
98
+
99
+ sg_template['egress'] << rule
100
+ end
101
+
102
+ template['security_groups'] << sg_template
103
+ end
104
+
105
+ puts template.to_yaml.gsub('"', '')
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,40 @@
1
+ require 'demeter/aws/manage_security_groups'
2
+ require 'terminal-table'
3
+ require_relative 'base'
4
+
5
+ module Demeter
6
+ module Commands
7
+ class Plan < Base
8
+ def start
9
+ sgs_manager = Demeter::Aws::ManageSecurityGroups.new(ec2: @ec2, options: @options)
10
+ diff = sgs_manager.diff_all
11
+ rows = []
12
+ i = 0
13
+ diff.each do |sg, diffs|
14
+ rows << :separator if i > 0
15
+ rows << [{:value => sg, :colspan => 3, :alignment => :left}]
16
+ rows << :separator
17
+
18
+ diffs.sort_by! { |d| d[0] }
19
+ diffs.each do |_diff|
20
+ if _diff[2].is_a? Array
21
+ _diff[2].each do |__diff|
22
+ rows << [_diff[0] == '+' ? _diff[0].colorize(:green) : _diff[0].colorize(:red), _diff[1], __diff.to_s]
23
+ end
24
+ else
25
+ rows << [_diff[0] == '+' ? _diff[0].colorize(:green) : _diff[0].colorize(:red), _diff[1], _diff[2].to_s]
26
+ end
27
+ end
28
+ i += 1
29
+ end
30
+
31
+ if rows.empty?
32
+ puts "All #{Demeter::env} security groups are in sync"
33
+ else
34
+ table = Terminal::Table.new :rows => rows
35
+ puts table
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,42 @@
1
+ require 'colorize'
2
+ require 'demeter/aws/manage_security_groups'
3
+ require 'terminal-table'
4
+ require_relative 'base'
5
+
6
+ module Demeter
7
+ module Commands
8
+ class Status < Base
9
+ def start
10
+ sgs_manager = Demeter::Aws::ManageSecurityGroups.new(ec2:@ec2, options:@options)
11
+ status = sgs_manager.status
12
+ rows = []
13
+
14
+ rows << [{:value => "### MANAGED SECURITY GROUPS ###".colorize(:green), :colspan => 3, :alignment => :left}]
15
+ rows << :separator
16
+ rows << ['Name', 'Group Name', 'Group ID']
17
+ rows << :separator
18
+
19
+ status[:managed].each do |sg|
20
+ rows << [sg[:name], sg[:group_name], sg[:group_id]]
21
+ end
22
+
23
+ rows << :separator
24
+ rows << [{:value => "### UNMANAGED SECURITY GROUPS ###".colorize(:red), :colspan => 3, :alignment => :left}]
25
+ rows << :separator
26
+ rows << ['Name', 'Group Name', 'Group ID']
27
+ rows << :separator
28
+
29
+ status[:unmanaged].each do |sg|
30
+ rows << [sg[:name], sg[:group_name], sg[:group_id]]
31
+ end
32
+
33
+ puts Terminal::Table.new :rows => rows
34
+
35
+ puts ""
36
+ puts "#{'MANAGED'.colorize(:green)}: #{status[:managed].count}"
37
+ puts "#{'UNMANAGED'.colorize(:red)}: #{status[:unmanaged].count}"
38
+ puts ""
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,6 @@
1
+ module Demeter
2
+ # Defines the version
3
+ #
4
+ # @since 0.0.1
5
+ VERSION = '0.0.4'.freeze
6
+ end
@@ -0,0 +1,77 @@
1
+ class Ec2Stub
2
+
3
+ def self.describe_with_security_groups
4
+ Aws::EC2::Client.new(stub_responses: {
5
+ describe_security_groups: {
6
+ security_groups: [{
7
+ group_name: "ec2::infra/apollo::web",
8
+ group_id: "sg-11111",
9
+ description: "Managed by Demeter",
10
+ ip_permissions: [
11
+ { ip_protocol: "tcp",
12
+ from_port: 22,
13
+ to_port: 22,
14
+ user_id_group_pairs: [],
15
+ ip_ranges: [
16
+ {cidr_ip: "10.0.0.0/8"},
17
+ {cidr_ip: "10.10.0.0/24"}
18
+ ],
19
+ prefix_list_ids: []
20
+ },
21
+ { ip_protocol: "tcp",
22
+ from_port: 22, to_port: 22,
23
+ user_id_group_pairs: [{ user_id: "2222", group_name: nil, group_id: "sg-11111" }],
24
+ ip_ranges: [],
25
+ prefix_list_ids: []
26
+ }
27
+ ],
28
+ ip_permissions_egress: [
29
+ { ip_protocol: "-1", from_port: nil, to_port: nil, user_id_group_pairs: [], ip_ranges: [{cidr_ip: "0.0.0.0/0"}], prefix_list_ids: [] }
30
+ ],
31
+ vpc_id: "vpc-12345",
32
+ tags: [
33
+ { :key => "Name", :value => "ec2::infra/apollo::web" }
34
+ ]
35
+ }]
36
+ }
37
+ })
38
+ end
39
+
40
+ def self.describe_with_no_groups
41
+ Aws::EC2::Client.new(stub_responses: {
42
+ describe_security_groups: {
43
+ security_groups: [{
44
+ group_name: "does-not-exist",
45
+ group_id: "sg-111111111",
46
+ description: "Some things",
47
+ ip_permissions: [
48
+ { ip_protocol: "tcp",
49
+ from_port: 22,
50
+ to_port: 22,
51
+ user_id_group_pairs: [],
52
+ ip_ranges: [
53
+ {cidr_ip: "10.0.0.0/8"},
54
+ {cidr_ip: "10.10.0.0/24"}
55
+ ],
56
+ prefix_list_ids: []
57
+ },
58
+ { ip_protocol: "tcp",
59
+ from_port: 22, to_port: 22,
60
+ user_id_group_pairs: [{ user_id: "564673040929", group_name: nil, group_id: "sg-111111111" }],
61
+ ip_ranges: [],
62
+ prefix_list_ids: []
63
+ }
64
+ ],
65
+ ip_permissions_egress: [
66
+ { ip_protocol: "-1", from_port: nil, to_port: nil, user_id_group_pairs: [], ip_ranges: [{cidr_ip: "0.0.0.0/0"}], prefix_list_ids: [] }
67
+ ],
68
+ vpc_id: "vpc-12345",
69
+ tags: [
70
+ { :key => "Name", :value => "does-not-exist" }
71
+ ]
72
+ }]
73
+ }
74
+ })
75
+ end
76
+
77
+ end
@@ -0,0 +1,72 @@
1
+ require_relative './spec_helper'
2
+ require_relative 'ec2_stub'
3
+ require 'demeter/aws/manage_security_groups'
4
+ require 'demeter/aws/security_group'
5
+ require 'demeter'
6
+
7
+ describe Demeter::Aws::ManageSecurityGroups do
8
+ describe '::describe' do
9
+
10
+ context 'with an existing aws SG and a local file' do
11
+ let(:ec2) do
12
+ Ec2Stub.describe_with_security_groups
13
+ end
14
+
15
+ let(:load_path) { File.expand_path(File.join(__dir__, "projects/simple/*.yml")) }
16
+ let(:vars_path) { File.expand_path(__dir__) }
17
+ let(:smanage) { Demeter::Aws::ManageSecurityGroups.new(ec2:ec2, project_path:load_path) }
18
+
19
+ it 'loads both aws and local file' do
20
+ Demeter.root(vars_path)
21
+ result = smanage.describe
22
+ expect(result).to_not eq(nil)
23
+ end
24
+
25
+ it 'shows the diff with both aws and local file' do
26
+ all_diffs = smanage.diff_all
27
+ end
28
+
29
+ end
30
+
31
+ context 'with non-existing aws SG and a local file' do
32
+ let(:ec2) do
33
+ Ec2Stub.describe_with_no_groups
34
+ end
35
+ let(:load_path) { File.expand_path(File.join(__dir__, "projects/simple/**/*.yml")) }
36
+ let(:smanage) { Demeter::Aws::ManageSecurityGroups.new(ec2:ec2, project_path:load_path) }
37
+
38
+ it 'creates the new security group' do
39
+ allow(ec2).to receive(:create_security_group).and_return(double('ec2_response', group_id: "sg-12345"))
40
+ smanage.create_all
41
+ expect(ec2).to have_received(:create_security_group)
42
+ end
43
+
44
+ it 'has no diffs' do
45
+ the_diff = smanage.diff_all
46
+ pluses = the_diff.select { |s| s[0] == "+" }
47
+ minuses = the_diff.select { |s| s[0] == "-" }
48
+ expect(minuses.size).to eq(0)
49
+ expect(pluses.size).to eq(0)
50
+ end
51
+
52
+ end
53
+
54
+ context 'with different config in the local file than exists in aws' do
55
+ let(:ec2) do
56
+ Ec2Stub.describe_with_security_groups
57
+ end
58
+ let(:load_path) { File.expand_path(File.join(__dir__, "projects/simple/ec2_apollo.yml")) }
59
+ let(:smanage) { Demeter::Aws::ManageSecurityGroups.new(ec2:ec2, project_path:load_path) }
60
+
61
+ it 'removes source security group and adds cidr block' do
62
+ the_diff = smanage.diff_all
63
+ pluses = the_diff[the_diff.keys.first].select { |s| s[0] == "+" }
64
+ minuses = the_diff[the_diff.keys.first].select { |s| s[0] == "-" }
65
+ expect(minuses.size).to eq(1)
66
+ expect(pluses.size).to eq(1)
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,17 @@
1
+ security_groups:
2
+ - name: "ec2::infra/apollo::web"
3
+ vpc_id: "vpc-11111"
4
+ ingress:
5
+ - from_port: 22
6
+ to_port: 22
7
+ protocol: "tcp"
8
+ cidr_blocks:
9
+ - "10.0.0.0/8"
10
+ - "10.20.0.0/8"
11
+ - "10.10.0.0/24"
12
+ egress:
13
+ - from_port: 0
14
+ to_port: 0
15
+ protocol: "-1"
16
+ cidr_blocks:
17
+ - "0.0.0.0/0"
@@ -0,0 +1,17 @@
1
+ security_groups:
2
+ - name: ec2::infra/apollo::web
3
+ vpc_id: <% env.vpc_id %>
4
+ ingress:
5
+ - from_port: 22
6
+ to_port: 22
7
+ protocol: tcp
8
+ cidr_blocks:
9
+ - 10.0.0.0/8
10
+ - 10.20.0.0/8
11
+ - 10.10.0.0/24
12
+ - <% global.var_cidr_blocks %>
13
+ source_security_groups:
14
+ - sg-12345
15
+ egress:
16
+ - from_port: -1
17
+ to_port: -1
@@ -0,0 +1,12 @@
1
+ security_groups:
2
+ - name: <% ec2::infra/bastion %>
3
+ vpc_id: <% var.vpc_id %>
4
+ ingress:
5
+ - from_port: 22
6
+ to_port: 22
7
+ protocol: tcp
8
+ cidr_blocks:
9
+ - 10.0.0.0/8
10
+ egress:
11
+ - from_port: -1
12
+ to_port: -1
@@ -0,0 +1,73 @@
1
+ require_relative './spec_helper'
2
+ require 'demeter/aws/security_group'
3
+
4
+ describe Demeter::Aws::SecurityGroup do
5
+ describe '::initialize()' do
6
+ context 'with aws security group' do
7
+ let(:ec2) {
8
+ Ec2Stub.describe_with_security_groups
9
+ }
10
+ let(:security_group_hash) { {name: 'ec2::infra::project', description: 'Demeter'} }
11
+ let(:security_group) { Demeter::Aws::SecurityGroup.new(ec2) }
12
+ let(:load_path) { File.expand_path(File.join(__dir__, "projects/with_vars/ec2_apollo.yml")) }
13
+ let(:vars_path) { File.expand_path(__dir__) }
14
+
15
+ it 'loads security group from aws result' do
16
+ sg = ec2.describe_security_groups(filters: [
17
+ {name: "group-name", values: ["ec2::infra/apollo::web"]}
18
+ ]).security_groups.first
19
+
20
+ result = security_group.load_aws(sg)
21
+ expect(result).to eq(true)
22
+ expect(security_group.group_name).to eq("ec2::infra/apollo::web")
23
+ end
24
+
25
+ it 'load_aws with the vars set' do
26
+ sg = ec2.describe_security_groups(filters: [
27
+ {name: "group-name", values: ["ec2::infra/apollo::web"]}
28
+ ]).security_groups.first
29
+ security_group.load_aws(sg)
30
+ my_project = security_group.project_key
31
+ expect(Demeter::vars).to include("security_group.#{my_project}.id")
32
+ end
33
+
34
+ it 'replaces configs with vars' do
35
+ sg = ec2.describe_security_groups(filters: [
36
+ {name: "group-name", values: ["ec2::infra/apollo::web"]}
37
+ ]).security_groups.first
38
+ security_group.load_aws(sg)
39
+ expect(security_group.diff).to eq([
40
+ ["-", "egress", [
41
+ {:protocol=>"-1", :from_port=>0, :to_port=>0, :cidr_block=>"0.0.0.0/0"}
42
+ ]],
43
+ ["-", "ingress", [
44
+ {:protocol=>"tcp", :from_port=>22, :to_port=>22, :cidr_block=>"10.0.0.0/8"},
45
+ {:protocol=>"tcp", :from_port=>22, :to_port=>22, :cidr_block=>"10.10.0.0/24"},
46
+ {:protocol=>"tcp", :from_port=>22, :to_port=>22, :source_security_group=>"sg-11111"}
47
+ ]],
48
+ ])
49
+ end
50
+
51
+ it 'replaces array config vars' do
52
+ Demeter::root(vars_path)
53
+ project_config = YAML::load_file(load_path)
54
+ security_group.load_local(project_config['security_groups'].first)
55
+ diffs = security_group.diff
56
+ vpcone = diffs.detect { |d| d[1] == "vpc_id" }
57
+ cidrs = diffs.detect { |s| s[2].is_a?(Array) && !s[2].empty? && s[2].first.has_key?(:cidr_block) }
58
+ expect(cidrs[2].size).to eq(7)
59
+ end
60
+
61
+ it 'replaces array config vars with update_vars' do
62
+ Demeter::root(vars_path)
63
+ project_config = YAML::load_file(load_path)
64
+ security_group.load_local(project_config['security_groups'].first)
65
+ updated_hash = security_group.update_vars
66
+ expect(updated_hash[:ingress].size).to eq(7)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+
73
+