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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b403349bdb310782fa8b2dae71361b5ee0874e1
|
4
|
+
data.tar.gz: 837d3ca13607c39c8aa1d451abb80ab9a1066196
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de55b1cddd338648732b811fc616a2e4d9484c1f585c3413e0532e1726d3b7b3748c7f8f78bece0b81120dcfcb665b37438082911baf788652ba583734d32eb1
|
7
|
+
data.tar.gz: f5cf0e8b41936beb85fbd4967ef45d8d56ab2c4edcc4fb474fe322759989b880e0a2a401c11c36199948383cbe225357c82c57cf0991ed4435cdaa67e3435aec
|
data/bin/setup_pipeline
CHANGED
data/lib/nli_pipeline.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
|
+
# import all ruby files in nli_pipeline
|
2
|
+
Dir["#{__dir__}/nli_pipeline/**/*.rb"].each do |file|
|
3
|
+
require file
|
4
|
+
end
|
5
|
+
|
1
6
|
# used to defined constants and include requirements for /bin/setup_pipeline
|
2
7
|
module NliPipeline
|
3
|
-
|
4
|
-
# constant use in nli_pipeline.gemspec and bin/setup_pipeline
|
5
|
-
# to determine current version of gem
|
6
|
-
# should also match tag on bitbucket
|
7
|
-
VERSION = "0.1.2"
|
8
|
-
|
9
|
-
require_relative 'nli_pipeline/file_manager'
|
10
|
-
require_relative 'nli_pipeline/setup_pipeline'
|
11
8
|
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module NliPipeline
|
2
|
+
# TODO: on v1.0.0 release, it may be possible to replace this with open struct
|
3
|
+
# if path is no longer a required argument
|
4
|
+
|
5
|
+
# Abstract Utilities for handling class instantiation
|
6
|
+
# similar to open struct, but enforces some arguments
|
7
|
+
module AbstractUtil
|
8
|
+
# override include to automatically extend classmethods
|
9
|
+
# i.e. functions in the module ClassMethods becom class methods
|
10
|
+
# when the AbstractUtil is included
|
11
|
+
def self.included(parent)
|
12
|
+
parent.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @raise [ArgumentError] unless all values in kwargs are truthy
|
16
|
+
# @return [Boolean]
|
17
|
+
def raise_unless_all(**kwargs)
|
18
|
+
message = "All #{kwargs} must be truthy to use"\
|
19
|
+
" #{self.class}.#{caller_locations(1, 1)[0].label}"
|
20
|
+
raise ArgumentError, message unless kwargs.values.all?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
# keys become instance variable names
|
25
|
+
# value is set using corresponding value in kwargs
|
26
|
+
# all instance variables have getters, but not setters
|
27
|
+
# add attributes to class
|
28
|
+
def add_attrs(**kwargs)
|
29
|
+
kwargs.each do |k, v|
|
30
|
+
instance_variable_set("@#{k}", v)
|
31
|
+
self.class.class_eval { attr_reader k.to_sym }
|
32
|
+
end
|
33
|
+
puts(to_s.yellow) if @debug
|
34
|
+
end
|
35
|
+
|
36
|
+
# support method for init_with_attrs to warn user about unused kwargs
|
37
|
+
def drop_forbidden_args_message(**kwargs)
|
38
|
+
forbidden_args = self.class.get_forbidden_kwargs(**kwargs)
|
39
|
+
show_warning = !forbidden_args.empty? && kwargs[:debug]
|
40
|
+
puts("Warning, dropping args #{forbidden_args} to #{self.class}".red) if show_warning
|
41
|
+
end
|
42
|
+
|
43
|
+
# similar to openstruct initialize
|
44
|
+
# assigns all keyword arguments as instance variables
|
45
|
+
# but uses attr_reader, not attr_accessor
|
46
|
+
def init_with_attrs(**kwargs)
|
47
|
+
sanitized_args = self.class.get_allowed_kwargs(**kwargs)
|
48
|
+
drop_forbidden_args_message(**kwargs)
|
49
|
+
unless self.class.required_args?(**kwargs)
|
50
|
+
# raise SystemWrapper::ConfigError.new()
|
51
|
+
msg = "#{self.class}.#{caller_locations(1, 1)[0].label}"\
|
52
|
+
" requires arguments: #{self.class.required_args}"
|
53
|
+
raise ArgumentError, msg
|
54
|
+
end
|
55
|
+
# merge passed args with default values for args
|
56
|
+
merged_args = self.class.supported_args.merge(sanitized_args)
|
57
|
+
add_attrs(**merged_args)
|
58
|
+
end
|
59
|
+
|
60
|
+
# override default to_s for nice formatting of instance vars
|
61
|
+
def to_s
|
62
|
+
format_instance_vars = lambda do |x, y|
|
63
|
+
x + format("\n%<key>-30s: %<value>s", key: y, value: instance_variable_get(y.to_sym))
|
64
|
+
end
|
65
|
+
vars = instance_variables.reduce('', &format_instance_vars)
|
66
|
+
"\n#{self.class} config#{vars}\n"
|
67
|
+
end
|
68
|
+
|
69
|
+
# methods to be loaded as static/class methods in class that extends AbstractUtil
|
70
|
+
module ClassMethods
|
71
|
+
# :nocov:
|
72
|
+
# use method_missing to force classes that use AbstractUtil to implement
|
73
|
+
# supported_args and required_args.
|
74
|
+
# @param method [String]
|
75
|
+
# @param args [Array]
|
76
|
+
def method_missing(method, *args)
|
77
|
+
# cast methods to array of symbols, compare to current missing method
|
78
|
+
if %i[supported_args required_args].include?(method.to_sym)
|
79
|
+
message = "Method #{method} must be implemented in #{self.class} "\
|
80
|
+
'in order to use AbstractUtil'
|
81
|
+
raise ArgumentError, message
|
82
|
+
end
|
83
|
+
super
|
84
|
+
end
|
85
|
+
# can't test call to super, so wrap in nocov tag
|
86
|
+
# :nocov:
|
87
|
+
|
88
|
+
# support for method_missing
|
89
|
+
# https://robots.thoughtbot.com/always-define-respond-to-missing-when-overriding
|
90
|
+
def respond_to_missing?(method, *args)
|
91
|
+
%i[supported_args required_args].include?(method.to_sym) || super
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [Hash] args that can be converted into instance vars for class
|
95
|
+
def get_allowed_kwargs(**kwargs)
|
96
|
+
kwargs.select { |k, _v| supported_args.key?(k) }
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [Hash] args that can't be converted into instance vars for class
|
100
|
+
def get_forbidden_kwargs(**kwargs)
|
101
|
+
kwargs.reject { |k, _v| supported_args.key?(k) }
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [Boolean] have all required args been passed?
|
105
|
+
def required_args?(**kwargs)
|
106
|
+
required_args.all? { |key| kwargs.key?(key) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'nli_pipeline/system_wrapper/call_wrapper'
|
3
|
+
require 'nli_pipeline/abstract_util/init_attrs'
|
4
|
+
|
5
|
+
module NliPipeline
|
6
|
+
# handle docker build, save and run for nli-pipelines
|
7
|
+
# this DOES NOT handle docker login
|
8
|
+
# if you want to push to dockerhub or pull private images
|
9
|
+
# you'll need to log in in advance
|
10
|
+
class DockerManager < SystemWrapper::CallWrapper
|
11
|
+
include AbstractUtil
|
12
|
+
|
13
|
+
# static methods required by NliPipeline::AbstractUtil::init_attrs
|
14
|
+
# @see NliPipeline::AbstractUtil#init_with_attrs
|
15
|
+
# @see NliPipeline::AbstractUtil#get_allowed_args
|
16
|
+
# @return [Hash]
|
17
|
+
def self.supported_args
|
18
|
+
{
|
19
|
+
path: '', debug: false, fail_on_error: false,
|
20
|
+
image_name: false, upstream_image_name: false, tar_name: false,
|
21
|
+
git_branch: false, proxy: false, container_name: false, commit: true,
|
22
|
+
mount: false, bash_commands: [], ports: []
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
# @see NliPipeline::AbstractUtil#init_with_attrs
|
27
|
+
# @see NliPipeline::AbstractUtil::ClassMethods#required_args?
|
28
|
+
# @return [Array[Symbol]]
|
29
|
+
def self.required_args
|
30
|
+
[:path]
|
31
|
+
end
|
32
|
+
|
33
|
+
# creates new DockerManager and assigns each keywoard argument as instance var with attr_reader
|
34
|
+
# uses image_name.tar as tar_name unless it's explicitly set
|
35
|
+
def initialize(**kwargs)
|
36
|
+
# add nlireland/ prefix to imagename if no upstream image name is set
|
37
|
+
can_guess_upstream = kwargs[:image_name] && !kwargs[:upstream_image_name]
|
38
|
+
kwargs[:upstream_image_name] = "nlireland/#{kwargs[:image_name]}" if can_guess_upstream
|
39
|
+
|
40
|
+
# set tar name as image name if image name is set but tar name isn't
|
41
|
+
can_make_tar = kwargs[:image_name] && !kwargs[:tar_name]
|
42
|
+
kwargs[:tar_name] = "#{kwargs[:image_name]}.tar" if can_make_tar
|
43
|
+
|
44
|
+
# set container name manually, should not be able to change it
|
45
|
+
kwargs[:container_name] = 'pipeline-container'
|
46
|
+
|
47
|
+
# set git branch if it's not defined
|
48
|
+
# pass debug flags down to GitManager (will show warning about extra args though)
|
49
|
+
kwargs[:git_branch] = GitManager.new(**kwargs).branch unless kwargs[:git_branch]
|
50
|
+
|
51
|
+
init_with_attrs(**kwargs)
|
52
|
+
end
|
53
|
+
|
54
|
+
# check if last commit contained build command
|
55
|
+
# @return Boolean
|
56
|
+
def build_commit?
|
57
|
+
# neeed \\ since command will be passed to the shell as \[.*\]
|
58
|
+
# escape chars all the way down
|
59
|
+
custom_error_message = "[docker build] or variant not in last commit.\nFailing Build."
|
60
|
+
custom_message = 'can build docker'
|
61
|
+
# will match any combination of 'docker' and 'build' inside square brackets
|
62
|
+
# TODO: refator to use GitManager and stub git log -1 call
|
63
|
+
last_commit_message = NliPipeline::GitManager.new.last_commit_message
|
64
|
+
command = "echo '#{last_commit_message}' | grep -o '\\[.*\\]' "\
|
65
|
+
"| grep -i 'docker' | grep -i 'build'"
|
66
|
+
|
67
|
+
pretty_print(custom_message: custom_message) do
|
68
|
+
call_system(command, custom_error_message: custom_error_message)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [String] build arguments
|
73
|
+
def build_args
|
74
|
+
args = []
|
75
|
+
args.push("proxy=#{@proxy}") if @proxy
|
76
|
+
args.push("commit=#{NliPipeline::GitManager.new.last_commit_url}") if @commit
|
77
|
+
args.reduce('', &->(x, y) { x + " --build-arg #{y}" })
|
78
|
+
end
|
79
|
+
|
80
|
+
# build docker image
|
81
|
+
# @return [Boolean] success/failure
|
82
|
+
def build
|
83
|
+
can_build = build_commit?
|
84
|
+
custom_message = 'built docker image'
|
85
|
+
|
86
|
+
# if we can't build, fail early
|
87
|
+
unless can_build
|
88
|
+
# if --fail-on-error is passed, this will throw and exception
|
89
|
+
# and "Skipping build" will not be output
|
90
|
+
ret = pretty_print(custom_message: custom_message) { false }
|
91
|
+
puts('Skipping build'.yellow)
|
92
|
+
return ret
|
93
|
+
end
|
94
|
+
|
95
|
+
command = 'docker build'
|
96
|
+
# command += " --build-arg proxy=#{@proxy}" if @proxy
|
97
|
+
command += build_args
|
98
|
+
command += " -t #{@image_name}" if @image_name
|
99
|
+
pretty_print(custom_message: custom_message) { call_system("#{command} #{@path}") }
|
100
|
+
end
|
101
|
+
|
102
|
+
# save docker image as tarfile
|
103
|
+
# @return [Boolean] success/failure
|
104
|
+
def save
|
105
|
+
# throw an error is image_name is not set
|
106
|
+
raise_unless_all(image_name: @image_name)
|
107
|
+
save_command = "docker save #{@image_name} -o #{@path}/#{@tar_name}"
|
108
|
+
puts 'saving docker image'.yellow
|
109
|
+
pretty_print(custom_message: 'saved docker image') { call_system(save_command) }
|
110
|
+
end
|
111
|
+
|
112
|
+
# save docker image as tarfile
|
113
|
+
# @return [Boolean] success/failure
|
114
|
+
def build_and_save
|
115
|
+
built = build
|
116
|
+
# if the build fails quit
|
117
|
+
return false unless built
|
118
|
+
save
|
119
|
+
end
|
120
|
+
|
121
|
+
# load saved docker image
|
122
|
+
# @return [Boolean] success/failure
|
123
|
+
def load
|
124
|
+
load_command = "docker load -i #{@path}/#{@tar_name}"
|
125
|
+
pretty_print(custom_message: "loading #{@tar_name}") { call_system(load_command) }
|
126
|
+
end
|
127
|
+
|
128
|
+
# def tar_exist?
|
129
|
+
# File.exist? "#{@path}/#{@tar_name}"
|
130
|
+
# end
|
131
|
+
|
132
|
+
# if the last commit was a build commit
|
133
|
+
# try to load and run the image
|
134
|
+
# otherwise pull and run the upstream image
|
135
|
+
#
|
136
|
+
# in prepare_run_func
|
137
|
+
# always:
|
138
|
+
# 1. run in detached mode with stdin open (-id)
|
139
|
+
# 2. pass CI=true to as a build argument
|
140
|
+
# 3. set the container name (pipeline-container)
|
141
|
+
#
|
142
|
+
# @raise [SystemWrapper::ConfigError] if the local image failed to be loaded /run,
|
143
|
+
# or the upstream image failed to run
|
144
|
+
# @return [Boolean] success/failure
|
145
|
+
def run
|
146
|
+
run_func = prepare_run_func
|
147
|
+
# if the last commit had a build instruction, load the built image
|
148
|
+
if build_commit? && @image_name
|
149
|
+
load
|
150
|
+
run_func.call(@image_name)
|
151
|
+
# otherwise try to pull the upstream image from dockerhub
|
152
|
+
elsif @upstream_image_name
|
153
|
+
run_func.call(@upstream_image_name)
|
154
|
+
# if neither image nor upstream image were set, throw config error
|
155
|
+
# can't use raise_unless_all since some, but not all, must be set
|
156
|
+
else
|
157
|
+
error_message = 'you must set image_name or upstream_image_name to run a docker image'
|
158
|
+
config_error = "Config Error: #{error_message}"
|
159
|
+
config = {
|
160
|
+
image_name: @image_name, upstream_image_name: @upstream_image_name,
|
161
|
+
build_commit: build_commit?
|
162
|
+
}
|
163
|
+
raise SystemWrapper::ConfigError.new(config: config, msg: config_error)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# create function to bind args to on docker run
|
168
|
+
# @return [Boolean] success/failure
|
169
|
+
def prepare_run_func
|
170
|
+
base_command = 'docker run -id'
|
171
|
+
base_command += " -v #{@path}:#{@mount}" if @mount
|
172
|
+
base_command = @ports.reduce(base_command, &->(x, y) { x + " -p #{y}:#{y}" })
|
173
|
+
base_command += " -e CI=TRUE --name #{@container_name}"
|
174
|
+
proc do |name|
|
175
|
+
pretty_print(custom_message: "running #{name}") { call_system("#{base_command} #{name}") }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# send bash command to running docker container
|
180
|
+
# avoid clash with exec!
|
181
|
+
# @return [Boolean] success/failure
|
182
|
+
def docker_exec
|
183
|
+
@bash_commands.each do |cmd|
|
184
|
+
pretty_print { call_system("docker exec #{@container_name} bash -c '#{cmd}'") }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# relies on tag to ensure image_name and upstream_image_name are set
|
189
|
+
# setup_pipeline sets fail-on-error to true for all deploys
|
190
|
+
# so build_commit? will raise an exception and stop the build
|
191
|
+
# if the last commit was not a build commit
|
192
|
+
# @return [Boolean] success/failure
|
193
|
+
def deploy_master
|
194
|
+
build_commit?
|
195
|
+
master_tags = ['latest', "latest-#{Time.now.strftime('%Y-%m-%d')}"]
|
196
|
+
master_tags.map do |t|
|
197
|
+
tag(t)
|
198
|
+
message = "pushing tag: #{t} to #{@upstream_image_name}"
|
199
|
+
push_command = "docker push #{@upstream_image_name}:#{t}"
|
200
|
+
# deploy each tag individually on the off change
|
201
|
+
# that another tag for the image that wasn't created by setup_pipeline
|
202
|
+
# exists and should not be pushed
|
203
|
+
pretty_print(custom_message: message) { call_system(push_command) }
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# setup_pipeline sets fail-on-error to true for all deploys
|
208
|
+
# so build_commit? will raise an exception and stop the build
|
209
|
+
# if the last commit was not a build commit
|
210
|
+
# @return [Boolean] success/failure
|
211
|
+
def deploy_branch
|
212
|
+
build_commit?
|
213
|
+
# if gitbranch is not set, raise an error
|
214
|
+
# todo: this may be unnecessary now with git branch default
|
215
|
+
raise_unless_all(git_branch: @git_branch)
|
216
|
+
message = "pushing tag: #{@git_branch} to #{@upstream_image_name}"
|
217
|
+
tag(@git_branch)
|
218
|
+
push_command = "docker push #{@upstream_image_name}:#{@git_branch}"
|
219
|
+
pretty_print(custom_message: message) { call_system(push_command) }
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
# @param tag [String] what to tag the docker image as
|
225
|
+
# @return [Boolean] success/failure
|
226
|
+
def tag(tag)
|
227
|
+
# docker tag requires a local and upstream image
|
228
|
+
# raise an error if either of these are missing
|
229
|
+
raise_unless_all(image_name: @image_name, upstream_image_name: @upstream_image_name)
|
230
|
+
imgs_and_tag = "#{@image_name} #{@upstream_image_name}:#{tag}"
|
231
|
+
print_arg = "tagging #{imgs_and_tag}"
|
232
|
+
call_system_arg = "docker tag #{imgs_and_tag}"
|
233
|
+
# ret = pretty_print(custom_message: print_arg) { call_system(call_system_arg) }
|
234
|
+
pretty_print(custom_message: print_arg) { call_system(call_system_arg) }
|
235
|
+
|
236
|
+
# # raise an error if fail on error is set
|
237
|
+
# error_messsage = "Failed to tag #{@upstream_image_name}:#{tag}"
|
238
|
+
# err = NliPipeline::SystemWrapper::CallWrapperError.new(error_messsage)
|
239
|
+
# raise err if !ret && @fail_on_error
|
240
|
+
# ret
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -1,144 +1,177 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'colorize'
|
2
|
+
require 'nli_pipeline/system_wrapper/call_wrapper'
|
3
|
+
require 'nli_pipeline/abstract_util/init_attrs'
|
3
4
|
|
4
|
-
|
5
|
-
#
|
6
|
-
# EXAMPLE:
|
7
|
-
# >> FileManager.new.copy_example_files()
|
8
|
-
# => Moving 1 .EXAMPLE files
|
9
|
-
class FileManager
|
10
|
-
|
11
|
-
# String: must be valid file path
|
12
|
-
attr_reader :path
|
13
|
-
# String: file extension default: .EXAMPLE
|
14
|
-
attr_accessor :extension
|
15
|
-
# Bool: show all system commands run by ruby
|
16
|
-
attr_accessor :debug
|
17
|
-
|
18
|
-
# Arguments:
|
19
|
-
# path: (string: path to directory)
|
20
|
-
# extension: (string)
|
21
|
-
# debug: (boolean)
|
22
|
-
# no_output: (boolean: used for suppressing output during tests)
|
23
|
-
def initialize(path, extension: ".EXAMPLE", debug: false, no_output: false)
|
24
|
-
puts("config = {path: '#{path}', extension: '#{extension}', debug: #{debug}, no_output: #{no_output}}".yellow()) if debug
|
25
|
-
@path = path
|
26
|
-
@extension = extension
|
27
|
-
@debug = debug
|
28
|
-
@no_output = no_output
|
29
|
-
@log_dir = "#{path}/.pipeline_logs"
|
30
|
-
@created_log_name = "created_files_"
|
31
|
-
|
32
|
-
unless File.directory?(@log_dir)
|
33
|
-
Dir.mkdir(@log_dir)
|
34
|
-
end
|
35
|
-
end
|
5
|
+
module NliPipeline
|
6
|
+
# TODO: enforce that command must be either cp or mv
|
36
7
|
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
8
|
+
# Manage example files
|
9
|
+
#
|
10
|
+
# EXAMPLE:
|
11
|
+
# >> NliPipeline::FileManager.new.copy_example_files()
|
12
|
+
# => Moving 1 .EXAMPLE files
|
13
|
+
class FileManager < SystemWrapper::CallWrapper
|
14
|
+
include AbstractUtil
|
15
|
+
|
16
|
+
# static methods required by NliPipeline::AbstractUtil::init_attrs
|
17
|
+
# @see NliPipeline::AbstractUtil#init_with_attrs
|
18
|
+
# @see NliPipeline::AbstractUtil#get_allowed_args
|
19
|
+
# @return [Hash]
|
20
|
+
def self.supported_args
|
21
|
+
{
|
22
|
+
path: '', extension: '.EXAMPLE', debug: false,
|
23
|
+
fail_on_error: false, log_dir: '.pipeline_logs',
|
24
|
+
created_get_log_name: 'created_files_', backup_path: ''
|
25
|
+
}
|
26
|
+
end
|
43
27
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
raise ArgumentError.new("No #{extension} Files found at #{example_file_path}")
|
50
|
-
else
|
51
|
-
puts("Moving #{example_files.count} #{extension} files".green()) unless @no_output
|
28
|
+
# @see NliPipeline::AbstractUtil#init_with_attrs
|
29
|
+
# @see NliPipeline::AbstractUtil::ClassMethods#required_args?
|
30
|
+
# @return [Array[Symbol]]
|
31
|
+
def self.required_args
|
32
|
+
[:path]
|
52
33
|
end
|
53
|
-
return example_files
|
54
|
-
end
|
55
34
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
35
|
+
# automatically set backup path
|
36
|
+
# init_with_attrs handles the rest
|
37
|
+
# @param kwargs [Hash] all keyword (only supported_args) will be added as vars
|
38
|
+
def initialize(**kwargs)
|
39
|
+
# backup_path should not be configurable
|
40
|
+
# set it directly regardless of whether it's passed in
|
41
|
+
kwargs[:backup_path] = "#{kwargs[:path]}/**/*.backup*"
|
42
|
+
init_with_attrs(**kwargs)
|
62
43
|
end
|
63
|
-
log_numbers = logs.map {|x| x.split("#{@created_log_name}").last}
|
64
|
-
latest_log_number = log_numbers.uniq.sort.first
|
65
|
-
return File.readlines("#{@log_dir}/#{@created_log_name}#{latest_log_number}")
|
66
|
-
end
|
67
44
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
45
|
+
# @return [Array[String]] all example files under @path
|
46
|
+
def all_example_files
|
47
|
+
example_file_path = "#{@path}/**/*#{@extension}"
|
48
|
+
example_files = Dir.glob(example_file_path)
|
49
|
+
if example_files.empty?
|
50
|
+
raise ArgumentError, "No #{@extension} Files found at #{example_file_path}"
|
51
|
+
end
|
52
|
+
puts("Moving #{example_files.count} #{@extension} files".green)
|
53
|
+
example_files
|
54
|
+
end
|
76
55
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
puts("
|
83
|
-
|
56
|
+
# read latest log in .pipeline_logs
|
57
|
+
# @return [String] files created by last setup_pipeline
|
58
|
+
def last_created_files
|
59
|
+
logs = Dir.glob("#{@path}/#{@log_dir}/*")
|
60
|
+
if logs.empty?
|
61
|
+
puts("No logs found in #{@path}/#{@log_dir}".red)
|
62
|
+
return false
|
84
63
|
end
|
64
|
+
log_numbers = logs.map { |x| x.split(@created_get_log_name.to_s).last }
|
65
|
+
latest_log_number = log_numbers.uniq.min
|
66
|
+
File.readlines(get_log_name(latest_log_number))
|
85
67
|
end
|
86
68
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
69
|
+
# copy all files ending in @extension under @path.
|
70
|
+
# if a non-exmaple version of the file exists, back it up.
|
71
|
+
# @param command [String] command to run (cp/mv)
|
72
|
+
def copy_example_files(command = 'cp')
|
73
|
+
time_stamp = Time.now.strftime('%Y%m%d%H%M')
|
74
|
+
puts("Setting up pipeline in #{@path}".yellow)
|
75
|
+
example_files = all_example_files
|
76
|
+
|
77
|
+
backup_non_example_files(example_files, time_stamp)
|
93
78
|
|
94
|
-
|
95
|
-
|
96
|
-
|
79
|
+
example_files.each do |example_file|
|
80
|
+
target_file = example_file.gsub(@extension, '')
|
81
|
+
call_system("#{command} #{example_file} #{target_file}")
|
82
|
+
|
83
|
+
if File.exist?(target_file)
|
84
|
+
add_to_log(target_file, time_stamp)
|
85
|
+
puts("\t add #{target_file} to log #{get_log_name(time_stamp)}") if @debug
|
86
|
+
end
|
97
87
|
end
|
98
88
|
end
|
99
89
|
|
100
|
-
|
90
|
+
# get backup dates in order from newest to oldest
|
91
|
+
def all_backups
|
92
|
+
backup_files = Dir.glob(@backup_path)
|
93
|
+
backup_numbers = backup_files.map { |x| x.split('.backup').last }
|
94
|
+
backup_numbers.uniq.sort
|
95
|
+
end
|
101
96
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
raise "
|
97
|
+
# for each example file,
|
98
|
+
# if a non-example version of that file exists,
|
99
|
+
# back it up.
|
100
|
+
#
|
101
|
+
# @param example_files [Array[string]] list of example files
|
102
|
+
# @param time_stamp [String] time of backup
|
103
|
+
# @param command [String] command to run (cp/mv)
|
104
|
+
def backup_non_example_files(example_files, time_stamp, command = 'cp')
|
105
|
+
possible_files = example_files.map { |f| f.gsub(@extension, '') }
|
106
|
+
files_to_backup = possible_files.select { |file| File.exist?(file) }
|
107
|
+
# raise ArgumentError.new("#{files_to_backup.to_s} #{example_files.to_s}")
|
108
|
+
return false if files_to_backup.empty?
|
109
|
+
|
110
|
+
# preset backup time so all backups fomr same batch have the same timestamp
|
111
|
+
puts("Backing up #{files_to_backup.count} files".green)
|
112
|
+
files_to_backup.each do |file_to_backup|
|
113
|
+
call_system("#{command} #{file_to_backup} #{file_to_backup}.backup#{time_stamp}")
|
114
|
+
end
|
113
115
|
end
|
114
116
|
|
115
|
-
|
116
|
-
|
117
|
+
# @param command [String] system command.
|
118
|
+
# use mv to remove backup,
|
119
|
+
# use cp to keep backup.
|
120
|
+
# @return Boolean
|
121
|
+
def load_from_backup(command = 'cp')
|
122
|
+
puts("loading backup in #{@path}".yellow)
|
123
|
+
backups = all_backups
|
124
|
+
if backups.empty?
|
125
|
+
puts("No backups found in #{@backup_path}".red)
|
126
|
+
return false
|
127
|
+
end
|
128
|
+
|
129
|
+
backup_path, backup_date = handle_backup_user_input(backups)
|
130
|
+
files_to_load = Dir.glob(backup_path)
|
131
|
+
puts "loading #{files_to_load.count} files from #{backup_path}".green
|
117
132
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
133
|
+
files_to_load.each do |backup_file|
|
134
|
+
target_file = backup_file.gsub("\.backup#{backup_date}", '')
|
135
|
+
call_system("#{command} #{backup_file} #{target_file}")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# keeps asking the use to choose a backup
|
140
|
+
# until they choose a valid one
|
141
|
+
# @param backups [Array[String]]
|
142
|
+
# @return [Array[String]]
|
143
|
+
def handle_backup_user_input(backups)
|
144
|
+
puts "which backup would you like to load?\n#{backups}".yellow
|
145
|
+
backup_date = STDIN.gets.chomp
|
146
|
+
|
147
|
+
until backups.include?(backup_date)
|
148
|
+
puts "#{backup_date} is not in backups".red
|
149
|
+
puts "please choose from: #{backups}".yellow
|
150
|
+
backup_date = STDIN.gets.chomp
|
151
|
+
end
|
152
|
+
["#{@path}/**/*.backup#{backup_date}", backup_date]
|
122
153
|
end
|
123
154
|
|
124
|
-
|
125
|
-
puts "loading #{files_to_load.count} files from #{@path}/**/*.backup#{latest_backup_date}".green()
|
155
|
+
private
|
126
156
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
system(full_command)
|
157
|
+
# create dir for pipeline logs
|
158
|
+
def create_log_dir
|
159
|
+
full_path_to_log = "#{@path}/#{@log_dir}"
|
160
|
+
Dir.mkdir(full_path_to_log) unless File.directory?(full_path_to_log)
|
132
161
|
end
|
133
|
-
end
|
134
162
|
|
135
|
-
|
163
|
+
# @param date [String]
|
164
|
+
# @return [String] full path to log
|
136
165
|
def get_log_name(date)
|
137
|
-
|
166
|
+
"#{@path}/#{@log_dir}/#{@created_get_log_name}#{date}"
|
138
167
|
end
|
139
168
|
|
169
|
+
# appends to log file
|
170
|
+
# @param line [String] line to append to log file
|
171
|
+
# @param date [String]
|
140
172
|
def add_to_log(line, date)
|
141
|
-
|
173
|
+
create_log_dir # if log doesn't exist
|
174
|
+
File.open(get_log_name(date), 'a') { |file| file.write(line) }
|
142
175
|
end
|
143
|
-
|
176
|
+
end
|
144
177
|
end
|