nli_pipeline 0.1.2 → 0.2.0
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 +4 -4
- data/bin/setup_pipeline +1 -1
- data/lib/nli_pipeline.rb +5 -8
- data/lib/nli_pipeline/abstract_util/init_attrs.rb +110 -0
- data/lib/nli_pipeline/docker_manager.rb +243 -0
- data/lib/nli_pipeline/file_manager.rb +146 -113
- data/lib/nli_pipeline/git_manager.rb +32 -0
- data/lib/nli_pipeline/setup_pipeline.rb +200 -105
- data/lib/nli_pipeline/system_wrapper/call_wrapper.rb +100 -0
- data/lib/nli_pipeline/system_wrapper/call_wrapper_error.rb +31 -0
- data/lib/nli_pipeline/system_wrapper/config_error.rb +43 -0
- data/lib/nli_pipeline/version.rb +6 -0
- metadata +65 -4
- data/History.txt +0 -52
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'nli_pipeline/system_wrapper/call_wrapper'
|
2
|
+
|
3
|
+
module NliPipeline
|
4
|
+
# simple class for managing git commands
|
5
|
+
class GitManager < SystemWrapper::CallWrapper
|
6
|
+
# @return [String] full url to commit
|
7
|
+
def last_commit_url
|
8
|
+
"#{remote}/commits/#{last_commit}"
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param upstream [String]
|
12
|
+
# @return [String] url for git remote origin
|
13
|
+
def remote(upstream: 'origin')
|
14
|
+
call_system("git ls-remote --get-url #{upstream}", return_output: true)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String] full length hash of last commit
|
18
|
+
def last_commit
|
19
|
+
call_system("git log -1 --pretty=format:'%H'", return_output: true)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String] full messaage for last commit, all as one line
|
23
|
+
def last_commit_message
|
24
|
+
call_system('git log -1 --format=%B --oneline', return_output: true)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String] current branch
|
28
|
+
def branch
|
29
|
+
call_system('git symbolic-ref --short HEAD', return_output: true)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,125 +1,220 @@
|
|
1
1
|
require 'optparse' # core gem, no install required
|
2
|
+
# require 'byebug'
|
3
|
+
|
4
|
+
module NliPipeline
|
5
|
+
# TODO: replace $PWD with arg to optionally pass directory
|
6
|
+
# and use Dir.pwd by default
|
7
|
+
# https://stackoverflow.com/questions/1937743/how-to-get-the-current-working-directorys-absolute-path-from-irb
|
8
|
+
|
9
|
+
# Class used to parse arguments from bin/setup_pipeline executable
|
10
|
+
class SetupPipeline
|
11
|
+
attr_reader :commands, :docker_commands, :dir_error, :options, :version
|
12
|
+
|
13
|
+
# Set up class attributes
|
14
|
+
def initialize
|
15
|
+
@version = NliPipeline::VERSION
|
16
|
+
|
17
|
+
@commands = {
|
18
|
+
undo: proc { |fm| fm.load_from_backup },
|
19
|
+
show_last_created: proc { |fm| puts(fm.last_created_files.to_s) }
|
20
|
+
}
|
21
|
+
|
22
|
+
@docker_commands = {
|
23
|
+
docker_build: proc { |dm| dm.build },
|
24
|
+
docker_build_commit: proc { |dm| dm.build_commit? },
|
25
|
+
docker_build_and_save: proc { |dm| dm.build_and_save },
|
26
|
+
docker_deploy_branch: proc { |dm| dm.deploy_branch },
|
27
|
+
docker_deploy_master: proc { |dm| dm.deploy_master },
|
28
|
+
docker_exec: proc { |dm| dm.docker_exec },
|
29
|
+
docker_run: proc { |dm| dm.run },
|
30
|
+
docker_save: proc { |dm| dm.save }
|
31
|
+
}
|
32
|
+
|
33
|
+
@dir_error = "
|
34
|
+
The first argument to setup_pipeline must be a valid directory.
|
35
|
+
For example, setting up a pipeline in the current directory:
|
36
|
+
|
37
|
+
setup_pipeline $PWD
|
38
|
+
|
39
|
+
The exceptions to this are:\n
|
40
|
+
setup_pipeline --version
|
41
|
+
setup_pipeline --help
|
42
|
+
setup_pipeline --show-commands
|
43
|
+
setup_pipeline --show-flags
|
44
|
+
"
|
45
|
+
end
|
46
|
+
|
47
|
+
# simple formatter
|
48
|
+
def show_commands
|
49
|
+
puts('FileManager commands')
|
50
|
+
puts(@commands.keys)
|
51
|
+
puts('DockerManager commands')
|
52
|
+
puts(@docker_commands.keys)
|
53
|
+
end
|
2
54
|
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# $2 (undo) (not required)
|
8
|
-
# --version
|
9
|
-
# --extension=(string: file extension)
|
10
|
-
# --debug (show system commands when enabled)
|
11
|
-
#
|
12
|
-
# Raises:
|
13
|
-
# ArgumentError if $1 is missing or is not a valid directory
|
14
|
-
|
15
|
-
# commands available from setup_pipeline (hash of proc)
|
16
|
-
attr_reader :commands
|
17
|
-
# custom error message for when dir is not provided as arg (string)
|
18
|
-
attr_reader :dir_error
|
19
|
-
|
20
|
-
# Set up class attributes
|
21
|
-
def initialize()
|
22
|
-
@commands = {
|
23
|
-
undo: Proc.new {|fm| fm.load_from_backup()},
|
24
|
-
show_last_created: Proc.new {|fm| puts(fm.get_last_created_files().to_s())},
|
25
|
-
}
|
26
|
-
|
27
|
-
@dir_error = "
|
28
|
-
The first argument to setup_pipeline must be a valid directory.
|
29
|
-
For example, setting up a pipeline in the current directory:
|
30
|
-
|
55
|
+
# print help screen to console
|
56
|
+
# @param opts [OptionParser]
|
57
|
+
def help(opts)
|
58
|
+
how_to_use = "HOW TO USE:\n
|
31
59
|
setup_pipeline $PWD
|
32
|
-
|
33
|
-
The exceptions to this are:\n
|
34
|
-
setup_pipeline --version
|
35
|
-
setup_pipeline --help
|
36
|
-
setup_pipeline --show-commands
|
37
|
-
setup_pipeline --show-flags
|
38
|
-
"
|
39
|
-
end
|
40
60
|
|
41
|
-
|
42
|
-
def show_commands()
|
43
|
-
puts(@commands.keys)
|
44
|
-
end
|
61
|
+
#{opts}
|
45
62
|
|
46
|
-
|
47
|
-
def help(opts)
|
48
|
-
how_to_use = "HOW TO USE:\n
|
49
|
-
setup_pipeline $PWD
|
50
|
-
|
51
|
-
#{opts}
|
63
|
+
FileManager commands: #{@commands.keys}
|
52
64
|
|
53
|
-
|
54
|
-
#{@commands.keys}
|
65
|
+
DockerManager commands: #{@docker_commands.keys}
|
55
66
|
|
56
|
-
|
67
|
+
"
|
57
68
|
|
58
|
-
|
59
|
-
|
60
|
-
|
69
|
+
errors = "COMMON ERRORS:\n
|
70
|
+
#{@dir_error}
|
71
|
+
"
|
61
72
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
73
|
+
docs = "FOR MORE INFORMATION PLEASE READ THE DOCS:\n
|
74
|
+
on bitbucket:\t\thttps://bitbucket.org/nlireland/nli-pipeline-gem\n
|
75
|
+
or on ruby gems:\t\thttps://rubygems.org/gems/nli_pipeline\n
|
76
|
+
"
|
66
77
|
|
67
|
-
|
68
|
-
|
78
|
+
puts("#{how_to_use}\n#{errors}\n#{docs}")
|
79
|
+
end
|
69
80
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
options
|
79
|
-
|
81
|
+
# Commandline Flags:
|
82
|
+
# $1 (file path) (required)
|
83
|
+
# --version
|
84
|
+
# --extension=(string: file extension)
|
85
|
+
# --debug (show all system commands when enabled)
|
86
|
+
def parse_flags
|
87
|
+
options = {}
|
88
|
+
OptionParser.new do |opts|
|
89
|
+
@options = opts
|
90
|
+
# break out of function, don't throw error about passing directory path
|
91
|
+
opts.on('-h', '--help') do |_v|
|
92
|
+
help(opts)
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
opts.on('--show-commands') do |_v|
|
96
|
+
show_commands
|
97
|
+
return false
|
98
|
+
end
|
99
|
+
opts.on('--show-flags') do |_v|
|
100
|
+
puts(self.options)
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
opts.on('-v', '--version') do |_v|
|
104
|
+
puts(self.version)
|
105
|
+
return false
|
106
|
+
end
|
107
|
+
opts.on('-cmds=', '--commands=a,b,c', Array, 'Array of bash commands') do |v|
|
108
|
+
options[:bash_commands] = v
|
109
|
+
end
|
110
|
+
opts.on('--debug') do |v|
|
111
|
+
options[:debug] = v
|
112
|
+
end
|
113
|
+
opts.on("--extension=[[\w\.]+]", String, 'String file extension') do |v|
|
114
|
+
options[:extension] = v
|
115
|
+
end
|
116
|
+
# override git branch (for docker deploy)
|
117
|
+
opts.on("--git-branch=[[\w\.]+]", String, 'String git branch') do |v|
|
118
|
+
options[:git_branch] = v
|
119
|
+
end
|
120
|
+
opts.on('--fail-on-error') do |v|
|
121
|
+
options[:fail_on_error] = v
|
122
|
+
end
|
123
|
+
opts.on("--image=[([\w\-\.\:]+)]", String, 'String docker image') do |v|
|
124
|
+
options[:image_name] = v
|
125
|
+
end
|
126
|
+
opts.on("--mount=[([\w\-\.\:]+)]", String, 'String directory to mount') do |v|
|
127
|
+
options[:mount] = v
|
128
|
+
end
|
129
|
+
opts.on('--ports=a,b,c', Array, 'Array of ports') do |v|
|
130
|
+
options[:ports] = v
|
131
|
+
end
|
132
|
+
opts.on("--proxy=[([\w\-\.\:]+)]", String, 'String proxy') do |v|
|
133
|
+
options[:proxy] = v
|
134
|
+
end
|
135
|
+
opts.on("--upstream-image=[([\w\-\.\:]+)]", String, 'String dockerhub image') do |v|
|
136
|
+
options[:upstream_image_name] = v
|
137
|
+
end
|
138
|
+
end.parse!
|
139
|
+
options
|
140
|
+
end
|
80
141
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
opts.on("-h", "--help") do |v|
|
95
|
-
help(opts)
|
96
|
-
return false
|
97
|
-
end
|
98
|
-
end.parse!
|
142
|
+
# Commandline Arguments:
|
143
|
+
# $1 (file path) (required)
|
144
|
+
# $2 (command to run)
|
145
|
+
#
|
146
|
+
# @raise [ArgumentError] if $1 is missing or is not a valid directory
|
147
|
+
def parse_args
|
148
|
+
raise ArgumentError, "\n#{@dir_error}\n" unless ARGV[0] && Dir.exist?(ARGV[0])
|
149
|
+
|
150
|
+
args = {}
|
151
|
+
args[:path] = ARGV[0]
|
152
|
+
args[:command] = ARGV[1].to_sym unless ARGV[1].nil? || ARGV[1].empty?
|
153
|
+
args
|
154
|
+
end
|
99
155
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
156
|
+
# handle case where invalid flag is passed
|
157
|
+
# show user help screen
|
158
|
+
def flags_or_help_screen
|
159
|
+
begin
|
160
|
+
flags = parse_flags
|
161
|
+
rescue OptionParser::InvalidOption => e
|
162
|
+
puts(e.to_s.red)
|
163
|
+
# inject --help as first arg
|
164
|
+
ARGV.insert(0, '--show-flags')
|
165
|
+
# call flags again to display help dialogue
|
166
|
+
parse_flags
|
167
|
+
# re-throw error
|
168
|
+
raise e
|
169
|
+
end
|
170
|
+
flags
|
104
171
|
end
|
105
|
-
end
|
106
172
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
command = ARGV[1].to_sym unless ARGV[1].nil? || ARGV[1].empty?
|
118
|
-
if @commands.keys.include?(command)
|
119
|
-
@commands[command].call(fm)
|
173
|
+
# handle what manager object to create based on flags / command
|
174
|
+
# @param flags [Hash[Symbol: String]]
|
175
|
+
# @param command [String]
|
176
|
+
def delegate_to_managers(flags, command)
|
177
|
+
if @docker_commands.key?(command)
|
178
|
+
# always fail on error if anything goes wrong with a deploy
|
179
|
+
flags[:fail_on_error] = true if command.to_s.include?('deploy')
|
180
|
+
dm = NliPipeline::DockerManager.new(**flags)
|
181
|
+
@docker_commands[command].call(dm)
|
182
|
+
# if not DockerManager, use FileManager
|
120
183
|
else
|
121
|
-
fm.
|
184
|
+
fm = NliPipeline::FileManager.new(**flags)
|
185
|
+
if @commands.key?(command)
|
186
|
+
@commands[command].call(fm)
|
187
|
+
# is no command is specified, copy example files
|
188
|
+
else
|
189
|
+
fm.copy_example_files
|
190
|
+
end
|
122
191
|
end
|
123
192
|
end
|
193
|
+
|
194
|
+
# TODO
|
195
|
+
# move undo arg handling to parse_flags?
|
196
|
+
# split out main into handler for FileManager and DockerManager
|
197
|
+
|
198
|
+
# Method called by bin/setup_pipeline
|
199
|
+
# Parse commands (command line arguments) and pass flags / flags to parse_flags
|
200
|
+
def main
|
201
|
+
# byebug
|
202
|
+
flags = flags_or_help_screen
|
203
|
+
# flags will be false if a flag that exits early is passed e.g. false
|
204
|
+
# otherwise flags will be some kind of hash, all of which are truthy, even {}
|
205
|
+
return false unless flags
|
206
|
+
|
207
|
+
# TODO
|
208
|
+
# document this better or
|
209
|
+
# get rid of commands? use separate executables in bin for FileManager vs DockerManager?
|
210
|
+
|
211
|
+
# command is the second argument
|
212
|
+
# e.g. setup_pipeline $PWD COMMAND_GOES_HERE --flags --go --here
|
213
|
+
args = parse_args
|
214
|
+
flags[:path] = args[:path]
|
215
|
+
command = args[:command]
|
216
|
+
|
217
|
+
delegate_to_managers(flags, command)
|
218
|
+
end
|
124
219
|
end
|
125
220
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'nli_pipeline/system_wrapper/call_wrapper_error'
|
3
|
+
require 'nli_pipeline/abstract_util/init_attrs'
|
4
|
+
|
5
|
+
module NliPipeline
|
6
|
+
# methods / classes that interact with the os
|
7
|
+
module SystemWrapper
|
8
|
+
# wrapper for system calls
|
9
|
+
# handles output / debugging
|
10
|
+
class CallWrapper
|
11
|
+
include NliPipeline::AbstractUtil
|
12
|
+
|
13
|
+
attr_accessor :last_return_code
|
14
|
+
|
15
|
+
# static methods required by NliPipeline::AbstractUtil::init_attrs
|
16
|
+
# @see NliPipeline::AbstractUtil#init_with_attrs
|
17
|
+
# @see NliPipeline::AbstractUtil#get_allowed_args
|
18
|
+
# @return [Hash]
|
19
|
+
def self.supported_args
|
20
|
+
{ debug: false, fail_on_error: false }
|
21
|
+
end
|
22
|
+
|
23
|
+
# no required args in this case, but method is required
|
24
|
+
# @see NliPipeline::AbstractUtil#init_with_attrs
|
25
|
+
# @see NliPipeline::AbstractUtil::ClassMethods#required_args?
|
26
|
+
# @return [Array]
|
27
|
+
def self.required_args
|
28
|
+
[]
|
29
|
+
end
|
30
|
+
|
31
|
+
# @see NliPipeline::AbstractUtil#init_with_attrs handles everything
|
32
|
+
def initialize(**kwargs)
|
33
|
+
init_with_attrs(**kwargs)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param command [String] command to run
|
37
|
+
# @param custom_error_message [String] custom error to display on error
|
38
|
+
# @param return_output [Boolean] if false return code, if true return stdout
|
39
|
+
# @return [String | Boolean]
|
40
|
+
def call_system(command, custom_error_message: false, return_output: false)
|
41
|
+
result = return_output ? `#{command}`.chomp : system(command)
|
42
|
+
return_code = $CHILD_STATUS
|
43
|
+
if @debug
|
44
|
+
puts("\t#{command}")
|
45
|
+
puts("\treturned: #{result} #{return_code}")
|
46
|
+
end
|
47
|
+
# can't return both output and code in return_output case
|
48
|
+
# so assign instance var
|
49
|
+
@last_return_code = return_code
|
50
|
+
# only catches cases where command finished but with non-zero exit code
|
51
|
+
# e.g. "echo 'hi" will throw an exception regardless of @fail_on_error
|
52
|
+
# "echo 'hi' grep | 'y'" will throw a CallWrapperError only if @fail_on_error is true
|
53
|
+
if @fail_on_error && !result
|
54
|
+
puts(custom_error_message.red) if custom_error_message
|
55
|
+
raise CallWrapperError.new(call: command, code: return_code)
|
56
|
+
end
|
57
|
+
result
|
58
|
+
end
|
59
|
+
|
60
|
+
# # on hold until we figure out how to test Open3.popen3 better
|
61
|
+
# # expect object to receive_message_chain ?
|
62
|
+
# def call_system(command, custom_error_message: false, return_output: false)
|
63
|
+
# # result = system(command)
|
64
|
+
# Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
|
65
|
+
# # return comes back as pid #{int} exit #{int}
|
66
|
+
# return_code = wait_thr.value.to_s.split(' ')[-1].to_i
|
67
|
+
# if @debug
|
68
|
+
# puts("\t#{command}")
|
69
|
+
# puts("\treturned: #{result} #{return_code}")
|
70
|
+
# end
|
71
|
+
# # only catches cases where command finished but with non-zero exit code
|
72
|
+
# # e.g. "echo 'hi" will throw an exception regardless of @fail_on_error
|
73
|
+
# # "echo 'hi' grep | 'y'" will throw a CallWrapperError only if @fail_on_error is true
|
74
|
+
# if @fail_on_error && return_code != 0
|
75
|
+
# puts(custom_error_message.red) if custom_error_message
|
76
|
+
# raise CallWrapperError.new(call: command, code: return_code)
|
77
|
+
# end
|
78
|
+
# return stdout.gets if return_output
|
79
|
+
# # return true if command exits with 0, otherwise return false
|
80
|
+
# return_code == 0
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# pretty print intended for use with @see #call_system
|
87
|
+
# @param custom_message [String]
|
88
|
+
def pretty_print(custom_message: false)
|
89
|
+
# assign return value of block to ret
|
90
|
+
ret = yield
|
91
|
+
out_str = ret.to_s
|
92
|
+
|
93
|
+
message_args = { custom_message: custom_message, out_str: out_str }
|
94
|
+
out_str = format('%<custom_message>-20s: %<out_str>s', message_args) if custom_message
|
95
|
+
puts(ret ? out_str.green : out_str.red)
|
96
|
+
ret
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|