demeter-cli 0.0.4

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,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
+