Skip to content
This repository has been archived by the owner on Dec 8, 2020. It is now read-only.

Commit

Permalink
Add installer and remove the need for any configuration (#61)
Browse files Browse the repository at this point in the history
* Add installer and remove the need for any configuration

* Fix readme

* Cleanup readme

* Add docs

* Finish installer

* Update

* Define config

* Fix tests

* Update config template

* Update readme

* Fix readme

* Fix tests

* Check for tagged logger

* Update schema version
  • Loading branch information
binarylogic committed Mar 17, 2017
1 parent e902787 commit 6c03473
Show file tree
Hide file tree
Showing 21 changed files with 834 additions and 367 deletions.
380 changes: 61 additions & 319 deletions README.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions bin/timber
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env ruby

$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
require "timber/cli"

begin
Timber::CLI.run
rescue => e
raise e if $DEBUG
STDERR.puts e.message
STDERR.puts e.backtrace.join("\n")
exit 1
end
1 change: 1 addition & 0 deletions lib/timber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require "json" # brings to_json to the core classes

require "timber/overrides/logger_add"
require "timber/overrides/lograge"
require "timber/overrides/rails_stdout_logging"

# Base (must come first, order matters)
Expand Down
5 changes: 5 additions & 0 deletions lib/timber/auth_check.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Timber
class AuthCheck

end
end
71 changes: 71 additions & 0 deletions lib/timber/cli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
require "optparse"
require "yaml"
require "timber"


require "timber/cli/api"
require "timber/cli/application"
require "timber/cli/io_helper"
require "timber/cli/messages"

require "timber/cli/install"

module Timber
class CLI
AVAILABLE_COMMANDS = %w(install).freeze

class << self
attr_accessor :options

def run(argv = ARGV)
@options = {}
global = global_option_parser
commands = command_option_parser
global.order!(argv)
command = argv.shift
if command
if AVAILABLE_COMMANDS.include?(command)
commands[command].parse!(argv)
case command.to_sym
when :install
Timber::CLI::Install.run(argv.shift)
end
else
puts "Command '#{command}' does not exist, run timber -h to "\
"see the help"
exit(1)
end
else
# Print help
puts global
exit(0)
end
end

def global_option_parser
OptionParser.new do |o|
o.banner = "Usage: timber <command> [options]"

o.on "-v", "--version", "Print version and exit" do |_arg|
puts "Timber #{Timber::VERSION}"
exit(0)
end

o.on "-h", "--help", "Show help and exit" do
puts o
exit(0)
end

o.separator ""
o.separator "Available commands: #{AVAILABLE_COMMANDS.join(", ")}"
end
end

def command_option_parser
{
"install" => OptionParser.new
}
end
end
end
end
104 changes: 104 additions & 0 deletions lib/timber/cli/api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
require "base64"
require "json"
require "net/https"
require "securerandom"
require "uri"

module Timber
class CLI
class API
class APIKeyInvalidError < StandardError
def message
"Uh oh! The API key supplied is invalid. Please ensure that you copied the" \
" key properly.\n\n#{Messages.obtain_key_instructions}"
end
end

class NoAPIKeyError < StandardError
def message
"Uh oh! You didn't supply an API key.\n\n#{Messages.obtain_key_instructions}"
end
end

TIMBER_API_URI = URI.parse('https://api.timber.io')
APPLICATION_PATH = "/installer/application".freeze
EVENT_PATH = "/installer/events".freeze
HAS_LOGS_PATH = "/installer/has_logs".freeze
USER_AGENT = "Timber Ruby/#{Timber::VERSION} (HTTP)".freeze

def initialize(api_key)
@api_key = api_key
@session_id = SecureRandom.uuid
end

def application!
get!(APPLICATION_PATH)
end

def event!(name, data = {})
post!(EVENT_PATH, event: {name: name, data: data})
end

def wait_for_logs(iteration = 0, &block)
if block_given?
yield iteration
end

sleep 0.5

res = get!(HAS_LOGS_PATH)

case res.code
when "202"
wait_for_logs(iteration + 1, &block)

when "204"
true
end
end

private
def get!(path)
req = Net::HTTP::Get.new(path)
issue!(req)
end

def post!(path, body)
req = Net::HTTP::Post.new(path)
req.body = body.to_json
req['Content-Type'] = "application/json"
issue!(req)
end

def issue!(req)
req['Authorization'] = "Basic #{encoded_api_key}"
req['User-Agent'] = USER_AGENT
req['X-Installer-Session-Id'] = @session_id
res = http.start do |http|
http.request(req)
end

if res.code == "401"
raise NoAPIKeyError.new
elsif res.code == "403"
raise APIKeyInvalidError.new
else
res
end
end

def http
@http ||= begin
http = Net::HTTP.new(TIMBER_API_URI.host, TIMBER_API_URI.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http
end
end

def encoded_api_key
Base64.urlsafe_encode64(@api_key).chomp
end
end
end
end
28 changes: 28 additions & 0 deletions lib/timber/cli/application.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require "json"

module Timber
class CLI
class Application

attr_reader :api_key, :environment, :framework_type, :heroku_drain_url, :language_type,
:name, :platform_type

def initialize(api)
res = api.application!
parsed_body = JSON.parse(res.body)
application_data = parsed_body.fetch("data")
@api_key = application_data.fetch("api_key")
@environment = application_data.fetch("environment")
@framework_type = application_data.fetch("framework_type")
@heroku_drain_url = application_data.fetch("heroku_drain_url")
@language_type = application_data.fetch("language_type")
@name = application_data.fetch("name")
@platform_type = application_data.fetch("platform_type")
end

def heroku?
platform_type == "heroku"
end
end
end
end
150 changes: 150 additions & 0 deletions lib/timber/cli/install.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
require "fileutils"

module Timber
class CLI
class Install
EXCLUDED_ENVIRONMENTS = ["test"].freeze

class << self
include IOHelper

def run(api_key)
puts ""
puts colorize(Messages.header, :green)
puts colorize(Messages.separator, :green)
puts colorize(Messages.contact, :green)
puts colorize(Messages.separator, :green)
puts ""

if !api_key
puts colorize(Messages.no_api_key_provided, :red)
return
end

api = API.new(api_key)

api.event!(:started)

app = Application.new(api)

puts "Woot! Your API 🔑 is valid. Here are you application details:"
puts ""
puts "Name: #{app.name} (#{app.environment})"
puts "Framework: #{app.framework_type}"
puts "Platform: #{app.platform_type}"
puts ""

case ask_yes_no("Are the above details correct?")
when :yes
if app.heroku?
puts ""
puts Messages.separator
puts ""
puts Messages.heroku_install(app)
puts ""

else
puts ""
puts Messages.separator
puts ""
puts "How would you like configure Timber?"
puts ""
puts "1) Using environment variables"
puts "2) Configuring in my app"
puts ""

case ask("Enter your choice: (1/2) ")
when "1"
puts ""
puts Messages.http_environment_variables(app.api_key)
puts ""

when "2"
puts ""
write Messages.task_start("Creating config/initializers/timber.rb")

create_initializer("http", app)

puts colorize(Messages.task_complete("Creating config/initializers/timber.rb"), :green)
end

send_test_messages(api_key)
end

api.wait_for_logs do |iteration|
write Messages.task_start("Waiting for logs")
write Messages.spinner(iteration)
end

puts colorize(Messages.task_complete("Waiting for logs"), :green)

puts ""
puts Messages.separator
puts ""
puts Messages.finish

api.event!(:success)

collect_feedback(api)

when :no
puts ""
puts "Bummer. Head to this URL to update the details:"
puts ""
puts " #{Messages.edit_app_url(app)}"
puts ""
puts "exiting..."
return false
end
end

private
def create_initializer(log_device, app)
body = "config = Timber::Config.instance\n" \
"config.log_device = Timber::LogDevices::HTTP.new(\"#{app.api_key}\")\n\n" \
"# More config options can be found at: https://timber.io/docs/ruby/configuration/\n" \
"#\n" \
"# Question? Need help?\n" \
"# * Docs: https://timber.io/docs\n" \
"# * Support: support@timber.io" \

FileUtils.mkdir_p(File.join(Dir.pwd, "config", "initializers"))
File.write(File.join(Dir.pwd, "config/initializers/timber.rb"), body)
end

def send_test_messages(api_key)
write Messages.task_start("Sending test logs")

http_device = LogDevices::HTTP.new(api_key)
logger = Logger.new(http_device)
logger.info("test")

puts colorize(Messages.task_complete("Sending test logs"), :green)
end

def collect_feedback(api)
puts ""
rating = ask("How would rate this install experience? 1 (bad) - 5 (perfect)")
case rating
when "4", "5"
api.event!(:feedback, rating: rating.to_i)
puts ""
puts Messages.we_love_you_too

when "1", "2", "3"
puts ""
puts Messages.bad_experience_message
puts ""

comments = ask("Type your comments (enter sends)")

api.event!(:feedback, rating: rating.to_i, comments: comments)

puts ""
puts "Thank you! We take feedback seriously and will work to improve this."
end
end
end
end
end
end
Loading

0 comments on commit 6c03473

Please sign in to comment.