mux_tf 0.14.2 → 0.16.0
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.
- checksums.yaml +4 -4
- data/lib/mux_tf/cli/current/plan_command.rb +123 -0
- data/lib/mux_tf/cli/current.rb +100 -168
- data/lib/mux_tf/cli/mux.rb +168 -5
- data/lib/mux_tf/cli/plan_summary.rb +12 -21
- data/lib/mux_tf/error_handling_methods.rb +79 -0
- data/lib/mux_tf/handlers/plan_handler.rb +8 -0
- data/lib/mux_tf/handlers.rb +6 -0
- data/lib/mux_tf/plan_formatter.rb +286 -248
- data/lib/mux_tf/plan_summary_handler.rb +36 -29
- data/lib/mux_tf/plan_utils.rb +20 -4
- data/lib/mux_tf/resource_tokenizer.rb +1 -1
- data/lib/mux_tf/stderr_line_handler.rb +145 -0
- data/lib/mux_tf/terraform_helpers.rb +44 -6
- data/lib/mux_tf/tmux.rb +55 -6
- data/lib/mux_tf/version.rb +1 -1
- data/lib/mux_tf/version_check.rb +1 -1
- data/lib/mux_tf.rb +6 -12
- data/mux_tf.gemspec +7 -1
- metadata +69 -8
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 8f2bcb9ac31603cfab773175a4d81a7ab85e8cd44124dbdf7287234dd70537db
         | 
| 4 | 
            +
              data.tar.gz: e50b7671fabc4ca94703452daef5fa3a0322f2f4fce103d2a1f02b05012e80a1
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 5bf42a828264de913553c309f8acf9274175a7a2a2e9bc71f04534e91649ca6e91727ccf21677406f52d71331771935ead9f2011c4bb6b4780988174b7860767
         | 
| 7 | 
            +
              data.tar.gz: abaaff54837201173a5c2ccaa796e2c501d48f4f8d72c46687b6275b6541037284be57dd8090c88d4d88c3eeee8983f68b03ce85e6a6bea4d98df914d1e62dbd
         | 
| @@ -0,0 +1,123 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module MuxTf
         | 
| 4 | 
            +
              module Cli
         | 
| 5 | 
            +
                module Current
         | 
| 6 | 
            +
                  class PlanCommand
         | 
| 7 | 
            +
                    include TerraformHelpers
         | 
| 8 | 
            +
                    include PiotrbCliUtils::CriCommandSupport
         | 
| 9 | 
            +
                    extend PiotrbCliUtils::Util
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    def plan_cmd
         | 
| 12 | 
            +
                      define_cmd("plan", summary: "Re-run plan") do |_opts, _args, _cmd|
         | 
| 13 | 
            +
                        run_validate && run_plan
         | 
| 14 | 
            +
                      end
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    # returns boolean true if succeeded
         | 
| 18 | 
            +
                    def run_validate(level: 1)
         | 
| 19 | 
            +
                      Current.remedy_retry_helper(from: :validate, level: level) do
         | 
| 20 | 
            +
                        validation_info = validate
         | 
| 21 | 
            +
                        PlanFormatter.print_validation_errors(validation_info)
         | 
| 22 | 
            +
                        remedies = PlanFormatter.process_validation(validation_info)
         | 
| 23 | 
            +
                        [remedies, validation_info]
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    def run_plan(targets: [], level: 1, retry_count: 0)
         | 
| 28 | 
            +
                      plan_status, = Current.remedy_retry_helper(from: :plan, level: level, attempt: retry_count) {
         | 
| 29 | 
            +
                        @last_lock_info = nil
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                        plan_filename = PlanFilenameGenerator.for_path
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                        plan_status, meta = create_plan(plan_filename, targets: targets)
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                        Current.print_errors_and_warnings(meta)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                        remedies = detect_remedies_from_plan(meta)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                        if remedies.include?(:unlock)
         | 
| 40 | 
            +
                          @last_lock_info = extract_lock_info(meta)
         | 
| 41 | 
            +
                          throw :abort, [plan_status, meta]
         | 
| 42 | 
            +
                        end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                        throw :abort, [plan_status, meta] if remedies.include?(:auth)
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                        [remedies, plan_status, meta]
         | 
| 47 | 
            +
                      }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                      case plan_status
         | 
| 50 | 
            +
                      when :ok
         | 
| 51 | 
            +
                        log "no changes", depth: 1
         | 
| 52 | 
            +
                      when :error
         | 
| 53 | 
            +
                        log "something went wrong", depth: 1
         | 
| 54 | 
            +
                      when :changes
         | 
| 55 | 
            +
                        unless ENV["JSON_PLAN"]
         | 
| 56 | 
            +
                          log "Printing Plan Summary ...", depth: 1
         | 
| 57 | 
            +
                          plan_filename = PlanFilenameGenerator.for_path
         | 
| 58 | 
            +
                          pretty_plan_summary(plan_filename)
         | 
| 59 | 
            +
                        end
         | 
| 60 | 
            +
                        puts plan_summary_text if ENV["JSON_PLAN"]
         | 
| 61 | 
            +
                      when :unknown
         | 
| 62 | 
            +
                        # nothing
         | 
| 63 | 
            +
                      end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                      plan_status
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                    private
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    def validate
         | 
| 71 | 
            +
                      log "Validating module ...", depth: 1
         | 
| 72 | 
            +
                      tf_validate # from Terraform Helpers
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    def create_plan(filename, targets: [])
         | 
| 76 | 
            +
                      log "Preparing Plan ...", depth: 1
         | 
| 77 | 
            +
                      exit_code, meta = PlanFormatter.pretty_plan(filename, targets: targets)
         | 
| 78 | 
            +
                      case exit_code
         | 
| 79 | 
            +
                      when 0
         | 
| 80 | 
            +
                        [:ok, meta]
         | 
| 81 | 
            +
                      when 1
         | 
| 82 | 
            +
                        [:error, meta]
         | 
| 83 | 
            +
                      when 2
         | 
| 84 | 
            +
                        [:changes, meta]
         | 
| 85 | 
            +
                      else
         | 
| 86 | 
            +
                        log pastel.yellow("terraform plan exited with an unknown exit code: #{exit_code}")
         | 
| 87 | 
            +
                        [:unknown, meta]
         | 
| 88 | 
            +
                      end
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    def detect_remedies_from_plan(meta)
         | 
| 92 | 
            +
                      remedies = Set.new
         | 
| 93 | 
            +
                      meta[:errors]&.each do |error|
         | 
| 94 | 
            +
                        remedies << :plan if error[:message].include?("timeout while waiting for plugin to start")
         | 
| 95 | 
            +
                      end
         | 
| 96 | 
            +
                      remedies << :unlock if lock_error?(meta)
         | 
| 97 | 
            +
                      remedies << :auth if meta[:need_auth]
         | 
| 98 | 
            +
                      remedies
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                    def lock_error?(meta)
         | 
| 102 | 
            +
                      meta && meta["error"] == "lock"
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    def extract_lock_info(meta)
         | 
| 106 | 
            +
                      {
         | 
| 107 | 
            +
                        lock_id: meta["ID"],
         | 
| 108 | 
            +
                        operation: meta["Operation"],
         | 
| 109 | 
            +
                        who: meta["Who"],
         | 
| 110 | 
            +
                        created: meta["Created"]
         | 
| 111 | 
            +
                      }
         | 
| 112 | 
            +
                    end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    def pretty_plan_summary(filename)
         | 
| 115 | 
            +
                      plan = PlanSummaryHandler.from_file(filename)
         | 
| 116 | 
            +
                      plan.simple_summary do |line|
         | 
| 117 | 
            +
                        log line, depth: 2
         | 
| 118 | 
            +
                      end
         | 
| 119 | 
            +
                    end
         | 
| 120 | 
            +
                  end
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
              end
         | 
| 123 | 
            +
            end
         | 
    
        data/lib/mux_tf/cli/current.rb
    CHANGED
    
    | @@ -11,10 +11,19 @@ module MuxTf | |
| 11 11 | 
             
                  extend PiotrbCliUtils::CmdLoop
         | 
| 12 12 | 
             
                  include Coloring
         | 
| 13 13 |  | 
| 14 | 
            -
                  class << self | 
| 14 | 
            +
                  class << self
         | 
| 15 | 
            +
                    attr_accessor :plan_command
         | 
| 16 | 
            +
             | 
| 15 17 | 
             
                    def run(args)
         | 
| 16 18 | 
             
                      version_check
         | 
| 17 19 |  | 
| 20 | 
            +
                      self.plan_command = PlanCommand.new
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                      ENV["TF_IN_AUTOMATION"] = "1"
         | 
| 23 | 
            +
                      ENV["TF_INPUT"] = "0"
         | 
| 24 | 
            +
                      ENV["TERRAGRUNT_JSON_LOG"] = "1"
         | 
| 25 | 
            +
                      ENV["TERRAGRUNT_FORWARD_TF_STDOUT"] = "1"
         | 
| 26 | 
            +
             | 
| 18 27 | 
             
                      if args[0] == "cli"
         | 
| 19 28 | 
             
                        cmd_loop
         | 
| 20 29 | 
             
                        return
         | 
| @@ -36,17 +45,14 @@ module MuxTf | |
| 36 45 | 
             
                      folder_name = File.basename(Dir.getwd)
         | 
| 37 46 | 
             
                      log "Processing #{pastel.cyan(folder_name)} ..."
         | 
| 38 47 |  | 
| 39 | 
            -
                       | 
| 40 | 
            -
                      ENV["TF_INPUT"] = "0"
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                      return launch_cmd_loop(:error) unless run_validate
         | 
| 48 | 
            +
                      return launch_cmd_loop(:error) unless @plan_command.run_validate
         | 
| 43 49 |  | 
| 44 50 | 
             
                      if ENV["TF_UPGRADE"]
         | 
| 45 51 | 
             
                        upgrade_status, _upgrade_meta = run_upgrade
         | 
| 46 52 | 
             
                        return launch_cmd_loop(:error) unless upgrade_status == :ok
         | 
| 47 53 | 
             
                      end
         | 
| 48 54 |  | 
| 49 | 
            -
                      plan_status = run_plan
         | 
| 55 | 
            +
                      plan_status = @plan_command.run_plan
         | 
| 50 56 |  | 
| 51 57 | 
             
                      case plan_status
         | 
| 52 58 | 
             
                      when :ok
         | 
| @@ -60,23 +66,6 @@ module MuxTf | |
| 60 66 | 
             
                      end
         | 
| 61 67 | 
             
                    end
         | 
| 62 68 |  | 
| 63 | 
            -
                    def plan_filename
         | 
| 64 | 
            -
                      PlanFilenameGenerator.for_path
         | 
| 65 | 
            -
                    end
         | 
| 66 | 
            -
             | 
| 67 | 
            -
                    private
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                    def version_check
         | 
| 70 | 
            -
                      return unless VersionCheck.has_updates?
         | 
| 71 | 
            -
             | 
| 72 | 
            -
                      log pastel.yellow("=" * 80)
         | 
| 73 | 
            -
                      log "New version of #{pastel.cyan('mux_tf')} is available!"
         | 
| 74 | 
            -
                      log "You are currently on version: #{pastel.yellow(VersionCheck.current_gem_version)}"
         | 
| 75 | 
            -
                      log "Latest version found is: #{pastel.green(VersionCheck.latest_gem_version)}"
         | 
| 76 | 
            -
                      log "Run `#{pastel.green('gem install mux_tf')}` to update!"
         | 
| 77 | 
            -
                      log pastel.yellow("=" * 80)
         | 
| 78 | 
            -
                    end
         | 
| 79 | 
            -
             | 
| 80 69 | 
             
                    # block is expected to return a touple, the first element is a list of remedies
         | 
| 81 70 | 
             
                    # the rest are any additional results
         | 
| 82 71 | 
             
                    def remedy_retry_helper(from:, level: 1, attempt: 0, &block)
         | 
| @@ -86,24 +75,61 @@ module MuxTf | |
| 86 75 | 
             
                          remedies, *results = block.call
         | 
| 87 76 | 
             
                          return results if remedies.empty?
         | 
| 88 77 |  | 
| 89 | 
            -
                          remedy_status,  | 
| 78 | 
            +
                          remedy_status, remedy_results = process_remedies(remedies, from: from, level: level)
         | 
| 79 | 
            +
                          throw :abort, false if remedy_results[:user_error]
         | 
| 90 80 | 
             
                          return remedy_status if remedy_status
         | 
| 91 81 | 
             
                        end
         | 
| 92 82 | 
             
                        log "!! giving up because attempt: #{attempt}"
         | 
| 93 83 | 
             
                      end
         | 
| 94 84 | 
             
                    end
         | 
| 95 85 |  | 
| 96 | 
            -
                     | 
| 97 | 
            -
             | 
| 98 | 
            -
                       | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
                         | 
| 102 | 
            -
                         | 
| 86 | 
            +
                    def print_errors_and_warnings(meta)
         | 
| 87 | 
            +
                      message = []
         | 
| 88 | 
            +
                      message << pastel.yellow("#{meta[:warnings].length} Warnings") if meta[:warnings]
         | 
| 89 | 
            +
                      message << pastel.red("#{meta[:errors].length} Errors") if meta[:errors]
         | 
| 90 | 
            +
                      if message.length.positive?
         | 
| 91 | 
            +
                        log ""
         | 
| 92 | 
            +
                        log "Encountered: #{message.join(' and ')}"
         | 
| 93 | 
            +
                        log ""
         | 
| 94 | 
            +
                      end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                      meta[:warnings]&.each do |warning|
         | 
| 97 | 
            +
                        log "-" * 20
         | 
| 98 | 
            +
                        log pastel.yellow("Warning: #{warning[:message]}")
         | 
| 99 | 
            +
                        warning[:body]&.each do |line|
         | 
| 100 | 
            +
                          log pastel.yellow(line), depth: 1
         | 
| 101 | 
            +
                        end
         | 
| 102 | 
            +
                        log ""
         | 
| 103 103 | 
             
                      end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                      meta[:errors]&.each do |error|
         | 
| 106 | 
            +
                        log "-" * 20
         | 
| 107 | 
            +
                        log pastel.red("Error: #{error[:message]}")
         | 
| 108 | 
            +
                        error[:body]&.each do |line|
         | 
| 109 | 
            +
                          log pastel.red(line), depth: 1
         | 
| 110 | 
            +
                        end
         | 
| 111 | 
            +
                        log ""
         | 
| 112 | 
            +
                      end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                      return unless message.length.positive?
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                      log ""
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    private
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                    def version_check
         | 
| 122 | 
            +
                      return unless VersionCheck.has_updates?
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                      log pastel.yellow("=" * 80)
         | 
| 125 | 
            +
                      log "New version of #{pastel.cyan('mux_tf')} is available!"
         | 
| 126 | 
            +
                      log "You are currently on version: #{pastel.yellow(VersionCheck.current_gem_version)}"
         | 
| 127 | 
            +
                      log "Latest version found is: #{pastel.green(VersionCheck.latest_gem_version)}"
         | 
| 128 | 
            +
                      log "Run `#{pastel.green('gem install mux_tf')}` to update!"
         | 
| 129 | 
            +
                      log pastel.yellow("=" * 80)
         | 
| 104 130 | 
             
                    end
         | 
| 105 131 |  | 
| 106 | 
            -
                    def process_remedies(remedies, from: nil, level: 1, retry_count: 0) | 
| 132 | 
            +
                    def process_remedies(remedies, from: nil, level: 1, retry_count: 0)
         | 
| 107 133 | 
             
                      remedies = remedies.dup
         | 
| 108 134 | 
             
                      remedy = nil
         | 
| 109 135 | 
             
                      wrap_log = lambda do |msg, color: nil|
         | 
| @@ -136,7 +162,7 @@ module MuxTf | |
| 136 162 | 
             
                      if remedies.delete?(:plan)
         | 
| 137 163 | 
             
                        remedy = :plan
         | 
| 138 164 | 
             
                        log wrap_log["Running terraform plan ..."], depth: 2
         | 
| 139 | 
            -
                        plan_status = run_plan(retry_count: retry_count)
         | 
| 165 | 
            +
                        plan_status = @plan_command.run_plan(retry_count: retry_count)
         | 
| 140 166 | 
             
                        results[:plan_status] = plan_status
         | 
