appsignal 0.6.7 → 0.7.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +3 -2
  3. data/.travis.yml +11 -11
  4. data/CHANGELOG.md +6 -0
  5. data/README.md +26 -0
  6. data/Rakefile +32 -3
  7. data/appsignal.gemspec +25 -16
  8. data/gemfiles/no_dependencies.gemfile +3 -0
  9. data/gemfiles/{3.0.gemfile → rails-3.0.gemfile} +2 -1
  10. data/gemfiles/{3.1.gemfile → rails-3.1.gemfile} +2 -1
  11. data/gemfiles/{3.2.gemfile → rails-3.2.gemfile} +2 -1
  12. data/gemfiles/{4.0.gemfile → rails-4.0.gemfile} +2 -1
  13. data/gemfiles/sinatra.gemfile +5 -0
  14. data/lib/appsignal.rb +30 -25
  15. data/lib/appsignal/agent.rb +37 -17
  16. data/lib/appsignal/aggregator.rb +2 -4
  17. data/lib/appsignal/aggregator/middleware.rb +4 -0
  18. data/lib/appsignal/aggregator/middleware/action_view_sanitizer.rb +23 -0
  19. data/lib/appsignal/aggregator/middleware/active_record_sanitizer.rb +64 -0
  20. data/lib/appsignal/aggregator/middleware/chain.rb +101 -0
  21. data/lib/appsignal/aggregator/middleware/delete_blanks.rb +16 -0
  22. data/lib/appsignal/aggregator/post_processor.rb +21 -18
  23. data/lib/appsignal/auth_check.rb +7 -21
  24. data/lib/appsignal/capistrano.rb +1 -36
  25. data/lib/appsignal/cli.rb +30 -46
  26. data/lib/appsignal/config.rb +46 -61
  27. data/lib/appsignal/integrations/capistrano.rb +44 -0
  28. data/lib/appsignal/{careful_logger.rb → integrations/capistrano/careful_logger.rb} +2 -0
  29. data/lib/appsignal/integrations/passenger.rb +6 -6
  30. data/lib/appsignal/integrations/rails.rb +33 -0
  31. data/lib/appsignal/integrations/sinatra.rb +20 -0
  32. data/lib/appsignal/marker.rb +9 -10
  33. data/lib/appsignal/rack/instrumentation.rb +28 -0
  34. data/lib/appsignal/rack/listener.rb +33 -0
  35. data/lib/appsignal/transaction.rb +18 -12
  36. data/lib/appsignal/transaction/formatter.rb +96 -0
  37. data/lib/appsignal/transaction/params_sanitizer.rb +80 -78
  38. data/lib/appsignal/transmitter.rb +10 -9
  39. data/lib/appsignal/version.rb +1 -1
  40. data/lib/generators/appsignal/appsignal_generator.rb +20 -21
  41. data/lib/generators/appsignal/templates/appsignal.yml +15 -21
  42. data/spec/{appsignal → lib/appsignal}/agent_spec.rb +69 -1
  43. data/spec/lib/appsignal/aggregator/middleware/action_view_sanitizer_spec.rb +32 -0
  44. data/spec/lib/appsignal/aggregator/middleware/active_record_sanitizer_spec.rb +215 -0
  45. data/spec/{appsignal → lib/appsignal/aggregator}/middleware/chain_spec.rb +5 -5
  46. data/spec/{appsignal → lib/appsignal/aggregator}/middleware/delete_blanks_spec.rb +2 -2
  47. data/spec/{appsignal → lib/appsignal}/aggregator/post_processor_spec.rb +15 -6
  48. data/spec/{appsignal → lib/appsignal}/aggregator_spec.rb +4 -1
  49. data/spec/{appsignal → lib/appsignal}/auth_check_spec.rb +8 -23
  50. data/spec/{appsignal → lib/appsignal}/cli_spec.rb +65 -66
  51. data/spec/lib/appsignal/config_spec.rb +132 -0
  52. data/spec/lib/appsignal/integrations/capistrano_spec.rb +123 -0
  53. data/spec/{appsignal → lib/appsignal}/integrations/passenger_spec.rb +0 -1
  54. data/spec/lib/appsignal/integrations/rails_spec.rb +38 -0
  55. data/spec/lib/appsignal/integrations/sinatra_spec.rb +43 -0
  56. data/spec/{appsignal → lib/appsignal}/marker_spec.rb +20 -23
  57. data/spec/lib/appsignal/rack/instrumentation_spec.rb +49 -0
  58. data/spec/{appsignal → lib/appsignal/rack}/listener_spec.rb +39 -6
  59. data/spec/{appsignal/transaction/transaction_formatter_spec.rb → lib/appsignal/transaction/formatter_spec.rb} +29 -6
  60. data/spec/{appsignal → lib/appsignal}/transaction/params_sanitizer_spec.rb +13 -12
  61. data/spec/{appsignal → lib/appsignal}/transaction_spec.rb +52 -7
  62. data/spec/{appsignal → lib/appsignal}/transmitter_spec.rb +27 -20
  63. data/spec/lib/appsignal_spec.rb +230 -0
  64. data/spec/lib/generators/appsignal/appsignal_generator_spec.rb +166 -0
  65. data/spec/lib/tmp/config/appsignal.yml +2 -0
  66. data/spec/spec_helper.rb +29 -20
  67. data/spec/support/delegate_matcher.rb +0 -1
  68. data/spec/support/fixtures/generated_config.yml +20 -0
  69. data/{log/.gitkeep → spec/support/fixtures/uploaded_file.txt} +0 -0
  70. data/spec/support/helpers/config_helpers.rb +24 -0
  71. data/spec/support/helpers/notification_helpers.rb +0 -2
  72. data/spec/support/helpers/transaction_helpers.rb +17 -2
  73. data/spec/support/project_fixture/config/appsignal.yml +18 -0
  74. data/spec/support/project_fixture/log/.gitkeep +0 -0
  75. data/spec/support/rails/my_app.rb +6 -0
  76. metadata +99 -83
  77. data/config/appsignal.yml +0 -10
  78. data/lib/appsignal/listener.rb +0 -21
  79. data/lib/appsignal/middleware.rb +0 -3
  80. data/lib/appsignal/middleware/action_view_sanitizer.rb +0 -21
  81. data/lib/appsignal/middleware/active_record_sanitizer.rb +0 -62
  82. data/lib/appsignal/middleware/chain.rb +0 -99
  83. data/lib/appsignal/middleware/delete_blanks.rb +0 -12
  84. data/lib/appsignal/railtie.rb +0 -37
  85. data/lib/appsignal/to_appsignal_hash.rb +0 -21
  86. data/lib/appsignal/transaction/transaction_formatter.rb +0 -67
  87. data/spec/appsignal/capistrano_spec.rb +0 -81
  88. data/spec/appsignal/config_spec.rb +0 -177
  89. data/spec/appsignal/inactive_railtie_spec.rb +0 -32
  90. data/spec/appsignal/middleware/action_view_sanitizer_spec.rb +0 -27
  91. data/spec/appsignal/middleware/active_record_sanitizer_spec.rb +0 -212
  92. data/spec/appsignal/railtie_spec.rb +0 -74
  93. data/spec/appsignal/to_appsignal_hash_spec.rb +0 -29
  94. data/spec/appsignal_spec.rb +0 -195
  95. data/spec/generators/appsignal/appsignal_generator_spec.rb +0 -181
@@ -2,7 +2,7 @@ module Appsignal
2
2
  class Aggregator
3
3
  attr_reader :queue, :slowness_index, :counts
4
4
 
5
- def initialize(queue = [], slowness_index = {})
5
+ def initialize(queue=::ThreadSafe::Array.new, slowness_index={})
6
6
  @queue = queue
7
7
  @slowness_index = slowness_index
8
8
  @counts = {:regular_request => 0, :slow_request => 0, :exception => 0}
@@ -37,7 +37,7 @@ module Appsignal
37
37
  # @returns [ Array ] Array of post processed Appsignal::Transaction objects
38
38
  def post_processed_queue!
39
39
  Appsignal.logger.debug("Post processing queue: #{counts.inspect}")
40
- Appsignal::PostProcessor.new(queue).post_processed_queue!
40
+ Appsignal::Aggregator::PostProcessor.new(queue).post_processed_queue!
41
41
  end
42
42
 
43
43
  protected
@@ -63,5 +63,3 @@ module Appsignal
63
63
  end
64
64
  end
65
65
  end
66
-
67
- require 'appsignal/aggregator/post_processor'
@@ -0,0 +1,4 @@
1
+ require 'appsignal/aggregator/middleware/chain'
2
+ require 'appsignal/aggregator/middleware/delete_blanks'
3
+ require 'appsignal/aggregator/middleware/action_view_sanitizer'
4
+ require 'appsignal/aggregator/middleware/active_record_sanitizer'
@@ -0,0 +1,23 @@
1
+ module Appsignal
2
+ class Aggregator
3
+ module Middleware
4
+ class ActionViewSanitizer
5
+ TARGET_EVENT_CATEGORY = 'action_view'.freeze
6
+
7
+ def call(event)
8
+ if event.name.end_with?(TARGET_EVENT_CATEGORY)
9
+ identifier = event.payload[:identifier]
10
+ if identifier
11
+ identifier.gsub!(root_path, '')
12
+ end
13
+ end
14
+ yield
15
+ end
16
+
17
+ def root_path
18
+ @root_path ||= "#{Rails.root.to_s}/"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,64 @@
1
+ module Appsignal
2
+ class Aggregator
3
+ module Middleware
4
+ class ActiveRecordSanitizer
5
+ TARGET_EVENT_NAME = 'sql.active_record'.freeze
6
+
7
+ SINGLE_QUOTE = /\\'/.freeze
8
+ DOUBLE_QUOTE = /\\"/.freeze
9
+ QUOTED_DATA = /(?:"[^"]+"|'[^']+')/.freeze
10
+ SINGLE_QUOTED_DATA = /(?:'[^']+')/.freeze
11
+ IN_ARRAY = /(IN \()[^\)]+(\))/.freeze
12
+ NUMERIC_DATA = /\b\d+\b/.freeze
13
+
14
+ SANITIZED_VALUE = '\1?\2'.freeze
15
+
16
+ def call(event)
17
+ if event.name == TARGET_EVENT_NAME
18
+ unless schema_query?(event) || adapter_uses_prepared_statements?
19
+ query_string = event.payload[:sql]
20
+ if query_string
21
+ if adapter_uses_double_quoted_table_names?
22
+ query_string.gsub!(SINGLE_QUOTE, SANITIZED_VALUE)
23
+ query_string.gsub!(SINGLE_QUOTED_DATA, SANITIZED_VALUE)
24
+ else
25
+ query_string.gsub!(SINGLE_QUOTE, SANITIZED_VALUE)
26
+ query_string.gsub!(DOUBLE_QUOTE, SANITIZED_VALUE)
27
+ query_string.gsub!(QUOTED_DATA, SANITIZED_VALUE)
28
+ end
29
+ query_string.gsub!(IN_ARRAY, SANITIZED_VALUE)
30
+ query_string.gsub!(NUMERIC_DATA, SANITIZED_VALUE)
31
+ end
32
+ end
33
+ event.payload.delete(:connection_id)
34
+ event.payload.delete(:binds)
35
+ end
36
+ yield
37
+ end
38
+
39
+ def schema_query?(event)
40
+ event.payload[:name] == 'SCHEMA'
41
+ end
42
+
43
+ def connection_config
44
+ @connection_config ||= if ActiveRecord::Base.respond_to?(:connection_config)
45
+ ActiveRecord::Base.connection_config
46
+ else
47
+ ActiveRecord::Base.connection_pool.spec.config
48
+ end
49
+ end
50
+
51
+ def adapter_uses_double_quoted_table_names?
52
+ adapter = connection_config[:adapter]
53
+ adapter =~ /postgres/ || adapter =~ /sqlite/
54
+ end
55
+
56
+ def adapter_uses_prepared_statements?
57
+ return false unless adapter_uses_double_quoted_table_names?
58
+ return true if connection_config[:prepared_statements].nil?
59
+ connection_config[:prepared_statements]
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,101 @@
1
+ module Appsignal
2
+ class Aggregator
3
+ # Middleware is code configured to run before/after a message is processed.
4
+ # It is patterned after Rack middleware.
5
+ #
6
+ # @example To add middleware:
7
+ #
8
+ # Appsignal.post_processing_middleware do |chain|
9
+ # chain.add MyPostProcessingHook
10
+ # end
11
+ #
12
+ # @example To insert immediately preceding another entry:
13
+ #
14
+ # Appsignal.post_process_middleware do |chain|
15
+ # chain.insert_before ActiveRecord, MyPostProcessingHook
16
+ # end
17
+ #
18
+ # @example To insert immediately after another entry:
19
+ #
20
+ # Appsignal.post_process_middleware do |chain|
21
+ # chain.insert_after ActiveRecord, MyPostProcessingHook
22
+ # end
23
+ #
24
+ # @example This is an example of a minimal middleware class:
25
+ #
26
+ # class MySHook
27
+ # def call(transaction)
28
+ # puts "Before post processing"
29
+ # yield
30
+ # puts "After post processing"
31
+ # end
32
+ # end
33
+ #
34
+ module Middleware
35
+ class Chain
36
+ attr_reader :entries
37
+
38
+ def initialize
39
+ @entries = []
40
+ yield self if block_given?
41
+ end
42
+
43
+ def remove(klass)
44
+ entries.delete_if { |entry| entry.klass == klass }
45
+ end
46
+
47
+ def add(klass, *args)
48
+ entries << Entry.new(klass, *args) unless exists?(klass)
49
+ end
50
+
51
+ def insert_before(oldklass, newklass, *args)
52
+ i = entries.index { |entry| entry.klass == newklass }
53
+ new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
54
+ i = entries.find_index { |entry| entry.klass == oldklass } || 0
55
+ entries.insert(i, new_entry)
56
+ end
57
+
58
+ def insert_after(oldklass, newklass, *args)
59
+ i = entries.index { |entry| entry.klass == newklass }
60
+ new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
61
+ i = entries.find_index { |entry| entry.klass == oldklass } || entries.count - 1
62
+ entries.insert(i+1, new_entry)
63
+ end
64
+
65
+ def exists?(klass)
66
+ entries.any? { |entry| entry.klass == klass }
67
+ end
68
+
69
+ def retrieve
70
+ @retrieve ||= entries.map(&:make_new)
71
+ end
72
+
73
+ def clear
74
+ entries.clear
75
+ end
76
+
77
+ def invoke(*args)
78
+ chain = retrieve.dup
79
+ traverse_chain = lambda do
80
+ unless chain.empty?
81
+ chain.shift.call(*args, &traverse_chain)
82
+ end
83
+ end
84
+ traverse_chain.call
85
+ end
86
+ end
87
+
88
+ class Entry
89
+ attr_reader :klass
90
+ def initialize(klass, *args)
91
+ @klass = klass
92
+ @args = args
93
+ end
94
+
95
+ def make_new
96
+ @klass.new(*@args)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,16 @@
1
+ module Appsignal
2
+ class Aggregator
3
+ module Middleware
4
+ class DeleteBlanks
5
+ def call(event)
6
+ event.payload.each do |key, value|
7
+ if value.respond_to?(:empty?) ? value.empty? : !value
8
+ event.payload.delete(key)
9
+ end
10
+ end
11
+ yield
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,27 +1,30 @@
1
1
  module Appsignal
