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