opswalrus 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a8289839c7b797b102518b31d579236e7e4750f6c0deea76a7afb8c378e0d98
4
- data.tar.gz: d126fcd38d30c21781cf631717aaea6b560927b9a62af6944beeca99bf6f05a6
3
+ metadata.gz: cf572dd5c13457782bb8256a38f4572ba897a7bb37ab4da0220fd3a69866b8ad
4
+ data.tar.gz: 6ebc7681b98c3bc745b310eee40f866b8d6d328c37b50552b409ca1362b07889
5
5
  SHA512:
6
- metadata.gz: 602c6567a3a191fb9bc63085a84039e16df9199ce94579cd16c74ff846560737541ba53cdbfb612f49e076e465832ed868c817ae4240d65cb7d89b97f24d0cc0
7
- data.tar.gz: 0b56dc14e1ee53936e0649cbcd58ebfc3ecf6abff2f0e3e558c5254dd0e0ec396b0c54ce90e4cdc09d848f6bec9ea61a0a65460bcec3124931d77ed70b6cd93c
6
+ metadata.gz: 2be383a35f09b51d6e42be8280eaf3760d8735aea0b747406e9258b237dc38b55594a56000a88d2a1c398ae7de56d1391903eed0d1c542850f9bd4f167348c9c
7
+ data.tar.gz: 71f1f58d4dda0533fdbd8fd35612daae8f2ad8390ae215d2d437432b6ab801c9ed87f614eb54c018db3ccbe72806625f38471d16b7edbff72ddfed376530437b
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- opswalrus (1.0.0)
4
+ opswalrus (1.0.1)
5
5
  bcrypt_pbkdf
6
6
  citrus
7
7
  ed25519
data/lib/opswalrus/app.rb CHANGED
@@ -39,6 +39,8 @@ module OpsWalrus
39
39
  @instance ||= new(*args)
40
40
  end
41
41
 
42
+ LOCAL_SUDO_PASSWORD_PROMPT = "[ops] Enter sudo password to run sudo in local environment: "
43
+
42
44
 
43
45
  attr_reader :local_hostname
44
46
 
@@ -119,7 +121,7 @@ module OpsWalrus
119
121
  end
120
122
 
121
123
  def prompt_sudo_password
122
- password = IO::console.getpass("[ops] Enter sudo password to run sudo in local environment: ")
124
+ password = IO::console.getpass(LOCAL_SUDO_PASSWORD_PROMPT)
123
125
  set_sudo_password(password)
124
126
  nil
125
127
  end
@@ -207,46 +209,52 @@ module OpsWalrus
207
209
  tmp_dir = nil
208
210
 
209
211
  case
212
+ when Dir.exist?(package_or_ops_file_reference)
213
+ dir = package_or_ops_file_reference
214
+ ops_file_path, operation_kv_args = find_entry_point_ops_file_in_dir(dir, package_operation_and_args)
215
+ [ops_file_path, operation_kv_args, tmp_dir]
210
216
  when File.exist?(package_or_ops_file_reference)
211
217
  first_filepath = package_or_ops_file_reference.to_pathname.realpath
212
- ops_file_path = case first_filepath.extname.downcase
218
+
219
+ ops_file_path, operation_kv_args = case first_filepath.extname.downcase
213
220
  when ".ops"
214
- first_filepath
221
+ [first_filepath, package_operation_and_args]
215
222
  when ".zip"
216
- tmp_dir = Dir.mktmpdir.to_pathname
223
+ tmp_dir = Dir.mktmpdir.to_pathname # this is the temporary bundle root dir
217
224
 
218
225
  # unzip the bundle into the temp directory
219
226
  DirZipper.unzip(first_filepath, tmp_dir)
220
227
 
221
- relative_ops_path = package_operation_and_args.slice!(0, 1).first
222
-
223
- tmp_dir.join(relative_ops_path)
228
+ find_entry_point_ops_file_in_dir(tmp_dir, package_operation_and_args)
224
229
  else
225
230
  raise Error, "Unknown file type for entrypoint: #{first_filepath}"
226
231
  end
227
- operation_kv_args = package_operation_and_args
228
- [ops_file_path, operation_kv_args, tmp_dir ]
232
+
233
+ # operation_kv_args = package_operation_and_args
234
+ [ops_file_path, operation_kv_args, tmp_dir]
229
235
  when repo_url = git_repo?(package_or_ops_file_reference)
