From 8a3b4e434c673dcd24c7e447835f8cf0c3d4a962 Mon Sep 17 00:00:00 2001 From: Hippolyte HENRY Date: Tue, 28 Jan 2020 19:15:26 +0100 Subject: [PATCH 1/3] Add `validate_tags` util to check that tags are an array of strings (#218) --- lib/dogapi/common.rb | 9 +++++++++ spec/unit/common_spec.rb | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/dogapi/common.rb b/lib/dogapi/common.rb index 711468d2..cbc91267 100644 --- a/lib/dogapi/common.rb +++ b/lib/dogapi/common.rb @@ -205,4 +205,13 @@ def Dogapi.find_localhost raise $ERROR_INFO unless $ERROR_INFO.class.name == 'Errno::ENOENT' @@hostname = Addrinfo.getaddrinfo(Socket.gethostname, nil, nil, nil, nil, Socket::AI_CANONNAME).first.canonname end + + def Dogapi.validate_tags(tags) + unless tags.is_a? Array + raise ArgumentError, "The tags parameter needs to be an array of string. Current value: #{tags}" + end + tags.each do |tag| + raise ArgumentError, "Each tag needs to be a string. Current value: #{tag}" unless tag.is_a? String + end + end end diff --git a/spec/unit/common_spec.rb b/spec/unit/common_spec.rb index 4d3a5baa..dfc62f3b 100644 --- a/spec/unit/common_spec.rb +++ b/spec/unit/common_spec.rb @@ -5,6 +5,23 @@ require_relative '../spec_helper' describe 'Common' do + describe 'validate_tags' do + it 'raises if tags is not an array' do + tags = 'foo:bar' + expect { Dogapi.validate_tags(tags) }.to raise_error(ArgumentError) + tags = nil + expect { Dogapi.validate_tags(tags) }.to raise_error(ArgumentError) + end + it 'raises if elements of tags are not strings' do + tags = ['toto:tata', { foo: 'bar' }] + expect { Dogapi.validate_tags(tags) }.to raise_error(ArgumentError) + end + it 'passes if tags are correct' do + tags = ['foo:bar', 'baz'] + Dogapi.validate_tags(tags) + end + end + describe Dogapi.find_datadog_host do it 'gives precedence to DATADOG_HOST env var' do allow(ENV).to receive(:[]).with('DATADOG_HOST').and_return('example.com') From 309664f2bc291fc5a00cf097f56f02c2ec8399ce Mon Sep 17 00:00:00 2001 From: Hippolyte HENRY Date: Thu, 30 Jan 2020 11:07:17 +0100 Subject: [PATCH 2/3] Check exit code of `hostname -f` for failures (#219) --- lib/dogapi/common.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/dogapi/common.rb b/lib/dogapi/common.rb index cbc91267..96e715f8 100644 --- a/lib/dogapi/common.rb +++ b/lib/dogapi/common.rb @@ -11,6 +11,7 @@ require 'rubygems' require 'multi_json' require 'set' +require 'open3' module Dogapi @@ -200,9 +201,13 @@ def Dogapi.find_datadog_host @@hostname = nil def Dogapi.find_localhost - @@hostname ||= %x[hostname -f].strip + unless @@hostname + out, status = Open3.capture2('hostname', '-f', err: File::NULL) + @@hostname = out.strip + # Get status to check if the call was successful + raise SystemCallError, 'Could not get hostname with `hostname -f`' unless status.exitstatus.zero? + end rescue SystemCallError - raise $ERROR_INFO unless $ERROR_INFO.class.name == 'Errno::ENOENT' @@hostname = Addrinfo.getaddrinfo(Socket.gethostname, nil, nil, nil, nil, Socket::AI_CANONNAME).first.canonname end From 0f2fa56465124e388823969e5f9ce2855df30bde Mon Sep 17 00:00:00 2001 From: Roman Kulayev Date: Thu, 30 Jan 2020 17:37:12 +0300 Subject: [PATCH 3/3] [capistrano] Add ability to use `etc.getpwuid` instead of `etc.getlogin` to get the user name (#146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Do not use getlogin() function to obtain username `getlogin()` obtains the logged-in username by looking up the current tty in `utmp` and reporting the login name found in that utmp record. If Capistrano is ran from Jenkins in the host it returns the user logged in to `pts/0`, which is not the same user as runs the Capistrano. Also Ruby docs recommends avoid using `getlogin()`: >getlogin → String >Returns the short user name of the currently logged in user. Unfortunately, it is often rather easy to fool ::getlogin. > >Avoid ::getlogin for security-related purposes. > >If ::getlogin fails, try ::getpwuid. > >See the unix manpage for getpwuid(3) for more detail. http://ruby-doc.org/stdlib-2.2.0/libdoc/etc/rdoc/Etc.html#method-c-getlogin * keep backward compat * rubocop * add doc Co-authored-by: Hippolyte HENRY --- .rubocop_todo.yml | 52 +++++++++++++++++------------------- lib/capistrano/README.md | 5 +++- lib/capistrano/datadog.rb | 8 +++--- lib/capistrano/datadog/v2.rb | 6 ++++- lib/capistrano/datadog/v3.rb | 7 ++++- 5 files changed, 43 insertions(+), 35 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 919a3b1d..6ffb40cd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2020-01-13 15:42:46 -0500 using RuboCop version 0.49.1. +# on 2020-01-30 14:50:03 +0100 using RuboCop version 0.49.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -42,14 +42,17 @@ Layout/EmptyLineAfterMagicComment: Exclude: - 'dogapi.gemspec' -# Offense count: 2 +# Offense count: 16 # Cop supports --auto-correct. Layout/EmptyLines: Exclude: + - 'config/deploy.rb' + - 'config/deploy/production.rb' + - 'config/deploy/staging.rb' - 'lib/capistrano/datadog/v2.rb' - 'lib/dogapi/facade.rb' -# Offense count: 89 +# Offense count: 92 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines @@ -129,7 +132,6 @@ Layout/SpaceAroundEqualsInParameterDefault: - 'lib/dogapi/common.rb' - 'lib/dogapi/event.rb' - 'lib/dogapi/facade.rb' - - 'lib/dogapi/v1/dash.rb' - 'lib/dogapi/v1/embed.rb' - 'lib/dogapi/v1/event.rb' - 'lib/dogapi/v1/metric.rb' @@ -201,9 +203,9 @@ Lint/UselessAssignment: # Offense count: 12 Metrics/AbcSize: - Max: 51 + Max: 53 -# Offense count: 28 +# Offense count: 29 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: Max: 123 @@ -215,23 +217,23 @@ Metrics/ClassLength: # Offense count: 3 Metrics/CyclomaticComplexity: - Max: 8 + Max: 9 # Offense count: 16 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 34 -# Offense count: 5 +# Offense count: 7 # Configuration parameters: CountKeywordArgs. Metrics/ParameterLists: Max: 7 # Offense count: 2 Metrics/PerceivedComplexity: - Max: 9 + Max: 10 -# Offense count: 13 +# Offense count: 19 Style/AccessorMethodName: Exclude: - 'lib/dogapi/facade.rb' @@ -240,8 +242,8 @@ Style/AccessorMethodName: - 'lib/dogapi/v1/dashboard.rb' - 'lib/dogapi/v1/embed.rb' - 'lib/dogapi/v1/screenboard.rb' - - 'lib/dogapi/v1/user.rb' - 'lib/dogapi/v1/synthetics.rb' + - 'lib/dogapi/v1/user.rb' # Offense count: 1 # Cop supports --auto-correct. @@ -279,7 +281,7 @@ Style/ClassCheck: - 'lib/dogapi/facade.rb' - 'lib/dogapi/v1/event.rb' -# Offense count: 2 +# Offense count: 3 # Cop supports --auto-correct. Style/ClassMethods: Exclude: @@ -297,14 +299,6 @@ Style/ColonMethodCall: - 'lib/capistrano/datadog.rb' - 'lib/capistrano/datadog/v2.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerBackticks. -# SupportedStyles: backticks, percent_x, mixed -Style/CommandLiteral: - Exclude: - - 'lib/dogapi/common.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions. @@ -313,7 +307,7 @@ Style/ConditionalAssignment: Exclude: - 'lib/capistrano/datadog.rb' -# Offense count: 21 +# Offense count: 20 # Cop supports --auto-correct. Style/DefWithParentheses: Exclude: @@ -340,7 +334,7 @@ Style/GuardClause: - 'lib/capistrano/datadog.rb' - 'lib/dogapi/facade.rb' -# Offense count: 86 +# Offense count: 88 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys @@ -361,7 +355,7 @@ Style/HashSyntax: - 'lib/dogapi/v1/snapshot.rb' - 'lib/dogapi/v1/tag.rb' -# Offense count: 15 +# Offense count: 16 # Cop supports --auto-correct. # Configuration parameters: MaxLineLength. Style/IfUnlessModifier: @@ -381,7 +375,7 @@ Style/LineEndConcatenation: Exclude: - 'dogapi.gemspec' -# Offense count: 18 +# Offense count: 21 # Cop supports --auto-correct. Style/MethodCallWithoutArgsParentheses: Exclude: @@ -396,7 +390,7 @@ Style/MultilineIfThen: Exclude: - 'lib/capistrano/datadog.rb' -# Offense count: 33 +# Offense count: 37 # Cop supports --auto-correct. Style/MutableConstant: Enabled: false @@ -416,12 +410,11 @@ Style/Not: Exclude: - 'lib/dogapi/v1/metric.rb' -# Offense count: 4 +# Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: Exclude: - - 'lib/dogapi/common.rb' - 'spec/integration/event_spec.rb' - 'spec/integration/monitor_spec.rb' @@ -477,14 +470,17 @@ Style/SpecialGlobalVars: Exclude: - 'dogapi.gemspec' -# Offense count: 25 +# Offense count: 35 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: Exclude: + - 'Capfile' - 'Gemfile' - 'Rakefile' + - 'config/deploy.rb' + - 'config/deploy/staging.rb' - 'dogapi.gemspec' - 'examples/Capfile' - 'examples/custom_event.rb' diff --git a/lib/capistrano/README.md b/lib/capistrano/README.md index f5011b7c..bb8f57b7 100644 --- a/lib/capistrano/README.md +++ b/lib/capistrano/README.md @@ -18,7 +18,10 @@ To set up your Capfile: # for instance, only push the production event # set :datadog_event_filter, proc { |event, hosts| event.msg_title.include?('ran production') ? [event, hosts] : nil } + # (optional) use the `Etc.getlogin` method (default) or the `Etc.getpwuid` method to get the user name + # default: use `Etc.getlogin` + # set :use_getlogin, true + You can find your Datadog API key [here](https://app.datadoghq.com/account/settings#api). If you don't have a Datadog account, you can sign up for one [here](http://www.datadoghq.com/). `capistrano/datadog` will capture each Capistrano task that that Capfile runs, including the roles that the task applies to and any logging output that it emits and submits them as events to Datadog at the end of the execution of all the tasks. If sending to Datadog fails for any reason, your scripts will still succeed. - diff --git a/lib/capistrano/datadog.rb b/lib/capistrano/datadog.rb index 7b36e9dc..f6f80ac8 100644 --- a/lib/capistrano/datadog.rb +++ b/lib/capistrano/datadog.rb @@ -28,11 +28,11 @@ def self.cap_version() @cap_version end - def self.submit(api_key) + def self.submit(api_key, use_getlogin=true) begin if api_key dog = Dogapi::Client.new(api_key) - reporter.report.each do |event, hosts| + reporter.report(use_getlogin).each do |event, hosts| if hosts.size > 0 hosts.each do |host| dog.emit_event event, host: host @@ -93,9 +93,9 @@ def record_hostname(hostname) end end - def report() + def report(use_getlogin=true) hostname = Dogapi.find_localhost - user = Etc.getlogin + user = use_getlogin ? Etc.getlogin : Etc.getpwuid.name # Lazy randomness aggregation_key = Digest::MD5.hexdigest "#{Time.new}|#{rand}" diff --git a/lib/capistrano/datadog/v2.rb b/lib/capistrano/datadog/v2.rb index 7ddbc395..91f3f703 100644 --- a/lib/capistrano/datadog/v2.rb +++ b/lib/capistrano/datadog/v2.rb @@ -62,7 +62,11 @@ def puts(message) namespace :datadog do desc 'Submit the tasks that have run to Datadog as events' task :submit do |ns| - Capistrano::Datadog.submit variables[:datadog_api_key] + if variables[:use_getlogin].nil? + Capistrano::Datadog.submit variables[:datadog_api_key] + else + Capistrano::Datadog.submit variables[:datadog_api_key], variables[:use_getlogin] + end end end end diff --git a/lib/capistrano/datadog/v3.rb b/lib/capistrano/datadog/v3.rb index 89558d12..b6e5d06b 100644 --- a/lib/capistrano/datadog/v3.rb +++ b/lib/capistrano/datadog/v3.rb @@ -80,5 +80,10 @@ def initialize(oio) at_exit do api_key = Capistrano::Configuration.env.fetch :datadog_api_key - Capistrano::Datadog.submit api_key + use_getlogin = Capistrano::Configuration.env.fetch :use_getlogin + if use_getlogin.nil? + Capistrano::Datadog.submit api_key + else + Capistrano::Datadog.submit api_key, use_getlogin + end end