Skip to content
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

Update newrelic deployment command to support New Relic REST API v2 #1461

Merged
merged 16 commits into from
Sep 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# New Relic Ruby Agent Release Notes #

## v8.11.0

* **Added support for New Relic REST API v2 when using `newrelic deployments` command**

Previously, the `newrelic deployments` command only supported the older version of the deployments api, which does not currently support newer license keys. Now you can use the New Relic REST API v2 to record deployments by providing your user api key to the agent configuration using `api_key`. When this configuration option is present, the `newrelic deployments` command will automatically use the New Relic REST API v2 deployment endpoint. [PR#1461](https://github.com/newrelic/newrelic-ruby-agent/pull/1461)

Thank you to @Arkham for bringing this to our attention!
kaylareopelle marked this conversation as resolved.
Show resolved Hide resolved





## v8.10.1

Expand Down
17 changes: 14 additions & 3 deletions lib/new_relic/agent/configuration/default_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,16 @@ def self.host
end

def self.api_host
# only used for deployment task
proc do
if String(NewRelic::Agent.config[:license_key]).start_with?('eu')
'rpm.eu.newrelic.com'
api_version = if NewRelic::Agent.config[:api_key].nil? || NewRelic::Agent.config[:api_key].empty?
"rpm"
else
'rpm.newrelic.com'
"api"
end
api_region = "eu." if String(NewRelic::Agent.config[:license_key]).start_with?('eu')

"#{api_version}.#{api_region}newrelic.com"
end
end

Expand Down Expand Up @@ -329,6 +333,13 @@ def self.enforce_fallback(allowed_values: nil, fallback: nil)
:allowed_from_server => false,
:description => 'Your New Relic [license key](/docs/apis/intro-apis/new-relic-api-keys/#ingest-license-key).'
},
:api_key => {
:default => '',
:public => true,
:type => String,
:allowed_from_server => false,
:description => 'Your New Relic API key. Required when using the New Relic REST API v2 to record deployments using the `newrelic deployments` command.'
},
:agent_enabled => {
:default => DefaultSource.agent_enabled,
:documentation_default => true,
Expand Down
92 changes: 72 additions & 20 deletions lib/new_relic/cli/commands/deployments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def initialize(command_line_args)
load_yaml_from_env(control.env)
@appname ||= NewRelic::Agent.config[:app_name][0] || control.env || 'development'
@license_key ||= NewRelic::Agent.config[:license_key]
@api_key ||= NewRelic::Agent.config[:api_key]

setup_logging(control.env)
end
Expand All @@ -64,29 +65,28 @@ def setup_logging(env)
def run
begin
@description = nil if @description && @description.strip.empty?
create_params = {}
{
:application_id => @appname,
:host => NewRelic::Agent::Hostname.get,
:description => @description,
:user => @user,
:revision => @revision,
:changelog => @changelog
}.each do |k, v|
create_params["deployment[#{k}]"] = v unless v.nil? || v == ''
end
http = ::NewRelic::Agent::NewRelicService.new(nil, control.api_server).http_connection

uri = "/deployments.xml"

if @license_key.nil? || @license_key.empty?
raise "license_key was not set in newrelic.yml for #{control.env}"
raise "license_key not set in newrelic.yml for #{control.env}. api_key also required to use New Relic REST API v2"
end

if !api_v1? && (@revision.nil? || @revision.empty?)
raise "revision required when using New Relic REST API v2 with api_key. Pass in revision using: -r, --revision=REV"
end
request = Net::HTTP::Post.new(uri, {'x-license-key' => @license_key})
request.content_type = "application/octet-stream"

request.set_form_data(create_params)
request = if api_v1?
uri = "/deployments.xml"
create_request(uri, {'x-license-key' => @license_key}, "application/octet-stream").tap do |req|
set_params_v1(req)
end
else
uri = "/v2/applications/#{application_id}/deployments.json"
create_request(uri, {"Api-Key" => @api_key}, "application/json").tap do |req|
set_params_v2(req)
end
end

http = ::NewRelic::Agent::NewRelicService.new(nil, control.api_server).http_connection
response = http.request(request)

if response.is_a?(Net::HTTPSuccess)
Expand All @@ -108,22 +108,74 @@ def run
end
end

def api_v1?
@api_key.nil? || @api_key.empty?
end

private

def create_request(uri, headers, content_type)
Net::HTTP::Post.new(uri, headers).tap do |req|
req.content_type = content_type
end
end

def application_id
return @application_id if @application_id

# Need to connect to collector to acquire application id from the connect response
# but set monitor_mode false because we don't want to actually report anything
begin
NewRelic::Agent.manual_start(monitor_mode: false)
NewRelic::Agent.agent.connect_to_server
@application_id = NewRelic::Agent.config[:primary_application_id]
ensure
NewRelic::Agent.shutdown
end
end

def set_params_v1(request)
params = {
:application_id => @appname,
:host => NewRelic::Agent::Hostname.get,
:description => @description,
:user => @user,
:revision => @revision,
:changelog => @changelog
}.each_with_object({}) do |(k, v), h|
h["deployment[#{k}]"] = v unless v.nil? || v == ''
end
tannalynn marked this conversation as resolved.
Show resolved Hide resolved
request.set_form_data(params)
end

def set_params_v2(request)
request.body = {
"deployment" => {
:description => @description,
:user => @user,
:revision => @revision,
:changelog => @changelog
}
}.to_json
end

def options
OptionParser.new(%Q(Usage: #{$0} #{self.class.command} [OPTIONS] ["description"] ), 40) do |o|
o.separator("OPTIONS:")
o.on("-a", "--appname=NAME", String,
"Set the application name.",
"Default is app_name setting in newrelic.yml") { |e| @appname = e }
"Default is app_name setting in newrelic.yml. Available only when using API v1.") { |e| @appname = e }
o.on("-i", "--appid=ID", String,
"Set the application ID",
"If not provided, will connect to the New Relic collector to get it") { |i| @application_id = i }
o.on("-e", "--environment=name", String,
"Override the (RAILS|RUBY|RACK)_ENV setting",
"currently: #{control.env}") { |e| @environment = e }
o.on("-u", "--user=USER", String,
"Specify the user deploying, for information only",
"Default: #{@user || '<none>'}") { |u| @user = u }
o.on("-r", "--revision=REV", String,
"Specify the revision being deployed") { |r| @revision = r }
"Specify the revision being deployed. Required when using New Relic REST API v2") { |r| @revision = r }
o.on("-l", "--license-key=KEY", String,
"Specify the license key of the account for the app being deployed") { |l| @license_key = l }
o.on("-c", "--changes",
Expand Down
1 change: 0 additions & 1 deletion test/multiverse/suites/rack/rack_builder_test.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# encoding: utf-8
# 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
Expand Down
63 changes: 63 additions & 0 deletions test/new_relic/cli/commands/deployments_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def setup

def teardown
super
mocha_teardown
return unless @deployment
puts @deployment.errors
puts @deployment.messages
Expand Down Expand Up @@ -61,6 +62,23 @@ def test_interactive
@deployment = nil
end

def test_interactive_v2
mock_the_connection
with_config(:api_key => 'fake_api_key') do
@deployment = NewRelic::Cli::Deployments.new(:appname => 'APP',
:revision => 3838,
:application_id => "appid",
:user => 'Bill',
:description => "Some lengthy description")
assert_nil @deployment.exit_status
assert_nil @deployment.errors
assert_equal '3838', @deployment.revision
@deployment.run
refute @deployment.api_v1?, "Using v1 when v2 should be used"
@deployment = nil
end
end

def test_command_line_run
mock_the_connection
# @mock_response.expects(:body).returns("<xml>deployment</xml>")
Expand All @@ -77,6 +95,19 @@ def test_command_line_run
@deployment = nil
end

def test_command_line_run_v2
mock_the_connection
with_config(:api_key => 'fake_api_key') do
@deployment = NewRelic::Cli::Deployments.new(%w[-a APP -r 3838 --user=Bill --appid=appid1234] << "Some lengthy description")
assert_nil @deployment.exit_status
assert_nil @deployment.errors
assert_equal '3838', @deployment.revision
@deployment.run
refute @deployment.api_v1?, "Using v1 when v2 should be used"
@deployment = nil
end
end

def test_error_if_no_license_key
with_config(:license_key => '') do
assert_raises NewRelic::Cli::Command::CommandFailure do
Expand All @@ -87,6 +118,16 @@ def test_error_if_no_license_key
@deployment = nil
end

def test_error_if_no_revision_with_api_key
with_config(:api_key => 'fake_api_key') do
assert_raises NewRelic::Cli::Command::CommandFailure do
deployment = NewRelic::Cli::Deployments.new(%w[-a APP --user=Bill] << "Some lengthy description")
deployment.run
end
end
@deployment = nil
end

def test_error_if_failed_yaml
NewRelic::Agent::Configuration::YamlSource.any_instance.stubs(:failed?).returns(true)

Expand Down Expand Up @@ -124,8 +165,30 @@ def test_with_unspecified_license_key
@deployment = nil
end

def test_gets_appid_from_connect_when_not_provided_with_v2
mock_the_connection
mock_the_collector

with_config(:api_key => 'fake_api_key') do
@deployment = NewRelic::Cli::Deployments.new(%w[-a APP -r 3838 --user=Bill] << "Some lengthy description")
assert_nil @deployment.exit_status
assert_nil @deployment.errors
assert_equal '3838', @deployment.revision
@deployment.run
@deployment = nil
end
end

private

def mock_the_collector
NewRelic::Agent.expects(:manual_start)
agent_mock = mock()
NewRelic::Agent.expects(:agent).returns(agent_mock)
agent_mock.expects(:connect_to_server)
NewRelic::Agent.expects(:shutdown)
end

def mock_the_connection
mock_connection = mock()
@mock_response = mock()
Expand Down