| 141 167 | 
             
                        return [false, results] unless [:ok, :changes].include?(plan_status)
         | 
| 142 168 | 
             
                      end
         | 
| @@ -160,6 +186,15 @@ module MuxTf | |
| 160 186 | 
             
                        log wrap_log["-" * 40, color: :red]
         | 
| 161 187 | 
             
                        log wrap_log["!! User Error, Please fix the issue and try again", color: :red]
         | 
| 162 188 | 
             
                        log wrap_log["-" * 40, color: :red]
         | 
| 189 | 
            +
                        results[:user_error] = true
         | 
| 190 | 
            +
                        return [false, results]
         | 
| 191 | 
            +
                      end
         | 
| 192 | 
            +
                      if remedies.delete? :auth
         | 
| 193 | 
            +
                        remedy = :auth
         | 
| 194 | 
            +
                        log wrap_log["auth error encountered!", color: :red]
         | 
| 195 | 
            +
                        log wrap_log["-" * 40, color: :red]
         | 
| 196 | 
            +
                        log wrap_log["!! Auth Error, Please fix the issue and try again", color: :red]
         | 
| 197 | 
            +
                        log wrap_log["-" * 40, color: :red]
         | 
| 163 198 | 
             
                        return [false, results]
         | 
| 164 199 | 
             
                      end
         | 
| 165 200 |  | 
| @@ -174,27 +209,6 @@ module MuxTf | |
| 174 209 | 
             
                      [true, results]
         | 
| 175 210 | 
             
                    end
         | 
| 176 211 |  | 
| 177 | 
            -
                    def validate
         | 
| 178 | 
            -
                      log "Validating module ...", depth: 1
         | 
| 179 | 
            -
                      tf_validate.parsed_output
         | 
| 180 | 
            -
                    end
         | 
| 181 | 
            -
             | 
| 182 | 
            -
                    def create_plan(filename, targets: [])
         | 
| 183 | 
            -
                      log "Preparing Plan ...", depth: 1
         | 
| 184 | 
            -
                      exit_code, meta = PlanFormatter.pretty_plan(filename, targets: targets)
         | 
| 185 | 
            -
                      case exit_code
         | 
| 186 | 
            -
                      when 0
         | 
| 187 | 
            -
                        [:ok, meta]
         | 
| 188 | 
            -
                      when 1
         | 
| 189 | 
            -
                        [:error, meta]
         | 
| 190 | 
            -
                      when 2
         | 
| 191 | 
            -
                        [:changes, meta]
         | 
| 192 | 
            -
                      else
         | 
| 193 | 
            -
                        log pastel.yellow("terraform plan exited with an unknown exit code: #{exit_code}")
         | 
| 194 | 
            -
                        [:unknown, meta]
         | 
| 195 | 
            -
                      end
         | 
| 196 | 
            -
                    end
         | 
| 197 | 
            -
             | 
| 198 212 | 
             
                    def launch_cmd_loop(status)
         | 
| 199 213 | 
             
                      return if ENV["NO_CMD"]
         | 
| 200 214 |  | 
| @@ -232,7 +246,7 @@ module MuxTf | |
| 232 246 | 
             
                    def build_root_cmd
         | 
| 233 247 | 
             
                      root_cmd = define_cmd(nil)
         | 
| 234 248 |  | 
| 235 | 
            -
                      root_cmd.add_command(plan_cmd)
         | 
| 249 | 
            +
                      root_cmd.add_command(@plan_command.plan_cmd)
         | 
| 236 250 | 
             
                      root_cmd.add_command(apply_cmd)
         | 
| 237 251 | 
             
                      root_cmd.add_command(shell_cmd)
         | 
| 238 252 | 
             
                      root_cmd.add_command(force_unlock_cmd)
         | 
| @@ -240,6 +254,7 @@ module MuxTf | |
| 240 254 | 
             
                      root_cmd.add_command(reconfigure_cmd)
         | 
| 241 255 | 
             
                      root_cmd.add_command(interactive_cmd)
         | 
| 242 256 | 
             
                      root_cmd.add_command(plan_details_cmd)
         | 
| 257 | 
            +
                      root_cmd.add_command(init_cmd)
         | 
| 243 258 |  | 
| 244 259 | 
             
                      root_cmd.add_command(exit_cmd)
         | 
| 245 260 | 
             
                      root_cmd.add_command(define_cmd("help", summary: "Show help for commands") { |_opts, _args, cmd| puts cmd.supercommand.help })
         | 
| @@ -261,20 +276,26 @@ module MuxTf | |
| 261 276 | 
             
                    def plan_details_cmd
         | 
| 262 277 | 
             
                      define_cmd("details", summary: "Show Plan Details") do |_opts, _args, _cmd|
         | 
| 263 278 | 
             
                        puts plan_summary_text
         | 
| 264 | 
            -
                      end
         | 
| 265 | 
            -
                    end
         | 
| 266 279 |  | 
| 267 | 
            -
             | 
| 268 | 
            -
             | 
| 269 | 
            -
             | 
| 280 | 
            +
                        unless ENV["JSON_PLAN"]
         | 
| 281 | 
            +
                          log "Printing Plan Summary ...", depth: 1
         | 
| 282 | 
            +
             | 
| 283 | 
            +
                          plan_filename = PlanFilenameGenerator.for_path
         | 
| 284 | 
            +
                          plan = PlanSummaryHandler.from_file(plan_filename)
         | 
| 285 | 
            +
                          plan.simple_summary do |line|
         | 
| 286 | 
            +
                            log line
         | 
| 287 | 
            +
                          end
         | 
| 288 | 
            +
                        end
         | 
| 289 | 
            +
                        # puts plan_summary_text if ENV["JSON_PLAN"]
         | 
| 270 290 | 
             
                      end
         | 
| 271 291 | 
             
                    end
         | 
| 272 292 |  | 
| 273 293 | 
             
                    def apply_cmd
         | 
| 274 294 | 
             
                      define_cmd("apply", summary: "Apply the current plan") do |_opts, _args, _cmd|
         | 
| 295 | 
            +
                        plan_filename = PlanFilenameGenerator.for_path
         | 
| 275 296 | 
             
                        status = tf_apply(filename: plan_filename)
         | 
| 276 297 | 
             
                        if status.success?
         | 
| 277 | 
            -
                          plan_status = run_plan
         | 
| 298 | 
            +
                          plan_status = @plan_command.run_plan
         | 
| 278 299 | 
             
                          throw :stop, :done if plan_status == :ok
         | 
| 279 300 | 
             
                        else
         | 
| 280 301 | 
             
                          log "Apply Failed!"
         | 
| @@ -335,6 +356,17 @@ module MuxTf | |
| 335 356 | 
             
                      end
         | 
| 336 357 | 
             
                    end
         | 
| 337 358 |  | 
| 359 | 
            +
                    def init_cmd
         | 
| 360 | 
            +
                      define_cmd("init", summary: "Re-run init") do |_opts, _args, _cmd|
         | 
| 361 | 
            +
                        exit_code, meta = PlanFormatter.run_tf_init
         | 
| 362 | 
            +
                        print_errors_and_warnings(meta)
         | 
| 363 | 
            +
                        if exit_code != 0
         | 
| 364 | 
            +
                          log meta.inspect unless meta.empty?
         | 
| 365 | 
            +
                          log "Init Failed!"
         | 
| 366 | 
            +
                        end
         | 
| 367 | 
            +
                      end
         | 
| 368 | 
            +
                    end
         | 
| 369 | 
            +
             | 
| 338 370 | 
             
                    def upgrade_cmd
         | 
| 339 371 | 
             
                      define_cmd("upgrade", summary: "Upgrade modules/plguins") do |_opts, _args, _cmd|
         | 
| 340 372 | 
             
                        status, meta = run_upgrade
         | 
| @@ -358,13 +390,18 @@ module MuxTf | |
| 358 390 |  | 
| 359 391 | 
             
                    def interactive_cmd
         | 
| 360 392 | 
             
                      define_cmd("interactive", summary: "Apply interactively") do |_opts, _args, _cmd|
         | 
| 393 | 
            +
                        plan_filename = PlanFilenameGenerator.for_path
         | 
| 361 394 | 
             
                        plan = PlanSummaryHandler.from_file(plan_filename)
         | 
| 362 395 | 
             
                        begin
         | 
