datadog_backup 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/rspec_and_release.yml +54 -0
- data/.gitignore +119 -0
- data/CHANGELOG.md +80 -0
- data/CODE_OF_CONDUCT.md +76 -0
- data/Gemfile +12 -0
- data/Guardfile +44 -0
- data/LICENSE +21 -0
- data/README.md +57 -0
- data/bin/datadog_backup +73 -0
- data/datadog_backup.gemspec +31 -0
- data/example/.github/workflows/backup.yml +31 -0
- data/example/.gitignore +1 -0
- data/example/Gemfile +3 -0
- data/example/README.md +17 -0
- data/lib/datadog_backup.rb +19 -0
- data/lib/datadog_backup/cli.rb +141 -0
- data/lib/datadog_backup/core.rb +113 -0
- data/lib/datadog_backup/dashboards.rb +50 -0
- data/lib/datadog_backup/local_filesystem.rb +86 -0
- data/lib/datadog_backup/monitors.rb +42 -0
- data/lib/datadog_backup/options.rb +46 -0
- data/lib/datadog_backup/thread_pool.rb +30 -0
- data/lib/datadog_backup/version.rb +5 -0
- data/release.config.js +43 -0
- data/spec/datadog_backup/cli_spec.rb +117 -0
- data/spec/datadog_backup/core_spec.rb +107 -0
- data/spec/datadog_backup/dashboards_spec.rb +102 -0
- data/spec/datadog_backup/local_filesystem_spec.rb +173 -0
- data/spec/datadog_backup/monitors_spec.rb +119 -0
- data/spec/datadog_backup_bin_spec.rb +59 -0
- data/spec/datadog_backup_spec.rb +6 -0
- data/spec/spec_helper.rb +37 -0
- metadata +199 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe DatadogBackup::Dashboards do
|
6
|
+
let(:client_double) { double }
|
7
|
+
let(:tempdir) { Dir.mktmpdir }
|
8
|
+
let(:dashboards) do
|
9
|
+
DatadogBackup::Dashboards.new(
|
10
|
+
action: 'backup',
|
11
|
+
client: client_double,
|
12
|
+
backup_dir: tempdir,
|
13
|
+
output_format: :json,
|
14
|
+
resources: [],
|
15
|
+
logger: Logger.new('/dev/null')
|
16
|
+
)
|
17
|
+
end
|
18
|
+
let(:dashboard_description) do
|
19
|
+
{
|
20
|
+
'description' => 'bar',
|
21
|
+
'id' => 'abc-123-def',
|
22
|
+
'title' => 'foo'
|
23
|
+
}
|
24
|
+
end
|
25
|
+
let(:all_boards) do
|
26
|
+
[
|
27
|
+
'200',
|
28
|
+
{
|
29
|
+
'dashboards' => [
|
30
|
+
dashboard_description
|
31
|
+
]
|
32
|
+
}
|
33
|
+
]
|
34
|
+
end
|
35
|
+
let(:example_dashboard) do
|
36
|
+
[
|
37
|
+
'200',
|
38
|
+
board_abc_123_def
|
39
|
+
]
|
40
|
+
end
|
41
|
+
let(:board_abc_123_def) do
|
42
|
+
{
|
43
|
+
'graphs' => [
|
44
|
+
{
|
45
|
+
'definition' => {
|
46
|
+
'viz' => 'timeseries',
|
47
|
+
'requests' => [
|
48
|
+
{
|
49
|
+
'q' => 'min:foo.bar{a:b}',
|
50
|
+
'stacked' => false
|
51
|
+
}
|
52
|
+
]
|
53
|
+
},
|
54
|
+
'title' => 'example graph'
|
55
|
+
}
|
56
|
+
],
|
57
|
+
'description' => 'example dashboard',
|
58
|
+
'title' => 'example dashboard'
|
59
|
+
}
|
60
|
+
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)
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#backup' do
|
67
|
+
subject { dashboards.backup }
|
68
|
+
|
69
|
+
it 'is expected to create a file' do
|
70
|
+
file = double('file')
|
71
|
+
allow(File).to receive(:open).with(dashboards.filename('abc-123-def'), 'w').and_return(file)
|
72
|
+
expect(file).to receive(:write).with(::JSON.pretty_generate(board_abc_123_def.deep_sort))
|
73
|
+
allow(file).to receive(:close)
|
74
|
+
|
75
|
+
dashboards.backup
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#all_boards' do
|
80
|
+
subject { dashboards.all_boards }
|
81
|
+
|
82
|
+
it 'calls get_all_boards' do
|
83
|
+
subject
|
84
|
+
expect(client_double).to have_received(:get_all_boards)
|
85
|
+
end
|
86
|
+
|
87
|
+
it { is_expected.to eq [dashboard_description] }
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#diff' do
|
91
|
+
it 'calls the api only once' do
|
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
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#get_by_id' do
|
99
|
+
subject { dashboards.get_by_id('abc-123-def') }
|
100
|
+
it { is_expected.to eq board_abc_123_def }
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe DatadogBackup::LocalFilesystem do
|
6
|
+
let(:client_double) { double }
|
7
|
+
let(:tempdir) { Dir.mktmpdir }
|
8
|
+
let(:core) do
|
9
|
+
DatadogBackup::Core.new(
|
10
|
+
action: 'backup',
|
11
|
+
client: client_double,
|
12
|
+
backup_dir: tempdir,
|
13
|
+
resources: [DatadogBackup::Dashboards],
|
14
|
+
output_format: :json,
|
15
|
+
logger: Logger.new('/dev/null')
|
16
|
+
)
|
17
|
+
end
|
18
|
+
let(:core_yaml) do
|
19
|
+
DatadogBackup::Core.new(
|
20
|
+
action: 'backup',
|
21
|
+
client: client_double,
|
22
|
+
backup_dir: tempdir,
|
23
|
+
resources: [],
|
24
|
+
output_format: :yaml,
|
25
|
+
logger: Logger.new('/dev/null')
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#all_files' do
|
30
|
+
before(:example) do
|
31
|
+
File.new("#{tempdir}/all_files.json", 'w')
|
32
|
+
end
|
33
|
+
|
34
|
+
after(:example) do
|
35
|
+
FileUtils.rm "#{tempdir}/all_files.json"
|
36
|
+
end
|
37
|
+
|
38
|
+
subject { core.all_files }
|
39
|
+
it { is_expected.to eq(["#{tempdir}/all_files.json"]) }
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#all_file_ids_for_selected_resources' do
|
43
|
+
before(:example) do
|
44
|
+
Dir.mkdir("#{tempdir}/dashboards")
|
45
|
+
Dir.mkdir("#{tempdir}/monitors")
|
46
|
+
File.new("#{tempdir}/dashboards/all_files.json", 'w')
|
47
|
+
File.new("#{tempdir}/monitors/12345.json", 'w')
|
48
|
+
end
|
49
|
+
|
50
|
+
after(:example) do
|
51
|
+
FileUtils.rm "#{tempdir}/dashboards/all_files.json"
|
52
|
+
FileUtils.rm "#{tempdir}/monitors/12345.json"
|
53
|
+
end
|
54
|
+
|
55
|
+
subject { core.all_file_ids_for_selected_resources }
|
56
|
+
it { is_expected.to eq(['all_files']) }
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#class_from_id' do
|
60
|
+
before(:example) do
|
61
|
+
core.write_file('abc', "#{tempdir}/core/abc-123-def.json")
|
62
|
+
end
|
63
|
+
|
64
|
+
after(:example) do
|
65
|
+
FileUtils.rm "#{tempdir}/core/abc-123-def.json"
|
66
|
+
end
|
67
|
+
subject { core.class_from_id('abc-123-def') }
|
68
|
+
it { is_expected.to eq DatadogBackup::Core }
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#dump' do
|
72
|
+
context ':json' do
|
73
|
+
subject { core.dump({ a: :b }) }
|
74
|
+
it { is_expected.to eq(%({\n "a": "b"\n})) }
|
75
|
+
end
|
76
|
+
|
77
|
+
context ':yaml' do
|
78
|
+
subject { core_yaml.dump({ 'a' => 'b' }) }
|
79
|
+
it { is_expected.to eq(%(---\na: b\n)) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#filename' do
|
84
|
+
context ':json' do
|
85
|
+
subject { core.filename('abc-123-def') }
|
86
|
+
it { is_expected.to eq("#{tempdir}/core/abc-123-def.json") }
|
87
|
+
end
|
88
|
+
|
89
|
+
context ':yaml' do
|
90
|
+
subject { core_yaml.filename('abc-123-def') }
|
91
|
+
it { is_expected.to eq("#{tempdir}/core/abc-123-def.yaml") }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#file_type' do
|
96
|
+
before(:example) do
|
97
|
+
File.new("#{tempdir}/file_type.json", 'w')
|
98
|
+
end
|
99
|
+
|
100
|
+
after(:example) do
|
101
|
+
FileUtils.rm "#{tempdir}/file_type.json"
|
102
|
+
end
|
103
|
+
|
104
|
+
subject { core.file_type("#{tempdir}/file_type.json") }
|
105
|
+
it { is_expected.to eq :json }
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#find_file_by_id' do
|
109
|
+
before(:example) do
|
110
|
+
File.new("#{tempdir}/find_file.json", 'w')
|
111
|
+
end
|
112
|
+
|
113
|
+
after(:example) do
|
114
|
+
FileUtils.rm "#{tempdir}/find_file.json"
|
115
|
+
end
|
116
|
+
|
117
|
+
subject { core.find_file_by_id('find_file') }
|
118
|
+
it { is_expected.to eq "#{tempdir}/find_file.json" }
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#load_from_file' do
|
122
|
+
context ':json' do
|
123
|
+
subject { core.load_from_file(%({\n "a": "b"\n}), :json) }
|
124
|
+
it { is_expected.to eq('a' => 'b') }
|
125
|
+
end
|
126
|
+
|
127
|
+
context ':yaml' do
|
128
|
+
subject { core.load_from_file(%(---\na: b\n), :yaml) }
|
129
|
+
it { is_expected.to eq('a' => 'b') }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#load_from_file_by_id' do
|
134
|
+
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
|
+
subject { core_yaml.load_from_file_by_id('abc-123-def') }
|
139
|
+
it { is_expected.to eq('a' => 'b') }
|
140
|
+
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
|
+
|
145
|
+
subject { core.load_from_file_by_id('abc-123-def') }
|
146
|
+
it { is_expected.to eq('a' => 'b') }
|
147
|
+
end
|
148
|
+
|
149
|
+
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
|
+
subject { core.load_from_file_by_id(12_345) }
|
154
|
+
it { is_expected.to eq('a' => 'b') }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe '#write_file' do
|
159
|
+
subject { core.write_file('abc123', "#{tempdir}/core/abc-123-def.json") }
|
160
|
+
let(:file_like_object) { double }
|
161
|
+
|
162
|
+
it 'writes a file to abc-123-def.json' do
|
163
|
+
allow(File).to receive(:open).and_call_original
|
164
|
+
allow(File).to receive(:open).with("#{tempdir}/core/abc-123-def.json", 'w').and_return(file_like_object)
|
165
|
+
allow(file_like_object).to receive(:write)
|
166
|
+
allow(file_like_object).to receive(:close)
|
167
|
+
|
168
|
+
subject
|
169
|
+
|
170
|
+
expect(file_like_object).to have_received(:write).with('abc123')
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe DatadogBackup::Monitors do
|
6
|
+
let(:client_double) { double }
|
7
|
+
let(:tempdir) { Dir.mktmpdir }
|
8
|
+
let(:monitors) do
|
9
|
+
DatadogBackup::Monitors.new(
|
10
|
+
action: 'backup',
|
11
|
+
client: client_double,
|
12
|
+
backup_dir: tempdir,
|
13
|
+
output_format: :json,
|
14
|
+
resources: [],
|
15
|
+
logger: Logger.new('/dev/null')
|
16
|
+
)
|
17
|
+
end
|
18
|
+
let(:monitor_description) do
|
19
|
+
{
|
20
|
+
'query' => 'bar',
|
21
|
+
'message' => 'foo',
|
22
|
+
'id' => 123_455,
|
23
|
+
'name' => 'foo',
|
24
|
+
'overall_state' => 'OK',
|
25
|
+
'overall_state_modified' => '2020-07-27T22:00:00+00:00'
|
26
|
+
}
|
27
|
+
end
|
28
|
+
let(:clean_monitor_description) do
|
29
|
+
{
|
30
|
+
'id' => 123_455,
|
31
|
+
'message' => 'foo',
|
32
|
+
'name' => 'foo',
|
33
|
+
'query' => 'bar'
|
34
|
+
}
|
35
|
+
end
|
36
|
+
let(:all_monitors) do
|
37
|
+
[
|
38
|
+
'200',
|
39
|
+
[
|
40
|
+
monitor_description
|
41
|
+
]
|
42
|
+
]
|
43
|
+
end
|
44
|
+
let(:example_monitor) do
|
45
|
+
[
|
46
|
+
'200',
|
47
|
+
monitor_description
|
48
|
+
]
|
49
|
+
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)
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#all_monitors' do
|
56
|
+
subject { monitors.all_monitors }
|
57
|
+
|
58
|
+
it 'calls get_all_monitors' do
|
59
|
+
subject
|
60
|
+
expect(client_double).to have_received(:get_all_monitors)
|
61
|
+
end
|
62
|
+
|
63
|
+
it { is_expected.to eq [monitor_description] }
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#backup' do
|
67
|
+
subject { monitors.backup }
|
68
|
+
|
69
|
+
it 'is expected to create a file' do
|
70
|
+
file = double('file')
|
71
|
+
allow(File).to receive(:open).with(monitors.filename(123_455), 'w').and_return(file)
|
72
|
+
expect(file).to receive(:write).with(::JSON.pretty_generate(clean_monitor_description))
|
73
|
+
allow(file).to receive(:close)
|
74
|
+
|
75
|
+
monitors.backup
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#diff' do
|
80
|
+
example 'it ignores `overall_state` and `overall_state_modified`' do
|
81
|
+
monitors.write_file(monitors.dump(monitor_description), monitors.filename(123_455))
|
82
|
+
allow(client_double).to receive(:get_all_monitors).and_return(
|
83
|
+
[
|
84
|
+
'200',
|
85
|
+
[
|
86
|
+
{
|
87
|
+
'query' => 'bar',
|
88
|
+
'message' => 'foo',
|
89
|
+
'id' => 123_455,
|
90
|
+
'name' => 'foo',
|
91
|
+
'overall_state' => 'NO DATA',
|
92
|
+
'overall_state_modified' => '2020-07-27T22:55:55+00:00'
|
93
|
+
}
|
94
|
+
]
|
95
|
+
]
|
96
|
+
)
|
97
|
+
|
98
|
+
expect(monitors.diff(123_455)).to eq ''
|
99
|
+
|
100
|
+
FileUtils.rm monitors.filename(123_455)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '#filename' do
|
105
|
+
subject { monitors.filename(123_455) }
|
106
|
+
it { is_expected.to eq("#{tempdir}/monitors/123455.json") }
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#get_by_id' do
|
110
|
+
context 'Integer' do
|
111
|
+
subject { monitors.get_by_id(123_455) }
|
112
|
+
it { is_expected.to eq monitor_description }
|
113
|
+
end
|
114
|
+
context 'String' do
|
115
|
+
subject { monitors.get_by_id('123455') }
|
116
|
+
it { is_expected.to eq monitor_description }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
require 'climate_control'
|
5
|
+
require 'timeout'
|
6
|
+
|
7
|
+
describe 'bin/datadog_backup' do
|
8
|
+
# Contract Or[nil,String] => self
|
9
|
+
def run_bin(args = '', input = nil)
|
10
|
+
status = nil
|
11
|
+
output = ''
|
12
|
+
cmd = "bin/datadog_backup #{args}"
|
13
|
+
Open3.popen2e(cmd) do |i, oe, t|
|
14
|
+
pid = t.pid
|
15
|
+
|
16
|
+
if input
|
17
|
+
i.puts input
|
18
|
+
i.close
|
19
|
+
end
|
20
|
+
|
21
|
+
Timeout.timeout(1.0) do
|
22
|
+
oe.each do |v|
|
23
|
+
output += v
|
24
|
+
end
|
25
|
+
end
|
26
|
+
rescue Timeout::Error
|
27
|
+
LOGGER.warn "Timing out #{t.inspect} after 1 second"
|
28
|
+
Process.kill(15, pid)
|
29
|
+
ensure
|
30
|
+
status = t.value
|
31
|
+
end
|
32
|
+
[output, status]
|
33
|
+
end
|
34
|
+
|
35
|
+
required_vars = %w[
|
36
|
+
DATADOG_API_KEY
|
37
|
+
DATADOG_APP_KEY
|
38
|
+
]
|
39
|
+
|
40
|
+
before do
|
41
|
+
required_vars.each do |v|
|
42
|
+
ClimateControl.env[v] = v.downcase
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
required_vars.map do |v|
|
47
|
+
it "dies unless given ENV[#{v}]" do
|
48
|
+
ClimateControl.env[v] = nil
|
49
|
+
_, status = run_bin('backup')
|
50
|
+
expect(status).to_not be_success
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'supplies help' do
|
55
|
+
out_err, status = run_bin('--help')
|
56
|
+
expect(out_err).to match(/Usage: datadog_backup/)
|
57
|
+
expect(status).to be_success
|
58
|
+
end
|
59
|
+
end
|