Completed
Push — master ( fde2ba...d387dd )
by Fike
01:07
created

Generator   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 103
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 20
c 1
b 0
f 0
dl 0
loc 103
rs 10

12 Methods

Rating   Name   Duplication   Size   Complexity  
A extract_comment() 0 5 3
A with_temporary_file() 0 9 1
A execute() 0 4 1
A generate_public_key() 0 20 3
A run_with_temporary_file() 0 5 1
A locate_binary! 0 8 2
A raise_execution_exception() 0 5 1
A raise_invalid_key_exception() 0 6 1
A compute_comment() 0 4 2
A initialize() 0 3 1
A public_key_fingerprint() 0 11 2
A locate_binary() 0 5 2
1
# frozen_string_literal: true
2
3
require 'English'
4
5
require_relative 'exception/invalid_key_exception'
6
require_relative 'exception/ssh_keygen_missing_exception'
7
8
module AMA
9
  module Chef
10
    module SSHPrivateKeys
11
      # A simple wrapper class for ssh-keygen CLI utility
12
      class Generator
13
        def initialize(binary)
14
          @binary = binary
15
        end
16
17
        def public_key_fingerprint(public_key)
18
          execution = run_with_temporary_file(public_key) do |path|
19
            return [@binary, '-l', '-f', path]
20
          end
21
          if execution.error
22
            message = 'Failed to generate fingerprint for ' \
23
              "public key #{public_key}"
24
            raise_execution_exception(execution, message)
25
          end
26
          execution
27
        end
28
29
        # @param [AMA::Chef::SSHPrivateKeys::Model::PrivateKey] private_key
30
        # @param [String, NilClass] comment
31
        # @return [AMA::Chef::SSHPrivateKeys::Model::PublicKey]
32
        def generate_public_key(private_key, comment = nil)
33
          execution = run_with_temporary_file(private_key.content) do |path|
34
            [@binary, '-y', '-f', path, '-P', private_key.passphrase.to_s]
35
          end
36
          if execution.error?
37
            message = 'Failed to create public key from private key'
38
            raise_execution_exception(execution, message)
39
          end
40
          raw = execution.stdout.chomp
41
          match_data = /([\w\-]+)\s+([^\s]+)(?:\s+(.*)\s*)?/.match(raw)
42
          unless match_data
43
            msg = "Failed to read public key created from private key: #{raw}"
44
            raise_invalid_key_exception(msg)
45
          end
46
          ::AMA::Chef::SSHPrivateKeys::Model::PublicKey.new.tap do |key|
47
            key.type = match_data[1]
48
            key.content = match_data[2]
49
            key.comment = compute_comment(match_data[3], comment)
50
          end
51
        end
52
53
        def self.locate_binary
54
          execution = ::Mixlib::ShellOut.new('which', 'ssh-keygen')
55
          execution.run_command
56
          execution.error? ? nil : execution.stdout.lines.first.chomp
57
        end
58
59
        def self.locate_binary!
60
          binary = locate_binary
61
          return binary if binary
62
          raise(
63
            ::AMA::Chef::SSHPrivateKeys::Exception::SSHKeygenMissingException,
64
            'Failed to locate ssh-keygen binary for key validation'
65
          )
66
        end
67
68
        private
69
70
        def compute_comment(parsed_comment, provided_comment)
71
          comment = extract_comment(parsed_comment)
72
          comment.nil? ? extract_comment(provided_comment) : comment
73
        end
74
75
        def extract_comment(comment)
76
          return nil if comment.nil?
77
          comment = comment.strip
78
          comment.empty? ? nil : comment
79
        end
80
81
        def execute(*command)
82
          ::Chef::Log.debug("Executing command: #{command}")
83
          ::Mixlib::ShellOut.new(*command).tap(&:run_command)
84
        end
85
86
        def run_with_temporary_file(content)
87
          with_temporary_file(content) do |path|
88
            execute(*yield(path))
89
          end
90
        end
91
92
        def with_temporary_file(content)
93
          target = Tempfile.new(['ama-ssh-private-keys-'])
94
          ::IO.write(target.path, content)
95
          begin
96
            return yield(target.path)
97
          ensure
98
            target.close(true)
99
          end
100
        end
101
102
        def raise_invalid_key_exception(*message)
103
          raise(
104
            ::AMA::Chef::SSHPrivateKeys::Exception::InvalidKeyException,
105
            message.join(" #{$ORS}")
106
          )
107
        end
108
109
        def raise_execution_exception(execution, *message)
110
          message.push("STDOUT: #{execution.stdout}")
111
          message.push("STDERR: #{execution.stderr}")
112
          raise_invalid_key_exception(*message)
113
        end
114
      end
115
    end
116
  end
117
end
118