2
- class PostProcessor
3
- attr_reader :transactions
2
+ class Aggregator
3
+ class PostProcessor
4
+ attr_reader :transactions
4
5
 
5
- def initialize(transactions)
6
- @transactions = transactions
7
- end
6
+ def initialize(transactions)
7
+ @transactions = transactions
8
+ end
8
9
 
9
- def post_processed_queue!
10
- transactions.map do |transaction|
11
- transaction.events.each do |event|
12
- Appsignal.post_processing_middleware.invoke(event)
10
+ def post_processed_queue!
11
+ transactions.map do |transaction|
12
+ transaction.events.each do |event|
13
+ Appsignal.post_processing_middleware.invoke(event)
14
+ end
15
+ transaction.to_hash
13
16
  end
14
- transaction.to_hash
15
17
  end
16
- end
17
18
 
18
- def self.default_middleware
19
- Middleware::Chain.new do |chain|
20
- chain.add Appsignal::Middleware::DeleteBlanks
21
- chain.add Appsignal::Middleware::ActionViewSanitizer
22
- if defined?(ActiveRecord)
23
- require 'appsignal/middleware/active_record_sanitizer'
24
- chain.add Appsignal::Middleware::ActiveRecordSanitizer
19
+ def self.default_middleware
20
+ Middleware::Chain.new do |chain|
21
+ chain.add Appsignal::Aggregator::Middleware::DeleteBlanks
22
+ if defined?(::ActionView)
23
+ chain.add Appsignal::Aggregator::Middleware::ActionViewSanitizer
24
+ end
25
+ if defined?(::ActiveRecord)
26
+ chain.add Appsignal::Aggregator::Middleware::ActiveRecordSanitizer
27
+ end
25
28
  end
