-
Notifications
You must be signed in to change notification settings - Fork 601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added lib/bootstrap.rb #2635
Merged
hannahramadan
merged 4 commits into
dev
from
not_dark_but_beautiful_and_terrible_as_the_dawn
May 23, 2024
Merged
added lib/bootstrap.rb #2635
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# This file is distributed under New Relic's license terms. | ||
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. | ||
# frozen_string_literal: true | ||
|
||
# This file is designed to bootstrap a `Bundler.require`-based Ruby app (such as | ||
# a Ruby on Rails app) so the app can be instrumented and observed by the | ||
# New Relic Ruby agent without the agent being added to the app as a dependency. | ||
# NOTE: introducing the agent into your application via bootstrap is in beta. | ||
# Use at your own risk. | ||
# | ||
# Given a production-ready Ruby app that optionally has a pre-packaged "frozen" | ||
# or "deployment"–gem bundle, the New Relic Ruby agent can be introduced | ||
# to the app without modifying the app and keeping all of the app's content | ||
# read-only. | ||
# | ||
# Prerequisites: | ||
# - Ruby (tested v2.4+) | ||
# - Bundler (included with Ruby, tested v1.17+) | ||
# | ||
# Instructions: | ||
# - First, make sure the New Relic Ruby agent exists on disk. For these | ||
# instructions, we'll assume the agent exists at `/newrelic`. | ||
# - The agent can be downloaded as the "newrelic_rpm" gem from RubyGems.org | ||
# and unpacked with "gem unpack" | ||
# - The agent can be cloned from the New Relic public GitHub repo: | ||
# https://github.com/newrelic/newrelic-ruby-agent | ||
# - Next, use the "RUBYOPT" environment variable to require ("-r") this | ||
# file (note that the ".rb" extension is dropped): | ||
# ``` | ||
# export RUBYOPT="-r /newrelic/lib/bootstrap" | ||
# ``` | ||
# - Add your New Relic license key as an environment variable. | ||
# ``` | ||
# export NEW_RELIC_LICENSE_KEY=1a2b3c4d5e67f8g9h0i | ||
# ``` | ||
# - Launch an existing Ruby app as usual. For a Ruby on Rails app, this might | ||
# involve running `bin/rails server`. | ||
# - In the Ruby app's directory, look for and inspect | ||
# `log/newrelic_agent.log`. If this file exists and there are no "WARN" or | ||
# "ERROR" entries within it, then the agent was successfully introduced to | ||
# the Ruby application. | ||
|
||
module NRBundlerPatch | ||
NR_AGENT_GEM = 'newrelic_rpm' | ||
|
||
def require(*_groups) | ||
super | ||
|
||
require_newrelic | ||
end | ||
|
||
def require_newrelic | ||
lib = File.dirname(__FILE__) | ||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) | ||
Kernel.require NR_AGENT_GEM | ||
end | ||
end | ||
|
||
class NRBundlerPatcher | ||
BUNDLER = 'bundler' | ||
RUBYOPT = 'RUBYOPT' | ||
|
||
def self.patch | ||
check_for_require | ||
check_for_rubyopt | ||
check_for_bundler | ||
Bundler::Runtime.prepend(NRBundlerPatch) | ||
end | ||
|
||
private | ||
|
||
def self.check_for_require | ||
warn_and_exit "#{__FILE__} is meant to be required, not invoked directly" if $PROGRAM_NAME == __FILE__ | ||
end | ||
|
||
def self.check_for_rubyopt | ||
unless ENV[RUBYOPT].to_s.match?("-r #{__FILE__.rpartition('.').first}") | ||
warn_and_exit "#{__FILE__} is meant to be required via the RUBYOPT env var" | ||
end | ||
end | ||
|
||
def self.check_for_bundler | ||
require_bundler | ||
|
||
warn_and_exit 'Required Ruby Bundler class Bundler::Runtime not defined!' unless defined?(Bundler::Runtime) | ||
|
||
unless Bundler::Runtime.method_defined?(:require) | ||
warn_and_exit "The active Ruby Bundler instance doesn't offer Bundler::Runtime#require" | ||
end | ||
end | ||
|
||
def self.require_bundler | ||
require BUNDLER | ||
rescue LoadError => e | ||
warn_and_exit "Required Ruby library '#{BUNDLER}' could not be required - #{e}" | ||
end | ||
|
||
def self.warn_and_exit(msg) | ||
warn "New Relic entrypoint at #{__FILE__} encountered an issue:\n\t#{msg}" | ||
|
||
exit 1 | ||
end | ||
end | ||
|
||
NRBundlerPatcher.patch |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# This file is distributed under New Relic's license terms. | ||
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details. | ||
# frozen_string_literal: true | ||
|
||
require_relative '../test_helper' | ||
|
||
class NewRelicBootstrapTest < Minitest::Test | ||
class PhonyBundler | ||
DEFAULT_GEM_NAME = 'tadpole' | ||
|
||
def require(*_groups) | ||
Kernel.require DEFAULT_GEM_NAME | ||
end | ||
end | ||
|
||
def setup | ||
require_bootstrap | ||
monkeypatch_phony_bundler | ||
end | ||
|
||
def test_the_overall_prepend_based_monkeypatch | ||
# Make sure that newrelic_rpm is required as a result of the patching | ||
required_gems = [] | ||
Kernel.stub :require, proc { |gem| required_gems << gem } do | ||
PhonyBundler.new.require | ||
end | ||
|
||
assert_equal 2, required_gems.size, "Expected 2 gems to be required, saw #{required_gems.size}." | ||
assert_equal [PhonyBundler::DEFAULT_GEM_NAME, 'newrelic_rpm'], required_gems, | ||
"Expected to see 'newrelic_rpm' required. Only saw #{required_gems}" | ||
end | ||
|
||
def test_check_for_require | ||
opn = $PROGRAM_NAME | ||
|
||
$PROGRAM_NAME = bootstrap_file | ||
msg = '' | ||
NRBundlerPatcher.stub :warn_and_exit, proc { |m| msg = m } do | ||
NRBundlerPatcher.check_for_require | ||
end | ||
|
||
assert_match(/meant to be required, not invoked/, msg, | ||
'Expected check_for_require to complain when bootstrap is invoked directly') | ||
ensure | ||
$PROGRAM_NAME = opn | ||
end | ||
|
||
def test_check_for_rubyopt | ||
oro = ENV.fetch('RUBYOPT', nil) | ||
|
||
ENV['RUBYOPT'] = "-r #{bootstrap_file}" | ||
|
||
refute NRBundlerPatcher.check_for_rubyopt | ||
ensure | ||
ENV['RUBYOPT'] = oro if oro | ||
end | ||
|
||
def test_check_for_bundler_class_not_defined | ||
oruntime = Bundler.send(:const_get, :Runtime) | ||
|
||
NRBundlerPatcher.stub :require_bundler, nil do | ||
Bundler.send(:remove_const, :Runtime) | ||
|
||
msg = '' | ||
NRBundlerPatcher.stub :warn_and_exit, proc { |m| msg = m; Bundler.send(:const_set, :Runtime, oruntime) } do | ||
NRBundlerPatcher.check_for_bundler | ||
end | ||
|
||
assert_match(/class Bundler::Runtime not defined!/, msg, | ||
'Expected check_for_bundler to complain if Bundler::Runtime is not defined') | ||
end | ||
ensure | ||
Bundler.send(:const_set, :Runtime, oruntime) | ||
end | ||
|
||
def test_check_for_bundler_method_not_defined | ||
skip_unless_minitest5_or_above | ||
|
||
NRBundlerPatcher.stub :require_bundler, nil do | ||
Bundler::Runtime.stub :method_defined?, false, [:require] do | ||
msg = '' | ||
NRBundlerPatcher.stub :warn_and_exit, proc { |m| msg = m } do | ||
NRBundlerPatcher.check_for_bundler | ||
end | ||
|
||
assert_match(/doesn't offer Bundler::Runtime#require/, msg, | ||
'Expected check_for_bundler to complain if Bundler::Runtime#require is not defined') | ||
end | ||
end | ||
end | ||
|
||
def test_require_bundler | ||
skip_unless_minitest5_or_above | ||
|
||
NRBundlerPatcher.stub :require, proc { |_gem| raise LoadError }, ['bundler'] do | ||
msg = '' | ||
NRBundlerPatcher.stub :warn_and_exit, proc { |m| msg = m } do | ||
NRBundlerPatcher.check_for_bundler | ||
end | ||
|
||
assert_match(/could not be required/, msg, | ||
'Expected require_bundler to complain if Bundler could not be required') | ||
end | ||
end | ||
|
||
private | ||
|
||
# Load the bootstrap file and anticipate the `warn` and `exit` calls | ||
# with assertions | ||
def require_bootstrap | ||
assert_raises SystemExit do | ||
assert_output(/New Relic entrypoint/) do | ||
require_relative '../../lib/bootstrap' | ||
end | ||
end | ||
end | ||
|
||
# Have the patcher patch our phony Bundler instead of the real one | ||
def monkeypatch_phony_bundler | ||
NRBundlerPatcher.stub :check_for_require, nil do | ||
NRBundlerPatcher.stub :check_for_rubyopt, nil do | ||
NRBundlerPatcher.stub :check_for_bundler, nil do | ||
Bundler::Runtime.stub :prepend, proc { |mod| PhonyBundler.prepend(mod) } do | ||
NRBundlerPatcher.patch | ||
end | ||
end | ||
end | ||
end | ||
end | ||
|
||
def bootstrap_file | ||
@bootstrap_file ||= File.expand_path('../../../lib/bootstrap.rb', __FILE__) | ||
end | ||
end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!