datadog_backup 0.10.2 → 1.0.0.alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54c83b8ce7b212ad43f91a1619363eb683b58cbf7089a24f05b56a5a6a72d250
4
- data.tar.gz: 82986e61ff8d93fed519d03c6b38095cce0c97f0981274bb904d94f7b762e056
3
+ metadata.gz: 04e0cf7686b2efa66728d2bb5d3fd021847368a6522b3b3ba3cc9012a118e32f
4
+ data.tar.gz: 22643d6160be735c52f6bc25c134f250adae2ce9b6751c62ee04265d2082b9d3
5
5
  SHA512:
6
- metadata.gz: ec5087654fd806a8cb562912d460dfbdf7c9e1bdb9d75728ce310b4c0b20ba690b7ccb42584c1c28dc4b5db6ba49cfe46d6cb64ad623e6dfb8939ad07dfed56c
7
- data.tar.gz: 072762f43d59b7727baccbff8a7273a84ab0b0e1fe0fbd1787486d6b1b08faad9b492b39d68a83be90637a09a6f2bbc436f386775b9da4751308253b0ff6bbc8
6
+ metadata.gz: 2293472fd1e18a59755e9e44ed1a39a17eb071e36de772c8ae146070138574f0726b016e9d0049cb8c8b8d625ce440a227ad5cf18a07a82ab77601580b96d01a
7
+ data.tar.gz: 583ff00c4a38005736f1bca85d80fc82eb6fbf8deb107044423ea2e7c3f87e05d82834f0051bf5fb7e5c2e476ea8c810292f93c392a3a1fe4e08a2be4327849e
data/.gitignore CHANGED
@@ -117,3 +117,5 @@ Gemfile.lock
117
117
 
118
118
  spec/helpers/failures.txt
119
119
  backup/
120
+
121
+ .envrc
@@ -0,0 +1 @@
1
+ require: rubocop-rspec
@@ -1,3 +1,43 @@
1
+ # [1.0.0-alpha.2](https://github.com/scribd/datadog_backup/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (2021-01-19)
2
+
3
+
4
+ ### Features
5
+
6
+ * If resource doesn't exist in Datadog, the resource is recreated. ([f2f9e1f](https://github.com/scribd/datadog_backup/commit/f2f9e1f7b244eb3eeaaf121025769f9f327f4e1e))
7
+
8
+
9
+ ### BREAKING CHANGES
10
+
11
+ * `datadog-backup` used to exit with an error if a resource
12
+ wasn't found in Datadog.
13
+
14
+ # [1.0.0-alpha.1](https://github.com/scribd/datadog_backup/compare/v0.11.0...v1.0.0-alpha.1) (2021-01-17)
15
+
16
+
17
+ ### Features
18
+
19
+ * If resource doesn't exist in Datadog, the resource is recreated. ([95d9699](https://github.com/scribd/datadog_backup/commit/95d9699e3fe6a6ecae0d6690dce225680f1d6a8a))
20
+
21
+
22
+ ### BREAKING CHANGES
23
+
24
+ * `datadog-backup` used to exit with an error if a resource
25
+ wasn't found in Datadog.
26
+
27
+ # [0.11.0](https://github.com/scribd/datadog_backup/compare/v0.10.3...v0.11.0) (2021-01-12)
28
+
29
+
30
+ ### Features
31
+
32
+ * Add force-restore flag to allow running in automation ([#46](https://github.com/scribd/datadog_backup/issues/46)) ([e067386](https://github.com/scribd/datadog_backup/commit/e0673862b6f6d86297e1352faaee872f2c4884c8))
33
+
34
+ ## [0.10.3](https://github.com/scribd/datadog_backup/compare/v0.10.2...v0.10.3) (2020-12-11)
35
+
36
+
37
+ ### Performance Improvements
38
+
39
+ * coerce patch release ([bc86649](https://github.com/scribd/datadog_backup/commit/bc86649b874cd5be1da2f6bc0d1b1ecd0728676c))
40
+
1
41
  ## [0.10.2](https://github.com/scribd/datadog_backup/compare/v0.10.1...v0.10.2) (2020-11-03)
2
42
 
3
43
 
data/Gemfile CHANGED
@@ -9,4 +9,5 @@ group :development, :test do
9
9
  gem 'guard-rspec'
10
10
  gem 'rspec'
11
11
  gem 'rubocop'
12
+ gem 'rubocop-rspec'
12
13
  end
data/Guardfile CHANGED
@@ -17,7 +17,7 @@
17
17
  #
18
18
  # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
19
19
 
20
- # Note: The cmd option is now required due to the increasing number of ways
20
+ # NOTE: The cmd option is now required due to the increasing number of ways
21
21
  # rspec may be run, below are examples of the most common uses.
22
22
  # * bundler: 'bundle exec rspec'
23
23
  # * bundler binstubs: 'bin/rspec'
@@ -23,6 +23,7 @@ LOGGER.level = Logger::INFO
23
23
  diff_format: :color,
24
24
  resources: [DatadogBackup::Dashboards, DatadogBackup::Monitors],
25
25
  output_format: :yaml,
26
+ force_restore: false,
26
27
  logger: LOGGER
27
28
  }
28
29
 
@@ -54,7 +55,10 @@ def prereqs
54
55
  opts.on('--dashboards-only') do
55
56
  @options[:resources] = [DatadogBackup::Dashboards]
56
57
  end
57
- opts.on('--json', 'format backups as JSON instead of YAML. Does not impact `diffs` nor `restore`, but do not mix formats in the same backup-dir.') do
58
+ opts.on(
59
+ '--json',
60
+ 'format backups as JSON instead of YAML. Does not impact `diffs` nor `restore`, but do not mix formats in the same backup-dir.'
61
+ ) do
58
62
  @options[:output_format] = :json
59
63
  end
60
64
  opts.on('--no-color', 'removes colored output from diff format') do
@@ -63,6 +67,9 @@ def prereqs
63
67
  opts.on('--diff-format FORMAT', 'one of `color`, `html_simple`, `html`') do |format|
64
68
  @options[:diff_format] = format.to_sym
65
69
  end
70
+ opts.on('--force-restore', 'force restore to Datadog') do
71
+ @options[:force_restore] = true
72
+ end
66
73
  end
67
74
  options.parse!
68
75
 
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_dependency 'concurrent-ruby-edge', '0.6.0'
25
25
  spec.add_dependency 'deepsort', '0.4.5'
26
26
  spec.add_dependency 'diffy', '3.4.0'
27
- spec.add_dependency 'dogapi', '1.42.0'
27
+ spec.add_dependency 'dogapi', '1.44.0'
28
28
 
29
29
  spec.add_development_dependency 'bundler'
30
30
  spec.add_development_dependency 'pry'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
- gem 'datadog_backup', :source => 'https://scribd.jfrog.io/artifactory/gems-local'
5
+ gem 'datadog_backup'
@@ -91,7 +91,7 @@ module DatadogBackup
91
91
  end
92
92
 
93
93
  def matching_resource_instance(klass)
94
- resource_instances.select { |resource_instance| resource_instance.class == klass }.first
94
+ resource_instances.select { |resource_instance| resource_instance.instance_of?(klass) }.first
95
95
  end
96
96
 
97
97
  def resource_instances
@@ -108,27 +108,30 @@ module DatadogBackup
108
108
  id, diff = *future.value!
109
109
  next unless diff
110
110
 
111
- puts '--------------------------------------------------------------------------------'
112
- puts format_diff_output([id, diff])
113
- puts '(r)estore to Datadog, overwrite local changes and (d)ownload, (s)kip, or (q)uit?'
114
- response = $stdin.gets.chomp
115
- case response
116
- when 'q'
117
- exit
118
- when 'r'
119
- puts "Restoring #{id} to Datadog."
120
- definitive_resource_instance(id).update_with_200(id, definitive_resource_instance(id).load_from_file_by_id(id))
121
- when 'd'
122
- puts "Downloading #{id} from Datadog."
123
- definitive_resource_instance(id).get_and_write_file(id)
124
- when 's'
125
- next
111
+ if @options[:force_restore]
112
+ definitive_resource_instance(id).restore(id)
126
113
  else
127
- puts 'Invalid response, please try again.'
114
+ puts '--------------------------------------------------------------------------------'
115
+ puts format_diff_output([id, diff])
116
+ puts '(r)estore to Datadog, overwrite local changes and (d)ownload, (s)kip, or (q)uit?'
128
117
  response = $stdin.gets.chomp
118
+ case response
119
+ when 'q'
120
+ exit
121
+ when 'r'
122
+ puts "Restoring #{id} to Datadog."
123
+ definitive_resource_instance(id).restore(id)
124
+ when 'd'
125
+ puts "Downloading #{id} from Datadog."
126
+ definitive_resource_instance(id).get_and_write_file(id)
127
+ when 's'
128
+ next
129
+ else
130
+ puts 'Invalid response, please try again.'
131
+ response = $stdin.gets.chomp
132
+ end
129
133
  end
130
134
  end
131
-
132
135
  watcher.join if watcher.status
133
136
  end
134
137
 
@@ -24,55 +24,48 @@ module DatadogBackup
24
24
  raise 'subclass is expected to implement #backup'
25
25
  end
26
26
 
27
- # Calls out to Datadog and checks for a '200' response
28
- def client_with_200(method, *id)
29
- max_retries = 6
30
- retries ||= 0
31
-
32
- response = client.send(method, *id)
33
-
34
- # logger.debug response
35
- raise "Method #{method} failed with error #{response}" unless response[0] == '200'
36
-
37
- response[1]
38
- rescue ::Net::OpenTimeout => e
39
- if (retries += 1) <= max_retries
40
- sleep(0.1 * retries**5) # 0.1, 3.2, 24.3, 102.4 seconds per retry
41
- retry
42
- else
43
- raise "Method #{method} failed with error #{e.message}"
44
- end
45
- end
46
-
47
27
  # Returns the diffy diff.
48
28
  # Optionally, supply an array of keys to remove from comparison
49
- def diff(id, banlist = [])
50
- current = except(get_by_id(id), banlist).deep_sort.to_yaml
51
- filesystem = except(load_from_file_by_id(id), banlist).deep_sort.to_yaml
29
+ def diff(id)
30
+ current = except(get_by_id(id)).deep_sort.to_yaml
31
+ filesystem = except(load_from_file_by_id(id)).deep_sort.to_yaml
52
32
  result = ::Diffy::Diff.new(current, filesystem, include_plus_and_minus_in_html: true).to_s(diff_format)
53
33
  logger.debug("Compared ID #{id} and found #{result}")
54
34
  result
55
35
  end
56
36
 
57
37
  # Returns a hash with banlist elements removed
58
- def except(hash, banlist)
38
+ def except(hash)
59
39
  hash.tap do # tap returns self
60
- banlist.each do |key|
40
+ @banlist.each do |key|
61
41
  hash.delete(key) # delete returns the value at the deleted key, hence the tap wrapper
62
42
  end
63
43
  end
64
44
  end
65
45
 
46
+ def get(id)
47
+ with_200 do
48
+ api_service.request(Net::HTTP::Get, "/api/#{api_version}/#{api_resource_name}/#{id}", nil, nil, false)
49
+ end
50
+ end
51
+
52
+ def get_all
53
+ with_200 do
54
+ api_service.request(Net::HTTP::Get, "/api/#{api_version}/#{api_resource_name}", nil, nil, false)
55
+ end
56
+ end
57
+
66
58
  def get_and_write_file(id)
67
59
  write_file(dump(get_by_id(id)), filename(id))
68
60
  end
69
61
 
70
- def get_by_id(_id)
71
- raise 'subclass is expected to implement #get_by_id(id)'
62
+ def get_by_id(id)
63
+ except(get(id))
72
64
  end
73
65
 
74
66
  def initialize(options)
75
67
  @options = options
68
+ @banlist = []
76
69
  ::FileUtils.mkdir_p(mydir)
77
70
  end
78
71
 
@@ -80,25 +73,46 @@ module DatadogBackup
80
73
  self.class.to_s.split(':').last.downcase
81
74
  end
82
75
 
83
- def restore
84
- raise 'subclass is expected to implement #restore'
76
+ # Calls out to Datadog and checks for a '200' response
77
+ def create(body)
78
+ result = with_200 do
79
+ api_service.request(Net::HTTP::Post, "/api/#{api_version}/#{api_resource_name}", nil, body, true)
80
+ end
81
+ logger.warn 'Successfully created in datadog.'
82
+ result
85
83
  end
86
84
 
85
+ # Calls out to Datadog and checks for a '200' response
87
86
  def update(id, body)
88
- api_service.request(Net::HTTP::Put, "/api/#{api_version}/#{api_resource_name}/#{id}", nil, body, true)
87
+ result = with_200 do
88
+ api_service.request(Net::HTTP::Put, "/api/#{api_version}/#{api_resource_name}/#{id}", nil, body, true)
89
+ end
90
+ logger.warn 'Successfully restored to datadog.'
91
+ result
89
92
  end
90
93
 
91
- # Calls out to Datadog and checks for a '200' response
92
- def update_with_200(id, body)
94
+ def restore(id)
95
+ body = load_from_file_by_id(id)
96
+ begin
97
+ update(id, body)
98
+ rescue RuntimeError => e
99
+ if e.message.include?('Request failed with error ["404"')
100
+ new_id = create(body).fetch('id')
101
+
102
+ FileUtils.rm(find_file_by_id(id))
103
+ get_and_write_file(new_id)
104
+ else
105
+ raise e.message
106
+ end
107
+ end
108
+ end
109
+
110
+ def with_200
93
111
  max_retries = 6
94
112
  retries ||= 0
95
113
 
96
- response = update(id, body)
97
-
98
- # logger.debug response
99
- raise "Update failed with error #{response}" unless response[0] == '200'
100
-
101
- logger.warn "Successfully restored #{id} to datadog."
114
+ response = yield
115
+ raise "Request failed with error #{response}" unless response[0] == '200'
102
116
 
103
117
  response[1]
104
118
  rescue ::Net::OpenTimeout => e
@@ -106,7 +120,7 @@ module DatadogBackup
106
120
  sleep(0.1 * retries**5) # 0.1, 3.2, 24.3, 102.4 seconds per retry
107
121
  retry
108
122
  else
109
- raise "Update failed with error #{e.message}"
123
+ raise "Net::OpenTimeout: #{e.message}"
110
124
  end
111
125
  end
112
126
  end
@@ -2,10 +2,8 @@
2
2
 
3
3
  module DatadogBackup
4
4
  class Dashboards < Core
5
- BANLIST = %w[modified_at url]
6
-
7
5
  def all_boards
8
- client_with_200(:get_all_boards).fetch('dashboards')
6
+ get_all.fetch('dashboards')
9
7
  end
10
8
 
11
9
  def api_service
@@ -37,14 +35,9 @@ module DatadogBackup
37
35
  Concurrent::Promises.zip(*futures).value!
38
36
  end
39
37
 
40
- def diff(id)
41
- super(id, BANLIST)
42
- end
43
-
44
- def get_by_id(id)
45
- except(client_with_200(:get_board, id), BANLIST)
38
+ def initialize(options)
39
+ super(options)
40
+ @banlist = %w[modified_at url].freeze
46
41
  end
47
-
48
- def restore!; end
49
42
  end
50
43
  end
@@ -31,9 +31,10 @@ module DatadogBackup
31
31
  end
32
32
 
33
33
  def dump(object)
34
- if output_format == :json
34
+ case output_format
35
+ when :json
35
36
  JSON.pretty_generate(object.deep_sort)
36
- elsif output_format == :yaml
37
+ when :yaml
37
38
  YAML.dump(object.deep_sort)
38
39
  else
39
40
  raise 'invalid output_format specified or not specified'
@@ -45,7 +46,7 @@ module DatadogBackup
45
46
  end
46
47
 
47
48
  def file_type(filepath)
48
- ::File.extname(filepath).strip.downcase[1..-1].to_sym
49
+ ::File.extname(filepath).strip.downcase[1..].to_sym
49
50
  end
50
51
 
51
52
  def find_file_by_id(id)
@@ -53,9 +54,10 @@ module DatadogBackup
53
54
  end
54
55
 
55
56
  def load_from_file(string, output_format)
56
- if output_format == :json
57
+ case output_format
58
+ when :json
57
59
  JSON.parse(string)
58
- elsif output_format == :yaml
60
+ when :yaml
59
61
  YAML.safe_load(string)
60
62
  else
61
63
  raise 'invalid output_format specified or not specified'
@@ -2,11 +2,8 @@
2
2
 
3
3
  module DatadogBackup
4
4
  class Monitors < Core
5
- API_SERVICE_NAME = :@monitor_svc
6
- BANLIST = %w[overall_state overall_state_modified matching_downtimes modified]
7
-
8
5
  def all_monitors
9
- @all_monitors ||= client_with_200(:get_all_monitors)
6
+ @all_monitors ||= get_all
10
7
  end
11
8
 
12
9
  def api_service
@@ -29,14 +26,13 @@ module DatadogBackup
29
26
  end
30
27
  end
31
28
 
32
- def diff(id)
33
- super(id, BANLIST)
34
- end
35
-
36
29
  def get_by_id(id)
37
- except(all_monitors.select { |monitor| monitor['id'].to_s == id.to_s }.first, BANLIST)
30
+ except(all_monitors.select { |monitor| monitor['id'].to_s == id.to_s }.first)
38
31
  end
39
32
 
40
- def restore!; end
33
+ def initialize(options)
34
+ super(options)
35
+ @banlist = %w[overall_state overall_state_modified matching_downtimes modified].freeze
36
+ end
41
37
  end
42
38
  end
@@ -42,5 +42,9 @@ module DatadogBackup
42
42
  def resources
43
43
  @options[:resources]
44
44
  end
45
+
46
+ def force_restore
47
+ @options[:force_restore]
48
+ end
45
49
  end
46
50
  end
@@ -11,7 +11,7 @@ module DatadogBackup
11
11
 
12
12
  def self.watcher(logger)
13
13
  Thread.new(TPOOL) do |pool|
14
- while pool.queue_length > 0
14
+ while pool.queue_length.positive?
15
15
  sleep 2
16
16
  logger.info("#{pool.queue_length} tasks remaining for execution.")
17
17
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DatadogBackup
4
- VERSION = '0.10.2'
4
+ VERSION = '1.0.0.alpha.2'
5
5
  end
@@ -3,6 +3,7 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe DatadogBackup::Cli do
6
+ let(:api_service_double) { double(Dogapi::APIService) }
6
7
  let(:client_double) { double }
7
8
  let(:tempdir) { Dir.mktmpdir }
8
9
  let(:options) do
@@ -18,10 +19,10 @@ describe DatadogBackup::Cli do
18
19
  resources: [DatadogBackup::Dashboards]
19
20
  }
20
21
  end
21
- let(:cli) { DatadogBackup::Cli.new(options) }
22
+ let(:cli) { described_class.new(options) }
22
23
  let(:dashboards) { DatadogBackup::Dashboards.new(options) }
23
24
 
24
- before(:example) do
25
+ before do
25
26
  allow(cli).to receive(:resource_instances).and_return([dashboards])
26
27
  end
27
28
 
@@ -38,13 +39,28 @@ describe DatadogBackup::Cli do
38
39
  }
39
40
  ]
