From e632fed37e193da14ab4e12cb0893024f1630b12 Mon Sep 17 00:00:00 2001 From: Yusuke KUOKA Date: Wed, 16 May 2018 14:40:41 +0900 Subject: [PATCH] feat: Filtering imported security groups by IDs `terraforming sg` now accepts one or more security groups via `--group-ids sg-12345 sg-234456`. This limits the tf output to include only the two security groups. Similarly, `terraform sg --tfstate --group-ids sg-12345` limits the tfstate output to include only the security group. An expected use-case to this flag is to gradually migrate hundreds of your security groups under the control of terraform, without worrying about the huge tf/tfstate diff on initial import. Run `terraforming help sg` to see the description of the flag: ``` bundle exec bin/terraforming help sg Usage: terraforming sg Options: [--group-ids=one two three] # Filter exported security groups by IDs [--merge=MERGE] # tfstate file to merge [--overwrite], [--no-overwrite] # Overwrite existing tfstate [--tfstate], [--no-tfstate] # Generate tfstate [--profile=PROFILE] # AWS credentials profile [--region=REGION] # AWS region [--assume=ASSUME] # Role ARN to assume [--use-bundled-cert], [--no-use-bundled-cert] # Use the bundled CA certificate from AWS SDK Security Group ``` --- lib/terraforming.rb | 2 +- lib/terraforming/cli.rb | 30 +++++-- lib/terraforming/resource/security_group.rb | 36 +++++++-- spec/lib/terraforming/cli_spec.rb | 7 ++ .../resource/security_group_spec.rb | 81 ++++++++++++++++++- 5 files changed, 143 insertions(+), 13 deletions(-) diff --git a/lib/terraforming.rb b/lib/terraforming.rb index 27f7eee2..6f732a5d 100644 --- a/lib/terraforming.rb +++ b/lib/terraforming.rb @@ -22,7 +22,6 @@ require "terraforming/util" require "terraforming/version" -require "terraforming/cli" require "terraforming/resource/alb" require "terraforming/resource/auto_scaling_group" require "terraforming/resource/cloud_watch_alarm" @@ -66,3 +65,4 @@ require "terraforming/resource/vpn_gateway" require "terraforming/resource/sns_topic" require "terraforming/resource/sns_topic_subscription" +require "terraforming/cli" diff --git a/lib/terraforming/cli.rb b/lib/terraforming/cli.rb index bacc38b0..775e0a21 100644 --- a/lib/terraforming/cli.rb +++ b/lib/terraforming/cli.rb @@ -1,5 +1,9 @@ module Terraforming class CLI < Thor + OPTIONS_AVAILABLE_TO_SUBCOMMANDS = [ + Terraforming::Resource::SecurityGroup::AVAILABLE_OPTIONS, + ].reduce(:concat).freeze + class_option :merge, type: :string, desc: "tfstate file to merge" class_option :overwrite, type: :boolean, desc: "Overwrite existing tfstate" class_option :tfstate, type: :boolean, desc: "Generate tfstate" @@ -191,6 +195,7 @@ def s3 end desc "sg", "Security Group" + method_option :"group-ids", type: :array, desc: "Filter exported security groups by IDs" def sg execute(Terraforming::Resource::SecurityGroup, options) end @@ -242,7 +247,13 @@ def configure_aws(options) def execute(klass, options) configure_aws(options) - result = options[:tfstate] ? tfstate(klass, options[:merge]) : tf(klass) + + subcommand_options = options.select { |k, v| OPTIONS_AVAILABLE_TO_SUBCOMMANDS.include? k } + result = if options[:tfstate] + tfstate(klass, options[:merge], subcommand_options) + else + tf(klass, subcommand_options) + end if options[:tfstate] && options[:merge] && options[:overwrite] open(options[:merge], "w+") do |f| @@ -254,14 +265,23 @@ def execute(klass, options) end end - def tf(klass) - klass.tf + def tf(klass, options={}) + if options.empty? + klass.tf + else + klass.tf(options) + end end - def tfstate(klass, tfstate_path) + def tfstate(klass, tfstate_path, options={}) tfstate = tfstate_path ? MultiJson.load(open(tfstate_path).read) : tfstate_skeleton tfstate["serial"] = tfstate["serial"] + 1 - tfstate["modules"][0]["resources"] = tfstate["modules"][0]["resources"].merge(klass.tfstate) + tfstate_addition = if options.empty? + klass.tfstate + else + klass.tfstate(options) + end + tfstate["modules"][0]["resources"] = tfstate["modules"][0]["resources"].merge(tfstate_addition) MultiJson.encode(tfstate, pretty: true) end diff --git a/lib/terraforming/resource/security_group.rb b/lib/terraforming/resource/security_group.rb index dfadd4b9..a7c33ca0 100644 --- a/lib/terraforming/resource/security_group.rb +++ b/lib/terraforming/resource/security_group.rb @@ -3,16 +3,35 @@ module Resource class SecurityGroup include Terraforming::Util - def self.tf(client: Aws::EC2::Client.new) - self.new(client).tf + module Options + GROUP_IDS = 'group-ids' end - def self.tfstate(client: Aws::EC2::Client.new) - self.new(client).tfstate + AVAILABLE_OPTIONS = [ + Options::GROUP_IDS, + ].freeze + + def self.tf(options={}) + opts = apply_defaults_to_options(options) + self + .new(opts[:client], opts) + .tf + end + + def self.tfstate(options={}) + opts = apply_defaults_to_options(options) + self.new(opts[:client], opts).tfstate + end + + def self.apply_defaults_to_options(options) + options.dup.tap { |o| + o[:client] ||= Aws::EC2::Client.new + } end - def initialize(client) + def initialize(client, options) @client = client + @group_ids = options[Options::GROUP_IDS] end def tf @@ -159,7 +178,12 @@ def self_referenced_permission?(security_group, permission) end def security_groups - @client.describe_security_groups.map(&:security_groups).flatten + description = if @group_ids + @client.describe_security_groups(group_ids: @group_ids) + else + @client.describe_security_groups + end + description.map(&:security_groups).flatten end def security_groups_in(permission, security_group) diff --git a/spec/lib/terraforming/cli_spec.rb b/spec/lib/terraforming/cli_spec.rb index c90f4f9f..f63e30ba 100644 --- a/spec/lib/terraforming/cli_spec.rb +++ b/spec/lib/terraforming/cli_spec.rb @@ -300,6 +300,13 @@ module Terraforming let(:command) { :sg } it_behaves_like "CLI examples" + + context "with --group-ids" do + it "should export tf" do + expect(klass).to receive(:tf).with({"group-ids" => %w| sg-1234abcd |}) + described_class.new.invoke(command, [], {"group-ids" => %w| sg-1234abcd |}) + end + end end describe "sqs" do diff --git a/spec/lib/terraforming/resource/security_group_spec.rb b/spec/lib/terraforming/resource/security_group_spec.rb index 58eb5440..d10590f0 100644 --- a/spec/lib/terraforming/resource/security_group_spec.rb +++ b/spec/lib/terraforming/resource/security_group_spec.rb @@ -178,7 +178,17 @@ module Resource end before do - client.stub_responses(:describe_security_groups, security_groups: security_groups) + client.stub_responses(:describe_security_groups, -> (context) { + group_ids = context.params[:group_ids] + groups = if group_ids + security_groups.select { |sg| + group_ids.include? sg[:group_id] + } + else + security_groups + end + { security_groups: groups } + }) end describe ".tf" do @@ -282,6 +292,36 @@ module Resource EOS end + + context "with --group-ids" do + it "should generate partial tf" do + expect(described_class.tf({:client => client, "group-ids" => %w| sg-1234abcd |})).to eq <<-EOS +resource "aws_security_group" "hoge" { + name = "hoge" + description = "Group for hoge" + vpc_id = "" + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + security_groups = ["987654321012/piyo"] + self = true + } + + +} + + EOS + end + end end describe ".tfstate" do @@ -405,6 +445,45 @@ module Resource }, }) end + + context "with --group-ids" do + it "should generate partial tfstate" do + expect(described_class.tfstate({:client => client, "group-ids" => %w| sg-1234abcd |})).to eq({ + "aws_security_group.hoge" => { + "type" => "aws_security_group", + "primary" => { + "id" => "sg-1234abcd", + "attributes" => { + "description" => "Group for hoge", + "id" => "sg-1234abcd", + "name" => "hoge", + "owner_id" => "012345678901", + "vpc_id" => "", + "tags.#" => "0", + "egress.#" => "0", + "ingress.#" => "2", + "ingress.2541437006.from_port" => "22", + "ingress.2541437006.to_port" => "22", + "ingress.2541437006.protocol" => "tcp", + "ingress.2541437006.cidr_blocks.#" => "1", + "ingress.2541437006.prefix_list_ids.#" => "0", + "ingress.2541437006.security_groups.#" => "0", + "ingress.2541437006.self" => "false", + "ingress.2541437006.cidr_blocks.0" => "0.0.0.0/0", + "ingress.3232230010.from_port" => "22", + "ingress.3232230010.to_port" => "22", + "ingress.3232230010.protocol" => "tcp", + "ingress.3232230010.cidr_blocks.#" => "0", + "ingress.3232230010.prefix_list_ids.#" => "0", + "ingress.3232230010.security_groups.#" => "1", + "ingress.3232230010.self" => "true", + "ingress.3232230010.security_groups.1889292513" => "987654321012/piyo", + } + } + }, + }) + end + end end end end