230
236
  destination_package_path = bundler.download_git_package(repo_url)
231
237
 
232
- ops_file_path = nil
233
- base_path = Pathname.new(destination_package_path)
234
- path_parts = 0
235
- package_operation_and_args.each do |candidate_path_arg|
236
- candidate_base_path = base_path.join(candidate_path_arg)
237
- candidate_ops_file = candidate_base_path.sub_ext(".ops")
238
- if candidate_ops_file.exist?
239
- path_parts += 1
240
- ops_file_path = candidate_ops_file
241
- break
242
- elsif candidate_base_path.exist?
243
- path_parts += 1
244
- else
245
- raise Error, "Operation not found in #{repo_url}: #{candidate_base_path}"
246
- end
247
- base_path = candidate_base_path
248
- end
249
- operation_kv_args = package_operation_and_args.drop(path_parts)
238
+ ops_file_path, operation_kv_args = find_entry_point_ops_file_in_dir(destination_package_path, package_operation_and_args)
239
+
240
+ # ops_file_path = nil
241
+ # base_path = Pathname.new(destination_package_path)
242
+ # path_parts = 0
243
+ # package_operation_and_args.each do |candidate_path_arg|
244
+ # candidate_base_path = base_path.join(candidate_path_arg)
245
+ # candidate_ops_file = candidate_base_path.sub_ext(".ops")
246
+ # if candidate_ops_file.exist?
247
+ # path_parts += 1
248
+ # ops_file_path = candidate_ops_file
249
+ # break
250
+ # elsif candidate_base_path.exist?
251
+ # path_parts += 1
252
+ # else
253
+ # raise Error, "Operation not found in #{repo_url}: #{candidate_base_path}"
254
+ # end
255
+ # base_path = candidate_base_path
256
+ # end
257
+ # operation_kv_args = package_operation_and_args.drop(path_parts)
250
258
 
251
259
  # for an original package_operation_and_args of ["github.com/davidkellis/my-package", "operation1", "arg1:val1", "arg2:val2", "arg3:val3"]
252
260
  # we return: [ "#{pwd}/#{Bundler::BUNDLE_DIR}/github-com-davidkellis-my-package/operation1.ops", ["arg1:val1", "arg2:val2", "arg3:val3"] ]
@@ -256,6 +264,31 @@ module OpsWalrus
256
264
  end
257
265
  end
258
266
 
267
+ # returns pair of the form: [ ops_file_path, operation_kv_args ]
268
+ def find_entry_point_ops_file_in_dir(base_dir, package_operation_and_args)
269
+ ops_file_path = nil
270
+ base_path = Pathname.new(base_dir)
271
+
272
+ path_parts = 0
273
+ package_operation_and_args.each do |candidate_path_arg|
274
+ candidate_base_path = base_path.join(candidate_path_arg)
275
+ candidate_ops_file = candidate_base_path.sub_ext(".ops")
276
+ if candidate_ops_file.exist?
277
+ path_parts += 1
278
+ ops_file_path = candidate_ops_file
279
+ break
280
+ elsif candidate_base_path.exist?
281
+ path_parts += 1
282
+ else
283
+ raise Error, "Operation not found in: #{candidate_base_path}"
284
+ end
285
+ base_path = candidate_base_path
286
+ end
287
+ operation_kv_args = package_operation_and_args.drop(path_parts)
288
+
289
+ [ops_file_path, operation_kv_args]
290
+ end
291
+
259
292
  # git_repo?("davidkellis/arborist") -> "https://github.com/davidkellis/arborist"
260
293
  # returns the repo URL
261
294
  def git_repo?(repo_reference)
@@ -268,6 +301,10 @@ module OpsWalrus
268
301
  working_repo_reference
269
302
  end
270
303
 
304
+ # def is_dir_git_repo?(dir_path)
305
+ # Git.ls_remote(reference) rescue nil
306
+ # end
307
+
271
308
  def bundle_status
272
309
  end
273
310
 
@@ -53,18 +53,67 @@ module OpsWalrus
53
53
  # execute('eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"')
54
54
  # end
55
55
 
56
- def run_ops(cmd)
57
- # e.g. /home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops bundle unzip tmpops.zip
58
- shell!("/home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops #{cmd}")
56
+ # runs the specified ops command with the specified command arguments
57
+ def run_ops(command, command_arguments, in_bundle_root_dir: true)
58
+ if in_bundle_root_dir
59
+ # e.g. /home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops bundle unzip tmpops.zip
60
+ shell!("/home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops #{command} #{@tmp_bundle_root_dir} #{command_arguments}")
61
+ else
62
+ shell!("/home/linuxbrew/.linuxbrew/bin/gem exec -g opswalrus ops #{command} #{command_arguments}")
63
+ end
59
64
  end
60
65
 
61
66
  end
62
67
 
68
+ class HostProxyOpsFileInvocationBuilder
69
+ def initialize(host_proxy, is_invocation_a_call_to_package_in_bundle_dir = false)
70
+ @host_proxy = host_proxy
71
+ @is_invocation_a_call_to_package_in_bundle_dir = is_invocation_a_call_to_package_in_bundle_dir
72
+ @method_chain = []
73
+ end
74
+
75
+ def method_missing(method_name, *args, **kwargs)
76
+ @method_chain << method_name.to_s
77
+
78
+ if args.empty? && kwargs.empty? # when there are no args and no kwargs, we are just drilling down through another namespace
79
+ self
80
+ else
81
+ # when there are args or kwargs, then the method invocation represents an attempt to run an OpsFile on a remote host,
82
+ # so we want to build up a command and send it to the remote host via HostDSL#run_ops
83
+ @method_chain.unshift(Bundler::BUNDLE_DIR) if @is_invocation_a_call_to_package_in_bundle_dir
84
+
85
+ # path_to_ops_file_basename = @method_chain.join(File::Separator)
86
+ # remote_run_command_args << path_to_ops_file_basename
87
+
88
+ remote_run_command_args = @method_chain.join(" ")
89
+
90
+ unless args.empty?
91
+ remote_run_command_args << " "
92
+ remote_run_command_args << args.join(" ")
93
+ end
94
+
95
+ unless kvargs.empty?
96
+ remote_run_command_args << " "
97
+ remote_run_command_args << kvargs.map{|k,v| "#{k}=#{v}" }.join(" ") unless kvargs.empty?
98
+ end
99
+
100
+ @host_proxy.run_ops(:run, remote_run_command_args)
101
+ end
102
+ end
103
+ end
104
+
105
+ # the subclasses of HostProxy will define methods that handle method dispatch via HostProxyOpsFileInvocationBuilder objects
63
106
  class HostProxy
107
+ attr_accessor :_host
108
+
64
109
  def initialize(host)
65
- @host = host
110
+ @_host = host
66
111
  end
67
- def method_missing()
112
+
113
+ # the subclasses of this class will define methods that handle method dispatch via HostProxyOpsFileInvocationBuilder objects
114
+
115
+ def method_missing(name, ...)
116
+ @_host.send(name, ...)
68
117
  end
69
118
  end
70
119
 
@@ -142,12 +191,17 @@ module OpsWalrus
142
191
  })
143
192
  end
144
193
 
145
- def set_ssh_connection(sshkit_backend)
194
+ def set_ssh_session_connection(sshkit_backend)
146
195
  @sshkit_backend = sshkit_backend
147
196
  end
148
197
 
149
- def clear_ssh_connection
150
- @sskit_backend = nil
198
+ def set_ssh_session_tmp_bundle_root_dir(tmp_bundle_root_dir)
199
+ @tmp_bundle_root_dir = tmp_bundle_root_dir
200
+ end
201
+
202
+ def clear_ssh_session
203
+ @sshkit_backend = nil
204
+ @tmp_bundle_root_dir = nil
151
205
  end
152
206
 
153
207
  def execute(*args)
@@ -169,10 +223,6 @@ module OpsWalrus
169
223
  @sshkit_backend.download!(remote_path.to_s, local_path.to_s)
170
224
  end
171
225
 
172
- def run
173
-
174
- end
175
-
176
226
  end
177
227
 
178
228
  end
@@ -25,6 +25,7 @@ module OpsWalrus
25
25
  def interaction_handler
26
26
  SSHKit::MappingInteractionHandler.new({
27
27
  /\[sudo\] password for .*?:\s*/ => "#{@sudo_password}\n",
28
+ App::LOCAL_SUDO_PASSWORD_PROMPT => "#{@sudo_password}\n",
28
29
  # /\s+/ => nil, # unnecessary
29
30
  }, :info)
30
31
  end
@@ -115,7 +115,7 @@ module OpsWalrus
115
115
  end
