practice_terraforming 0.1.3
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.
- 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 -%>
|