26
29
  end
27
30
  end
@@ -2,29 +2,15 @@ module Appsignal
2
2
  class AuthCheck
3
3
  ACTION = 'auth'.freeze
4
4
 
5
- attr_reader :environment, :logger
6
- attr_accessor :transmitter
5
+ attr_reader :config, :logger
7
6
 
8
- def initialize(*args)
9
- @environment = args.shift
10
- options = args.empty? ? {} : args.last
11
- @config = options[:config]
12
- @logger = options[:logger]
13
- end
14
-
15
- def uri
16
- transmitter.uri
17
- end
18
-
19
- def config
20
- @config ||= Appsignal::Config.new(Rails.root, environment, logger).load
7
+ def initialize(config, logger)
8
+ @config = config
9
+ @logger = logger
21
10
  end
22
11
 
23
12
  def perform
24
- self.transmitter = Appsignal::Transmitter.new(
25
- config[:endpoint], ACTION, config[:api_key]
26
- )
27
- transmitter.transmit({})
13
+ Appsignal::Transmitter.new(ACTION, config).transmit({})
28
14
  end
29
15
 
30
16
  def perform_with_result
@@ -37,12 +23,12 @@ module Appsignal
37
23
  result = 'API key not valid with AppSignal...'
38
24
  else
39
25
  result = 'Could not confirm authorization: '\
