datadog_backup 0.10.2 → 1.0.0.alpha.2

Sign up to get free protection for your applications and to get access to all the features.
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: