#!/usr/bin/env ruby

require 'veil'
require 'json'
require 'optparse'

options = {
  secrets_file: "/etc/opscode/private-chef-secrets.json",
  pack: false,
  use_file: false,
  debug: false,
  secrets: [],
  optional_secrets: []
}

OptionParser.new do |opts|
  opts.banner = "Usage: veil-env-helper [options] COMMAND"

  opts.on("--[no-]debug", "Emit diagnostic messages.  This will print secrets. Do not use this in production") do |d|
    options[:debug] = d
  end

  opts.on("--pack", "Pass secrets in a single CHEF_SECRETS_DATA environment variable") do |p|
    options[:pack] = p
  end

  opts.on("--use-file", "Pass secrets via a unlinked file available at the FD specified in CHEF_SECRETS_FD") do |fd|
    options[:use_file] = fd
  end

  opts.on("-s SECRET_SPEC", "--secret SECRET_SPEC", "Secret to put in the environment") do |spec|
    options[:secrets] << spec
  end

  opts.on("-o SECRET_SPEC", "--optional-secret SECRET_SPEC", "Optional secrets to put in the environment (if it exists)") do |spec|
    options[:optional_secrets] << spec
  end

  opts.on("-f SECRETS_FILE", "--secrets-file SECRETS_FILE", "Location of veil-managed secrets file. Default: /etc/opscode/private-chef-secrets.json") do |file|
    options[:secrets_file] = file
  end
end.parse!

def from_secret_spec(secret, start = {})
  parts = secret.split("=")
  env_name, secret_name = case parts.length
                          when 1
                              ["CHEF_SECRET_#{parts[0].upcase}", parts[0]]
                          when 2
                              [parts[0].upcase, parts[1]]
                          else
                              raise "Bad secret spec: #{secret}"
                          end

  start.merge({ args: secret_name.split("."),
                env_name: env_name,
                name: secret_name })
end

veil = Veil::CredentialCollection::ChefSecretsFile.from_file(options[:secrets_file])
packed_data = Hash.new()

secrets = options[:secrets].map { |spec| from_secret_spec(spec) }
secrets += options[:optional_secrets].map { |spec| from_secret_spec(spec, { optional: true }) }

secrets.each do |secret|
  veil_args = secret[:args]

  begin
    secret_value = veil.get(*veil_args)
  rescue
    raise unless secret[:optional]
    next
  end

  if options[:pack] || options[:use_file]
    STDERR.puts "Packing data using #{veil_args.inspect} and #{secret_value}" if options[:debug]
    if veil_args.length == 2
      packed_data[veil_args[0]] ||= {}
      packed_data[veil_args[0]][veil_args[1]] = secret_value
    elsif veil_args.length == 1
      packed_data[veil_args[0]] = secret_value
    elsif !secret[:optional]
      raise "Invalid secrets name: #{secret[:name]}"
    end
  else
    STDERR.puts "Setting #{secret[:env_name]}=#{secret_value}" if options[:debug]
    ENV[secret[:env_name]] = secret_value
  end
end

if options[:pack] && !options[:use_file]
  ENV['CHEF_SECRETS_DATA'] = packed_data.to_json
end

if options[:use_file]
  rd, wd = IO.pipe
  wd.puts packed_data.to_json
  wd.close
  rd.close_on_exec = false
  ENV['CHEF_SECRETS_FD'] = rd.to_i.to_s
end

exec(*ARGV, close_others: false)
