From 346c9b7ea846acd604dbe929fab71e2ae0a5cf71 Mon Sep 17 00:00:00 2001 From: "Dr. Strangelove" Date: Mon, 1 May 2023 13:58:18 -0400 Subject: [PATCH] feat: Connection handling with Gazer (#184) * feat: connection cat - get a json of a connection * feat: added connection import|rm|test commands * fix: needed chomp to remove new line for --prompt mode with connection import --- lib/gzr/commands/connection.rb | 68 ++++++++++++++++++++++- lib/gzr/commands/connection/cat.rb | 66 ++++++++++++++++++++++ lib/gzr/commands/connection/import.rb | 78 ++++++++++++++++++++++++++ lib/gzr/commands/connection/rm.rb | 50 +++++++++++++++++ lib/gzr/commands/connection/test.rb | 69 +++++++++++++++++++++++ lib/gzr/modules/connection.rb | 80 +++++++++++++++++++++++++++ 6 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 lib/gzr/commands/connection/cat.rb create mode 100644 lib/gzr/commands/connection/import.rb create mode 100644 lib/gzr/commands/connection/rm.rb create mode 100644 lib/gzr/commands/connection/test.rb diff --git a/lib/gzr/commands/connection.rb b/lib/gzr/commands/connection.rb index d9c3d71..aec3cf9 100644 --- a/lib/gzr/commands/connection.rb +++ b/lib/gzr/commands/connection.rb @@ -1,6 +1,6 @@ # The MIT License (MIT) -# Copyright (c) 2018 Mike DeAngelo Looker Data Sciences, Inc. +# Copyright (c) 2023 Mike DeAngelo Google, Inc. # Permission is hereby granted, free of charge, to any person obtaining a copy of # this software and associated documentation files (the "Software"), to deal in @@ -64,6 +64,72 @@ def ls(*) Gzr::Commands::Connection::Ls.new(options).execute end end + + desc 'cat CONNECTION_NAME', 'Output the JSON representation of a connection to the screen or a file' + method_option :help, aliases: '-h', type: :boolean, + desc: 'Display usage information' + method_option :dir, type: :string, + desc: 'Directory to store output file' + method_option :simple_filename, type: :boolean, + desc: 'Use simple filename for output (Connection_.json)' + method_option :trim, type: :boolean, + desc: 'Trim output to minimal set of fields for later import' + def cat(connection_name) + if options[:help] + invoke :help, ['cat'] + else + require_relative 'connection/cat' + Gzr::Commands::Connection::Cat.new(connection_name, options).execute + end + end + + desc 'rm CONNECTION_NAME', 'Delete a connection' + method_option :help, aliases: '-h', type: :boolean, + desc: 'Display usage information' + def rm(connection_name) + if options[:help] + invoke :help, ['rm'] + else + require_relative 'connection/rm' + Gzr::Commands::Connection::Rm.new(connection_name, options).execute + end + end + + desc 'import FILE', 'Import a connection from a file' + method_option :help, aliases: '-h', type: :boolean, + desc: 'Display usage information' + method_option :force, type: :boolean, + desc: 'Overwrite an existing connection' + method_option :prompt, type: :boolean, + desc: 'Prompt for the password' + method_option :plain, type: :boolean, default: false, + desc: 'print without any extra formatting' + def import(file) + if options[:help] + invoke :help, ['import'] + else + require_relative 'connection/import' + Gzr::Commands::Connection::Import.new(file, options).execute + end + end + + desc 'test CONNECTION_NAME', 'Test the given connection' + method_option :help, aliases: '-h', type: :boolean, + desc: 'Display usage information' + method_option :fields, type: :string, default: 'name,status,message', + desc: 'Fields to display' + method_option :plain, type: :boolean, default: false, + desc: 'print without any extra formatting' + method_option :csv, type: :boolean, default: false, + desc: 'output in csv format per RFC4180' + def test(connection_name) + if options[:help] + invoke :help, ['test'] + else + require_relative 'connection/test' + Gzr::Commands::Connection::Test.new(connection_name,options).execute + end + end end end end diff --git a/lib/gzr/commands/connection/cat.rb b/lib/gzr/commands/connection/cat.rb new file mode 100644 index 0000000..10d9df5 --- /dev/null +++ b/lib/gzr/commands/connection/cat.rb @@ -0,0 +1,66 @@ +# The MIT License (MIT) + +# Copyright (c) 2023 Mike DeAngelo Google, Inc. + +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# frozen_string_literal: true + +require 'json' +require_relative '../../command' +require_relative '../../modules/connection' +require_relative '../../modules/filehelper' + +module Gzr + module Commands + class Connection + class Cat < Gzr::Command + include Gzr::Connection + include Gzr::FileHelper + def initialize(connection_id,options) + super() + @connection_id = connection_id + @options = options + end + + def execute(*args, input: $stdin, output: $stdout) + say_warning("options: #{@options.inspect}") if @options[:debug] + with_session do + data = cat_connection(@connection_id) + if data.nil? + say_warning "Connection #{@connection_id} not found" + return + end + data = trim_connection(data) if @options[:trim] + + outputJSON = JSON.pretty_generate(data) + + file_name = if @options[:dir] + @options[:simple_filename] ? "Connection_#{data[:name]}.json" : "Connection_#{data[:name]}_#{data[:dialect_name]}.json" + else + nil + end + write_file(file_name, @options[:dir], nil, output) do |f| + f.puts outputJSON + end + end + end + end + end + end +end diff --git a/lib/gzr/commands/connection/import.rb b/lib/gzr/commands/connection/import.rb new file mode 100644 index 0000000..c23dd33 --- /dev/null +++ b/lib/gzr/commands/connection/import.rb @@ -0,0 +1,78 @@ +# The MIT License (MIT) + +# Copyright (c) 2023 Mike DeAngelo Google, Inc. + +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# frozen_string_literal: true + +require_relative '../../../gzr' +require_relative '../../command' +require_relative '../../modules/connection' +require_relative '../../modules/filehelper' + +module Gzr + module Commands + class Connection + class Import < Gzr::Command + include Gzr::Connection + include Gzr::FileHelper + def initialize(file, options) + super() + @file = file + @options = options + end + + def execute(input: $stdin, output: $stdout) + say_warning("options: #{@options.inspect}", output: output) if @options[:debug] + with_session do + connection = nil + + if @options[:prompt] + reader = TTY::Reader.new + @secret = reader.read_line("Enter your connection password:", echo: false) + @secret.chomp! + end + + read_file(@file) do |data| + if !!cat_connection(data[:name]) + name = data[:name] + if !@options[:force] + raise Gzr::CLI::Error, "Connection #{name} already exists\nUse --force if you want to overwrite it" + end + data.select! do |k,v| + keys_to_keep('update_connection').include? k + end + data[:password] = @secret if @secret + connection = update_connection(name, data) + else + data.select! do |k,v| + keys_to_keep('create_connection').include? k + end + data[:password] = @secret if @secret + connection = create_connection(data) + end + output.puts "Imported connection #{connection[:name]}" unless @options[:plain] + output.puts connection[:id] if @options[:name] + end + end + end + end + end + end +end diff --git a/lib/gzr/commands/connection/rm.rb b/lib/gzr/commands/connection/rm.rb new file mode 100644 index 0000000..b40f469 --- /dev/null +++ b/lib/gzr/commands/connection/rm.rb @@ -0,0 +1,50 @@ +# The MIT License (MIT) + +# Copyright (c) 2023 Mike DeAngelo Google, Inc. + +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# frozen_string_literal: true + +require 'json' +require_relative '../../command' +require_relative '../../modules/connection' +require_relative '../../modules/filehelper' + +module Gzr + module Commands + class Connection + class Rm < Gzr::Command + include Gzr::Connection + include Gzr::FileHelper + def initialize(connection_name,options) + super() + @connection_name = connection_name + @options = options + end + + def execute(*args, input: $stdin, output: $stdout) + say_warning("options: #{@options.inspect}") if @options[:debug] + with_session do + delete_connection(@connection_name) + end + end + end + end + end +end diff --git a/lib/gzr/commands/connection/test.rb b/lib/gzr/commands/connection/test.rb new file mode 100644 index 0000000..d6e0840 --- /dev/null +++ b/lib/gzr/commands/connection/test.rb @@ -0,0 +1,69 @@ +# The MIT License (MIT) + +# Copyright (c) 2018 Mike DeAngelo Looker Data Sciences, Inc. + +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# frozen_string_literal: true + +require_relative '../../command' +require_relative '../../modules/connection' +require 'tty-table' + +module Gzr + module Commands + class Connection + class Test < Gzr::Command + include Gzr::Connection + def initialize(connection_name,options) + super() + @connection_name = connection_name + @options = options + end + + def execute(input: $stdin, output: $stdout) + say_warning(@options) if @options[:debug] + with_session do + data = test_connection(@connection_name) + + table_hash = Hash.new + fields = field_names(@options[:fields]) + table_hash[:header] = fields unless @options[:plain] + expressions = fields.collect { |fn| field_expression(fn) } + table_hash[:rows] = data.map do |row| + expressions.collect do |e| + eval "row.#{e}" + end + end + table = TTY::Table.new(table_hash) + alignments = fields.collect do |k| + (k =~ /id$/) ? :right : :left + end + begin + if @options[:csv] then + output.puts render_csv(table) + else + output.puts table.render(if @options[:plain] then :basic else :ascii end, alignments: alignments, multiline: if @options[:plain] then false else true end, width: @options[:width] || TTY::Screen.width) + end + end if table + end + end + end + end + end +end diff --git a/lib/gzr/modules/connection.rb b/lib/gzr/modules/connection.rb index 4c15485..f912fc5 100644 --- a/lib/gzr/modules/connection.rb +++ b/lib/gzr/modules/connection.rb @@ -48,5 +48,85 @@ def query_all_dialects(fields=nil) data end + def test_connection(name, tests=nil) + data = nil + + if tests.nil? + connection = cat_connection(name) + if connection.nil? + return nil + end + tests = connection[:dialect][:connection_tests] + end + + begin + data = @sdk.test_connection(name, {}, tests: tests) + rescue LookerSDK::NotFound => e + return nil + rescue LookerSDK::Error => e + say_error "Error executing test_connection(#{name},#{tests})" + say_error e + raise + end + data + end + + def delete_connection(name) + begin + @sdk.delete_connection(name) + rescue LookerSDK::NotFound => e + say_warning "Connection #{name} not found" + rescue LookerSDK::Error => e + say_error "Error executing delete_connection(#{name})" + say_error e + raise + end + end + + def create_connection(body) + data = nil + begin + data = @sdk.create_connection(body).to_attrs + rescue LookerSDK::Error => e + say_error "Error executing create_connection(#{JSON.pretty_generate(body)})" + say_error e + raise + end + data + end + + def update_connection(name,body) + data = nil + begin + data = @sdk.connection(name,body).to_attrs + rescue LookerSDK::NotFound => e + say_warning "Connection #{name} not found" + rescue LookerSDK::Error => e + say_error "Error executing update_connection(#{name},#{JSON.pretty_generate(body)})" + say_error e + raise + end + data + end + + def cat_connection(name) + data = nil + begin + data = @sdk.connection(name).to_attrs + rescue LookerSDK::NotFound => e + return nil + rescue LookerSDK::Error => e + say_error "Error executing connection(#{name})" + say_error e + raise + end + data + end + + def trim_connection(data) + data.select do |k,v| + (keys_to_keep('create_connection') + [:pdt_context_override]).include? k + end + end end end