class Biosphere::CLI::TerraformPlanning

Public Instance Methods

build_terraform_targetting_plan(deployment, changes) click to toggle source
# File lib/biosphere/cli/terraformplanning.rb, line 344
def build_terraform_targetting_plan(deployment, changes)

    # This function will output an array of objects which describe a proper and safe
    # plan for terraform resources to be applied.
    #
    # We can include following sets in this array:
    # - resources which do not belong to any group
    # - resources which belong to a group where count(group) == 1
    # - a single resource from each group where count(group) > 1
    #
    # Each item is an object with the following fields:
    # - :resource_name
    # - :target_group (might be null)
    # - :reason (human readable reason)
    # - :action (symbol :not_picked, :relaunch, :change, :create, :destroy)
    #
    plan = TerraformPlan.new()

    group_changes_map = {}
    resource_to_target_group_map = {}

    resources_not_in_any_target_group = {}
    deployment.all_resources.each do |resource|
        belong_to_target_group = false
        resource_name = resource[:type] + "." + resource[:name]

        deployment.target_groups.each do |group_name, resources|
            if resources.include?(resource_name)
                resource_to_target_group_map[resource_name] = group_name
                belong_to_target_group = true
            end
        end

        if !belong_to_target_group
            resources_not_in_any_target_group[resource_name] = {
                :resource_name => resource_name,
                :target_group => "",
                :reason => :no_target_group,
                :action => :relaunch
            }
        end
    end

    # Easy case first: new resources. We just want to lookup the group so that we can show that to the user
    changes[:new_resources].each do |change|
        group = resource_to_target_group_map[change]
        if group
            plan.items << {
                :resource_name => change,
                :target_group => group,
                :reason => "new resource",
                :action => :create
            }
        else
            plan.items << {
                :resource_name => change,
                :target_group => "",
                :reason => "new resource",
                :action => :create
            }
            
        end
    end

    # Easy case first: new resources. We just want to lookup the group so that we can show that to the user
    changes[:changes].each do |change|
        group = resource_to_target_group_map[change]
        if group
            plan.items << {
                :resource_name => change,
                :target_group => group,
                :reason => "non-destructive change",
                :action => :change
            }
        else
            plan.items << {
                :resource_name => change,
                :target_group => "",
                :reason => "non-destructive change",
                :action => :change
            }
            
        end
    end

    # Relaunches are more complex: we need to bucket resources based on group, so that we can later pick just one change from each group
    changes[:relaunches].each do |change|
        group = resource_to_target_group_map[change]
        if group
            group_changes_map[group] = (group_changes_map[group] ||= []) << change
        elsif resources_not_in_any_target_group[change]
            # this handles a change to a resource which does not belong to any target group
            plan.items << resources_not_in_any_target_group[change]
        else
            # this handles the case where a resource was removed from the definition and
            # now terraform wants to destroy this resource
            plan.items << {
                :resource_name => change,
                :target_group => "",
                :reason => "resource definition has been removed",
                :action => :destroy
            }
            
        end
    end

    # Handle safe groups: just one changing resource in the group
    safe_groups = group_changes_map.select { |name, resources| resources.length <= 1 }
    safe_groups.each do |group_name, resources|
        resources.each do |resource_name|
            plan.items << {
                :resource_name => resource_name,
                :target_group => group_name,
                :reason => "only member in its group",
                :action => :relaunch
            }
        end
    end

    # Handle problematic groups: select one from each group where count(group) > 1
    problematic_groups = group_changes_map.select { |name, resources| resources.length > 1 }
    problematic_groups.each do |group_name, resources|
        original_length = resources.length
        plan.items << {
            :resource_name => resources.shift,
            :target_group => group_name,
            :reason => "group has total #{original_length} resources. Picked this as the first",
            :action => :relaunch
        }

        resources.each do |resource_name|
            plan.items << {
                :resource_name => resource_name,
                :target_group => group_name,
                :reason => "not selected from this group",
                :action => :not_picked
            }
        end
        
    end

    return plan
end
generate_plan(deployment, tf_output_str, tf_graph_str = nil) click to toggle source
# File lib/biosphere/cli/terraformplanning.rb, line 294
def generate_plan(deployment, tf_output_str, tf_graph_str = nil)

    if tf_graph_str
        @graph = Biosphere::TerraformGraph.new
        @graph.load(tf_graph_str)
    end

    data = parse_terraform_plan_output(tf_output_str)

    plan = build_terraform_targetting_plan(deployment, data)

    if @graph
        plan = @graph.filter_tf_plan(plan)
    end
    return plan
end
parse_terraform_plan_output(str) click to toggle source

returns object which contains interesting information on the terraform plan output.

:relaunches contains a list of resources which will be changed by a relaunch

# File lib/biosphere/cli/terraformplanning.rb, line 316
def parse_terraform_plan_output(str)
    relaunches = []
    changes = []
    new_resources = []
    lines = str.split("\n")
    lines.each do |line|
        # the gsub is to strip possible ansi colors away
        # the match is to pick the TF notation about how the resource is about to change following the resource name itself
        m = line.gsub(/\e\[[0-9;]*m/, "").match(/^([-~+\/]+)\s(\S+)/)
        if m
            # If the resource action contains a minus ('-' or '-/+') then
            # we know that the action will be destructive.
            if m[1].match(/[-]/)
                relaunches << m[2]
            elsif m[1] == "~"
                changes << m[2]
            elsif m[1] == "+"
                new_resources << m[2]
            end
        end
    end
    return {
        :relaunches => relaunches,
        :changes => changes,
        :new_resources => new_resources,
    }
end