opswalrus 1.0.8 → 1.0.10
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/CNAME +1 -0
- data/Gemfile.lock +1 -1
- data/README.md +29 -16
- data/lib/opswalrus/app.rb +21 -7
- data/lib/opswalrus/bootstrap.sh +5 -0
- data/lib/opswalrus/cli.rb +16 -15
- data/lib/opswalrus/host.rb +115 -71
- data/lib/opswalrus/invocation.rb +446 -0
- data/lib/opswalrus/operation_runner.rb +3 -3
- data/lib/opswalrus/ops_file.rb +71 -32
- data/lib/opswalrus/ops_file_script.rb +55 -473
- data/lib/opswalrus/ops_file_script_dsl.rb +297 -0
- data/lib/opswalrus/patches.rb +1 -0
- data/lib/opswalrus/runtime_environment.rb +40 -9
- data/lib/opswalrus/sshkit_ext.rb +6 -3
- data/lib/opswalrus/version.rb +1 -1
- data/opswalrus.gemspec +1 -1
- metadata +5 -2
| @@ -1,337 +1,30 @@ | |
| 1 | 
            -
            require 'json'
         | 
| 2 1 | 
             
            require 'set'
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            require 'socket'
         | 
| 5 | 
            -
            require 'stringio'
         | 
| 6 | 
            -
             | 
| 7 | 
            -
            # require 'ed25519'
         | 
| 8 | 
            -
            require 'sshkit'
         | 
| 9 | 
            -
            require 'sshkit/dsl'
         | 
| 10 | 
            -
             | 
| 11 | 
            -
            require_relative 'host'
         | 
| 12 | 
            -
            require_relative 'sshkit_ext'
         | 
| 13 | 
            -
            require_relative 'walrus_lang'
         | 
| 2 | 
            +
            require_relative 'ops_file_script_dsl'
         | 
| 14 3 |  | 
| 15 4 | 
             
            module OpsWalrus
         | 
| 16 | 
            -
              class ArrayOrHashNavigationProxy
         | 
| 17 | 
            -
                def initialize(array_or_hash)
         | 
| 18 | 
            -
                  @obj = array_or_hash
         | 
| 19 | 
            -
                end
         | 
| 20 | 
            -
                def [](index, *args, **kwargs, &block)
         | 
| 21 | 
            -
                  @obj.method(:[]).call(index, *args, **kwargs, &block)
         | 
| 22 | 
            -
                end
         | 
| 23 | 
            -
                def respond_to_missing?(method, *)
         | 
| 24 | 
            -
                  @obj.is_a?(Hash) && @obj.respond_to?(method)
         | 
| 25 | 
            -
                end
         | 
| 26 | 
            -
                def method_missing(name, *args, **kwargs, &block)
         | 
| 27 | 
            -
                  case @obj
         | 
| 28 | 
            -
                  when Array
         | 
| 29 | 
            -
                    @obj.method(name).call(*args, **kwargs, &block)
         | 
| 30 | 
            -
                  when Hash
         | 
| 31 | 
            -
                    if @obj.respond_to?(name)
         | 
| 32 | 
            -
                      @obj.method(name).call(*args, **kwargs, &block)
         | 
| 33 | 
            -
                    else
         | 
| 34 | 
            -
                      value = self[name.to_s]
         | 
| 35 | 
            -
                      case value
         | 
| 36 | 
            -
                      when Array, Hash
         | 
| 37 | 
            -
                        ArrayOrHashNavigationProxy.new(value)
         | 
| 38 | 
            -
                      else
         | 
| 39 | 
            -
                        value
         | 
| 40 | 
            -
                      end
         | 
| 41 | 
            -
                    end
         | 
| 42 | 
            -
                  end
         | 
| 43 | 
            -
                end
         | 
| 44 | 
            -
              end
         | 
| 45 | 
            -
             | 
| 46 | 
            -
              class Params
         | 
| 47 | 
            -
                # params : Hash
         | 
| 48 | 
            -
                def initialize(params)
         | 
| 49 | 
            -
                  @params = params
         | 
| 50 | 
            -
                end
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                def [](key)
         | 
| 53 | 
            -
                  key = key.to_s if key.is_a? Symbol
         | 
| 54 | 
            -
                  @params[key]
         | 
| 55 | 
            -
                end
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                def dig(*keys)
         | 
| 58 | 
            -
                  # keys = keys.map {|key| key.is_a?(Integer) ? key : key.to_s }
         | 
| 59 | 
            -
                  @params.dig(*keys)
         | 
| 60 | 
            -
                end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
                def method_missing(name, *args, **kwargs, &block)
         | 
| 63 | 
            -
                  if @params.respond_to?(name)
         | 
| 64 | 
            -
                    @params.method(name).call(*args, **kwargs, &block)
         | 
| 65 | 
            -
                  else
         | 
| 66 | 
            -
                    value = self[name]
         | 
| 67 | 
            -
                    case value
         | 
| 68 | 
            -
                    when Array, Hash
         | 
| 69 | 
            -
                      ArrayOrHashNavigationProxy.new(value)
         | 
| 70 | 
            -
                    else
         | 
| 71 | 
            -
                      value
         | 
| 72 | 
            -
                    end
         | 
| 73 | 
            -
                  end
         | 
| 74 | 
            -
                end
         | 
| 75 | 
            -
              end
         | 
| 76 | 
            -
             | 
| 77 | 
            -
              class Env
         | 
| 78 | 
            -
                # env : Hash
         | 
| 79 | 
            -
                def initialize(env)
         | 
| 80 | 
            -
                  @env = env
         | 
| 81 | 
            -
                end
         | 
| 82 | 
            -
             | 
| 83 | 
            -
                def [](key)
         | 
| 84 | 
            -
                  key = key.to_s if key.is_a? Symbol
         | 
| 85 | 
            -
                  @env[key]
         | 
| 86 | 
            -
                end
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                def dig(*keys)
         | 
| 89 | 
            -
                  keys = keys.map {|key| key.is_a?(Integer) ? key : key.to_s }
         | 
| 90 | 
            -
                  @env.dig(*keys)
         | 
| 91 | 
            -
                end
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                def method_missing(name, *args, **kwargs, &block)
         | 
| 94 | 
            -
                  if @env.respond_to?(name)
         | 
| 95 | 
            -
                    @env.method(name).call(*args, **kwargs, &block)
         | 
| 96 | 
            -
                  else
         | 
| 97 | 
            -
                    value = self[name]
         | 
| 98 | 
            -
                    case value
         | 
| 99 | 
            -
                    when Array, Hash
         | 
| 100 | 
            -
                      ArrayOrHashNavigationProxy.new(value)
         | 
| 101 | 
            -
                    else
         | 
| 102 | 
            -
                      value
         | 
| 103 | 
            -
                    end
         | 
| 104 | 
            -
                  end
         | 
| 105 | 
            -
                end
         | 
| 106 | 
            -
              end
         | 
| 107 | 
            -
             | 
| 108 | 
            -
              # BootstrapLinuxHostShellScript = <<~SCRIPT
         | 
| 109 | 
            -
              #   #!/usr/bin/env bash
         | 
| 110 | 
            -
              #   ...
         | 
| 111 | 
            -
              # SCRIPT
         | 
| 112 | 
            -
             | 
| 113 | 
            -
              module DSL
         | 
| 114 | 
            -
                def ssh(*args, **kwargs, &block)
         | 
| 115 | 
            -
                  host_proxy_class = @ops_file_script.host_proxy_class
         | 
| 116 | 
            -
                  hosts = inventory(*args, **kwargs).map {|host| host_proxy_class.new(host) }
         | 
| 117 | 
            -
                  sshkit_hosts = hosts.map(&:sshkit_host)
         | 
| 118 | 
            -
                  sshkit_host_to_ops_host_map = sshkit_hosts.zip(hosts).to_h
         | 
| 119 | 
            -
                  runtime_env = @runtime_env
         | 
| 120 | 
            -
                  local_host = self
         | 
| 121 | 
            -
                  # bootstrap_shell_script = BootstrapLinuxHostShellScript
         | 
| 122 | 
            -
                  # on sshkit_hosts do |sshkit_host|
         | 
| 123 | 
            -
                  SSHKit::Coordinator.new(sshkit_hosts).each(in: kwargs[:in] || :parallel) do |sshkit_host|
         | 
| 124 | 
            -
             | 
| 125 | 
            -
                    # in this context, self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
         | 
| 126 | 
            -
             | 
| 127 | 
            -
                    host = sshkit_host_to_ops_host_map[sshkit_host]
         | 
| 128 | 
            -
                    # puts "#{host.alias} / #{host}:"
         | 
| 129 | 
            -
             | 
| 130 | 
            -
                    begin
         | 
| 131 | 
            -
                      host.set_runtime_env(runtime_env)
         | 
| 132 | 
            -
                      host.set_ssh_session_connection(self)  # self is an instance of one of the subclasses of SSHKit::Backend::Abstract, e.g. SSHKit::Backend::Netssh
         | 
| 133 | 
            -
             | 
| 134 | 
            -
                      # copy over bootstrap shell script
         | 
| 135 | 
            -
                      # io = StringIO.new(bootstrap_shell_script)
         | 
| 136 | 
            -
                      io = File.open(__FILE__.to_pathname.dirname.join("bootstrap.sh"))
         | 
| 137 | 
            -
                      upload_success = host.upload(io, "tmpopsbootstrap.sh")
         | 
| 138 | 
            -
                      io.close
         | 
| 139 | 
            -
                      raise Error, "Unable to upload bootstrap shell script to remote host" unless upload_success
         | 
| 140 | 
            -
                      host.execute(:chmod, "755", "tmpopsbootstrap.sh")
         | 
| 141 | 
            -
                      host.execute(:sh, "tmpopsbootstrap.sh")
         | 
| 142 | 
            -
             | 
| 143 | 
            -
                      # copy over ops bundle zip file
         | 
| 144 | 
            -
                      zip_bundle_path = runtime_env.zip_bundle_path
         | 
| 145 | 
            -
                      upload_success = host.upload(zip_bundle_path, "tmpops.zip")
         | 
| 146 | 
            -
                      raise Error, "Unable to upload ops bundle to remote host" unless upload_success
         | 
| 147 | 
            -
             | 
| 148 | 
            -
                      stdout, stderr, exit_status = host.run_ops(:bundle, "unzip tmpops.zip", in_bundle_root_dir: false)
         | 
| 149 | 
            -
                      raise Error, "Unable to unzip ops bundle on remote host" unless exit_status == 0
         | 
| 150 | 
            -
                      tmp_bundle_root_dir = stdout.strip
         | 
| 151 | 
            -
                      host.set_ssh_session_tmp_bundle_root_dir(tmp_bundle_root_dir)
         | 
| 152 | 
            -
             | 
| 153 | 
            -
                      # we run the block in the context of the host, s.t. `self` within the block evaluates to `host`
         | 
| 154 | 
            -
                      retval = host.instance_exec(local_host, &block)    # host is passed as the argument to the block
         | 
| 155 | 
            -
             | 
| 156 | 
            -
                      puts retval.inspect
         | 
| 157 | 
            -
             | 
| 158 | 
            -
                      # cleanup
         | 
| 159 | 
            -
                      if tmp_bundle_root_dir =~ /tmp/   # sanity check the temp path before we blow away something we don't intend
         | 
| 160 | 
            -
                        host.execute(:rm, "-rf", "tmpopsbootstrap.sh", "tmpops.zip", tmp_bundle_root_dir)
         | 
| 161 | 
            -
                      else
         | 
| 162 | 
            -
                        host.execute(:rm, "-rf", "tmpopsbootstrap.sh", "tmpops.zip")
         | 
| 163 | 
            -
                      end
         | 
| 164 | 
            -
             | 
| 165 | 
            -
                      retval
         | 
| 166 | 
            -
                    rescue SSHKit::Command::Failed => e
         | 
| 167 | 
            -
                      puts "[!] Command failed:"
         | 
| 168 | 
            -
                      puts e.message
         | 
| 169 | 
            -
                    rescue Net::SSH::ConnectionTimeout
         | 
| 170 | 
            -
                      puts "[!] The host '#{host}' not alive!"
         | 
| 171 | 
            -
                    rescue Net::SSH::Timeout
         | 
| 172 | 
            -
                      puts "[!] The host '#{host}' disconnected/timeouted unexpectedly!"
         | 
| 173 | 
            -
                    rescue Errno::ECONNREFUSED
         | 
| 174 | 
            -
                      puts "[!] Incorrect port #{port} for #{host}"
         | 
| 175 | 
            -
                    rescue Net::SSH::HostKeyMismatch => e
         | 
| 176 | 
            -
                      puts "[!] The host fingerprint does not match the last observed fingerprint for #{host}"
         | 
| 177 | 
            -
                      puts e.message
         | 
| 178 | 
            -
                      puts "You might try `ssh-keygen -f ~/.ssh/known_hosts -R \"#{host}\"`"
         | 
| 179 | 
            -
                    rescue Net::SSH::AuthenticationFailed
         | 
| 180 | 
            -
                      puts "Wrong Password: #{host} | #{user}:#{password}"
         | 
| 181 | 
            -
                    rescue Net::SSH::Authentication::DisallowedMethod
         | 
| 182 | 
            -
                      puts "[!] The host '#{host}' doesn't accept password authentication method."
         | 
| 183 | 
            -
                    rescue Errno::EHOSTUNREACH => e
         | 
| 184 | 
            -
                      puts "[!] The host '#{host}' is unreachable"
         | 
| 185 | 
            -
                    rescue => e
         | 
| 186 | 
            -
                      puts e.class
         | 
| 187 | 
            -
                      puts e.message
         | 
| 188 | 
            -
                      # puts e.backtrace.join("\n")
         | 
| 189 | 
            -
                    ensure
         | 
| 190 | 
            -
                      host.clear_ssh_session
         | 
| 191 | 
            -
                    end
         | 
| 192 | 
            -
                  end
         | 
| 193 | 
            -
                end
         | 
| 194 | 
            -
             | 
| 195 | 
            -
                def inventory(*args, **kwargs)
         | 
| 196 | 
            -
                  tags = args.map(&:to_s)
         | 
| 197 | 
            -
             | 
| 198 | 
            -
                  kwargs = kwargs.transform_keys(&:to_s)
         | 
| 199 | 
            -
                  tags.concat(kwargs["tags"]) if kwargs["tags"]
         | 
| 200 | 
            -
             | 
| 201 | 
            -
                  @runtime_env.app.inventory(tags)
         | 
| 202 | 
            -
                end
         | 
| 203 | 
            -
             | 
| 204 | 
            -
                def exit(exit_status, message = nil)
         | 
| 205 | 
            -
                  if message
         | 
| 206 | 
            -
                    puts message
         | 
| 207 | 
            -
                  end
         | 
| 208 | 
            -
                  result = if exit_status == 0
         | 
| 209 | 
            -
                    Invocation::Success.new(nil)
         | 
| 210 | 
            -
                  else
         | 
| 211 | 
            -
                    Invocation::Error.new(nil, exit_status)
         | 
| 212 | 
            -
                  end
         | 
| 213 | 
            -
                  throw :exit_now, result
         | 
| 214 | 
            -
                end
         | 
| 215 | 
            -
             | 
| 216 | 
            -
                def env(*keys)
         | 
| 217 | 
            -
                  keys = keys.map(&:to_s)
         | 
| 218 | 
            -
                  if keys.empty?
         | 
| 219 | 
            -
                    @env
         | 
| 220 | 
            -
                  else
         | 
| 221 | 
            -
                    @env.dig(*keys)
         | 
| 222 | 
            -
                  end
         | 
| 223 | 
            -
                end
         | 
| 224 | 
            -
             | 
| 225 | 
            -
                # currently, import may only be used to import a package that is referenced in the script's package file
         | 
| 226 | 
            -
                # I may decide to extend this to work with dynamic package references
         | 
| 227 | 
            -
                #
         | 
| 228 | 
            -
                # local_package_name is the local package name defined for the package dependency that is attempting to be referenced
         | 
| 229 | 
            -
                def import(local_package_name)
         | 
| 230 | 
            -
                  local_package_name = local_package_name.to_s
         | 
| 231 | 
            -
                  package_reference = @ops_file_script.ops_file.package_file&.dependency(local_package_name)
         | 
| 232 | 
            -
                  raise Error, "Unknown package reference: #{local_package_name}" unless package_reference
         | 
| 233 | 
            -
                  import_reference = PackageDependencyReference.new(local_package_name, package_reference)
         | 
| 234 | 
            -
                  # puts "import: #{import_reference.inspect}"
         | 
| 235 | 
            -
                  @runtime_env.resolve_import_reference(@ops_file_script.ops_file, import_reference)
         | 
| 236 | 
            -
                end
         | 
| 237 | 
            -
             | 
| 238 | 
            -
                def params(*keys, default: nil)
         | 
| 239 | 
            -
                  keys = keys.map(&:to_s)
         | 
| 240 | 
            -
                  if keys.empty?
         | 
| 241 | 
            -
                    @params
         | 
| 242 | 
            -
                  else
         | 
| 243 | 
            -
                    @params.dig(*keys) || default
         | 
| 244 | 
            -
                  end
         | 
| 245 | 
            -
                end
         | 
| 246 | 
            -
             | 
| 247 | 
            -
                # returns the stdout from the command
         | 
| 248 | 
            -
                def sh(desc_or_cmd = nil, cmd = nil, input: nil, &block)
         | 
| 249 | 
            -
                  out, err, status = *shell!(desc_or_cmd, cmd, block, input: input)
         | 
| 250 | 
            -
                  out
         | 
| 251 | 
            -
                end
         | 
| 252 | 
            -
             | 
| 253 | 
            -
                # returns the tuple: [stdout, stderr, exit_status]
         | 
| 254 | 
            -
                def shell(desc_or_cmd = nil, cmd = nil, input: nil, &block)
         | 
| 255 | 
            -
                  shell!(desc_or_cmd, cmd, block, input: input)
         | 
| 256 | 
            -
                end
         | 
| 257 | 
            -
             | 
| 258 | 
            -
                # returns the tuple: [stdout, stderr, exit_status]
         | 
| 259 | 
            -
                def shell!(desc_or_cmd = nil, cmd = nil, block = nil, input: nil)
         | 
| 260 | 
            -
                  # description = nil
         | 
| 261 | 
            -
             | 
| 262 | 
            -
                  return ["", "", 0] if !desc_or_cmd && !cmd && !block    # we were told to do nothing; like hitting enter at the bash prompt; we can do nothing successfully
         | 
| 263 | 
            -
             | 
| 264 | 
            -
                  description = desc_or_cmd if cmd || block
         | 
| 265 | 
            -
                  cmd = block.call if block
         | 
| 266 | 
            -
                  cmd ||= desc_or_cmd
         | 
| 267 | 
            -
             | 
| 268 | 
            -
                  cmd = WalrusLang.render(cmd, block.binding) if block && cmd =~ /{{.*}}/
         | 
| 269 | 
            -
             | 
| 270 | 
            -
                  #cmd = Shellwords.escape(cmd)
         | 
| 271 | 
            -
             | 
| 272 | 
            -
                  # puts "shell! self: #{self.inspect}"
         | 
| 273 | 
            -
             | 
| 274 | 
            -
                  print "[#{@runtime_env.local_hostname}] "
         | 
| 275 | 
            -
                  print "#{description}: " if description
         | 
| 276 | 
            -
                  puts cmd
         | 
| 277 | 
            -
             | 
| 278 | 
            -
                  return unless cmd && !cmd.strip.empty?
         | 
| 279 | 
            -
             | 
| 280 | 
            -
                  # sudo_password = @runtime_env.sudo_password
         | 
| 281 | 
            -
                  # sudo_password &&= sudo_password.gsub(/\n+$/,'')     # remove trailing newlines from sudo_password
         | 
| 282 | 
            -
             | 
| 283 | 
            -
                  # puts "shell: #{cmd}"
         | 
| 284 | 
            -
                  # puts "shell: #{cmd.inspect}"
         | 
| 285 | 
            -
                  # puts "sudo_password: #{sudo_password}"
         | 
| 286 | 
            -
             | 
| 287 | 
            -
                  # sshkit_cmd = SSHKit::Backend::LocalNonBlocking.new {
         | 
| 288 | 
            -
                  # sshkit_cmd = SSHKit::Backend::LocalPty.new {
         | 
| 289 | 
            -
                  # sshkit_cmd = backend.execute_cmd(cmd, interaction_handler: SudoPasswordMapper.new(sudo_password).interaction_handler, verbosity: :info)
         | 
| 290 | 
            -
                    # execute_cmd(cmd, interaction_handler: SudoPromptInteractionHandler.new, verbosity: :info)
         | 
| 291 | 
            -
                  # }.run
         | 
| 292 | 
            -
             | 
| 293 | 
            -
                  sshkit_cmd = @runtime_env.handle_input(input) do |interaction_handler|
         | 
| 294 | 
            -
                    backend.execute_cmd(cmd, interaction_handler: interaction_handler, verbosity: :info)
         | 
| 295 | 
            -
                  end
         | 
| 296 | 
            -
             | 
| 297 | 
            -
                  [sshkit_cmd.full_stdout, sshkit_cmd.full_stderr, sshkit_cmd.exit_status]
         | 
| 298 | 
            -
                end
         | 
| 299 | 
            -
             | 
| 300 | 
            -
                # def init_brew
         | 
| 301 | 
            -
                #   execute('eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"')
         | 
| 302 | 
            -
                # end
         | 
| 303 | 
            -
             | 
| 304 | 
            -
              end
         | 
| 305 5 |  | 
| 306 6 | 
             
              class OpsFileScript
         | 
| 307 | 
            -
                attr_accessor :ops_file
         | 
| 308 7 |  | 
| 309 | 
            -
                def  | 
| 310 | 
            -
                   | 
| 311 | 
            -
                  @ruby_script = ruby_script
         | 
| 312 | 
            -
                  @invocation_class = define_invocation_class
         | 
| 313 | 
            -
                  @host_proxy_class = define_host_proxy_class
         | 
| 314 | 
            -
                end
         | 
| 8 | 
            +
                def self.define_for(ops_file, ruby_script)
         | 
| 9 | 
            +
                  klass = Class.new(OpsFileScript)
         | 
| 315 10 |  | 
| 316 | 
            -
             | 
| 317 | 
            -
                  klass = Class.new(Invocation)
         | 
| 11 | 
            +
                  # puts "OpsFileScript.define_for(#{ops_file.to_s}, #{ruby_script.to_s})"
         | 
| 318 12 |  | 
| 319 13 | 
             
                  methods_defined = Set.new
         | 
| 320 14 |  | 
| 321 | 
            -
                  # define methods for  | 
| 15 | 
            +
                  # define methods for the OpsFile's local_symbol_table: local imports and private lib directory
         | 
| 322 16 | 
             
                  ops_file.local_symbol_table.each do |symbol_name, import_reference|
         | 
| 323 17 | 
             
                    unless methods_defined.include? symbol_name
         | 
| 18 | 
            +
                      # puts "defining method for local symbol table entry: #{symbol_name}"
         | 
| 324 19 | 
             
                      klass.define_method(symbol_name) do |*args, **kwargs, &block|
         | 
| 325 | 
            -
                        # puts " | 
| 326 | 
            -
                         | 
| 327 | 
            -
                        # puts  | 
| 328 | 
            -
             | 
| 329 | 
            -
                        namespace_or_ops_file = @runtime_env.resolve_import_reference(@ops_file_script.ops_file, import_reference)
         | 
| 330 | 
            -
                        # puts namespace_or_ops_file.inspect
         | 
| 331 | 
            -
                        # puts "0" * 80
         | 
| 20 | 
            +
                        # puts "resolving local symbol table entry: #{symbol_name}"
         | 
| 21 | 
            +
                        namespace_or_ops_file = @runtime_env.resolve_import_reference(ops_file, import_reference)
         | 
| 22 | 
            +
                        # puts "namespace_or_ops_file=#{namespace_or_ops_file.to_s}"
         | 
| 23 | 
            +
             | 
| 332 24 | 
             
                        case namespace_or_ops_file
         | 
| 333 25 | 
             
                        when Namespace
         | 
| 334 26 | 
             
                          namespace_or_ops_file
         | 
| 27 | 
            +
                          namespace_or_ops_file._invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs)
         | 
| 335 28 | 
             
                        when OpsFile
         | 
| 336 29 | 
             
                          params_hash = namespace_or_ops_file.build_params_hash(*args, **kwargs)
         | 
| 337 30 | 
             
                          namespace_or_ops_file.invoke(@runtime_env, params_hash)
         | 
| @@ -342,22 +35,23 @@ module OpsWalrus | |
| 342 35 | 
             
                  end
         | 
| 343 36 |  | 
| 344 37 | 
             
                  # define methods for every Namespace or OpsFile within the namespace that the OpsFile resides within
         | 
| 345 | 
            -
                   | 
| 346 | 
            -
                   | 
| 347 | 
            -
                   | 
| 348 | 
            -
                   | 
| 38 | 
            +
                  sibling_symbol_table_names = Set.new
         | 
| 39 | 
            +
                  sibling_symbol_table_names |= ops_file.dirname.glob("*.ops").map {|ops_file_path| ops_file_path.basename(".ops").to_s }   # OpsFiles
         | 
| 40 | 
            +
                  sibling_symbol_table_names |= ops_file.dirname.glob("*").select(&:directory?).map {|dir_path| dir_path.basename.to_s }    # Namespaces
         | 
| 41 | 
            +
                  # puts "sibling_symbol_table_names=#{sibling_symbol_table_names}"
         | 
| 42 | 
            +
                  # puts "methods_defined=#{methods_defined}"
         | 
| 43 | 
            +
                  sibling_symbol_table_names.each do |symbol_name|
         | 
| 349 44 | 
             
                    unless methods_defined.include? symbol_name
         | 
| 45 | 
            +
                      # puts "defining method for implicit imports: #{symbol_name}"
         | 
| 350 46 | 
             
                      klass.define_method(symbol_name) do |*args, **kwargs, &block|
         | 
| 351 | 
            -
                        # puts " | 
| 352 | 
            -
                         | 
| 353 | 
            -
                        # puts  | 
| 354 | 
            -
             | 
| 355 | 
            -
                        namespace_or_ops_file = @runtime_env.resolve_sibling_symbol(@ops_file_script.ops_file, symbol_name)
         | 
| 356 | 
            -
                        # puts namespace_or_ops_file.inspect
         | 
| 357 | 
            -
                        # puts "0" * 80
         | 
| 47 | 
            +
                        # puts "resolving implicit import: #{symbol_name}"
         | 
| 48 | 
            +
                        namespace_or_ops_file = @runtime_env.resolve_sibling_symbol(ops_file, symbol_name)
         | 
| 49 | 
            +
                        # puts "namespace_or_ops_file=#{namespace_or_ops_file.to_s}"
         | 
| 50 | 
            +
             | 
| 358 51 | 
             
                        case namespace_or_ops_file
         | 
| 359 52 | 
             
                        when Namespace
         | 
| 360 53 | 
             
                          namespace_or_ops_file
         | 
| 54 | 
            +
                          namespace_or_ops_file._invoke_if_namespace_has_ops_file_of_same_name(*args, **kwargs)
         | 
| 361 55 | 
             
                        when OpsFile
         | 
| 362 56 | 
             
                          params_hash = namespace_or_ops_file.build_params_hash(*args, **kwargs)
         | 
| 363 57 | 
             
                          namespace_or_ops_file.invoke(@runtime_env, params_hash)
         | 
| @@ -367,116 +61,38 @@ module OpsWalrus | |
| 367 61 | 
             
                    end
         | 
| 368 62 | 
             
                  end
         | 
| 369 63 |  | 
| 370 | 
            -
                   | 
| 371 | 
            -
             | 
| 372 | 
            -
             | 
| 373 | 
            -
             | 
| 374 | 
            -
                   | 
| 375 | 
            -
             | 
| 376 | 
            -
                   | 
| 377 | 
            -
             | 
| 378 | 
            -
                  #  | 
| 379 | 
            -
                   | 
| 380 | 
            -
                     | 
| 381 | 
            -
                       | 
| 382 | 
            -
                       | 
| 383 | 
            -
             | 
| 384 | 
            -
                        # we know we're dealing with a package dependency reference, so we want to run an ops file contained within the bundle directory,
         | 
| 385 | 
            -
                        # therefore, we want to reference the specified ops file with respect to the bundle dir
         | 
| 386 | 
            -
                        when PackageDependencyReference
         | 
| 387 | 
            -
                          HostProxyOpsFileInvocationBuilder.new(self, true)
         | 
| 388 | 
            -
             | 
| 389 | 
            -
                        # we know we're dealing with a directory reference or OpsFile reference outside of the bundle dir, so we want to reference
         | 
| 390 | 
            -
                        # the specified ops file with respect to the root directory, and not with respect to the bundle dir
         | 
| 391 | 
            -
                        when DirectoryReference, OpsFileReference
         | 
| 392 | 
            -
                          HostProxyOpsFileInvocationBuilder.new(self, false)
         | 
| 393 | 
            -
                        end
         | 
| 394 | 
            -
             | 
| 395 | 
            -
                        invocation_builder.send(symbol_name, *args, **kwargs, &block)
         | 
| 396 | 
            -
                      end
         | 
| 397 | 
            -
                      methods_defined << symbol_name
         | 
| 64 | 
            +
                  # the evaluation context needs to be a module with all of the following:
         | 
| 65 | 
            +
                  # - OpsFileScriptDSL methods
         | 
| 66 | 
            +
                  # - @runtime_env
         | 
| 67 | 
            +
                  # - @params
         | 
| 68 | 
            +
                  # - #host_proxy_class
         | 
| 69 | 
            +
                  # - #backend
         | 
| 70 | 
            +
                  # - #debug?
         | 
| 71 | 
            +
                  # - #verbose?
         | 
| 72 | 
            +
                  # - all the dynamically defined methods in the subclass of Invocation
         | 
| 73 | 
            +
                  invoke_method_definition = <<~INVOKE_METHOD
         | 
| 74 | 
            +
                    def _invoke(runtime_env, params_hash)
         | 
| 75 | 
            +
                      @runtime_env = runtime_env
         | 
| 76 | 
            +
                      @params = InvocationParams.new(params_hash)
         | 
| 77 | 
            +
                      #{ruby_script}
         | 
| 398 78 | 
             
                    end
         | 
| 399 | 
            -
                   | 
| 79 | 
            +
                  INVOKE_METHOD
         | 
| 400 80 |  | 
| 401 | 
            -
                   | 
| 402 | 
            -
                   | 
| 403 | 
            -
                  sibling_symbol_table |= ops_file.dirname.glob("*.ops").map {|ops_file_path| ops_file_path.basename(".ops").to_s }   # OpsFiles
         | 
| 404 | 
            -
                  sibling_symbol_table |= ops_file.dirname.glob("*").select(&:directory?).map {|dir_path| dir_path.basename.to_s }    # Namespaces
         | 
| 405 | 
            -
                  sibling_symbol_table.each do |symbol_name|
         | 
| 406 | 
            -
                    unless methods_defined.include? symbol_name
         | 
| 407 | 
            -
                      # puts "2. defining: #{symbol_name}(...)"
         | 
| 408 | 
            -
                      klass.define_method(symbol_name) do |*args, **kwargs, &block|
         | 
| 409 | 
            -
                        invocation_builder = HostProxyOpsFileInvocationBuilder.new(self, false)
         | 
| 410 | 
            -
                        invocation_builder.invoke(symbol_name, *args, **kwargs, &block)
         | 
| 411 | 
            -
                      end
         | 
| 412 | 
            -
                      methods_defined << symbol_name
         | 
| 413 | 
            -
                    end
         | 
| 414 | 
            -
                  end
         | 
| 81 | 
            +
                  invoke_method_line_count_prior_to_ruby_script_from_ops_file = 3
         | 
| 82 | 
            +
                  klass.module_eval(invoke_method_definition, ops_file.ops_file_path.to_s, ops_file.script_line_offset - invoke_method_line_count_prior_to_ruby_script_from_ops_file)
         | 
| 415 83 |  | 
| 416 84 | 
             
                  klass
         | 
| 417 85 | 
             
                end
         | 
| 418 86 |  | 
| 419 | 
            -
                def host_proxy_class
         | 
| 420 | 
            -
                  @host_proxy_class
         | 
| 421 | 
            -
                end
         | 
| 422 | 
            -
             | 
| 423 | 
            -
                def script
         | 
| 424 | 
            -
                  @ruby_script
         | 
| 425 | 
            -
                end
         | 
| 426 | 
            -
             | 
| 427 | 
            -
                def invoke(runtime_env, params_hash)
         | 
| 428 | 
            -
                  # Invocation.new(self, runtime_env, params_hash).evaluate
         | 
| 429 | 
            -
                  # puts "INVOKE" * 10
         | 
| 430 | 
            -
                  # puts runtime_env.inspect
         | 
| 431 | 
            -
                  @invocation_class.new(self, runtime_env, params_hash).evaluate
         | 
| 432 | 
            -
                end
         | 
| 433 | 
            -
             | 
| 434 | 
            -
                def to_s
         | 
| 435 | 
            -
                  @ruby_script
         | 
| 436 | 
            -
                end
         | 
| 437 | 
            -
              end
         | 
| 438 | 
            -
             | 
| 439 | 
            -
              # An Invocation object represents a stack frame, and the params_hash represents the
         | 
| 440 | 
            -
              # arguments that the caller has supplied for that stack frame to reference
         | 
| 441 | 
            -
              class Invocation
         | 
| 442 | 
            -
                class Result
         | 
| 443 | 
            -
                  attr_accessor :value
         | 
| 444 | 
            -
                  attr_accessor :exit_status
         | 
| 445 | 
            -
                  def initialize(value, exit_status = 0)
         | 
| 446 | 
            -
                    @value = value
         | 
| 447 | 
            -
                    @exit_status = exit_status
         | 
| 448 | 
            -
                  end
         | 
| 449 | 
            -
                  def success?
         | 
| 450 | 
            -
                    !failure?
         | 
| 451 | 
            -
                  end
         | 
| 452 | 
            -
                  def failure?
         | 
| 453 | 
            -
                    !success?
         | 
| 454 | 
            -
                  end
         | 
| 455 | 
            -
                end
         | 
| 456 | 
            -
                class Success < Result
         | 
| 457 | 
            -
                  def initialize(value)
         | 
| 458 | 
            -
                    super(value, 0)
         | 
| 459 | 
            -
                  end
         | 
| 460 | 
            -
                  def success?
         | 
| 461 | 
            -
                    true
         | 
| 462 | 
            -
                  end
         | 
| 463 | 
            -
                end
         | 
| 464 | 
            -
                class Error < Result
         | 
| 465 | 
            -
                  def initialize(value, exit_status = 1)
         | 
| 466 | 
            -
                    super(value, exit_status == 0 ? 1 : exit_status)
         | 
| 467 | 
            -
                  end
         | 
| 468 | 
            -
                  def failure?
         | 
| 469 | 
            -
                    true
         | 
| 470 | 
            -
                  end
         | 
| 471 | 
            -
                end
         | 
| 472 87 |  | 
| 88 | 
            +
                include OpsFileScriptDSL
         | 
| 473 89 |  | 
| 474 | 
            -
                 | 
| 90 | 
            +
                attr_accessor :ops_file
         | 
| 475 91 |  | 
| 476 | 
            -
                def initialize( | 
| 477 | 
            -
                  @ | 
| 478 | 
            -
                  @ | 
| 479 | 
            -
                  @ | 
| 92 | 
            +
                def initialize(ops_file, ruby_script)
         | 
| 93 | 
            +
                  @ops_file = ops_file
         | 
| 94 | 
            +
                  @script = ruby_script
         | 
| 95 | 
            +
                  @runtime_env = nil    # this is set at the very first line of #_invoke
         | 
| 480 96 | 
             
                end
         | 
| 481 97 |  | 
| 482 98 | 
             
                def backend
         | 
| @@ -491,52 +107,18 @@ module OpsWalrus | |
| 491 107 | 
             
                  @runtime_env.verbose?
         | 
| 492 108 | 
             
                end
         | 
| 493 109 |  | 
| 494 | 
            -
                def  | 
| 495 | 
            -
                   | 
| 496 | 
            -
                    eval(@ops_file_script.script, nil, @ops_file_script.ops_file.ops_file_path.to_s, @ops_file_script.ops_file.script_line_offset)
         | 
| 497 | 
            -
                  # end
         | 
| 110 | 
            +
                def host_proxy_class
         | 
| 111 | 
            +
                  @ops_file.host_proxy_class
         | 
| 498 112 | 
             
                end
         | 
| 499 113 |  | 
| 500 | 
            -
                #  | 
| 501 | 
            -
                 | 
| 502 | 
            -
             | 
| 503 | 
            -
                 | 
| 504 | 
            -
                #     end
         | 
| 505 | 
            -
                #   rescue => e
         | 
| 506 | 
            -
                #     $stderr.puts "Error: Ops script crashed."
         | 
| 507 | 
            -
                #     $stderr.puts e.message
         | 
| 508 | 
            -
                #     $stderr.puts e.backtrace.join("\n")
         | 
| 509 | 
            -
                #     Error.new(e)
         | 
| 510 | 
            -
                #   end
         | 
| 511 | 
            -
             | 
| 512 | 
            -
                #   if ruby_script_return.is_a? Result
         | 
| 513 | 
            -
                #     ruby_script_return
         | 
| 514 | 
            -
                #   else
         | 
| 515 | 
            -
                #     Success.new(ruby_script_return)
         | 
| 516 | 
            -
                #   end
         | 
| 517 | 
            -
                # end
         | 
| 518 | 
            -
             | 
| 519 | 
            -
                # def method_missing(name, *args, **kwargs, &block)
         | 
| 520 | 
            -
                #   puts "1" * 80
         | 
| 521 | 
            -
                #   import_reference = @ops_file_script.ops_file.resolve_import(name)
         | 
| 522 | 
            -
                #   if import_reference
         | 
| 523 | 
            -
                #     puts "2" * 80
         | 
| 524 | 
            -
                #     resolved_value = @runtime_env.resolve_import_reference(@ops_file_script.ops_file, import_reference)
         | 
| 525 | 
            -
                #     return resolved_value if resolved_value
         | 
| 526 | 
            -
                #   end
         | 
| 114 | 
            +
                # The _invoke method is dynamically defined as part of OpsFileScript.define_for
         | 
| 115 | 
            +
                def _invoke(runtime_env, params_hash)
         | 
| 116 | 
            +
                  raise "Not implemented in base class."
         | 
| 117 | 
            +
                end
         | 
| 527 118 |  | 
| 528 | 
            -
                 | 
| 529 | 
            -
             | 
| 530 | 
            -
                 | 
| 531 | 
            -
                #     puts "4" * 80
         | 
| 532 | 
            -
                #     namespace_or_ops_file
         | 
| 533 | 
            -
                #   when OpsFile
         | 
| 534 | 
            -
                #     puts "5" * 80
         | 
| 535 | 
            -
                #     namespace_or_ops_file.invoke(@runtime_env, *args, **kwargs, &block)
         | 
| 536 | 
            -
                #   else
         | 
| 537 | 
            -
                #     raise NoMethodError, "No method named '#{name}'"
         | 
| 538 | 
            -
                #   end
         | 
| 539 | 
            -
                # end
         | 
| 119 | 
            +
                def to_s
         | 
| 120 | 
            +
                  @script
         | 
| 121 | 
            +
                end
         | 
| 540 122 | 
             
              end
         | 
| 541 123 |  | 
| 542 124 | 
             
            end
         |