116
116
 
117
117
  def invoke(runtime_env, params_hash)
118
- puts "invoking: #{ops_file_path}"
118
+ # puts "invoking: #{ops_file_path}"
119
119
  script.invoke(runtime_env, params_hash)
120
120
  end
121
121
 
@@ -146,7 +146,7 @@ module OpsWalrus
146
146
  @local_symbol_table ||= begin
147
147
  local_symbol_table = {}
148
148
 
149
- local_symbol_table.merge(imports)
149
+ local_symbol_table.merge!(imports)
150
150
 
151
151
  # this is the import for the private lib directory if it exists
152
152
  if private_lib_dir.exist?
@@ -8,6 +8,7 @@ require 'stringio'
8
8
  require 'sshkit'
9
9
  require 'sshkit/dsl'
10
10
 
11
+ require_relative 'host'
11
12
  require_relative 'sshkit_ext'
12
13
  require_relative 'walrus_lang'
13
14
 
@@ -71,24 +72,24 @@ module OpsWalrus
71
72
  end
72
73
 
73
74
  class Env
74
- # params : Hash
75
- def initialize(params)
76
- @params = params
75
+ # env : Hash
76
+ def initialize(env)
77
+ @env = env
77
78
  end
78
79
 
79
80
  def [](key)
80
81
  key = key.to_s if key.is_a? Symbol
81
- @params[key]
82
+ @env[key]
82
83
  end
83
84
 
84
85
  def dig(*keys)
85
86
  keys = keys.map {|key| key.is_a?(Integer) ? key : key.to_s }
86
- @params.dig(*keys)
87
+ @env.dig(*keys)
87
88
  end
88
89
 
89
90
  def method_missing(name, *args, **kwargs, &block)
90
- if @params.respond_to?(name)
91
- @params.method(name).call(*args, **kwargs, &block)
91
+ if @env.respond_to?(name)
92
+ @env.method(name).call(*args, **kwargs, &block)
92
93
  else
93
94
  value = self[name]
94
95
  case value
@@ -107,10 +108,9 @@ module OpsWalrus
107
108
  # SCRIPT
108
109
 
109
110
  module DSL
110
- # include SSHKit::DSL
111
-
112
111
  def ssh(*args, **kwargs, &block)
113
- hosts = inventory(*args, **kwargs)
112
+ host_proxy_class = @ops_file_script.host_proxy_class
113
+ hosts = inventory(*args, **kwargs).map {|host| host_proxy_class.new(host) }
114
114
  sshkit_hosts = hosts.map(&:sshkit_host)
115
115
  sshkit_host_to_ops_host_map = sshkit_hosts.zip(hosts).to_h
116
116
  runtime_env = @runtime_env
@@ -124,7 +124,7 @@ module OpsWalrus
124
124
 
125
125
  begin
126
126
  # in this context, self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
127
- host.set_ssh_connection(self)
127
+ host.set_ssh_session_connection(self)
128
128
 
129
129
  # copy over bootstrap shell script
130
130
  # io = StringIO.new(bootstrap_shell_script)
@@ -140,9 +140,10 @@ module OpsWalrus
140
140
  upload_success = host.upload(zip_bundle_path, "tmpops.zip")
141
141
  raise Error, "Unable to upload ops bundle to remote host" unless upload_success
142
142
 
143
- stdout, stderr, exit_status = host.run_ops("bundle unzip tmpops.zip")
143
+ stdout, stderr, exit_status = host.run_ops(:bundle, "unzip tmpops.zip", in_bundle_root_dir: false)
144
144
  raise Error, "Unable to unzip ops bundle on remote host" unless exit_status == 0
145
- tmp_bundle_dir = stdout.strip
145
+ tmp_bundle_root_dir = stdout.strip
146
+ host.set_ssh_session_tmp_bundle_root_dir(tmp_bundle_root_dir)
146
147
 
147
148
  # we run the block in the context of the host, s.t. `self` within the block evaluates to `host`
148
149
  retval = host.instance_exec(local_host, &block) # host is passed as the argument to the block
@@ -179,7 +180,7 @@ module OpsWalrus
179
180
  puts e.message
180
181
  # puts e.backtrace.join("\n")
181
182
  ensure
182
- host.clear_ssh_connection
183
+ host.clear_ssh_session
183
184
  end
184
185
  end
185
186
  end
@@ -291,6 +292,7 @@ module OpsWalrus
291
292
  @ops_file = ops_file
292
293
  @ruby_script = ruby_script
293
294
  @invocation_class = define_invocation_class
295
+ @host_proxy_class = define_host_proxy_class
294
296
  end
295
297
 
296
298
  def define_invocation_class
@@ -350,6 +352,56 @@ module OpsWalrus
350
352
  klass
351
353
  end
352
354
 
355
+ def define_host_proxy_class
356
+ klass = Class.new(HostProxy)
357
+
358
+ methods_defined = Set.new
359
+
360
+ # define methods for every import in the script
361
+ ops_file.local_symbol_table.each do |symbol_name, import_reference|
362
+ unless methods_defined.include? symbol_name
363
+ # puts "1. defining: #{symbol_name}(...)"
364
+ klass.define_method(symbol_name) do |*args, **kwargs, &block|
365
+ invocation_builder = case import_reference
366
+ # we know we're dealing with a package dependency reference, so we want to run an ops file contained within the bundle directory,
367
+ # therefore, we want to reference the specified ops file with respect to the bundle dir
368
+ when PackageDependencyReference
369
+ HostProxyOpsFileInvocationBuilder.new(self, true)
370
+
371
+ # we know we're dealing with a directory reference or OpsFile reference outside of the bundle dir, so we want to reference
372
+ # the specified ops file with respect to the root directory, and not with respect to the bundle dir
373
+ when DirectoryReference, OpsFileReference
374
+ HostProxyOpsFileInvocationBuilder.new(self, false)
375
+ end
376
+
377
+ invocation_builder.send(symbol_name, *args, **kwargs, &block)
378
+ end
379
+ methods_defined << symbol_name
380
+ end
381
+ end
382
+
383
+ # define methods for every Namespace or OpsFile within the namespace that the OpsFile resides within
384
+ sibling_symbol_table = Set.new
385
+ sibling_symbol_table |= ops_file.dirname.glob("*.ops").map {|ops_file_path| ops_file_path.basename(".ops").to_s } # OpsFiles
386
+ sibling_symbol_table |= ops_file.dirname.glob("*").select(&:directory?).map {|dir_path| dir_path.basename.to_s } # Namespaces
387
+ sibling_symbol_table.each do |symbol_name|
388
+ unless methods_defined.include? symbol_name
389
+ # puts "2. defining: #{symbol_name}(...)"
390
+ klass.define_method(symbol_name) do |*args, **kwargs, &block|
391
+ invocation_builder = HostProxyOpsFileInvocationBuilder.new(self, false)
392
+ invocation_builder.invoke(symbol_name, *args, **kwargs, &block)
393
+ end
394
+ methods_defined << symbol_name
395
+ end
396
+ end
397
+
398
+ klass
399
+ end
400
+
401
+ def host_proxy_class
402
+ @host_proxy_class
403
+ end
404
+
353
405
  def script
354
406
  @ruby_script
355
407
  end
@@ -1,3 +1,3 @@
1
1
  module OpsWalrus
2
- VERSION = "1.0.1"
2
+ VERSION = "1.0.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opswalrus
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Ellis
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-13 00:00:00.000000000 Z
11
+ date: 2023-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: citrus
@@ -114,7 +114,6 @@ email:
114
114
  - david@conquerthelawn.com
115
115
  executables:
116
116
  - ops
117
- - run_ops_bundle
118
117
  extensions: []
119
118
  extra_rdoc_files: []
120
119
  files:
@@ -125,7 +124,6 @@ files:
125
124
  - README.md
126
125
  - Rakefile
127
126
  - exe/ops
128
- - exe/run_ops_bundle
129
127
  - lib/opswalrus.rb
130
128
  - lib/opswalrus/app.rb
131
129
  - lib/opswalrus/bootstrap.sh
data/exe/run_ops_bundle DELETED
@@ -1,22 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'opswalrus'
4
-
5
- def main
6
- zip_file_path = ARGV.shift.to_pathname
7
-
8
- exit_status = 0
9
-
10
- Dir.mktmpdir do |dir|
11
- dir = dir.to_pathname
12
-
13
- # unzip the bundle into the temp directory
14
- OpsWalrus::DirZipper.unzip(zip_file_path, dir)
15
-
16
- exit_status = OpsWalrus::Cli.run(ARGV)
17
- end
18
-
19
- exit exit_status
20
- end
21
-
22
- main