class Hiera::Backend::Eyaml::Subcommands::Edit

Public Class Methods

description() click to toggle source
# File lib/hiera/backend/eyaml/subcommands/edit.rb, line 21
def self.description
  'edit an eyaml file'
end
execute() click to toggle source
# File lib/hiera/backend/eyaml/subcommands/edit.rb, line 72
def self.execute
  editor = EditHelper.find_editor

  Parser::EncToken.set_encrypt_unchanged(false)

  # The 'no_' option has special handling - bypass that and just check if a flag was set.
  if Eyaml::Options[:no_decrypt_given]
    decrypted_input = Eyaml::Options[:input_data]
    decrypted_file_content = Eyaml::Options[:no_preamble] ? decrypted_input : (preamble + decrypted_input)
  else
    encrypted_parser = Parser::ParserFactory.encrypted_parser
    tokens = encrypted_parser.parse Eyaml::Options[:input_data]
    decrypted_input = tokens.each_with_index.to_a.map { |(t, index)| t.to_decrypted index: index }.join
    decrypted_file_content = Eyaml::Options[:no_preamble] ? decrypted_input : (preamble + decrypted_input)
  end

  begin
    decrypted_file ||= EditHelper.write_tempfile decrypted_file_content
    system "#{editor} \"#{decrypted_file}\""
    status = $?

    raise StandardError, 'File was moved by editor' unless File.file? decrypted_file

    raw_edited_file = File.read decrypted_file
    # strip comments at start of file
    edited_file = raw_edited_file.split($/, -1).drop_while { |line| line.start_with?(prefix) }.join($/)

    raise StandardError, "Editor #{editor} has not exited?" unless status.exited?

    unless status.exitstatus == 0
      raise StandardError,
            "Editor did not exit successfully (exit code #{status.exitstatus}), aborting"
    end
    raise StandardError, 'Edited file is blank' if edited_file.empty?

    if edited_file == decrypted_input
      LoggingHelper.info 'No changes detected, exiting'
    else
      decrypted_parser = Parser::ParserFactory.decrypted_parser
      edited_tokens = decrypted_parser.parse(edited_file)

      # check that the tokens haven't been copy / pasted
      used_ids = edited_tokens.find_all { |t| t.class.name =~ /::EncToken$/ and !t.id.nil? }.map { |t| t.id }
      if used_ids.length != used_ids.uniq.length
        raise RecoverableError,
              "A duplicate DEC(ID) was found so I don't know how to proceed. This is probably because you copy and pasted a value - if you do this please delete the ID and parentheses"
      end

      # replace untouched values with the source values
      edited_denoised_tokens = edited_tokens.map do |token|
        if token.class.name =~ /::EncToken$/ && !token.id.nil?
          old_token = tokens[token.id]
          if old_token.plain_text.eql? token.plain_text
            old_token
          else
            token
          end
        else
          token
        end
      end

      encrypted_output = edited_denoised_tokens.map { |t| t.to_encrypted }.join

      filename = Eyaml::Options[:eyaml]
      File.write("#{filename}", encrypted_output)
    end
  rescue RecoverableError => e
    LoggingHelper.info e
    raise e unless agree 'Return to the editor to try again?'

    retry
  ensure
    EditHelper.secure_file_delete file: decrypted_file,
                                  num_bytes: [edited_file.length,
                                              decrypted_input.length,].max
  end

  nil
end
helptext() click to toggle source
# File lib/hiera/backend/eyaml/subcommands/edit.rb, line 25
def self.helptext
  'Usage: eyaml edit [options] <some-eyaml-file>'
end
options() click to toggle source
# File lib/hiera/backend/eyaml/subcommands/edit.rb, line 13
def self.options
  [{ name: :no_preamble,
     description: "Don't prefix edit sessions with the informative preamble", },
   { name: :no_decrypt,
     short: '-d',
     description: 'Do not decrypt existing encrypted content. New content marked properly will be encrypted.', },]
end
preamble() click to toggle source
# File lib/hiera/backend/eyaml/subcommands/edit.rb, line 33
          def self.preamble
            tags = (['pkcs7'] + Plugins.plugins.collect do |plugin|
              plugin.name.split('hiera-eyaml-').last
            end).collect { |name| Encryptor.find(name).tag }

            preamble = <<~EOS
              This is eyaml edit mode. This text (lines starting with #{prefix} at the top of
              the file) will be removed when you save and exit.
               - To edit encrypted values, change the content of the DEC(<num>)::PKCS7[]!
                 block#{(tags.size > 1) ? " (or #{tags.drop(1).collect { |tag| "DEC(<num>)::#{tag}[]!" }.join(' or ')})." : '.'}
                 WARNING: DO NOT change the number in the parentheses.
               - To add a new encrypted value copy and paste a new block from the
                 appropriate example below. Note that:
                  * the text to encrypt goes in the square brackets
                  * ensure you include the exclamation mark when you copy and paste
                  * you must not include a number when adding a new block
                 e.g. #{tags.collect { |tag| "DEC::#{tag}[]!" }.join(' -or- ')}
            EOS

            preamble.gsub(/^/, "#{prefix} ")
          end
prefix() click to toggle source
# File lib/hiera/backend/eyaml/subcommands/edit.rb, line 29
def self.prefix
  '# |'
end
validate(options) click to toggle source
# File lib/hiera/backend/eyaml/subcommands/edit.rb, line 55
def self.validate(options)
  Optimist.die 'You must specify an eyaml file' if ARGV.empty?
  options[:source] = :eyaml
  options[:eyaml] = ARGV.shift
  if File.exist? options[:eyaml]
    begin
      options[:input_data] = File.read options[:eyaml]
    rescue StandardError
      raise StandardError, "Could not open file for reading: #{options[:eyaml]}"
    end
  else
    LoggingHelper.info "#{options[:eyaml]} doesn't exist, editing new file"
    options[:input_data] = '---'
  end
  options
end