40
41
  end
41
- before(:example) do
42
+
43
+ before do
42
44
  dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/stillthere.json")
43
45
  dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/alsostillthere.json")
44
46
  dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/deleted.json")
45
47
 
46
- allow(client_double).to receive(:get_all_boards).and_return(all_boards)
47
- allow(client_double).to receive(:get_board).and_return(['200', {}])
48
+ allow(client_double).to receive(:instance_variable_get).with(:@dashboard_service).and_return(api_service_double)
49
+ allow(api_service_double).to receive(:request).with(Net::HTTP::Get,
50
+ '/api/v1/dashboard',
51
+ nil,
52
+ nil,
53
+ false).and_return(all_boards)
54
+ allow(api_service_double).to receive(:request).with(Net::HTTP::Get,
55
+ '/api/v1/dashboard/stillthere',
56
+ nil,
57
+ nil,
58
+ false).and_return(['200', {}])
59
+ allow(api_service_double).to receive(:request).with(Net::HTTP::Get,
60
+ '/api/v1/dashboard/alsostillthere',
61
+ nil,
62
+ nil,
63
+ false).and_return(['200', {}])
48
64
  end
49
65
 
50
66
  it 'deletes the file locally as well' do
@@ -55,16 +71,18 @@ describe DatadogBackup::Cli do
55
71
  end
56
72
 
57
73
  describe '#diffs' do
58
- before(:example) do
74
+ subject { cli.diffs }
75
+
76
+ before do
59
77
  dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/diffs1.json")
60
78
  dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/diffs2.json")
61
79
  dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/diffs3.json")
62
80
  allow(dashboards).to receive(:get_by_id).and_return({ 'text' => 'diff2' })
63
81
  allow(cli).to receive(:initialize_client).and_return(client_double)
64
82
  end
65
- subject { cli.diffs }
83
+
66
84
  it {
67
- is_expected.to include(
85
+ expect(subject).to include(
68
86
  " ---\n id: diffs1\n ---\n-text: diff2\n+text: diff\n",
69
87
  " ---\n id: diffs3\n ---\n-text: diff2\n+text: diff\n",
70
88
  " ---\n id: diffs2\n ---\n-text: diff2\n+text: diff\n"
@@ -73,44 +91,46 @@ describe DatadogBackup::Cli do
73
91
  end
74
92
 
75
93
  describe '#restore' do
76
- before(:example) do
94
+ subject { cli.restore }
95
+
96
+ before do
77
97
  dashboards.write_file('{"text": "diff"}', "#{tempdir}/dashboards/diffs1.json")
78
98
  allow(dashboards).to receive(:get_by_id).and_return({ 'text' => 'diff2' })
79
99
  allow(cli).to receive(:initialize_client).and_return(client_double)
80
100
  end
81
101
 
82
- subject { cli.restore }
83
-
84
102
  example 'starts interactive restore' do
85
103
  allow($stdin).to receive(:gets).and_return('q')
86
- begin
87
- expect { subject }.to(
88
- output(/\(r\)estore to Datadog, overwrite local changes and \(d\)ownload, \(s\)kip, or \(q\)uit\?/).to_stdout
89
- .and(raise_error(SystemExit))
90
- )
91
- end
104
+
105
+ expect { subject }.to(
106
+ output(/\(r\)estore to Datadog, overwrite local changes and \(d\)ownload, \(s\)kip, or \(q\)uit\?/).to_stdout
107
+ .and(raise_error(SystemExit))
108
+ )
92
109
  end
93
110
 
94
111
  example 'restore' do
95
112
  allow($stdin).to receive(:gets).and_return('r')
96
- expect(dashboards).to receive(:update_with_200).with('diffs1', { 'text' => 'diff' })
113
+ expect(dashboards).to receive(:update).with('diffs1', { 'text' => 'diff' })
97
114
  subject
98
115
  end
116
+
99
117
  example 'download' do
100
118
  allow($stdin).to receive(:gets).and_return('d')
101
119
  expect(dashboards).to receive(:write_file).with(%({\n "text": "diff2"\n}), "#{tempdir}/dashboards/diffs1.json")
102
120
  subject
103
121
  end
122
+
104
123
  example 'skip' do
105
124
  allow($stdin).to receive(:gets).and_return('s')
106
- expect(dashboards).to_not receive(:write_file)
107
- expect(dashboards).to_not receive(:update)
125
+ expect(dashboards).not_to receive(:write_file)
126
+ expect(dashboards).not_to receive(:update)
108
127
  subject
109
128
  end
129
+
110
130
  example 'quit' do
111
131
  allow($stdin).to receive(:gets).and_return('q')
112
- expect(dashboards).to_not receive(:write_file)
113
- expect(dashboards).to_not receive(:update)
132
+ expect(dashboards).not_to receive(:write_file)
133
+ expect(dashboards).not_to receive(:update)
114
134
  expect { subject }.to raise_error(SystemExit)
115
135
  end
116
136
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe DatadogBackup::Core do
@@ -5,7 +7,7 @@ describe DatadogBackup::Core do
5
7
  let(:client_double) { double }
6
8
  let(:tempdir) { Dir.mktmpdir }
7
9
  let(:core) do
8
- DatadogBackup::Core.new(
10
+ described_class.new(
9
11
  action: 'backup',
10
12
  api_service: api_service_double,
11
13
  client: client_double,
@@ -19,24 +21,19 @@ describe DatadogBackup::Core do
19
21
 
20
22
  describe '#client' do
21
23
  subject { core.client }
24
+
22
25
  it { is_expected.to eq client_double }
23
26
  end
24
27
 
25
- describe '#client_with_200' do
26
- subject { core.client_with_200(:get_all_boards) }
27
-
28
+ describe '#with_200' do
28
29
  context 'with 200' do
29
- before(:example) do
30
- allow(client_double).to receive(:get_all_boards).and_return(['200', { foo: :bar }])
31
- end
30
+ subject { core.with_200 { ['200', { foo: :bar }] } }
32
31
 
33
32
  it { is_expected.to eq({ foo: :bar }) }
34
33
  end
35
34
 
36
35
  context 'with not 200' do
37
- before(:example) do
38
- allow(client_double).to receive(:get_all_boards).and_return(['401', {}])
39
- end
36
+ subject { core.with_200 { ['400', 'Error message'] } }
40
37
 
41
38
  it 'raises an error' do
42
39
  expect { subject }.to raise_error(RuntimeError)
@@ -45,43 +42,33 @@ describe DatadogBackup::Core do
45
42
  end
46
43
 
47
44
  describe '#diff' do
48
- before(:example) do
45
+ subject { core.diff('diff') }
46
+
47
+ before do
49
48
  allow(core).to receive(:get_by_id).and_return({ 'text' => 'diff1', 'extra' => 'diff1' })
50
49
  core.write_file('{"text": "diff2", "extra": "diff2"}', "#{tempdir}/core/diff.json")
51
50
  end
52
51
 
53
- context 'without banlist' do
54
- subject { core.diff('diff') }
55
- it {
56
- is_expected.to eq <<~EOF
57
- ---
58
- -extra: diff1
59
- -text: diff1
60
- +extra: diff2
61
- +text: diff2
62
- EOF
63
- }
64
- end
65
-
66
- context 'with banlist' do
67
- subject { core.diff('diff', ['extra']) }
68
- it {
69
- is_expected.to eq <<~EOF
70
- ---
71
- -text: diff1
72
- +text: diff2
73
- EOF
74
- }
75
- end
52
+ it {
53
+ expect(subject).to eq <<~EOF
54
+ ---
55
+ -extra: diff1
56
+ -text: diff1
57
+ +extra: diff2
58
+ +text: diff2
59
+ EOF
60
+ }
76
61
  end
77
62
 
78
63
  describe '#except' do
79
- subject { core.except({ a: :b, b: :c }, [:b]) }
80
- it { is_expected.to eq({ a: :b }) }
64
+ subject { core.except({ a: :b, b: :c }) }
65
+
66
+ it { is_expected.to eq({ a: :b, b: :c }) }
81
67
  end
82
68
 
83
69
  describe '#initialize' do
84
70
  subject { core }
71
+
85
72
  it 'makes the subdirectories' do
86
73
  expect(FileUtils).to receive(:mkdir_p).with("#{tempdir}/core")
87
74
  subject
@@ -90,18 +77,93 @@ describe DatadogBackup::Core do
90
77
 
91
78
  describe '#myclass' do
92
79
  subject { core.myclass }
80
+
93
81
  it { is_expected.to eq 'core' }
94
82
  end
95
83
 
84
+ describe '#create' do
85
+ subject { core.create({ 'a' => 'b' }) }
86
+
87
+ example 'it calls Dogapi::APIService.request' do
88
+ stub_const('Dogapi::APIService::API_VERSION', 'v1')
89
+ allow(core).to receive(:api_service).and_return(api_service_double)
90
+ allow(core).to receive(:api_version).and_return('v1')
91
+ allow(core).to receive(:api_resource_name).and_return('dashboard')
92
+ expect(api_service_double).to receive(:request).with(Net::HTTP::Post,
93
+ '/api/v1/dashboard',
94
+ nil,
95
+ { 'a' => 'b' },
96
+ true).and_return(['200', { 'id' => 'whatever-id-abc' }])
97
+ subject
98
+ end
99
+ end
100
+
96
101
  describe '#update' do
97
- subject { core.update_with_200('abc-123-def', '{"a": "b"}') }
102
+ subject { core.update('abc-123-def', { 'a' => 'b' }) }
103
+
98
104
  example 'it calls Dogapi::APIService.request' do
99
105
  stub_const('Dogapi::APIService::API_VERSION', 'v1')
100
106
  allow(core).to receive(:api_service).and_return(api_service_double)
101
107
  allow(core).to receive(:api_version).and_return('v1')
102
108
  allow(core).to receive(:api_resource_name).and_return('dashboard')
103
- expect(api_service_double).to receive(:request).with(Net::HTTP::Put, '/api/v1/dashboard/abc-123-def', nil, '{"a": "b"}', true).and_return(%w[200 Created])
109
+ expect(api_service_double).to receive(:request).with(Net::HTTP::Put,
110
+ '/api/v1/dashboard/abc-123-def',
111
+ nil,
112
+ { 'a' => 'b' },
113
+ true).and_return(['200', { 'id' => 'whatever-id-abc' }])
104
114
  subject
105
115
  end
106
116
  end
117
+
118
+ describe '#restore' do
119
+ before do
120
+ allow(core).to receive(:api_service).and_return(api_service_double)
121
+ allow(core).to receive(:api_version).and_return('api-version-string')
122
+ allow(core).to receive(:api_resource_name).and_return('api-resource-name-string')
123
+ allow(api_service_double).to receive(:request).with(Net::HTTP::Get,
124
+ '/api/api-version-string/api-resource-name-string/abc-123-def',
125
+ nil,
126
+ nil,
127
+ false).and_return(['200', { test: :ok }])
128
+ allow(api_service_double).to receive(:request).with(Net::HTTP::Get,
129
+ '/api/api-version-string/api-resource-name-string/bad-123-id',
130
+ nil,
131
+ nil,
132
+ false).and_return(['404', { error: :blahblah_not_found }])
133
+ allow(core).to receive(:load_from_file_by_id).and_return({ 'load' => 'ok' })
134
+ end
135
+
136
+ context 'when id exists' do
137
+ subject { core.restore('abc-123-def') }
138
+
139
+ example 'it calls out to update' do
140
+ expect(core).to receive(:update).with('abc-123-def', { 'load' => 'ok' })
141
+ subject
142
+ end
143
+ end
144
+
145
+ context 'when id does not exist' do
146
+ subject { core.restore('bad-123-id') }
147
+
148
+ before do
149
+ allow(api_service_double).to receive(:request).with(Net::HTTP::Put,
150
+ '/api/api-version-string/api-resource-name-string/bad-123-id',
151
+ nil, { 'load' => 'ok' },
152
+ true).and_return(['404', { 'Error' => 'my not found' }])
153
+ allow(api_service_double).to receive(:request).with(Net::HTTP::Post,
154
+ '/api/api-version-string/api-resource-name-string',
155
+ nil,
156
+ { 'load' => 'ok' },
157
+ true).and_return(['200', { 'id' => 'my-new-id' }])
158
+ end
159
+
160
+ example 'it calls out to create then saves the new file and deletes the new file' do
161
+ expect(core).to receive(:create).with({ 'load' => 'ok' }).and_return({ 'id' => 'my-new-id' })
162
+ expect(core).to receive(:get_and_write_file).with('my-new-id')
163
+ allow(core).to receive(:find_file_by_id).with('bad-123-id').and_return('/path/to/bad-123-id.json')
164
+ expect(FileUtils).to receive(:rm).with('/path/to/bad-123-id.json')
165
+ subject
166
+ end
167
+ end
168
+ end
107
169
  end
@@ -3,10 +3,11 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe DatadogBackup::Dashboards do
6
+ let(:api_service_double) { double(Dogapi::APIService) }
6
7
  let(:client_double) { double }
7
8
  let(:tempdir) { Dir.mktmpdir }
8
9
  let(:dashboards) do
9
- DatadogBackup::Dashboards.new(
10
+ described_class.new(
10
11
  action: 'backup',
11
12
  client: client_double,
12
13
  backup_dir: tempdir,
@@ -58,9 +59,13 @@ describe DatadogBackup::Dashboards do
58
59
  'title' => 'example dashboard'
59
60
  }
60
61
  end
61
- before(:example) do
62
- allow(client_double).to receive(:get_all_boards).and_return(all_boards)
63
- allow(client_double).to receive(:get_board).and_return(example_dashboard)
62
+
63
+ before do
64
+ allow(client_double).to receive(:instance_variable_get).with(:@dashboard_service).and_return(api_service_double)
65
+ allow(api_service_double).to receive(:request).with(Net::HTTP::Get, '/api/v1/dashboard', nil, nil,
66
+ false).and_return(all_boards)
67
+ allow(api_service_double).to receive(:request).with(Net::HTTP::Get, '/api/v1/dashboard/abc-123-def', nil, nil,
68
+ false).and_return(example_dashboard)
64
69
  end
65
70
 
66
71
  describe '#backup' do
@@ -79,24 +84,38 @@ describe DatadogBackup::Dashboards do
79
84
  describe '#all_boards' do
80
85
  subject { dashboards.all_boards }
81
86
 
82
- it 'calls get_all_boards' do
83
- subject
84
- expect(client_double).to have_received(:get_all_boards)
85
- end
86
-
87
87
  it { is_expected.to eq [dashboard_description] }
88
88
  end
89
89
 
90
90
  describe '#diff' do
91
91
  it 'calls the api only once' do
92
92
  dashboards.write_file('{"a":"b"}', dashboards.filename('abc-123-def'))
93
- dashboards.diff('abc-123-def')
94
- expect(client_double).to have_received(:get_board).exactly(1).times
93
+ expect(dashboards.diff('abc-123-def')).to eq(<<~EOF
94
+ ---
95
+ -description: example dashboard
96
+ -graphs:
97
+ -- definition:
98
+ - requests:
99
+ - - q: min:foo.bar{a:b}
100
+ - stacked: false
101
+ - viz: timeseries
102
+ - title: example graph
103
+ -title: example dashboard
104
+ +a: b
105
+ EOF
106
+ )
95
107
  end
96
108
  end
97
109
 
110
+ describe '#except' do
111
+ subject { dashboards.except({ :a => :b, 'modified_at' => :c, 'url' => :d }) }
112
+
113
+ it { is_expected.to eq({ a: :b }) }
114
+ end
115
+
98
116
  describe '#get_by_id' do
99
117
  subject { dashboards.get_by_id('abc-123-def') }
118
+
100
119
  it { is_expected.to eq board_abc_123_def }
101
120
  end
102
121
  end
@@ -27,55 +27,61 @@ describe DatadogBackup::LocalFilesystem do
27
27
  end
28
28
 
29
29
  describe '#all_files' do
30
- before(:example) do
30
+ subject { core.all_files }
31
+
32
+ before do
31
33
  File.new("#{tempdir}/all_files.json", 'w')
32
34
  end
33
35
 
34
- after(:example) do
36
+ after do
35
37
  FileUtils.rm "#{tempdir}/all_files.json"
36
38
  end
37
39
 
38
- subject { core.all_files }
39
40
  it { is_expected.to eq(["#{tempdir}/all_files.json"]) }
40
41
  end
41
42
 
42
43
  describe '#all_file_ids_for_selected_resources' do
43
- before(:example) do
44
+ subject { core.all_file_ids_for_selected_resources }
45
+
46
+ before do
44
47
  Dir.mkdir("#{tempdir}/dashboards")
45
48
  Dir.mkdir("#{tempdir}/monitors")
46
49
  File.new("#{tempdir}/dashboards/all_files.json", 'w')
47
50
  File.new("#{tempdir}/monitors/12345.json", 'w')
48
51
  end
49
52
 
50
- after(:example) do
53
+ after do
51
54
  FileUtils.rm "#{tempdir}/dashboards/all_files.json"
52
55
  FileUtils.rm "#{tempdir}/monitors/12345.json"
53
56
  end
54
57
 
55
- subject { core.all_file_ids_for_selected_resources }
56
58
  it { is_expected.to eq(['all_files']) }
57
59
  end
58
60
 
59
61
  describe '#class_from_id' do
60
- before(:example) do
62
+ subject { core.class_from_id('abc-123-def') }
63
+
64
+ before do
61
65
  core.write_file('abc', "#{tempdir}/core/abc-123-def.json")
62
66
  end
63
67
 
64
- after(:example) do
68
+ after do
65
69
  FileUtils.rm "#{tempdir}/core/abc-123-def.json"
66
70
  end
67
- subject { core.class_from_id('abc-123-def') }
71
+
68
72
  it { is_expected.to eq DatadogBackup::Core }
69
73
  end
70
74
 
71
75
  describe '#dump' do
72
76
  context ':json' do
73
77
  subject { core.dump({ a: :b }) }
78
+
74
79
  it { is_expected.to eq(%({\n "a": "b"\n})) }
75
80
  end
76
81
 
77
82
  context ':yaml' do
78
83
  subject { core_yaml.dump({ 'a' => 'b' }) }
84
+
79
85
  it { is_expected.to eq(%(---\na: b\n)) }
80
86
  end
81
87
  end
@@ -83,80 +89,94 @@ describe DatadogBackup::LocalFilesystem do
83
89
  describe '#filename' do
84
90
  context ':json' do
85
91
  subject { core.filename('abc-123-def') }
92
+
86
93
  it { is_expected.to eq("#{tempdir}/core/abc-123-def.json") }
87
94
  end
88
95
 
89
96
  context ':yaml' do
90
97
  subject { core_yaml.filename('abc-123-def') }
98
+
91
99
  it { is_expected.to eq("#{tempdir}/core/abc-123-def.yaml") }
92
100
  end
93
101
  end
94
102
 
95
103
  describe '#file_type' do
96
- before(:example) do
104
+ subject { core.file_type("#{tempdir}/file_type.json") }
105
+
106
+ before do
97
107
  File.new("#{tempdir}/file_type.json", 'w')
98
108
  end
99
109
 
100
- after(:example) do
110
+ after do
101
111
  FileUtils.rm "#{tempdir}/file_type.json"
102
112
  end
103
113
 
104
- subject { core.file_type("#{tempdir}/file_type.json") }
105
114
  it { is_expected.to eq :json }
106
115
  end
107
116
 
108
117
  describe '#find_file_by_id' do
109
- before(:example) do
118
+ subject { core.find_file_by_id('find_file') }
119
+
120
+ before do
110
121
  File.new("#{tempdir}/find_file.json", 'w')
111
122
  end
112
123
 
113
- after(:example) do
124
+ after do
114
125
  FileUtils.rm "#{tempdir}/find_file.json"
115
126
  end
116
127
 
117
- subject { core.find_file_by_id('find_file') }
118
128
  it { is_expected.to eq "#{tempdir}/find_file.json" }
119
129
  end
120
130
 
121
131
  describe '#load_from_file' do
122
132
  context ':json' do
123
133
  subject { core.load_from_file(%({\n "a": "b"\n}), :json) }
134
+
124
135
  it { is_expected.to eq('a' => 'b') }
125
136
  end
126
137
 
127
138
  context ':yaml' do
128
139
  subject { core.load_from_file(%(---\na: b\n), :yaml) }
140
+
129
141
  it { is_expected.to eq('a' => 'b') }
130
142
  end
131
143
  end
132
144
 
133
145
  describe '#load_from_file_by_id' do
134
146
  context 'written in json read in yaml mode' do
135
- before(:example) { core.write_file(%({"a": "b"}), "#{tempdir}/core/abc-123-def.json") }
136
- after(:example) { FileUtils.rm "#{tempdir}/core/abc-123-def.json" }
137
-
138
147
  subject { core_yaml.load_from_file_by_id('abc-123-def') }
148
+
149
+ before { core.write_file(%({"a": "b"}), "#{tempdir}/core/abc-123-def.json") }
150
+
151
+ after { FileUtils.rm "#{tempdir}/core/abc-123-def.json" }
152
+
139
153
  it { is_expected.to eq('a' => 'b') }
140
154
  end
141
- context 'written in yaml read in json mode' do
142
- before(:example) { core.write_file(%(---\na: b), "#{tempdir}/core/abc-123-def.yaml") }
143
- after(:example) { FileUtils.rm "#{tempdir}/core/abc-123-def.yaml" }
144
155
 
156
+ context 'written in yaml read in json mode' do
145
157
  subject { core.load_from_file_by_id('abc-123-def') }
158
+
159
+ before { core.write_file(%(---\na: b), "#{tempdir}/core/abc-123-def.yaml") }
160
+
161
+ after { FileUtils.rm "#{tempdir}/core/abc-123-def.yaml" }
162
+
146
163
  it { is_expected.to eq('a' => 'b') }
147
164
  end
148
165
 
149
166
  context 'Integer as parameter' do
150
- before(:example) { core.write_file(%(---\na: b), "#{tempdir}/core/12345.yaml") }
151
- after(:example) { FileUtils.rm "#{tempdir}/core/12345.yaml" }
152
-
153
167
  subject { core.load_from_file_by_id(12_345) }
168
+
169
+ before { core.write_file(%(---\na: b), "#{tempdir}/core/12345.yaml") }
170
+
171
+ after { FileUtils.rm "#{tempdir}/core/12345.yaml" }
172
+
154
173
  it { is_expected.to eq('a' => 'b') }
155
174
  end
156
175
  end
157
176
 
158
177
  describe '#write_file' do
159
178
  subject { core.write_file('abc123', "#{tempdir}/core/abc-123-def.json") }
179
+
160
180
  let(:file_like_object) { double }
161
181
 
162
182
  it 'writes a file to abc-123-def.json' do
@@ -3,10 +3,11 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe DatadogBackup::Monitors do
6
+ let(:api_service_double) { double(Dogapi::APIService) }
6
7
  let(:client_double) { double }
7
8
  let(:tempdir) { Dir.mktmpdir }
8
9
  let(:monitors) do
9
- DatadogBackup::Monitors.new(
10
+ described_class.new(
10
11
  action: 'backup',
11
12
  client: client_double,
12
13
  backup_dir: tempdir,
@@ -47,19 +48,18 @@ describe DatadogBackup::Monitors do
47
48
  monitor_description
48
49
  ]
49
50
  end
50
- before(:example) do
51
- allow(client_double).to receive(:get_all_monitors).and_return(all_monitors)
52
- allow(client_double).to receive(:get_monitor).and_return(example_monitor)
51
+
52
+ before do
53
+ allow(client_double).to receive(:instance_variable_get).with(:@monitor_svc).and_return(api_service_double)
54
+ allow(api_service_double).to receive(:request).with(Net::HTTP::Get, '/api/v1/monitor', nil, nil,
55
+ false).and_return(all_monitors)
56
+ allow(api_service_double).to receive(:request).with(Net::HTTP::Get, '/api/v1/dashboard/123455', nil, nil,
57
+ false).and_return(example_monitor)
53
58
  end
54
59
 
55
60
  describe '#all_monitors' do
56
61
  subject { monitors.all_monitors }
57
62
 
58
- it 'calls get_all_monitors' do
59
- subject
60
- expect(client_double).to have_received(:get_all_monitors)
61
- end
62
-
63
63
  it { is_expected.to eq [monitor_description] }
64
64
  end
65
65
 
@@ -76,10 +76,10 @@ describe DatadogBackup::Monitors do
76
76
  end
77
77
  end
78
78
 
79
- describe '#diff' do
79
+ describe '#diff and #except' do
80
80
  example 'it ignores `overall_state` and `overall_state_modified`' do
81
81
  monitors.write_file(monitors.dump(monitor_description), monitors.filename(123_455))
82
- allow(client_double).to receive(:get_all_monitors).and_return(
82
+ allow(api_service_double).to receive(:request).and_return(
83
83
  [
84
84
  '200',
85
85
  [
@@ -88,8 +88,8 @@ describe DatadogBackup::Monitors do
88
88
  'message' => 'foo',
89
89
  'id' => 123_455,
90
90
  'name' => 'foo',
91
- 'overall_state' => 'NO DATA',
92
- 'overall_state_modified' => '2020-07-27T22:55:55+00:00'
91
+ 'overall_state' => 'ZZZZZZZZZZZZZZZZZZZZZZZZZZZ',
92
+ 'overall_state_modified' => '9999-07-27T22:55:55+00:00'
93
93
  }
94
94
  ]
95
95
  ]
@@ -103,16 +103,20 @@ describe DatadogBackup::Monitors do
103
103
 
104
104
  describe '#filename' do
105
105
  subject { monitors.filename(123_455) }
106
+
106
107
  it { is_expected.to eq("#{tempdir}/monitors/123455.json") }
107
108
  end
108
109
 
109
110
  describe '#get_by_id' do
110
111
  context 'Integer' do
111
112
  subject { monitors.get_by_id(123_455) }
113
+
112
114
  it { is_expected.to eq monitor_description }
113
115
  end
116
+
114
117
  context 'String' do
115
118
  subject { monitors.get_by_id('123455') }
119
+
116
120
  it { is_expected.to eq monitor_description }
117
121
  end
118
122
  end
@@ -47,7 +47,7 @@ describe 'bin/datadog_backup' do
47
47
  it "dies unless given ENV[#{v}]" do
48
48
  ClimateControl.env[v] = nil
49
49
  _, status = run_bin('backup')
50
- expect(status).to_not be_success
50
+ expect(status).not_to be_success
51
51
  end
52
52
  end
53
53
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  $LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
2
4
 
3
5
  require 'datadog_backup'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datadog_backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 1.0.0.alpha.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kamran Farhadi
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-11-03 00:00:00.000000000 Z
12
+ date: 2021-01-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: amazing_print
@@ -87,14 +87,14 @@ dependencies:
87
87
  requirements:
88
88
  - - '='
89
89
  - !ruby/object:Gem::Version
90
- version: 1.42.0
90
+ version: 1.44.0
91
91
  type: :runtime
92
92
  prerelease: false
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - '='
96
96
  - !ruby/object:Gem::Version
97
- version: 1.42.0
97
+ version: 1.44.0
98
98
  - !ruby/object:Gem::Dependency
99
99
  name: bundler
100
100
  requirement: !ruby/object:Gem::Requirement
@@ -135,6 +135,7 @@ files:
135
135
  - ".github/dependabot.yml"
136
136
  - ".github/workflows/rspec_and_release.yml"
137
137
  - ".gitignore"
138
+ - ".rubocop.yml"
138
139
  - CHANGELOG.md
139
140
  - CODE_OF_CONDUCT.md
140
141
  - Gemfile
@@ -183,9 +184,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
183
184
  version: '0'
184
185
  required_rubygems_version: !ruby/object:Gem::Requirement
185
186
  requirements:
186
- - - ">="
187
+ - - ">"
187
188
  - !ruby/object:Gem::Version
188
- version: '0'
189
+ version: 1.3.1
189
190
  requirements: []
190
191
  rubygems_version: 3.1.4
191
192
  signing_key: