Class: App::Replacer

Inherits:
Object
  • Object
show all
Defined in:
lib/core/replacer.rb

Constant Summary collapse

REGEXP_MATCHER =
/\$\{\{[^}]+\}\}/
REGEXP_MULTILINE =
/^[A-Za-z0-9]+:\s*\|$/
REGEXP_MODIFIER =
/^\$\{\{awx-(for|end).*\}\}$/
TYPE_YML =
'yml'
VALID_MODIFIERS =
%w(base64encode)
OP_SCAN =
'scan'
OP_REPLACE =
'replace'
SSH_USERS =
'SSHUsers'

Class Method Summary collapse

Class Method Details

.replace_string(line, data) ⇒ Object

Takes a string, replaces all the matchers and returns string.

Returns:

  • string

Raises:

  • (RuntimeError)


170
171
172
173
174
175
176
# File 'lib/core/replacer.rb', line 170

def self.replace_string(line, data)
    return nil if line.nil?
    raise RuntimeError, "Expected String, instead got #{line.class}" unless line.is_a?(String)
    raise RuntimeError, "Expected Hash, instead got #{data.class}" unless data.is_a?(Hash)
    line, x, y = process_matchers(line, {}, [], OP_REPLACE, data: data)
    return line
end

.replace_yml(path_and_file, data) ⇒ Object

Takes file path and returns array of lines (that can then be used to write same/new file).

Raises:

  • (RuntimeError)


180
181
182
183
184
185
186
187
188
189
# File 'lib/core/replacer.rb', line 180

def self.replace_yml(path_and_file, data)
    raise RuntimeError, "Expected String, instead got #{path_and_file.class}" unless path_and_file.is_a?(String)
    raise RuntimeError, "Expected Hash, instead got #{data.class}" unless data.is_a?(Hash)
    extension = Blufin::Files::extract_file_name(path_and_file).split('.')
    extension = extension[extension.length - 1].downcase
    raise RuntimeError, "Expected YML file, instead got: #{path_and_file}" unless %w(yml yaml).include?(extension)
    new_lines = []
    scan_file(path_and_file, OP_REPLACE, data: data, new_lines: new_lines)
    new_lines
end

.scan_file(path_and_file, operation = OP_SCAN, data: nil, new_lines: nil) ⇒ Object

Return Array of Hashes containing all matchers. Use this for validation.

Returns:

  • Hash

Raises:

  • (RuntimeError)


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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/core/replacer.rb', line 30

