Skip to content

Instantly share code, notes, and snippets.

@tsprlng
Created April 4, 2018 19:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tsprlng/465d4856fb8fd454eea459a9584df0cd to your computer and use it in GitHub Desktop.
Save tsprlng/465d4856fb8fd454eea459a9584df0cd to your computer and use it in GitHub Desktop.
Working around the pain of moving from aws_security_group with ingress/egress to aws_security_group_rules in Terraform

In Terraform you might want to replace ingress/egress rules directly on an aws_security_group with individual aws_security_group_rules, so that they work properly.

To do this, first make the required *.tf changes. Great. Now the plan contains only rule additions, and application fails due to the collision with the undeleted old rules.

    terraform state rm aws_security_group.the_sg
    terraform import aws_security_group.the_sg sg-deadbeef

Great. Now it's imported a bunch of aws_security_group_rules called aws_security_group_rule.the_sg and aws_security_group_rule.the_sg-1 up to -whatever, rather than the aws_security_group_rule.descriptive_name you wanted.

At this point the plan will pointlessly wish to delete the numbered ones and recreate them as named ones. This will either fail again, or cause service to be interrupted.

The script in this gist is designed to easily rename the imported rules directly to your intended name in the state so that this doesn't happen.

    (for r in aws_security_group_rule.the_sg{,-{1..11}}; do echo "$r"; terraform state show "$r"; echo; done) | tee old-rules
    terraform plan -etc -etc | perl -pe 's(\x1b\[[0-9;]*[a-zA-Z])()g' | tee new-rules  # The perl is to get rid of the color escape codes from the planning output so it's more easily parseable
    
    ./autorename-sg-rules.rb  # filenames are hardcoded because boring

Now, it will output the required commands to rename the rules in the state, so the plan will make sense:

    terraform state mv aws_security_group_rule.the_sg aws_security_group_rule.let_in_users
    terraform state mv aws_security_group_rule.the_sg-1 aws_security_group_rule.let_out_logs
    terraform state mv aws_security_group_rule.the_sg-2 aws_security_group_rule.stupid_hack
    [...etc etc]
#!/usr/bin/ruby
old_rule_txt = IO::read './old-rules'
new_rule_txt = IO::read './new-rules'
old_rules = []
new_rules = []
collector = []
old_rule_txt.each_line do |l|
l.strip!
if l == ''
old_rules << { raw_lines: collector } unless collector.empty?
collector = []
else
collector << l
end
end
old_rules << collector unless collector.empty?
raise "wtf" unless collector.empty?
new_rule_txt.each_line do |l|
l.strip!
if l == ''
new_rules << { raw_lines: collector } if ! collector.empty? and collector[0].start_with? '+ aws_security_group_rule'
collector = []
else
collector << l
end
end
new_rules << collector if ! collector.empty? and collector[0].start_with? '+ aws_security_group_rule'
raise "wtf" unless collector.empty?
old_rules.each do |r|
r[:id] = r[:raw_lines][0]
r[:v] = 0
i = r[:i] = {}
i[:sg] = r[:raw_lines].collect {|l| l.start_with? 'security_group_id' and / = (.*)/ =~ l and $1}.select(&:itself).first
i[:source] = r[:raw_lines].collect {|l| l.start_with? 'source_security_group_id' and / = (.*)/ =~ l and $1}.select(&:itself).first
i[:source] = 'self' if r[:raw_lines].any? {|l| l =~ /self.*=.*true/ }
i[:cidrs] = r[:raw_lines].collect {|l| /^cidr_blocks.\d+.* = (.*)/ =~ l and $1}.select(&:itself).to_a
i[:from_port] = r[:raw_lines].collect {|l| l.start_with? 'from_port' and / = (.*)/ =~ l and $1}.select(&:itself).first.to_i
i[:to_port] = r[:raw_lines].collect {|l| l.start_with? 'to_port' and / = (.*)/ =~ l and $1}.select(&:itself).first.to_i
i[:protocol] = r[:raw_lines].collect {|l| l.start_with? 'protocol' and / = (.*)/ =~ l and $1}.select(&:itself).first
end
new_rules.each do |r|
r[:id] = r[:raw_lines][0].sub "+ ", ''
r[:v] = 1
i = r[:i] = {}
i[:sg] = r[:raw_lines].collect {|l| l.start_with? 'security_group_id:' and /"(.*)"/ =~ l and $1}.select(&:itself).first
i[:source] = r[:raw_lines].collect {|l| l.include? 'source_security_group_id:' and /"(.*)"/ =~ l and $1}.select(&:itself).first
i[:source] = 'self' if r[:raw_lines].any? {|l| l =~ /self.*true/ }
i[:cidrs] = r[:raw_lines].collect {|l| /^cidr_blocks.\d+:.*"(.*)"/ =~ l and $1}.select(&:itself).to_a
i[:from_port] = r[:raw_lines].collect {|l| l.include? 'from_port:' and /"(.*)"/ =~ l and $1}.select(&:itself).first.to_i
i[:to_port] = r[:raw_lines].collect {|l| l.include? 'to_port:' and /"(.*)"/ =~ l and $1}.select(&:itself).first.to_i
i[:protocol] = r[:raw_lines].collect {|l| l.include? 'protocol:' and /"(.*)"/ =~ l and $1}.select(&:itself).first
end
(old_rules + new_rules).group_by {|r| r[:i] }.map {|k, v| v.sort_by {|v| v[:v] }.to_a }.each do |o, n|
puts "terraform state mv \"#{o[:id]}\" \"#{n[:id]}\"" unless o.nil? or n.nil?
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment