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.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/bin/cic +4 -0
- data/bin/exercise +3 -0
- data/lib/commands.rb +3 -0
- data/lib/commands/cic.rb +2 -0
- data/lib/commands/cic/command.rb +90 -0
- data/lib/commands/cic/helpers.rb +21 -0
- data/lib/commands/exercise/command.rb +214 -0
- data/lib/commands/exercise/headless_browser_driver.rb +15 -0
- data/lib/commands/exercise/instructions.rb +197 -0
- data/lib/commands/exercise/output.rb +31 -0
- data/lib/commands/exercise/output/ansible.rb +15 -0
- data/lib/commands/exercise/output/cic.rb +18 -0
- data/lib/commands/exercise/output/pytest.rb +15 -0
- data/lib/commands/exercise/render_methods.rb +150 -0
- data/lib/commands/track.rb +3 -0
- data/lib/commands/track/command.rb +68 -0
- data/lib/commands/track/errors.rb +57 -0
- data/lib/commands/track/exercise.rb +52 -0
- data/lib/commands/track/helpers.rb +101 -0
- data/lib/commands/track/learning_track.rb +23 -0
- data/lib/utils/commandline.rb +53 -0
- data/lib/utils/commandline/output.rb +31 -0
- data/lib/utils/commandline/return.rb +37 -0
- data/lib/utils/docker.rb +30 -0
- metadata +351 -0
|
@@ -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
|
+
#  %>)
|
|
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,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
|