Module: Tfctl::Executor

Defined in:
lib/tfctl/executor.rb

Class Method Summary collapse

Class Method Details

.plan_file_args(plan_file, subcmd) ⇒ Object

Adds plan file to ‘plan` and `apply` sub commands



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/tfctl/executor.rb', line 84

def plan_file_args(plan_file, subcmd)
    return ["-out=#{plan_file}"] if subcmd == 'plan'

    if subcmd == 'apply'
        raise Tfctl::Error, "Plan file not found in #{plan_file}.  Run plan first." unless File.exist?(plan_file)

        return [plan_file.to_s]
    end

    return []
end

.run(account_name:, config_name:, log:, cmd: nil, argv: [], unbuffered: true) ⇒ Object

Execute terraform command



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/tfctl/executor.rb', line 13

def run(account_name:, config_name:, log:, cmd: nil, argv: [], unbuffered: true)

    if cmd.nil?
        # use project terraform binary if available
        cmd = File.exist?("#{PROJECT_ROOT}/bin/terraform") ? "#{PROJECT_ROOT}/bin/terraform" : 'terraform'
    end

    path       = "#{PROJECT_ROOT}/.tfctl/#{config_name}/#{account_name}"
    cwd        = FileUtils.pwd
    plan_file  = "#{path}/tfplan"
    semaphore  = Mutex.new
    output     = []

    # Extract terraform sub command from argument list
    args       = Array.new(argv)
    subcmd     = args[0]
    args.delete_at(0)

    # Enable plan file for `plan` and `apply` sub commands
    args += plan_file_args(plan_file, subcmd)

    # Create the command
    exec = [cmd] + [subcmd] + args

    # Set environment variables for Terraform
    env = {
        'TF_INPUT'           => '0',
        'CHECKPOINT_DISABLE' => '1',
        'TF_IN_AUTOMATION'   => 'true',
        # 'TF_LOG'             => 'TRACE'
    }

    log.debug "#{account_name}: Executing: #{exec.shelljoin}"

    FileUtils.cd path
    Open3.popen3(env, exec.shelljoin) do |stdin, stdout, stderr, wait_thr|
        stdin.close_write

        # capture stdout and stderr in separate threads to prevent deadlocks
        Thread.new do
            stdout.each do |line|
                semaphore.synchronize do
                    unbuffered ? log.info("#{account_name}: #{line.chomp}") : output << ['info', line]
                end
            end
        end
        Thread.new do
            stderr.each do |line|
                semaphore.synchronize do
                    unbuffered ? log.error("#{account_name}: #{line.chomp}") : output << ['error', line]
                end
            end
        end

        status = wait_thr.value

        # log the output
        output.each do |line|
            log.send(line[0], "#{account_name}: #{line[1].chomp}")
        end

        FileUtils.cd cwd
        FileUtils.rm_f plan_file if args[0] == 'apply' # tidy up the plan file

        unless status.exitstatus.zero?
            raise Tfctl::Error, "#{cmd} failed with exit code: #{status.exitstatus}"
        end
    end
end