def self.scan_file(path_and_file, operation = OP_SCAN, data: nil, new_lines: nil)
    raise RuntimeError, "File does not exist: #{path_and_file}" unless Blufin::Files::file_exists(path_and_file)
    raise RuntimeError, "Cannot call scan_file without new_lines being an Array when operation is #{OP_REPLACE}." if operation == OP_REPLACE && !new_lines.is_a?(Array)
    matchers   = {}
    errors     = []
    multiline  = nil
    awx_for    = nil
    line_count = 0
    Blufin::Files::read_file(path_and_file).each do |line|
        next if line =~ /^\s*#/ && line !~ /^\s*#cloud-config/ # Skip comments (but not #cloud-config).
        line       = line.gsub("\n", '')
        line_count += 1
        # This handles multi-line block (IE -> content: | ) which basically gets skipped.
        # If you want to make replace work within comments, you will need to write more code (or possibly just add a flag?).
        if line.strip =~ REGEXP_MULTILINE
            # Stores the number of spaces from start.
            multiline = line[/\A */].size
            awx_for[:lines] << line if awx_for.is_a?(Hash)
            new_lines << line if operation == OP_REPLACE && awx_for.nil?
            next
        elsif !multiline.nil?
            whitespace_from_start = line[/\A */].size
            # If white-space from beginning is same or higher, we're probably still in a multi-line.
            if line.strip == '' || whitespace_from_start > multiline
                awx_for[:lines] << line if awx_for.is_a?(Hash)
                new_lines << line if operation == OP_REPLACE && awx_for.nil?
                next
            end
            # Otherwise, break-out.
            multiline = nil
        end
        # This handles ${{awx-[modifier]}} tags.
        if line.strip =~ REGEXP_MODIFIER
            awx_modifier = line.strip.gsub(/^\$\{\{awx-/, '').gsub(/\}\}$/, '')
            # Handle end-tag first.
            if awx_modifier == 'end'
                if awx_for.is_a?(Hash)
                    if operation == OP_SCAN
                        # Handle matchers.
                        if awx_for[:matchers].any?
                            awx_for[:matchers].each do |k, v|
                                if k != awx_for[:item]
                                    matchers[k] = [] unless matchers.has_key?(k)
                                    matchers[k].push(*v)
                                end

                            end
                        end
                        # Handle errors.
                        errors.push(*awx_for[:errors]) if awx_for[:errors].any?
                    else
                        ms = awx_for[:item_source].split(':')
                        raise RuntimeError, "Invalid key: #{ms[0]}" unless data.has_key?(ms[0])
                        raise RuntimeError, "Invalid key: #{ms[0]}.#{ms[1]}" unless data[ms[0]].has_key?(ms[1])
                        is           = data[ms[0]][ms[1]]
                        awx_for_data = data
                        if is.is_a?(Array)
                            if ms[1] == SSH_USERS
                                # SSH Users get handled differently.
                                is.each do |ssh_key_file|
                                    raise RuntimeError, "File not found: #{ssh_key_file}" unless Blufin::Files::file_exists(ssh_key_file)
                                    contents = []
                                    Blufin::Files::read_file(ssh_key_file).each do |c|
                                        next if c.strip == ''
                                        contents << c
                                    end
                                    awx_for_data[awx_for[:item]] = {
                                        'name'    => Blufin::Files::extract_file_name(ssh_key_file).gsub(/\.pub$/i, '').gsub(/\./, '-'),
                                        'pub_key' => contents.join("\n")
                                    }
                                    new_lines                    = process_awx_for_loop(awx_for, awx_for_data, new_lines)
                                end
                            else

                                # TODO - Finish this.
                                raise RuntimeError, 'Not yet implemented!'

                            end
                        else

                            # TODO - Finish this.
                            raise RuntimeError, 'Not yet implemented!'

                        end
                    end
                    awx_for = nil
                end
                next
            end
            # If a tag is already open, return an error (or throw one).
            unless awx_for.nil?
                tag_open_error = "Detected #{line.strip} tag even though another one is already open (Line: #{line_count})."
                if operation == OP_SCAN
                    errors << tag_open_error
                    next
                else
                    raise RuntimeError, tag_open_error
                end
            end
            # Hits here at the start of a for loop (IE: ${{awx-for(user in Parameters:SSHUsers)}})
            if awx_modifier =~ /for\([A-Za-z0-9]+\s*in\s*[A-Za-z0-9]+:[A-Za-z0-9]+\)/
                ams                    = awx_modifier.strip.gsub(/^for\(/, '').gsub(/\)$/, '')
                ams                    = ams.split(' ')
                awx_for                = {
                    :lines       => [],
                    :item        => ams[0],
                    :item_source => ams[2],
                    :matchers    => {},
                    :errors      => []
                }
                line, matchers, errors = process_matchers("${{#{ams[2]}}}", matchers, errors, OP_SCAN, data: data, file: path_and_file)
                next
            else
                raise RuntimeError, "Unsupported awx modifier: #{awx_modifier}"
            end
        end
        # If we're in a for loop, start buffering content.
        if awx_for.is_a?(Hash)
            if operation == OP_SCAN
                line, awx_for[:matchers], awx_for[:errors] = process_matchers(line, awx_for[:matchers], awx_for[:errors], operation, data: data, file: path_and_file)
            end
            awx_for[:lines] << line
            next
        end
        # This just processes a regular line.
        line, matchers, errors = process_matchers(line, matchers, errors, operation, data: data, file: path_and_file) if line =~ REGEXP_MATCHER
        new_lines << line if operation == OP_REPLACE
    end
    if operation == OP_SCAN
        return {
            :matchers => matchers,
            :errors   => errors
        }
    else
        return new_lines
    end
end

.scan_string(line) ⇒ Object

Return Array of Hashes containing all matchers. Use this for validation.

Returns:

  • Array



17
18
19
20
21
22
23
24
25
# File 'lib/core/replacer.rb', line 17

def self.scan_string(line)
    matchers            = {}
    errors              = []
    x, matchers, errors = process_matchers(line, matchers, errors, OP_SCAN) if line =~ REGEXP_MATCHER
    return {
        :matchers => matchers,
        :errors   => errors
    }
end