Skip to content

Instantly share code, notes, and snippets.

@danelowe
Created December 18, 2024 19:47
Show Gist options
  • Save danelowe/03d0cc71efc2a844791f941c446efdf1 to your computer and use it in GitHub Desktop.
Save danelowe/03d0cc71efc2a844791f941c446efdf1 to your computer and use it in GitHub Desktop.
Terraform Module Directories
class Command
def call(&block)
execute(&block)
end
private
def command
raise NotImplementedError
end
def spawn_options
{}
end
def execute(&block)
cmd = command
opts = spawn_options
opts[:chdir] = chdir if chdir
in_r, in_w = IO.pipe
opts[:in] = in_r
in_w.sync = true
out_r, out_w = IO.pipe
opts[:out] = out_w
err_r, err_w = IO.pipe
opts[:err] = err_w
child_io = [in_r, out_w, err_w]
parent_io = [in_w, out_r, err_r]
pid = spawn(*cmd, opts)
wait_thr = Process.detach(pid)
child_io.each(&:close)
begin
result = block&.call(Result.new(stdin: in_w, stdout: out_r, stderr: err_r, thread: wait_thr))
raise(err_r.read || "non-zero exit code") unless wait_thr.value.success?
ensure
parent_io.each(&:close)
wait_thr.join
end
result
end
class Result < Struct.new(:stdin, :stdout, :stderr, :thread)
end
end
require "json"
require_relative "../lib/command"
class Workspace
ENVIRONMENTS = %w[production staging]
def initialize(env:, name:)
raise ArgumentError, "Invalid workspace name: #{name}" unless /^[a-z0-9_-]+$/.match?(name)
raise ArgumentError, "Invalid environment: #{env}" unless ENVIRONMENTS.include?(env)
@env = env
@name = name
end
attr_reader :env, :name
def module_directories
unless @module_directories
TerraformCommand.new(%w[init -input=false -get-plugins=false], workspace_path: path).call
modules_output = TerraformCommand.new(%w[modules -json], workspace_path: path).call { |result| JSON.parse(result.stdout.read) }
modules = modules_output["modules"].each_with_object({}) do |mod, acc|
acc[mod["key"]] = mod["source"]
end
@module_directories = [path] + modules.keys.map { |k| resolve_path(k, modules) }
end
@module_directories
end
def path
"infrastructure/#{@env}/#{@name}"
end
private
def resolve_path(module_name, modules)
paths = resolve_path_parts(module_name, modules)
absolute_path = File.realpath(File.join(path, *paths))
absolute_pwd = File.realpath(".")
raise "Invalid path #{absolute_path}" unless absolute_path.start_with?(absolute_pwd)
Pathname.new(absolute_path).relative_path_from(absolute_pwd).to_s
end
def resolve_path_parts(module_name, modules)
rest, tail = module_name.split(/\.([^.]*)/)
if tail
[*resolve_path_parts(rest, modules), modules[module_name]]
else
[modules[module_name]]
end
end
class TerraformCommand < Command
def initialize(args, workspace_path:)
raise ArgumentError, "args must be an array" unless args.is_a?(Array)
raise ArgumentError, "Invalid workspace path: #{workspace_path}" unless %r{^[a-z0-9_\-\/]+$}.match?(workspace_path)
@args = args
@path = workspace_path
end
def command
["terraform", *@args]
end
def chdir
@path
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment