cic-tools 0.0.1.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ require 'page_magic'
2
+
3
+ HeadlessChrome = PageMagic::Driver.new(:headless_chrome) do |app, _options, _browser_alias_chosen|
4
+ # Write the code necessary to initialise the driver you have chosen
5
+ require 'selenium/webdriver'
6
+ capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
7
+ chromeOptions: { args: %w[headless no-sandbox disable-gpu window-size=1024,768] }
8
+ )
9
+
10
+ Capybara::Selenium::Driver.new app,
11
+ browser: :chrome,
12
+ desired_capabilities: capabilities
13
+ end
14
+
15
+ PageMagic.drivers.register HeadlessChrome
@@ -0,0 +1,197 @@
1
+ require_relative '../../utils/commandline'
2
+ require_relative 'output'
3
+ require 'fileutils'
4
+ require 'yaml'
5
+ require_relative 'headless_browser_driver'
6
+
7
+ module Exercise
8
+ # module Instructions - Helper methods to be used within templates
9
+ module Instructions
10
+ include Commandline
11
+ include Commandline::Output
12
+
13
+ # class TimeoutError - error raised when something has taken to long
14
+ class TimeoutError < StandardError
15
+ end
16
+
17
+ # class EnvironmentVariableMissingError - error raised when environment variable is missing
18
+ class EnvironmentVariableMissingError < StandardError
19
+ def initialize(variable_name)
20
+ super "Environment variable not set for: #{variable_name}"
21
+ end
22
+ end
23
+
24
+ # Runs the given command after the current template has been rendered. This is useful for running commands to clean
25
+ # clean up. E.g. stopping a server that was previously started.
26
+ # @example
27
+ # In ERB template:
28
+ # <%= after_rendering_run('cic down') %>
29
+ # @param [String] command the command to be run.
30
+ # @return [String] the command input parameter is returned so that it can be displayed within a template
31
+ def after_rendering_run(command)
32
+ after_rendering_commands << command
33
+ command
34
+ end
35
+
36
+ # Captures and saves screenshots.
37
+ # Useful for adding images to your templates.
38
+ # @example
39
+ # In ERB template:
40
+ # ![Screenshot](<%= capture('http://the.url.com', 'filename.png') %>)
41
+ # @param [String] url the url to take a screenshot of.
42
+ # @param [String] filename the filename to save the screenshot to.
43
+ # @return [String] The filename of the saved screenshot
44
+ def capture(url, filename)
45
+ page_class = Class.new { include PageMagic }
46
+ session = PageMagic.session(browser: :headless_chrome, url: url)
47
+ session.visit(page_class, url: url)
48
+ session.browser.save_screenshot(filename)
49
+ filename
50
+ end
51
+
52
+ # Change directory
53
+ # Change directory so that subsequent commands are executed in the correct context
54
+ # @example
55
+ # In ERB template:
56
+ # <%= cd('path') %>
57
+ # @param [String] path the path to move to
58
+ # @return [String] the path moved to
59
+ def cd(path)
60
+ Dir.chdir(path)
61
+ "cd #{path}"
62
+ end
63
+
64
+ # Execute a command
65
+ # @example
66
+ # In ERB template:
67
+ # <%= command('mkdir my_directory') %>
68
+ # @param [String] command the command to execute
69
+ # @param [Boolean] fail_on_error whether to raise an error if the command fails to execute
70
+ # @return [String] the command that was executed
71
+ # @raise [CommandError] if command errors and if fail_on_error is set to to true
72
+ def command(command, fail_on_error: true)
73
+ result = test_command(command, fail_on_error: fail_on_error)
74
+ raise CommandError if result.error? && fail_on_error
75
+
76
+ command
77
+ end
78
+
79
+ # get the output of a command
80
+ # @example
81
+ # In ERB template:
82
+ # <%= command_output('mkdir my_directory') %>
83
+ # @param [String] command the command to execute.
84
+ # @return [String] the output of the command executed.
85
+ def command_output(command)
86
+ command command
87
+ last_command_output
88
+ end
89
+
90
+ # get value of an Environment variable
91
+ # @example
92
+ # In ERB template:
93
+ # <%= env('VARIABLE_NAME') %>
94
+ # @param [String] variable_name the variable name
95
+ # @return [String] the value of the specified environment variable.
96
+ # # @raise [EnvironmentVariableMissingError] if environment variable is undefined
97
+ def env(variable_name)
98
+ ENV[variable_name.to_s] || (raise EnvironmentVariableMissingError, variable_name)
99
+ end
100
+
101
+ # get the output of the last command that was run.
102
+ # @example
103
+ # In ERB template:
104
+ # <%= last_command_output %>
105
+ # @return [String] the output of the last command.
106
+ def last_command_output
107
+ Output.new(@result.stdout)
108
+ end
109
+
110
+ # Validate a path
111
+ # @example
112
+ # In ERB template:
113
+ # <%= path('the/path') %>
114
+ # @return [String] the output of the last command.
115
+ # @raise [RuntimeError] if given path does not exist
116
+ def path(path)
117
+ raise "#{path} does not exist" unless File.exist?(path)
118
+
119
+ path
120
+ end
121
+
122
+ # store strings that should be subsituted in the template on rendering
123
+ # @example
124
+ # In ERB template:
125
+ # <% substitute({'localhost' => '127.0.0.1'}) %>
126
+ # @param hash [Hash] values and their substitutes.
127
+ def substitute(hash)
128
+ @substitutes = hash
129
+ end
130
+
131
+ # Wait until the given block evaluates to true
132
+ # @example
133
+ # In ERB template:
134
+ # <%
135
+ # wait_until do
136
+ # # code
137
+ # end
138
+ # %>
139
+ # @param timeout_after [Fixnum] the number of seconds to wait before timing out.
140
+ # @param retry_every [Float] the number of seconds to wait before re-evaluating the given block again
141
+ # @raise [TimeoutError] if given block does not return true within the allowed time.
142
+ def wait_until(timeout_after: 5, retry_every: 0.1)
143
+ start_time = Time.now
144
+ until Time.now > start_time + timeout_after
145
+ return true if yield == true
146
+
147
+ sleep retry_every
148
+ end
149
+ raise TimeoutError, 'Action took to long'
150
+ end
151
+
152
+ # Write the given content to file
153
+ # @example
154
+ # In ERB template:
155
+ # <%
156
+ # write_to_file('path/file.txt', 'content')
157
+ # %>
158
+ # @param [String] path the path to write the file to.
159
+ # @param [Float] content the content to write to file
160
+ # @return [String] the path that the file was written to.
161
+ def write_to_file(path, content)
162
+ directory = File.dirname(path)
163
+ FileUtils.mkdir_p(directory)
164
+ File.write(path, content)
165
+ after_rendering_run("rm -rf #{path}")
166
+ path
167
+ end
168
+
169
+ protected
170
+
171
+ def after_rendering_commands
172
+ @after_rendering_commands ||= []
173
+ end
174
+
175
+ def test_command(command, fail_on_error: true)
176
+ say "running: #{command}" unless quiet?
177
+
178
+ result = @result = run(command)
179
+
180
+ if result.error? && fail_on_error
181
+ say error("failed to run: #{command}\n\n#{result}")
182
+ elsif quiet?
183
+ output.print '.'.green
184
+ else
185
+ say ok("Successfully ran: #{command}")
186
+ end
187
+
188
+ result
189
+ end
190
+
191
+ private
192
+
193
+ def substitutes
194
+ @substitutes ||= {}
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'output/ansible'
2
+ require_relative 'output/pytest'
3
+ require_relative 'output/cic'
4
+ module Exercise
5
+ class Output < String
6
+ def initialize(string)
7
+ string = string.scrub
8
+ bytes = string.bytes.delete_if { |byte| byte == 27 }
9
+ string = bytes.pack('U*')
10
+ super normalise(string.chomp)
11
+ end
12
+
13
+ def to_ansible_output
14
+ Ansible.new(self)
15
+ end
16
+
17
+ def to_cic_output
18
+ CIC.new(self)
19
+ end
20
+
21
+ def to_pytest_output
22
+ Pytest.new(self)
23
+ end
24
+
25
+ private
26
+
27
+ def normalise(string)
28
+ string.gsub(/\[[\d;]+m/, '')
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,15 @@
1
+ module Exercise
2
+ class Output < String
3
+ class Ansible < String
4
+ attr_reader :tasks, :play, :play_recap
5
+
6
+ def initialize(string)
7
+ super
8
+
9
+ @tasks = string.scan(/(TASK .*\**$\n.*\n)/).flatten
10
+ @play = string.scan(/(PLAY \[.*\**$)/).flatten.first
11
+ @play_recap = string.scan(/(PLAY RECAP.*\**$\n.*)/).flatten.first
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ module Exercise
2
+ class Output < String
3
+ class CIC < String
4
+ attr_reader :container_id, :cic_start_command, :cic_connect_command, :cic_stop_command
5
+
6
+ def initialize(string)
7
+ @container_id = chomp(string[/cic connect (.*)/, 1])
8
+ @cic_start_command = chomp(string[/(cic start .*)/, 1])
9
+ @cic_connect_command = chomp(string[/(cic connect .*)/, 1])
10
+ @cic_stop_command = chomp(string[/(cic stop .*)/, 1])
11
+ end
12
+
13
+ def chomp(string)
14
+ string&.chomp
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module Exercise
2
+ class Output < String
3
+ class Pytest < String
4
+ attr_reader :summary
5
+
6
+ def initialize(string)
7
+ @summary = chomp(string[/(=+.*100%\])/m, 1])
8
+ end
9
+
10
+ def chomp(string)
11
+ string&.chomp
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,150 @@
1
+ require 'digest'
2
+ require 'tmpdir'
3
+ require_relative 'instructions'
4
+ module Exercise
5
+ module DigestMethods
6
+ def digest(path:, digest_component:, excludes: [])
7
+ excludes = paths(*excludes)
8
+
9
+ files = files(path).sort.reject do |f|
10
+ excludes.include?(f) || ignored?(path, f)
11
+ end
12
+
13
+ content = files.map { |f| File.read(f) }.join
14
+ Digest::MD5.hexdigest(content << digest_component).to_s
15
+ end
16
+
17
+ def excluded_files(template)
18
+ all_files_templates = files(templates_directory(template))
19
+ rendered_files = all_files_templates.collect { |t| filename(t) }.find_all { |f| File.exist?(f) }
20
+ all_files_templates.reject { |file| file == template }.concat(rendered_files)
21
+ end
22
+
23
+ def paths(*paths)
24
+ paths.find_all { |excluded_file| File.exist?(excluded_file) }.collect { |path| full_path(path) }
25
+ end
26
+
27
+ private
28
+
29
+ def files(path)
30
+ files = paths(*Dir.glob("#{path}/**/*", ::File::FNM_DOTMATCH))
31
+ files.find_all { |f| !File.directory?(f) }
32
+ end
33
+
34
+ def filename(template)
35
+ "#{File.expand_path("#{File.dirname(template)}/..")}/#{File.basename(template, '.erb')}"
36
+ end
37
+
38
+ def full_path(path)
39
+ File.expand_path(path)
40
+ end
41
+
42
+ def git_ignore_content(path)
43
+ git_ignore_file = "#{path}/.gitignore"
44
+ File.exist?(git_ignore_file) ? File.read(git_ignore_file) : ''
45
+ end
46
+
47
+ def ignored?(path, file)
48
+ ignored_files(path).find { |ignore| file.include?(ignore) || Pathname.new(file).fnmatch?(ignore) }
49
+ end
50
+
51
+ def ignored_files(path)
52
+ files = git_ignore_content(path).lines.collect { |line| sanitise(line) }
53
+ files << '.git'
54
+ end
55
+ end
56
+
57
+ module RenderMethods
58
+ include Commandline::Output
59
+ include Instructions
60
+ include DigestMethods
61
+
62
+ # rubocop:disable Metrics/AbcSize
63
+ # rubocop:disable Metrics/MethodLength
64
+ def render_exercise(template, digest_component: '')
65
+ say "Rendering: #{template}"
66
+ template = full_path(template)
67
+ current_dir = Dir.pwd
68
+
69
+ content = render(template)
70
+ File.open(filename(template), 'w') { |f| f.write("#{content}\n#{stamp(digest_component, template)}") }
71
+
72
+ say ok "Finished: #{template}"
73
+ true
74
+ rescue StandardError => e
75
+ say error "Failed to generate file from: #{template}"
76
+ say "#{e.message}\n#{e.backtrace}"
77
+ false
78
+ ensure
79
+ Dir.chdir(current_dir)
80
+ end
81
+
82
+ # rubocop:enable Metrics/AbcSize
83
+ # rubocop:enable Metrics/MethodLength
84
+
85
+ def render_file_path(template)
86
+ template.gsub(%r{.templates/.*?erb}, File.basename(template)).gsub('.erb', '')
87
+ end
88
+
89
+ def templates_directory(template)
90
+ full_path(File.dirname(template))
91
+ end
92
+
93
+ private
94
+
95
+ def anonymise(string)
96
+ substitutes.each do |key, value|
97
+ string = string.gsub(key, value)
98
+ end
99
+ string
100
+ end
101
+
102
+ def stamp(digest_component, template)
103
+ " \n\nRevision: #{digest(path: full_path("#{templates_directory(template)}/.."),
104
+ digest_component: digest_component.to_s,
105
+ excludes: excluded_files(template))}"
106
+ end
107
+
108
+ def reset
109
+ @result = nil
110
+ @after_rendering_commands = []
111
+ end
112
+
113
+ def render(template)
114
+ reset
115
+ template_content = File.read(File.expand_path(template))
116
+
117
+ erb_template = ERB.new(template_content)
118
+
119
+ result = run_in_temp_dir?(template_content) ? render_in_temp_dir(erb_template) : erb_template.result(binding)
120
+
121
+ anonymise(result)
122
+ ensure
123
+ after_rendering_commands.each { |command| test_command(command) }
124
+ say '' if quiet?
125
+ end
126
+
127
+ def filename(template)
128
+ "#{File.expand_path("#{File.dirname(template)}/..")}/#{File.basename(template, '.erb')}"
129
+ end
130
+
131
+ def render_in_temp_dir(erb_template)
132
+ output = nil
133
+ Dir.mktmpdir do |path|
134
+ original_dir = Dir.pwd
135
+ Dir.chdir(path)
136
+ output = erb_template.result(binding)
137
+ Dir.chdir(original_dir)
138
+ end
139
+ output
140
+ end
141
+
142
+ def run_in_temp_dir?(template_content)
143
+ /<%#\s*instruction:run_in_temp_directory\s*%>/.match?(template_content)
144
+ end
145
+
146
+ def sanitise(string)
147
+ string.chomp.strip
148
+ end
149
+ end
150
+ end