practice_terraforming 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "practice_terraforming"
5
+
6
+ PracticeTerraforming::CLI.start(ARGV)
@@ -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 -%>