practice_terraforming 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +34 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +93 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +95 -0
- data/LICENSE.txt +21 -0
- data/README.md +259 -0
- data/Rakefile +8 -0
- data/bin/practice_terraforming +6 -0
- data/lib/practice_terraforming.rb +16 -0
- data/lib/practice_terraforming/cli.rb +92 -0
- data/lib/practice_terraforming/resource/iam_policy_attachment.rb +85 -0
- data/lib/practice_terraforming/resource/iam_role.rb +59 -0
- data/lib/practice_terraforming/resource/iam_role_policy_attachment.rb +74 -0
- data/lib/practice_terraforming/resource/s3.rb +71 -0
- data/lib/practice_terraforming/template/tf/iam_policy_attachment.erb +10 -0
- data/lib/practice_terraforming/template/tf/iam_role.erb +11 -0
- data/lib/practice_terraforming/template/tf/iam_role_policy_attachment.erb +7 -0
- data/lib/practice_terraforming/template/tf/s3.erb +12 -0
- data/lib/practice_terraforming/util.rb +32 -0
- data/lib/practice_terraforming/version.rb +5 -0
- data/practice_terraforming.gemspec +35 -0
- data/script/console +15 -0
- data/script/generate +96 -0
- data/script/setup +7 -0
- data/templates/resource.erb.erb +3 -0
- data/templates/resource.rb.erb +31 -0
- data/templates/resource_spec.rb.erb +39 -0
- metadata +202 -0
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "aws-sdk-iam"
|
4
|
+
require "aws-sdk-s3"
|
5
|
+
require "multi_json"
|
6
|
+
require "thor"
|
7
|
+
require "erb"
|
8
|
+
|
9
|
+
require "practice_terraforming/util"
|
10
|
+
require 'practice_terraforming/version'
|
11
|
+
|
12
|
+
require "practice_terraforming/cli"
|
13
|
+
require "practice_terraforming/resource/iam_role"
|
14
|
+
require "practice_terraforming/resource/iam_role_policy_attachment"
|
15
|
+
require "practice_terraforming/resource/iam_policy_attachment"
|
16
|
+
require "practice_terraforming/resource/s3"
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PracticeTerraforming
|
4
|
+
class CLI < Thor
|
5
|
+
class_option :merge, type: :string, desc: "tfstate file to merge"
|
6
|
+
class_option :overwrite, type: :boolean, desc: "Overwrite existing tfstate"
|
7
|
+
class_option :tfstate, type: :boolean, desc: "Generate tfstate"
|
8
|
+
class_option :profile, type: :string, desc: "AWS credentials profile"
|
9
|
+
class_option :region, type: :string, desc: "AWS region"
|
10
|
+
class_option :assume, type: :string, desc: "Role ARN to assume"
|
11
|
+
class_option :use_bundled_cert,
|
12
|
+
type: :boolean,
|
13
|
+
desc: "Use the bundled CA certificate from AWS SDK"
|
14
|
+
|
15
|
+
# Subcommand name should be acronym.
|
16
|
+
desc "iamr", "Iam Role"
|
17
|
+
def iamr
|
18
|
+
execute(PracticeTerraforming::Resource::IAMRole, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "s3", "S3"
|
22
|
+
def s3
|
23
|
+
execute(PracticeTerraforming::Resource::S3, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "iampa", "Iam Policy Attachment"
|
27
|
+
def iampa
|
28
|
+
execute(PracticeTerraforming::Resource::IamPolicyAttachment, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "iamrpa", "Iam Role Policy Attachment"
|
32
|
+
def iamrpa
|
33
|
+
execute(PracticeTerraforming::Resource::IamRolePolicyAttachment, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def configure_aws(options)
|
39
|
+
Aws.config[:credentials] = Aws::SharedCredentials.new(profile_name: options[:profile]) if options[:profile]
|
40
|
+
Aws.config[:region] = options[:region] if options[:region]
|
41
|
+
|
42
|
+
if options[:assume]
|
43
|
+
args = { role_arn: options[:assume], role_session_name: "terraforming-session-#{Time.now.to_i}" }
|
44
|
+
args[:client] = Aws::STS::Client.new(profile: options[:profile]) if options[:profile]
|
45
|
+
Aws.config[:credentials] = Aws::AssumeRoleCredentials.new(args)
|
46
|
+
end
|
47
|
+
|
48
|
+
Aws.use_bundled_cert! if options[:use_bundled_cert]
|
49
|
+
end
|
50
|
+
|
51
|
+
def execute(klass, options)
|
52
|
+
configure_aws(options)
|
53
|
+
result = options[:tfstate] ? tfstate(klass, options[:merge]) : tf(klass)
|
54
|
+
|
55
|
+
if options[:tfstate] && options[:merge] && options[:overwrite]
|
56
|
+
open(options[:merge], "w+") do |f|
|
57
|
+
f.write(result)
|
58
|
+
f.flush
|
59
|
+
end
|
60
|
+
else
|
61
|
+
puts result
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def tf(klass)
|
66
|
+
klass.tf
|
67
|
+
end
|
68
|
+
|
69
|
+
def tfstate(klass, tfstate_path)
|
70
|
+
tfstate = tfstate_path ? MultiJson.load(open(tfstate_path).read) : tfstate_skeleton
|
71
|
+
tfstate["serial"] = tfstate["serial"] + 1
|
72
|
+
tfstate["modules"][0]["resources"] = tfstate["modules"][0]["resources"].merge(klass.tfstate)
|
73
|
+
MultiJson.encode(tfstate, pretty: true)
|
74
|
+
end
|
75
|
+
|
76
|
+
def tfstate_skeleton
|
77
|
+
{
|
78
|
+
"version" => 1,
|
79
|
+
"serial" => 0,
|
80
|
+
"modules" => [
|
81
|
+
{
|
82
|
+
"path" => [
|
83
|
+
"root"
|
84
|
+
],
|
85
|
+
"outputs" => {},
|
86
|
+
"resources" => {}
|
87
|
+
}
|
88
|
+
]
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PracticeTerraforming
|
4
|
+
module Resource
|
5
|
+
class IamPolicyAttachment
|
6
|
+
include PracticeTerraforming::Util
|
7
|
+
|
8
|
+
def self.tf(client: Aws::IAM::Client.new)
|
9
|
+
self.new(client).tf
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.tfstate(client: Aws::IAM::Client.new)
|
13
|
+
self.new(client).tfstate
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(client)
|
17
|
+
@client = client
|
18
|
+
end
|
19
|
+
|
20
|
+
def tf
|
21
|
+
apply_template(@client, "tf/iam_policy_attachment")
|
22
|
+
end
|
23
|
+
|
24
|
+
def tfstate
|
25
|
+
iam_policy_attachments.inject({}) do |resources, policy_attachment|
|
26
|
+
attributes = {
|
27
|
+
"id" => policy_attachment[:name],
|
28
|
+
"name" => policy_attachment[:name],
|
29
|
+
"policy_arn" => policy_attachment[:arn],
|
30
|
+
"groups.#" => policy_attachment[:entities].policy_groups.length.to_s,
|
31
|
+
"users.#" => policy_attachment[:entities].policy_users.length.to_s,
|
32
|
+
"roles.#" => policy_attachment[:entities].policy_roles.length.to_s
|
33
|
+
}
|
34
|
+
resources["aws_iam_policy_attachment.#{module_name_of(policy_attachment)}"] = {
|
35
|
+
"type" => "aws_iam_policy_attachment",
|
36
|
+
"primary" => {
|
37
|
+
"id" => policy_attachment[:name],
|
38
|
+
"attributes" => attributes
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
resources
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def attachment_name_from(policy)
|
49
|
+
"#{policy.policy_name}-policy-attachment"
|
50
|
+
end
|
51
|
+
|
52
|
+
def entities_for_policy(policy)
|
53
|
+
result = Aws::IAM::Types::ListEntitiesForPolicyResponse.new
|
54
|
+
result.policy_groups = []
|
55
|
+
result.policy_users = []
|
56
|
+
result.policy_roles = []
|
57
|
+
@client.list_entities_for_policy(policy_arn: policy.arn).each do |resp|
|
58
|
+
result.policy_groups += resp.policy_groups
|
59
|
+
result.policy_users += resp.policy_users
|
60
|
+
result.policy_roles += resp.policy_roles
|
61
|
+
end
|
62
|
+
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
66
|
+
def iam_policies
|
67
|
+
@client.list_policies(scope: "All", only_attached: true).map(&:policies).flatten
|
68
|
+
end
|
69
|
+
|
70
|
+
def iam_policy_attachments
|
71
|
+
iam_policies.map do |policy|
|
72
|
+
{
|
73
|
+
arn: policy.arn,
|
74
|
+
entities: entities_for_policy(policy),
|
75
|
+
name: attachment_name_from(policy)
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def module_name_of(policy_attachment)
|
81
|
+
normalize_module_name(policy_attachment[:name])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PracticeTerraforming
|
4
|
+
module Resource
|
5
|
+
class IAMRole
|
6
|
+
include PracticeTerraforming::Util
|
7
|
+
|
8
|
+
def self.tf(client: Aws::IAM::Client.new)
|
9
|
+
self.new(client).tf
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.tfstate(client: Aws::IAM::Client.new)
|
13
|
+
self.new(client).tfstate
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(client)
|
17
|
+
@client = client
|
18
|
+
end
|
19
|
+
|
20
|
+
def tf
|
21
|
+
apply_template(@client, "tf/iam_role")
|
22
|
+
end
|
23
|
+
|
24
|
+
def tfstate
|
25
|
+
iam_roles.inject({}) do |resources, role|
|
26
|
+
attributes = {
|
27
|
+
"arn" => role.arn,
|
28
|
+
"assume_role_policy" =>
|
29
|
+
prettify_policy(role.assume_role_policy_document, breakline: true, unescape: true),
|
30
|
+
"id" => role.role_name,
|
31
|
+
"name" => role.role_name,
|
32
|
+
"description" => role.description,
|
33
|
+
"path" => role.path,
|
34
|
+
"unique_id" => role.role_id
|
35
|
+
}
|
36
|
+
resources["aws_iam_role.#{module_name_of(role)}"] = {
|
37
|
+
"type" => "aws_iam_role",
|
38
|
+
"primary" => {
|
39
|
+
"id" => role.role_name,
|
40
|
+
"attributes" => attributes
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
resources
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def iam_roles
|
51
|
+
@client.list_roles.map(&:roles).flatten
|
52
|
+
end
|
53
|
+
|
54
|
+
def module_name_of(role)
|
55
|
+
normalize_module_name(role.role_name)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PracticeTerraforming
|
4
|
+
module Resource
|
5
|
+
class IamRolePolicyAttachment
|
6
|
+
include PracticeTerraforming::Util
|
7
|
+
|
8
|
+
def self.tf(client: Aws::IAM::Client.new)
|
9
|
+
self.new(client).tf
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.tfstate(client: Aws::IAM::Client.new)
|
13
|
+
self.new(client).tfstate
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(client)
|
17
|
+
@client = client
|
18
|
+
end
|
19
|
+
|
20
|
+
def tf
|
21
|
+
apply_template(@client, "tf/iam_role_policy_attachment")
|
22
|
+
end
|
23
|
+
|
24
|
+
def tfstate
|
25
|
+
iam_role_policy_attachments.inject({}) do |resources, role_policy_attachment|
|
26
|
+
attributes = {
|
27
|
+
"id" => role_policy_attachment[:name],
|
28
|
+
"policy_arn" => role_policy_attachment[:policy_arn],
|
29
|
+
"role" => role_policy_attachment[:role]
|
30
|
+
}
|
31
|
+
resources["aws_iam_role_policy_attachment.#{module_name_of(role_policy_attachment)}"] = {
|
32
|
+
"type" => "aws_iam_role_policy_attachment",
|
33
|
+
"primary" => {
|
34
|
+
"id" => role_policy_attachment[:name],
|
35
|
+
"attributes" => attributes
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
resources
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def attachment_name_from(role, policy)
|
46
|
+
"#{role.role_name}-#{policy.policy_name}-attachment"
|
47
|
+
end
|
48
|
+
|
49
|
+
def iam_roles
|
50
|
+
@client.list_roles.map(&:roles).flatten
|
51
|
+
end
|
52
|
+
|
53
|
+
def policies_attached_to(role)
|
54
|
+
@client.list_attached_role_policies(role_name: role.role_name).attached_policies
|
55
|
+
end
|
56
|
+
|
57
|
+
def iam_role_policy_attachments
|
58
|
+
iam_roles.map do |role|
|
59
|
+
policies_attached_to(role).map do |policy|
|
60
|
+
{
|
61
|
+
role: role.role_name,
|
62
|
+
policy_arn: policy.policy_arn,
|
63
|
+
name: attachment_name_from(role, policy)
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end.flatten
|
67
|
+
end
|
68
|
+
|
69
|
+
def module_name_of(role_policy_attachment)
|
70
|
+
normalize_module_name(role_policy_attachment[:name])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PracticeTerraforming
|
4
|
+
module Resource
|
5
|
+
class S3
|
6
|
+
include PracticeTerraforming::Util
|
7
|
+
|
8
|
+
def self.tf(client: Aws::S3::Client.new)
|
9
|
+
self.new(client).tf
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.tfstate(client: Aws::S3::Client.new)
|
13
|
+
self.new(client).tfstate
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(client)
|
17
|
+
@client = client
|
18
|
+
end
|
19
|
+
|
20
|
+
def tf
|
21
|
+
apply_template(@client, "tf/s3")
|
22
|
+
end
|
23
|
+
|
24
|
+
def tfstate
|
25
|
+
buckets.inject({}) do |resources, bucket|
|
26
|
+
bucket_policy = bucket_policy_of(bucket)
|
27
|
+
resources["aws_s3_bucket.#{module_name_of(bucket)}"] = {
|
28
|
+
"type" => "aws_s3_bucket",
|
29
|
+
"primary" => {
|
30
|
+
"id" => bucket.name,
|
31
|
+
"attributes" => {
|
32
|
+
"acl" => "private",
|
33
|
+
"bucket" => bucket.name,
|
34
|
+
"force_destroy" => "false",
|
35
|
+
"id" => bucket.name,
|
36
|
+
"policy" => bucket_policy ? bucket_policy.policy.read : ""
|
37
|
+
}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
resources
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def bucket_location_of(bucket)
|
48
|
+
@client.get_bucket_location(bucket: bucket.name).location_constraint
|
49
|
+
end
|
50
|
+
|
51
|
+
def bucket_policy_of(bucket)
|
52
|
+
@client.get_bucket_policy(bucket: bucket.name)
|
53
|
+
rescue Aws::S3::Errors::NoSuchBucketPolicy
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def buckets
|
58
|
+
@client.list_buckets.map(&:buckets).flatten.select { |bucket| same_region?(bucket) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def module_name_of(bucket)
|
62
|
+
normalize_module_name(bucket.name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def same_region?(bucket)
|
66
|
+
bucket_location = bucket_location_of(bucket)
|
67
|
+
(bucket_location == @client.config.region) || (bucket_location == "" && @client.config.region == "us-east-1")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<% iam_policy_attachments.each do |policy_attachment| -%>
|
2
|
+
resource "aws_iam_policy_attachment" "<%= module_name_of(policy_attachment) %>" {
|
3
|
+
name = "<%= policy_attachment[:name] %>"
|
4
|
+
policy_arn = "<%= policy_attachment[:arn] %>"
|
5
|
+
groups = <%= policy_attachment[:entities].policy_groups.map(&:group_name).inspect %>
|
6
|
+
users = <%= policy_attachment[:entities].policy_users.map(&:user_name).inspect %>
|
7
|
+
roles = <%= policy_attachment[:entities].policy_roles.map(&:role_name).inspect %>
|
8
|
+
}
|
9
|
+
|
10
|
+
<% end -%>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% iam_roles.each do |role| -%>
|
2
|
+
resource "aws_iam_role" "<%= module_name_of(role) %>" {
|
3
|
+
name = "<%= role.role_name %>"
|
4
|
+
description = "<%= role.description %>"
|
5
|
+
path = "<%= role.path %>"
|
6
|
+
assume_role_policy = <<POLICY
|
7
|
+
<%= prettify_policy(role.assume_role_policy_document, unescape: true) %>
|
8
|
+
POLICY
|
9
|
+
}
|
10
|
+
|
11
|
+
<% end -%>
|