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.
@@ -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
- # Class used to parse arguments from bin/setup_pipeline executable
4
- class SetupPipeline
5
- # Commandline Arguments:
6
- # $1 (file path) (required)
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
- # simple formatter
42
- def show_commands()
43
- puts(@commands.keys)
44
- end
61
+ #{opts}
45
62
 
46
- # print help screen to console
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
- other commands:\n
54
- #{@commands.keys}
65
+ DockerManager commands: #{@docker_commands.keys}
55
66
 
56
- "
67
+ "
57
68
 
58
- errors = "COMMON ERRORS:\n
59
- #{@dir_error}
60
- "
69
+ errors = "COMMON ERRORS:\n
70
+ #{@dir_error}
71
+ "
61
72
 
62
- docs = "FOR MORE INFORMATION PLEASE READ THE DOCS:\n
63
- on bitbucket:\t\thttps://bitbucket.org/nlireland/nli-pipeline-gem\n
64
- or on ruby gems:\t\thttps://rubygems.org/gems/nli_pipeline\n
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
- puts("#{how_to_use}\n#{errors}\n#{docs}")
68
- end
78
+ puts("#{how_to_use}\n#{errors}\n#{docs}")
79
+ end
69
80
 
70
- # parse flags (and check if first argument if not valid directory)
71
- def parse_flags()
72
- options = {}
73
- OptionParser.new do |opts|
74
- opts.on("--extension=[\w+]") do |v|
75
- options[:extension] = v
76
- end
77
- opts.on("--debug") do |v|
78
- options[:debug] = v
79
- end
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
- # break out of function, don't throw exception about passing directory path
82
- opts.on("-v", "--version") do |v|
83
- puts NliPipeline::VERSION
84
- return false
85
- end
86
- opts.on("--show-commands") do |v|
87
- show_commands()
88
- return false
89
- end
90
- opts.on("--show-flags") do |v|
91
- puts(opts)
92
- return false
93
- end
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
- if !ARGV[0] || !Dir.exist?(ARGV[0])
101
- raise ArgumentError.new("\n\n#{@dir_error}\n\n")
102
- else
103
- return [ARGV[0], options]
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
- # Method called by bin/setup_pipeline
108
- # Parse commands (command line arguments) and pass flags / flags to parse_flags
109
- # TODO: move undo arg handling to parse_flags?
110
- def main()
111
- flags = parse_flags()
112
-
113
- if flags
114
- dir, options = flags
115
- # pass options hash as keyword arguments
116
- fm = FileManager.new(dir, **options)
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.copy_example_files()
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