opswalrus 1.0.4 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +43 -0
- data/Gemfile.lock +8 -8
- data/LICENSE +277 -674
- data/README.md +16 -0
- data/build.ops +20 -0
- data/lib/opswalrus/app.rb +16 -39
- data/lib/opswalrus/bundler.rb +19 -12
- data/lib/opswalrus/git.rb +22 -0
- data/lib/opswalrus/host.rb +4 -3
- data/lib/opswalrus/operation_runner.rb +1 -1
- data/lib/opswalrus/ops_file.rb +21 -34
- data/lib/opswalrus/ops_file_script.rb +12 -5
- data/lib/opswalrus/package_file.rb +11 -2
- data/lib/opswalrus/runtime_environment.rb +34 -2
- data/lib/opswalrus/version.rb +1 -1
- data/lib/opswalrus/walrus_lang.rb +11 -2
- data/opswalrus.gemspec +18 -17
- metadata +35 -34
- data/lib/opswalrus/bootstrap_linux_host1.sh +0 -12
- data/lib/opswalrus/bootstrap_linux_host2.sh +0 -37
- data/lib/opswalrus/bootstrap_linux_host3.sh +0 -21
data/README.md
CHANGED
@@ -2,6 +2,22 @@
|
|
2
2
|
|
3
3
|
opswalrus is a tool that runs scripts against hosts. It's kind of like Ansible, but aims to be simpler to use.
|
4
4
|
|
5
|
+
# Getting started
|
6
|
+
|
7
|
+
You have two options:
|
8
|
+
- Install via Rubygems
|
9
|
+
- Install via Docker
|
10
|
+
|
11
|
+
## Rubygems install
|
12
|
+
|
13
|
+
## Docker install
|
14
|
+
|
15
|
+
```shell
|
16
|
+
alias ops='docker run --rm -it -v $HOME/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock -v ${PWD}/:/workdir ghcr.io/opswalrus/ops'
|
17
|
+
|
18
|
+
ops --version
|
19
|
+
```
|
20
|
+
|
5
21
|
# Examples
|
6
22
|
|
7
23
|
```bash
|
data/build.ops
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
params:
|
2
|
+
version: string
|
3
|
+
|
4
|
+
imports:
|
5
|
+
core: "https://github.com/opswalrus/core.git"
|
6
|
+
...
|
7
|
+
|
8
|
+
version = params.version
|
9
|
+
|
10
|
+
template = <<TEMPLATE
|
11
|
+
module OpsWalrus
|
12
|
+
VERSION = "{{ version }}"
|
13
|
+
end
|
14
|
+
TEMPLATE
|
15
|
+
|
16
|
+
puts "Write version.rb for version #{version}"
|
17
|
+
core.template.write(path: "./lib/opswalrus/version.rb", template: template, variables: {version: version})
|
18
|
+
|
19
|
+
sh("Build gem") { 'gem build opswalrus.gemspec' }
|
20
|
+
sh("Build docker image") { 'docker build -t opswalrus/ops:{{ version }} .' }
|
data/lib/opswalrus/app.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require "citrus"
|
2
|
-
require "git"
|
3
2
|
require "io/console"
|
4
3
|
require "json"
|
5
4
|
require "random/formatter"
|
@@ -8,6 +7,7 @@ require "socket"
|
|
8
7
|
require "stringio"
|
9
8
|
require "yaml"
|
10
9
|
require "pathname"
|
10
|
+
require_relative "git"
|
11
11
|
require_relative "host"
|
12
12
|
require_relative "hosts_file"
|
13
13
|
require_relative "operation_runner"
|
@@ -158,24 +158,11 @@ module OpsWalrus
|
|
158
158
|
ops_file = OpsFile.new(self, ops_file_path)
|
159
159
|
|
160
160
|
# if the ops file is part of a package, then set the package directory as the app's pwd
|
161
|
-
# puts "run1: #{ops_file.ops_file_path}"
|
162
161
|
if ops_file.package_file && ops_file.package_file.dirname.to_s !~ /#{Bundler::BUNDLE_DIR}/
|
163
|
-
|
164
|
-
set_pwd(ops_file.package_file.dirname)
|
165
|
-
rebased_ops_file_relative_path = ops_file.ops_file_path.relative_path_from(ops_file.package_file.dirname)
|
166
|
-
# note: rebased_ops_file_relative_path is a relative path that is relative to ops_file.package_file.dirname
|
167
|
-
# puts "rebased path: #{rebased_ops_file_relative_path}"
|
168
|
-
absolute_ops_file_path = ops_file.package_file.dirname.join(rebased_ops_file_relative_path)
|
169
|
-
# puts "absolute path: #{absolute_ops_file_path}"
|
170
|
-
ops_file = OpsFile.new(self, absolute_ops_file_path)
|
162
|
+
ops_file = set_pwd_to_ops_file_package_directory(ops_file)
|
171
163
|
end
|
172
|
-
# puts "run2: #{ops_file.ops_file_path}"
|
173
164
|
|
174
165
|
op = OperationRunner.new(self, ops_file)
|
175
|
-
# if op.requires_sudo?
|
176
|
-
# prompt_sudo_password unless sudo_password
|
177
|
-
# end
|
178
|
-
# exit_status, out, err, script_output_structure = op.run(operation_kv_args, params_json_hash: @params, verbose: @verbose)
|
179
166
|
result = op.run(operation_kv_args, params_json_hash: @params, verbose: @verbose)
|
180
167
|
exit_status = result.exit_status
|
181
168
|
|
@@ -183,14 +170,7 @@ module OpsWalrus
|
|
183
170
|
puts "Op exit_status"
|
184
171
|
puts exit_status
|
185
172
|
|
186
|
-
# puts "Op stdout"
|
187
|
-
# puts out
|
188
|
-
|
189
|
-
# puts "Op stderr"
|
190
|
-
# puts err
|
191
|
-
|
192
173
|
puts "Op output"
|
193
|
-
# puts script_output_structure ? JSON.pretty_generate(script_output_structure) : nil.inspect
|
194
174
|
puts JSON.pretty_generate(result.value)
|
195
175
|
end
|
196
176
|
|
@@ -203,6 +183,19 @@ module OpsWalrus
|
|
203
183
|
FileUtils.remove_entry(tmp_dir) if tmp_dir
|
204
184
|
end
|
205
185
|
|
186
|
+
# sets the App's pwd to the ops file's package directory and
|
187
|
+
# returns a new OpsFile that points at the revised pathname with considered as relative to the package file's directory
|
188
|
+
def set_pwd_to_ops_file_package_directory(ops_file)
|
189
|
+
# puts "set pwd: #{ops_file.package_file.dirname}"
|
190
|
+
set_pwd(ops_file.package_file.dirname)
|
191
|
+
rebased_ops_file_relative_path = ops_file.ops_file_path.relative_path_from(ops_file.package_file.dirname)
|
192
|
+
# note: rebased_ops_file_relative_path is a relative path that is relative to ops_file.package_file.dirname
|
193
|
+
# puts "rebased path: #{rebased_ops_file_relative_path}"
|
194
|
+
absolute_ops_file_path = ops_file.package_file.dirname.join(rebased_ops_file_relative_path)
|
195
|
+
# puts "absolute path: #{absolute_ops_file_path}"
|
196
|
+
OpsFile.new(self, absolute_ops_file_path)
|
197
|
+
end
|
198
|
+
|
206
199
|
# package_operation_and_args can take one of the following forms:
|
207
200
|
# - ["github.com/davidkellis/my-package", "operation1", "arg1:val1", "arg2:val2", "arg3:val3"]
|
208
201
|
# - ["foo.zip", "foo/myfile.ops", "arg1:val1", "arg2:val2", "arg3:val3"]
|
@@ -244,7 +237,7 @@ module OpsWalrus
|
|
244
237
|
|
245
238
|
# operation_kv_args = package_operation_and_args
|
246
239
|
[ops_file_path, operation_kv_args, tmp_dir]
|
247
|
-
when repo_url =
|
240
|
+
when repo_url = Git.repo?(package_or_ops_file_reference)
|
248
241
|
destination_package_path = bundler.download_git_package(repo_url)
|
249
242
|
|
250
243
|
ops_file_path, operation_kv_args = find_entry_point_ops_file_in_dir(destination_package_path, package_operation_and_args)
|
@@ -301,22 +294,6 @@ module OpsWalrus
|
|
301
294
|
[ops_file_path, operation_kv_args]
|
302
295
|
end
|
303
296
|
|
304
|
-
# git_repo?("davidkellis/arborist") -> "https://github.com/davidkellis/arborist"
|
305
|
-
# returns the repo URL
|
306
|
-
def git_repo?(repo_reference)
|
307
|
-
candidate_repo_references = [
|
308
|
-
repo_reference,
|
309
|
-
repo_reference =~ /(\.(com|net|org|dev|io|local))\// && "https://#{repo_reference}",
|
310
|
-
repo_reference !~ /github\.com\// && repo_reference =~ /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}\/([\w\.@\:\-~]+)$/i && "https://github.com/#{repo_reference}" # this regex is from https://www.npmjs.com/package/github-username-regex and https://www.debuggex.com/r/H4kRw1G0YPyBFjfm
|
311
|
-
].compact
|
312
|
-
working_repo_reference = candidate_repo_references.find {|reference| Git.ls_remote(reference) rescue nil }
|
313
|
-
working_repo_reference
|
314
|
-
end
|
315
|
-
|
316
|
-
# def is_dir_git_repo?(dir_path)
|
317
|
-
# Git.ls_remote(reference) rescue nil
|
318
|
-
# end
|
319
|
-
|
320
297
|
def bundle_status
|
321
298
|
end
|
322
299
|
|
data/lib/opswalrus/bundler.rb
CHANGED
@@ -22,8 +22,8 @@ module OpsWalrus
|
|
22
22
|
@bundle_dir
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
26
|
-
FileUtils.mkdir_p(@bundle_dir)
|
25
|
+
def ensure_pwd_bundle_directory_exists
|
26
|
+
FileUtils.mkdir_p(@bundle_dir) unless @bundle_dir.exist?
|
27
27
|
end
|
28
28
|
|
29
29
|
# # returns the OpsFile within the bundle directory that represents the given ops_file (which is outside of the bundle directory)
|
@@ -50,7 +50,7 @@ module OpsWalrus
|
|
50
50
|
# end
|
51
51
|
|
52
52
|
def update
|
53
|
-
|
53
|
+
ensure_pwd_bundle_directory_exists
|
54
54
|
|
55
55
|
package_yaml_files = pwd.glob("./**/package.yaml") - pwd.glob("./**/#{BUNDLE_DIR}/**/package.yaml")
|
56
56
|
package_files_within_pwd = package_yaml_files.map {|path| PackageFile.new(path.realpath) }
|
@@ -83,7 +83,7 @@ module OpsWalrus
|
|
83
83
|
|
84
84
|
# returns the self_pkg directory within the bundle directory
|
85
85
|
# def include_directory_in_bundle_as_self_pkg(dirname = pwd)
|
86
|
-
#
|
86
|
+
# ensure_pwd_bundle_directory_exists
|
87
87
|
|
88
88
|
# destination_package_path = @bundle_dir.join("self_pkg")
|
89
89
|
|
@@ -103,7 +103,7 @@ module OpsWalrus
|
|
103
103
|
# This method downloads a package_url that is a dependency referenced in the specified package_file
|
104
104
|
# returns the destination directory that the package was downloaded to
|
105
105
|
def download_package(package_file, package_reference)
|
106
|
-
|
106
|
+
ensure_pwd_bundle_directory_exists
|
107
107
|
|
108
108
|
local_name = package_reference.local_name
|
109
109
|
package_url = package_reference.package_uri
|
@@ -144,19 +144,26 @@ module OpsWalrus
|
|
144
144
|
end
|
145
145
|
|
146
146
|
def download_git_package(package_url, version = nil, destination_package_path = nil)
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
147
|
+
ensure_pwd_bundle_directory_exists
|
148
|
+
|
149
|
+
destination_package_path ||= dynamic_package_path_for_git_package(package_url, version)
|
150
|
+
|
151
|
+
return destination_package_path if destination_package_path.exist?
|
152
|
+
|
152
153
|
if version
|
153
|
-
Git.clone(package_url, destination_package_path, branch: version, config: ['submodule.recurse=true'])
|
154
|
+
::Git.clone(package_url, destination_package_path, branch: version, config: ['submodule.recurse=true'])
|
154
155
|
else
|
155
|
-
Git.clone(package_url, destination_package_path, config: ['submodule.recurse=true'])
|
156
|
+
::Git.clone(package_url, destination_package_path, config: ['submodule.recurse=true'])
|
156
157
|
end
|
158
|
+
|
157
159
|
destination_package_path
|
158
160
|
end
|
159
161
|
|
162
|
+
def dynamic_package_path_for_git_package(package_url, version = nil)
|
163
|
+
package_reference_dirname = sanitize_path(package_url)
|
164
|
+
bundle_dir.join(package_reference_dirname)
|
165
|
+
end
|
166
|
+
|
160
167
|
def sanitize_path(path)
|
161
168
|
# found this at https://apidock.com/rails/v5.2.3/ActiveStorage/Filename/sanitized
|
162
169
|
path.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "git"
|
2
|
+
|
3
|
+
module OpsWalrus
|
4
|
+
class Git
|
5
|
+
# repo?("davidkellis/arborist") -> "https://github.com/davidkellis/arborist"
|
6
|
+
# returns the repo URL or directory path
|
7
|
+
def self.repo?(repo_reference)
|
8
|
+
if Dir.exist?(repo_reference)
|
9
|
+
::Git.ls_remote(repo_reference) && repo_reference rescue nil
|
10
|
+
else
|
11
|
+
candidate_repo_references = [
|
12
|
+
repo_reference,
|
13
|
+
repo_reference =~ /(\.(com|net|org|dev|io|local))\// && "https://#{repo_reference}",
|
14
|
+
repo_reference !~ /github\.com\// && repo_reference =~ /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}\/([\w\.@\:\-~]+)$/i && "https://github.com/#{repo_reference}" # this regex is from https://www.npmjs.com/package/github-username-regex and https://www.debuggex.com/r/H4kRw1G0YPyBFjfm
|
15
|
+
].compact
|
16
|
+
working_repo_reference = candidate_repo_references.find {|reference| ::Git.ls_remote(reference) rescue nil }
|
17
|
+
working_repo_reference
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
data/lib/opswalrus/host.rb
CHANGED
@@ -27,6 +27,10 @@ module OpsWalrus
|
|
27
27
|
cmd = block.call if block
|
28
28
|
cmd ||= desc_or_cmd
|
29
29
|
|
30
|
+
cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
31
|
+
|
32
|
+
#cmd = Shellwords.escape(cmd)
|
33
|
+
|
30
34
|
if self.alias
|
31
35
|
print "[#{self.alias} | #{host}] "
|
32
36
|
else
|
@@ -35,11 +39,8 @@ module OpsWalrus
|
|
35
39
|
print "#{description}: " if description
|
36
40
|
puts cmd
|
37
41
|
|
38
|
-
cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
39
42
|
return unless cmd && !cmd.strip.empty?
|
40
43
|
|
41
|
-
#cmd = Shellwords.escape(cmd)
|
42
|
-
|
43
44
|
# puts "shell: #{cmd}"
|
44
45
|
# puts "shell: #{cmd.inspect}"
|
45
46
|
# puts "sudo_password: #{sudo_password}"
|
data/lib/opswalrus/ops_file.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require 'yaml'
|
3
|
+
require_relative 'git'
|
3
4
|
require_relative 'ops_file_script'
|
4
5
|
|
5
6
|
module OpsWalrus
|
@@ -91,12 +92,16 @@ module OpsWalrus
|
|
91
92
|
imports_hash.map do |local_name, yaml_import_reference|
|
92
93
|
local_name = local_name.to_s
|
93
94
|
import_reference = case yaml_import_reference
|
95
|
+
|
96
|
+
# when the imports line says:
|
97
|
+
# imports:
|
98
|
+
# my_package: my_package
|
94
99
|
in String => import_str
|
95
100
|
case
|
96
101
|
when package_reference = package_file&.dependency(import_str) # package dependency reference
|
97
102
|
# in this context, import_str is the local package name documented in the package's dependencies
|
98
103
|
PackageDependencyReference.new(local_name, package_reference)
|
99
|
-
when import_str.to_pathname.exist?
|
104
|
+
when import_str.to_pathname.exist? # path reference
|
100
105
|
path = import_str.to_pathname
|
101
106
|
case
|
102
107
|
when path.directory?
|
@@ -106,13 +111,26 @@ module OpsWalrus
|
|
106
111
|
else
|
107
112
|
raise Error, "Unknown import reference: #{local_name} -> #{import_str.inspect}"
|
108
113
|
end
|
114
|
+
when Git.repo?(import_str) # ops file has imported an ad-hoc git repo
|
115
|
+
package_uri = import_str
|
116
|
+
destination_package_path = app.bundler.dynamic_package_path_for_git_package(package_uri)
|
117
|
+
# puts "DynamicPackageImportReference: #{local_name} -> #{destination_package_path}"
|
118
|
+
DynamicPackageImportReference.new(local_name, DynamicPackageReference.new(local_name, package_uri, nil))
|
119
|
+
else
|
120
|
+
raise Error, "Unknown import reference: #{local_name}: #{yaml_import_reference.inspect}"
|
109
121
|
end
|
110
|
-
|
122
|
+
|
123
|
+
# when the imports line says:
|
124
|
+
# imports:
|
125
|
+
# my_package:
|
126
|
+
# url: https://...
|
127
|
+
# version: 2.1
|
128
|
+
# in Hash => package_defn
|
111
129
|
# url = package_defn["url"]
|
112
130
|
# version = package_defn["version"]
|
113
131
|
# PackageReference.new(local_name, url, version&.to_s)
|
114
132
|
else
|
115
|
-
raise Error, "Unknown
|
133
|
+
raise Error, "Unknown import reference: #{local_name}: #{yaml_import_reference.inspect}"
|
116
134
|
end
|
117
135
|
[local_name, import_reference]
|
118
136
|
end.to_h
|
@@ -205,36 +223,5 @@ module OpsWalrus
|
|
205
223
|
dirname.glob("*").select(&:directory?)
|
206
224
|
end
|
207
225
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
def supporting_library_include_dir_and_require_lib
|
216
|
-
if Dir.exist?(ops_file_helper_library_directory)
|
217
|
-
[ops_file_helper_library_directory, ops_file_helper_library_basename]
|
218
|
-
elsif File.exist?(ops_file_sibling_helper_library_file)
|
219
|
-
[dirname, ops_file_helper_library_basename]
|
220
|
-
else
|
221
|
-
[nil, nil]
|
222
|
-
end
|
223
|
-
end
|
224
|
-
|
225
|
-
def ops_file_helper_library_basename
|
226
|
-
basename.sub_ext(".rb")
|
227
|
-
end
|
228
|
-
|
229
|
-
# "/home/david/sync/projects/ops/ops/core/host/info.ops" => "/home/david/sync/projects/ops/ops/core/host/info"
|
230
|
-
def ops_file_helper_library_directory
|
231
|
-
File.join(dirname, basename)
|
232
|
-
end
|
233
|
-
|
234
|
-
# "/home/david/sync/projects/ops/ops/core/host/info.ops" => "/home/david/sync/projects/ops/ops/core/host/info.rb"
|
235
|
-
def ops_file_sibling_helper_library_file
|
236
|
-
"#{ops_file_helper_library_directory}.rb"
|
237
|
-
end
|
238
|
-
|
239
226
|
end
|
240
227
|
end
|
@@ -20,6 +20,9 @@ module OpsWalrus
|
|
20
20
|
def [](index, *args, **kwargs, &block)
|
21
21
|
@obj.method(:[]).call(index, *args, **kwargs, &block)
|
22
22
|
end
|
23
|
+
def respond_to_missing?(method, *)
|
24
|
+
@obj.is_a?(Hash) && @obj.respond_to?(method)
|
25
|
+
end
|
23
26
|
def method_missing(name, *args, **kwargs, &block)
|
24
27
|
case @obj
|
25
28
|
when Array
|
@@ -214,6 +217,9 @@ module OpsWalrus
|
|
214
217
|
end
|
215
218
|
end
|
216
219
|
|
220
|
+
# currently, import may only be used to import a package that is referenced in the script's package file
|
221
|
+
# I may decide to extend this to work with dynamic package references
|
222
|
+
#
|
217
223
|
# local_package_name is the local package name defined for the package dependency that is attempting to be referenced
|
218
224
|
def import(local_package_name)
|
219
225
|
local_package_name = local_package_name.to_s
|
@@ -224,12 +230,12 @@ module OpsWalrus
|
|
224
230
|
@runtime_env.resolve_import_reference(@ops_file_script.ops_file, import_reference)
|
225
231
|
end
|
226
232
|
|
227
|
-
def params(*keys)
|
233
|
+
def params(*keys, default: nil)
|
228
234
|
keys = keys.map(&:to_s)
|
229
235
|
if keys.empty?
|
230
236
|
@params
|
231
237
|
else
|
232
|
-
@params.dig(*keys)
|
238
|
+
@params.dig(*keys) || default
|
233
239
|
end
|
234
240
|
end
|
235
241
|
|
@@ -254,17 +260,18 @@ module OpsWalrus
|
|
254
260
|
cmd = block.call if block
|
255
261
|
cmd ||= desc_or_cmd
|
256
262
|
|
263
|
+
cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
264
|
+
|
265
|
+
#cmd = Shellwords.escape(cmd)
|
266
|
+
|
257
267
|
# puts "shell! self: #{self.inspect}"
|
258
268
|
|
259
269
|
print "[#{@runtime_env.local_hostname}] "
|
260
270
|
print "#{description}: " if description
|
261
271
|
puts cmd
|
262
272
|
|
263
|
-
cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
|
264
273
|
return unless cmd && !cmd.strip.empty?
|
265
274
|
|
266
|
-
#cmd = Shellwords.escape(cmd)
|
267
|
-
|
268
275
|
sudo_password = @runtime_env.sudo_password
|
269
276
|
sudo_password &&= sudo_password.gsub(/\n+$/,'') # remove trailing newlines from sudo_password
|
270
277
|
|
@@ -5,6 +5,7 @@ require_relative 'bundler'
|
|
5
5
|
|
6
6
|
module OpsWalrus
|
7
7
|
|
8
|
+
# these are static package references defined ahead of time in the package file
|
8
9
|
class PackageReference
|
9
10
|
attr_accessor :local_name
|
10
11
|
attr_accessor :package_uri
|
@@ -23,7 +24,7 @@ module OpsWalrus
|
|
23
24
|
path.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
|
24
25
|
end
|
25
26
|
|
26
|
-
# important: the
|
27
|
+
# important: the import_resolution_dirname implemented as the local_name is critical because Bundler#download_package downloads
|
27
28
|
# package dependencies to the name that this method returns, which must match the package reference's local name
|
28
29
|
# so that later, when the package is being looked up on the load path (in LoadPath#resolve_import_reference),
|
29
30
|
# the package reference's referenced git repo or file path may not exist or be available, and so the package
|
@@ -32,12 +33,20 @@ module OpsWalrus
|
|
32
33
|
# If this implementation changes, then Bundler#download_package and LoadPath#resolve_import_reference must also
|
33
34
|
# change in order for the three things to reconcile with respect to one another, since all three bits of logic are
|
34
35
|
# what make bundling package dependencies and loading them function properly.
|
35
|
-
def
|
36
|
+
def import_resolution_dirname
|
36
37
|
local_name
|
37
38
|
end
|
38
39
|
|
39
40
|
end
|
40
41
|
|
42
|
+
# these are dynamic package references defined at runtime when an OpsFile's imports are being evaluated.
|
43
|
+
# this will usually be the case when an ops file does not belong to a package
|
44
|
+
class DynamicPackageReference < PackageReference
|
45
|
+
def import_resolution_dirname
|
46
|
+
sanitized_package_uri
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
41
50
|
class PackageFile
|
42
51
|
attr_accessor :package_file_path
|
43
52
|
attr_accessor :yaml
|
@@ -32,6 +32,14 @@ module OpsWalrus
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
class DynamicPackageImportReference < ImportReference
|
36
|
+
attr_accessor :package_reference
|
37
|
+
def initialize(local_name, package_reference)
|
38
|
+
super(local_name)
|
39
|
+
@package_reference = package_reference
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
35
43
|
class OpsFileReference < ImportReference
|
36
44
|
attr_accessor :ops_file_path
|
37
45
|
def initialize(local_name, ops_file_path)
|
@@ -86,6 +94,8 @@ module OpsWalrus
|
|
86
94
|
@dir = dir
|
87
95
|
@root_namespace = build_symbol_resolution_tree(@dir)
|
88
96
|
@path_map = build_path_map(@root_namespace)
|
97
|
+
|
98
|
+
@dynamic_package_additions_memo = {}
|
89
99
|
end
|
90
100
|
|
91
101
|
# returns a tree of Namespace -> {Namespace* -> {Namespace* -> ..., OpsFile*}, OpsFile*}
|
@@ -129,6 +139,19 @@ module OpsWalrus
|
|
129
139
|
path_map
|
130
140
|
end
|
131
141
|
|
142
|
+
def dynamically_add_new_package_dir(new_package_dir)
|
143
|
+
# patch the symbol resolution (namespace) tree
|
144
|
+
dir_basename = new_package_dir.basename
|
145
|
+
unless @root_namespace.resolve_symbol(dir_basename)
|
146
|
+
new_child_namespace = build_symbol_resolution_tree(new_package_dir)
|
147
|
+
@root_namespace.add(dir_basename, new_child_namespace)
|
148
|
+
|
149
|
+
# patch the path map
|
150
|
+
new_partial_path_map = build_path_map(new_child_namespace)
|
151
|
+
@path_map.merge!(new_partial_path_map)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
132
155
|
# returns a Namespace
|
133
156
|
def lookup_namespace(ops_file)
|
134
157
|
@path_map[ops_file.dirname]
|
@@ -144,7 +167,16 @@ module OpsWalrus
|
|
144
167
|
case import_reference
|
145
168
|
when PackageDependencyReference
|
146
169
|
# puts "root namespace: #{@root_namespace.symbol_table}"
|
147
|
-
@root_namespace.resolve_symbol(import_reference.package_reference.
|
170
|
+
@root_namespace.resolve_symbol(import_reference.package_reference.import_resolution_dirname) # returns the Namespace associated with the bundled package dirname (i.e. the local name)
|
171
|
+
when DynamicPackageImportReference
|
172
|
+
dynamic_package_reference = import_reference.package_reference
|
173
|
+
@dynamic_package_additions_memo[dynamic_package_reference] ||= begin
|
174
|
+
# puts "Downloading dynamic package: #{dynamic_package_reference.inspect}"
|
175
|
+
dynamically_added_package_dir = @runtime_env.app.bundler.download_git_package(dynamic_package_reference.package_uri, dynamic_package_reference.version)
|
176
|
+
dynamically_add_new_package_dir(dynamically_added_package_dir)
|
177
|
+
dynamically_added_package_dir
|
178
|
+
end
|
179
|
+
@root_namespace.resolve_symbol(import_reference.package_reference.import_resolution_dirname) # returns the Namespace associated with the bundled package dirname (i.e. the sanitized package uri)
|
148
180
|
when DirectoryReference
|
149
181
|
@path_map[import_reference.dirname]
|
150
182
|
when OpsFileReference
|
@@ -244,7 +276,7 @@ module OpsWalrus
|
|
244
276
|
case import_reference
|
245
277
|
|
246
278
|
# we know we're dealing with a package dependency reference, so we want to do the lookup in the bundle load path, where package dependencies live
|
247
|
-
when PackageDependencyReference
|
279
|
+
when PackageDependencyReference, DynamicPackageImportReference
|
248
280
|
@bundle_load_path.resolve_import_reference(origin_ops_file, import_reference)
|
249
281
|
|
250
282
|
# we know we're dealing with a directory reference or OpsFile reference outside of the bundle dir, so we want to do the lookup in the app load path
|
data/lib/opswalrus/version.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "citrus"
|
2
|
+
require "ostruct"
|
2
3
|
require "stringio"
|
3
4
|
|
4
5
|
module WalrusLang
|
@@ -54,9 +55,17 @@ module WalrusLang
|
|
54
55
|
|
55
56
|
Citrus.eval(Grammar)
|
56
57
|
|
57
|
-
|
58
|
+
# binding_obj : Binding | Hash
|
59
|
+
def self.render(template, binding_obj)
|
60
|
+
binding_obj = binding_obj.to_binding if binding_obj.respond_to?(:to_binding)
|
58
61
|
ast = WalrusLang::Parser.parse(template)
|
59
|
-
ast.render(
|
62
|
+
ast.render(binding_obj)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Hash
|
67
|
+
def to_binding
|
68
|
+
OpenStruct.new(self).instance_eval { binding }
|
60
69
|
end
|
61
70
|
end
|
62
71
|
|
data/opswalrus.gemspec
CHANGED
@@ -3,17 +3,18 @@
|
|
3
3
|
require_relative "lib/opswalrus/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name
|
7
|
-
spec.version
|
8
|
-
spec.authors
|
9
|
-
spec.email
|
10
|
-
|
11
|
-
spec.summary
|
12
|
-
spec.description
|
13
|
-
spec.homepage
|
6
|
+
spec.name = "opswalrus"
|
7
|
+
spec.version = OpsWalrus::VERSION
|
8
|
+
spec.authors = ["David Ellis"]
|
9
|
+
spec.email = ["david@conquerthelawn.com"]
|
10
|
+
|
11
|
+
spec.summary = "opswalrus is a tool that runs scripts against a fleet of hosts"
|
12
|
+
spec.description = "opswalrus is a tool that runs scripts against a fleet of hosts hosts. It's kind of like Ansible, but aims to be simpler to use."
|
13
|
+
spec.homepage = "https://github.com/opswalrus/opswalrus"
|
14
|
+
spec.license = "EPL-2.0"
|
14
15
|
spec.required_ruby_version = ">= 2.6.0"
|
15
16
|
|
16
|
-
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server
|
17
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server - https://example.com"
|
17
18
|
|
18
19
|
spec.metadata["homepage_uri"] = spec.homepage
|
19
20
|
spec.metadata["source_code_uri"] = "https://github.com/opswalrus/opswalrus"
|
@@ -31,14 +32,14 @@ Gem::Specification.new do |spec|
|
|
31
32
|
spec.require_paths = ["lib"]
|
32
33
|
|
33
34
|
# gem dependencies
|
34
|
-
spec.add_dependency "citrus"
|
35
|
-
spec.add_dependency "gli"
|
36
|
-
spec.add_dependency "git"
|
37
|
-
spec.add_dependency "rubyzip"
|
38
|
-
|
39
|
-
spec.add_dependency "bcrypt_pbkdf"
|
40
|
-
spec.add_dependency "ed25519"
|
41
|
-
spec.add_dependency "sshkit"
|
35
|
+
spec.add_dependency "citrus", "~> 3.0"
|
36
|
+
spec.add_dependency "gli", "~> 2.21"
|
37
|
+
spec.add_dependency "git", "~> 1.18"
|
38
|
+
spec.add_dependency "rubyzip", "~> 2.3"
|
39
|
+
|
40
|
+
spec.add_dependency "bcrypt_pbkdf", "~> 1.1"
|
41
|
+
spec.add_dependency "ed25519", "~> 1.3"
|
42
|
+
spec.add_dependency "sshkit", "~> 1.21"
|
42
43
|
|
43
44
|
# For more information and examples about making a new gem, check out our
|
44
45
|
# guide at: https://bundler.io/guides/creating_gem.html
|