| 363 | 
            -
                          abort_message = catch(:abort) { | 
| 396 | 
            +
                          abort_message = catch(:abort) {
         | 
| 397 | 
            +
                            result = plan.run_interactive
         | 
| 398 | 
            +
                            log "Re-running apply with the selected resources ..."
         | 
| 399 | 
            +
                            @plan_command.run_plan(targets: result)
         | 
| 400 | 
            +
                          }
         | 
| 364 401 | 
             
                          if abort_message
         | 
| 365 402 | 
             
                            log pastel.red("Aborted: #{abort_message}")
         | 
| 366 403 | 
             
                          else
         | 
| 367 | 
            -
                            run_plan
         | 
| 404 | 
            +
                            @plan_command.run_plan
         | 
| 368 405 | 
             
                          end
         | 
| 369 406 | 
             
                        rescue Exception => e # rubocop:disable Lint/RescueException
         | 
| 370 407 | 
             
                          log e.full_message
         | 
| @@ -373,99 +410,6 @@ module MuxTf | |
| 373 410 | 
             
                      end
         | 
| 374 411 | 
             
                    end
         | 
| 375 412 |  | 
| 376 | 
            -
                    def print_errors_and_warnings(meta) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
         | 
| 377 | 
            -
                      message = []
         | 
| 378 | 
            -
                      message << pastel.yellow("#{meta[:warnings].length} Warnings") if meta[:warnings]
         | 
| 379 | 
            -
                      message << pastel.red("#{meta[:errors].length} Errors") if meta[:errors]
         | 
| 380 | 
            -
                      if message.length.positive?
         | 
| 381 | 
            -
                        log ""
         | 
| 382 | 
            -
                        log "Encountered: #{message.join(' and ')}"
         | 
| 383 | 
            -
                        log ""
         | 
| 384 | 
            -
                      end
         | 
| 385 | 
            -
             | 
| 386 | 
            -
                      meta[:warnings]&.each do |warning|
         | 
| 387 | 
            -
                        log "-" * 20
         | 
| 388 | 
            -
                        log pastel.yellow("Warning: #{warning[:message]}")
         | 
| 389 | 
            -
                        warning[:body]&.each do |line|
         | 
| 390 | 
            -
                          log pastel.yellow(line), depth: 1
         | 
| 391 | 
            -
                        end
         | 
| 392 | 
            -
                        log ""
         | 
| 393 | 
            -
                      end
         | 
| 394 | 
            -
             | 
| 395 | 
            -
                      meta[:errors]&.each do |error|
         | 
| 396 | 
            -
                        log "-" * 20
         | 
| 397 | 
            -
                        log pastel.red("Error: #{error[:message]}")
         | 
| 398 | 
            -
                        error[:body]&.each do |line|
         | 
| 399 | 
            -
                          log pastel.red(line), depth: 1
         | 
| 400 | 
            -
                        end
         | 
| 401 | 
            -
                        log ""
         | 
| 402 | 
            -
                      end
         | 
| 403 | 
            -
             | 
| 404 | 
            -
                      return unless message.length.positive?
         | 
| 405 | 
            -
             | 
| 406 | 
            -
                      log ""
         | 
| 407 | 
            -
                    end
         | 
| 408 | 
            -
             | 
| 409 | 
            -
                    def detect_remedies_from_plan(meta)
         | 
| 410 | 
            -
                      remedies = Set.new
         | 
| 411 | 
            -
                      meta[:errors]&.each do |error|
         | 
| 412 | 
            -
                        remedies << :plan if error[:message].include?("timeout while waiting for plugin to start")
         | 
| 413 | 
            -
                      end
         | 
| 414 | 
            -
                      remedies << :unlock if lock_error?(meta)
         | 
| 415 | 
            -
                      remedies
         | 
| 416 | 
            -
                    end
         | 
| 417 | 
            -
             | 
| 418 | 
            -
                    def lock_error?(meta)
         | 
| 419 | 
            -
                      meta && meta["error"] == "lock"
         | 
| 420 | 
            -
                    end
         | 
| 421 | 
            -
             | 
| 422 | 
            -
                    def extract_lock_info(meta)
         | 
| 423 | 
            -
                      {
         | 
| 424 | 
            -
                        lock_id: meta["ID"],
         | 
| 425 | 
            -
                        operation: meta["Operation"],
         | 
| 426 | 
            -
                        who: meta["Who"],
         | 
| 427 | 
            -
                        created: meta["Created"]
         | 
| 428 | 
            -
                      }
         | 
| 429 | 
            -
                    end
         | 
| 430 | 
            -
             | 
| 431 | 
            -
                    def run_plan(targets: [], level: 1, retry_count: 0)
         | 
| 432 | 
            -
                      plan_status, = remedy_retry_helper(from: :plan, level: level, attempt: retry_count) {
         | 
| 433 | 
            -
                        @last_lock_info = nil
         | 
| 434 | 
            -
             | 
| 435 | 
            -
                        plan_status, meta = create_plan(plan_filename, targets: targets)
         | 
| 436 | 
            -
             | 
| 437 | 
            -
                        print_errors_and_warnings(meta)
         | 
| 438 | 
            -
             | 
| 439 | 
            -
                        remedies = detect_remedies_from_plan(meta)
         | 
| 440 | 
            -
             | 
| 441 | 
            -
                        if remedies.include?(:unlock)
         | 
| 442 | 
            -
                          @last_lock_info = extract_lock_info(meta)
         | 
| 443 | 
            -
                          throw :abort, [plan_status, meta]
         | 
| 444 | 
            -
                        end
         | 
| 445 | 
            -
             | 
| 446 | 
            -
                        [remedies, plan_status, meta]
         | 
| 447 | 
            -
                      }
         | 
| 448 | 
            -
             | 
| 449 | 
            -
                      case plan_status
         | 
| 450 | 
            -
                      when :ok
         | 
| 451 | 
            -
                        log "no changes", depth: 1
         | 
| 452 | 
            -
                      when :error
         | 
| 453 | 
            -
                        log "something went wrong", depth: 1
         | 
| 454 | 
            -
                      when :changes
         | 
| 455 | 
            -
                        unless ENV["JSON_PLAN"]
         | 
| 456 | 
            -
                          log "Printing Plan Summary ...", depth: 1
         | 
| 457 | 
            -
                          pretty_plan_summary(plan_filename)
         | 
| 458 | 
            -
                        end
         | 
| 459 | 
            -
                        puts plan_summary_text if ENV["JSON_PLAN"]
         | 
| 460 | 
            -
                      when :unknown
         | 
| 461 | 
            -
                        # nothing
         | 
| 462 | 
            -
                      end
         | 
| 463 | 
            -
             | 
| 464 | 
            -
                      plan_status
         | 
| 465 | 
            -
                    end
         | 
| 466 | 
            -
             | 
| 467 | 
            -
                    public :run_plan
         | 
| 468 | 
            -
             | 
| 469 413 | 
             
                    def run_upgrade
         | 
| 470 414 | 
             
                      exit_code, meta = PlanFormatter.run_tf_init(upgrade: true)
         | 
| 471 415 | 
             
                      print_errors_and_warnings(meta)
         | 
| @@ -479,18 +423,6 @@ module MuxTf | |
| 479 423 | 
             
                        [:unknown, meta]
         | 
| 480 424 | 
             
                      end
         | 
| 481 425 | 
             
                    end
         | 
| 482 | 
            -
             | 
| 483 | 
            -
                    def pretty_plan_summary(filename)
         | 
| 484 | 
            -
                      plan = PlanSummaryHandler.from_file(filename)
         | 
| 485 | 
            -
                      plan.flat_summary.each do |line|
         | 
| 486 | 
            -
                        log line, depth: 2
         | 
| 487 | 
            -
                      end
         | 
| 488 | 
            -
                      plan.output_summary.each do |line|
         | 
| 489 | 
            -
                        log line, depth: 2
         | 
| 490 | 
            -
                      end
         | 
| 491 | 
            -
                      log "", depth: 2
         | 
| 492 | 
            -
                      log plan.summary, depth: 2
         | 
| 493 | 
            -
                    end
         | 
| 494 426 | 
             
                  end
         | 
| 495 427 | 
             
                end
         | 
| 496 428 | 
             
              end
         |