40
- "#{status.nil? ? 'nil' : status}"
26
+ "#{status.nil? ? 'nil' : status}"
41
27
  end
42
28
  [status, result]
43
29
  rescue Exception => e
44
30
  result = 'Something went wrong while trying to '\
45
- "authenticate with AppSignal: #{e}"
31
+ "authenticate with AppSignal: #{e}"
46
32
  [nil, result]
47
33
  end
48
34
  end
@@ -1,37 +1,2 @@
1
- require 'capistrano'
2
1
  require 'appsignal'
3
-
4
- module Appsignal
5
- class Capistrano
6
- def self.tasks(config)
7
- config.load do
8
- after "deploy", "appsignal:deploy"
9
- after "deploy:migrations", "appsignal:deploy"
10
-
11
- namespace :appsignal do
12
- task :deploy do
13
- rails_env = fetch(:rails_env, 'production')
14
- user = ENV['USER'] || ENV['USERNAME']
15
-
16
- marker_data = {
17
- :revision => current_revision,
18
- :repository => repository,
19
- :user => user
20
- }
21
-
22
- marker = Marker.new(marker_data, ENV['PWD'], rails_env, logger)
23
- if config.dry_run
24
- logger.info("Dry run: Deploy marker not actually sent.")
25
- else
26
- marker.transmit
27
- end
28
- end
29
- end
30
- end
31
- end
32
- end
33
- end
34
-
35
- if Capistrano::Configuration.instance
36
- Appsignal::Capistrano.tasks(Capistrano::Configuration.instance)
37
- end
2
+ require 'appsignal/integrations/capistrano'
data/lib/appsignal/cli.rb CHANGED
@@ -5,21 +5,15 @@ require 'appsignal'
5
5
 
6
6
  module Appsignal
7
7
  class CLI
8
- AVAILABLE_COMMANDS = %w(notify_of_deploy api_check).freeze
9
- PROJECT_ROOT = ENV['PWD']
8
+ AVAILABLE_COMMANDS = %w(notify_of_deploy).freeze
10
9
 
11
10
  class << self
12
- def run(argv=ARGV)
13
- unless File.exists?(File.join(PROJECT_ROOT, 'config/appsignal.yml'))
14
- puts 'No config file present at config/appsignal.yml'
15
- puts 'Log in to https://appsignal.com to get instructions on how to '\
16
- 'generate the config file.'
17
- exit(1)
18
- end
19
- options = {}
20
- global = global_option_parser(options)
21
- commands = command_option_parser(options)
11
+ attr_accessor :options, :config
22
12
 
13
+ def run(argv=ARGV)
14
+ @options = {}
15
+ global = global_option_parser
16
+ commands = command_option_parser
23
17
  global.order!(argv)
24
18
  command = argv.shift
25
19
  if command
@@ -27,9 +21,7 @@ module Appsignal
27
21
  commands[command].parse!(argv)
28
22
  case command.to_sym
29
23
  when :notify_of_deploy
30
- notify_of_deploy(options)
31
- when :api_check
32
- api_check
24
+ notify_of_deploy
33
25
  end
34
26
  else
35
27
  puts "Command '#{command}' does not exist, run appsignal -h to "\
@@ -47,7 +39,15 @@ module Appsignal
47
39
  Logger.new($stdout)
48
40
  end
49
41
 
50
- def global_option_parser(options)
42
+ def config
43
+ @config ||= Appsignal::Config.new(
44
+ ENV['PWD'],
45
+ options[:environment],
46
+ logger
47
+ )
48
+ end
49
+
50
+ def global_option_parser
51
51
  OptionParser.new do |o|
52
52
  o.banner = 'Usage: appsignal <command> [options]'
53
53
 
@@ -66,7 +66,7 @@ module Appsignal
66
66
  end
67
67
  end
68
68
 
69
- def command_option_parser(options)
69
+ def command_option_parser
70
70
  {
71
71
  'notify_of_deploy' => OptionParser.new do |o|
72
72
  o.banner = 'Usage: appsignal notify_of_deploy [options]'
@@ -86,51 +86,28 @@ module Appsignal
86
86
  o.on '--environment=<rails_env>', "The environment you're deploying to" do |arg|
87
87
  options[:environment] = arg
88
88
  end
89
- end,
90
- 'api_check' => OptionParser.new do |o|
91
- o.banner = %q(Usage: appsignal api_check
92
-
93
- This command checks the config file in config/appsignal.yml
94
- and tries to use the api_keys available in each environment to
95
- see if they work.)
96
89
  end
97
90
  }
98
91
  end
99
92
 
100
- def notify_of_deploy(options)
101
- validate_required_options([:revision, :repository, :user, :environment], options)
93
+ def notify_of_deploy
94
+ validate_config_loaded
95
+ validate_required_options([:revision, :repository, :user, :environment])
96
+
102
97
  Appsignal::Marker.new(
103
98
  {
104
99
  :revision => options[:revision],
105
100
  :repository => options[:repository],
106
101
  :user => options[:user]
107
102
  },
108
- PROJECT_ROOT,
109
- options[:environment],
103
+ config,
110
104
  logger
111
105
  ).transmit
112
106
  end
113
107
 
114
- def api_check
115
- puts "\nReading config/appsignal.yml and attempting to use the config "\
116
- "in order to check if it is set up the way it should be.\n\n"
117
- Appsignal::Config.new(
118
- PROJECT_ROOT, '', logger
119
- ).load_all.each do |env, config|
120
- auth_check = ::Appsignal::AuthCheck.new(
121
- env,
122
- {:config => config, :logger => logger}
123
- )
124
- puts "[#{env}]"
125
- puts ' * Configured not to monitor this environment' unless config[:active]
126
- status, result = auth_check.perform_with_result
127
- puts " * #{result}"
128
- end
129
- end
130
-
131
108
  protected
132
109
 
133
- def validate_required_options(required_options, options)
110
+ def validate_required_options(required_options)
134
111
  missing = required_options.select do |required_option|
135
112
  options[required_option].blank?
136
113
  end
@@ -139,6 +116,13 @@ module Appsignal
139
116
  exit(1)
140
117
  end
141
118
  end
119
+
120
+ def validate_config_loaded
121
+ unless config.loaded?
122
+ puts 'Exiting: No config file or push api key env var found'
123
+ exit(1)
124
+ end
125
+ end
142
126
  end
143
127
  end
144
128
  end