grafana_sync 1.0.0
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 +7 -0
- data/.github/workflows/ruby.yml +32 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.tool-versions +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +101 -0
- data/README.md +52 -0
- data/exe/grafync +43 -0
- data/grafana_sync.gemspec +39 -0
- data/lib/grafana_sync.rb +24 -0
- data/lib/grafana_sync/stage.rb +256 -0
- data/lib/grafana_sync/version.rb +3 -0
- metadata +173 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ffc1428f36126020a5bf8ec2724ca40a262726f47775ca4f86c5fe22bdcc9606
|
4
|
+
data.tar.gz: 8ffa6b47005e7ba87b3f22e9f080d120421767bc113959cb36004a4f2955340b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7e6089aa7662c1ec68245a686c05360adae35f394a53d4ff6d42fd1bad29ce63a3abf2fa2964e626914fd0b0a9168f2f982b8c3e78013c19308477539080fe96
|
7
|
+
data.tar.gz: 6a2b0c3e8127bd6ff67fdc361446c079cb9f4edefd5b0d2797033884f5518d56f8eb21ccddfe05cda7f9fbccc104db81d0528fca904fec672a0bbfdeb9c1fd25
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Ruby
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [ master ]
|
13
|
+
pull_request:
|
14
|
+
branches: [ master ]
|
15
|
+
|
16
|
+
jobs:
|
17
|
+
test:
|
18
|
+
|
19
|
+
runs-on: ubuntu-latest
|
20
|
+
|
21
|
+
steps:
|
22
|
+
- uses: actions/checkout@v2
|
23
|
+
- name: Set up Ruby
|
24
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
25
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
26
|
+
uses: ruby/setup-ruby@v1
|
27
|
+
with:
|
28
|
+
ruby-version: 2.7
|
29
|
+
- name: Install dependencies
|
30
|
+
run: bundle install
|
31
|
+
- name: Run tests
|
32
|
+
run: bundle exec rspec
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.tool-versions
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby 2.7.1
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
grafana_sync (1.0.0)
|
5
|
+
activesupport (~> 6.0)
|
6
|
+
diffy (~> 3.3)
|
7
|
+
http (~> 4.4)
|
8
|
+
httplog (~> 1.4)
|
9
|
+
methadone (~> 2.0)
|
10
|
+
|
11
|
+
GEM
|
12
|
+
remote: https://rubygems.org/
|
13
|
+
specs:
|
14
|
+
activesupport (6.0.2.2)
|
15
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
16
|
+
i18n (>= 0.7, < 2)
|
17
|
+
minitest (~> 5.1)
|
18
|
+
tzinfo (~> 1.1)
|
19
|
+
zeitwerk (~> 2.2)
|
20
|
+
addressable (2.7.0)
|
21
|
+
public_suffix (>= 2.0.2, < 5.0)
|
22
|
+
byebug (11.1.1)
|
23
|
+
coderay (1.1.2)
|
24
|
+
concurrent-ruby (1.1.6)
|
25
|
+
crack (0.4.3)
|
26
|
+
safe_yaml (~> 1.0.0)
|
27
|
+
diff-lcs (1.3)
|
28
|
+
diffy (3.3.0)
|
29
|
+
domain_name (0.5.20190701)
|
30
|
+
unf (>= 0.0.5, < 1.0.0)
|
31
|
+
ffi (1.12.2)
|
32
|
+
ffi-compiler (1.0.1)
|
33
|
+
ffi (>= 1.0.0)
|
34
|
+
rake
|
35
|
+
hashdiff (1.0.1)
|
36
|
+
http (4.4.1)
|
37
|
+
addressable (~> 2.3)
|
38
|
+
http-cookie (~> 1.0)
|
39
|
+
http-form_data (~> 2.2)
|
40
|
+
http-parser (~> 1.2.0)
|
41
|
+
http-cookie (1.0.3)
|
42
|
+
domain_name (~> 0.5)
|
43
|
+
http-form_data (2.3.0)
|
44
|
+
http-parser (1.2.1)
|
45
|
+
ffi-compiler (>= 1.0, < 2.0)
|
46
|
+
httplog (1.4.2)
|
47
|
+
rack (>= 1.0)
|
48
|
+
rainbow (>= 2.0.0)
|
49
|
+
i18n (1.8.2)
|
50
|
+
concurrent-ruby (~> 1.0)
|
51
|
+
methadone (2.0.2)
|
52
|
+
bundler
|
53
|
+
method_source (1.0.0)
|
54
|
+
minitest (5.14.0)
|
55
|
+
pry (0.13.1)
|
56
|
+
coderay (~> 1.1)
|
57
|
+
method_source (~> 1.0)
|
58
|
+
pry-byebug (3.9.0)
|
59
|
+
byebug (~> 11.0)
|
60
|
+
pry (~> 0.13.0)
|
61
|
+
public_suffix (4.0.4)
|
62
|
+
rack (2.2.2)
|
63
|
+
rainbow (3.0.0)
|
64
|
+
rake (13.0.1)
|
65
|
+
rspec (3.9.0)
|
66
|
+
rspec-core (~> 3.9.0)
|
67
|
+
rspec-expectations (~> 3.9.0)
|
68
|
+
rspec-mocks (~> 3.9.0)
|
69
|
+
rspec-core (3.9.1)
|
70
|
+
rspec-support (~> 3.9.1)
|
71
|
+
rspec-expectations (3.9.1)
|
72
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
73
|
+
rspec-support (~> 3.9.0)
|
74
|
+
rspec-mocks (3.9.1)
|
75
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
76
|
+
rspec-support (~> 3.9.0)
|
77
|
+
rspec-support (3.9.2)
|
78
|
+
safe_yaml (1.0.5)
|
79
|
+
thread_safe (0.3.6)
|
80
|
+
tzinfo (1.2.7)
|
81
|
+
thread_safe (~> 0.1)
|
82
|
+
unf (0.1.4)
|
83
|
+
unf_ext
|
84
|
+
unf_ext (0.0.7.7)
|
85
|
+
webmock (3.8.3)
|
86
|
+
addressable (>= 2.3.6)
|
87
|
+
crack (>= 0.3.2)
|
88
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
89
|
+
zeitwerk (2.3.0)
|
90
|
+
|
91
|
+
PLATFORMS
|
92
|
+
ruby
|
93
|
+
|
94
|
+
DEPENDENCIES
|
95
|
+
grafana_sync!
|
96
|
+
pry-byebug
|
97
|
+
rspec
|
98
|
+
webmock
|
99
|
+
|
100
|
+
BUNDLED WITH
|
101
|
+
2.1.4
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+

|
2
|
+

|
3
|
+
|
4
|
+
# Grafana Sync
|
5
|
+
|
6
|
+
Syncs dashboards between Grafana instances. Lifts the burden of migrating
|
7
|
+
changes between environment instances by hand. Unleashes the power of Linux
|
8
|
+
command-line tools on Grafana config files.
|
9
|
+
|
10
|
+
Tested against Grafana 6.
|
11
|
+
|
12
|
+
## Installation and Usage
|
13
|
+
|
14
|
+
Suggested workflow is:
|
15
|
+
- for each separately deployed project create a repo based off of a `sample_repo`
|
16
|
+
and head to it
|
17
|
+
```
|
18
|
+
cp sample_repo ../mars_shuttle
|
19
|
+
cd ../mars_shuttle
|
20
|
+
```
|
21
|
+
- install GrafanaSync locally (with e.g. [asdf](https://github.com/asdf-vm/asdf))
|
22
|
+
```
|
23
|
+
asdf install
|
24
|
+
gem install bundler -v 2.1.4
|
25
|
+
bundle
|
26
|
+
```
|
27
|
+
or globally
|
28
|
+
```
|
29
|
+
gem install grafana_sync
|
30
|
+
```
|
31
|
+
- adjust `config.rb` to suit your needs. For each environment there has to be
|
32
|
+
specified Grafana URL and Grafana folder. One environment (say "staging") is
|
33
|
+
tweaked manually through Grafana web-interface, optionally stored in a VCS and
|
34
|
+
then deployed to other environments (say "production").
|
35
|
+
- tweak manually staging Grafana through web-interface to fit your needs
|
36
|
+
- fetch staging Grafana configs
|
37
|
+
```
|
38
|
+
grafync staging pull
|
39
|
+
```
|
40
|
+
- optionally review changes and commit
|
41
|
+
- see what are the changes to be applied to production
|
42
|
+
```
|
43
|
+
grafync production diff
|
44
|
+
```
|
45
|
+
same with paging
|
46
|
+
```
|
47
|
+
grafync production diff | less -R
|
48
|
+
```
|
49
|
+
- apply Grafana configs to production
|
50
|
+
```
|
51
|
+
grafync production push
|
52
|
+
```
|
data/exe/grafync
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'grafana_sync'
|
4
|
+
require 'methadone'
|
5
|
+
|
6
|
+
include Methadone::Main
|
7
|
+
include Methadone::CLILogging
|
8
|
+
|
9
|
+
version GrafanaSync::VERSION
|
10
|
+
description 'Syncs dashboards between Grafana instances'
|
11
|
+
# TODO: bash completion
|
12
|
+
on('-f', '--make-folders', 'Make missing Grafana folders')
|
13
|
+
on('-d', '--debug', 'Turn on debugging messages')
|
14
|
+
arg :stage, 'One of the environments specified in config.rb to apply command for'
|
15
|
+
arg :command, '''pull: download remote <stage> dashboard configs into "dashboards/"
|
16
|
+
diff: preview what changes would be made by <push>
|
17
|
+
push: upload local dashboard configs to <stage>
|
18
|
+
'''
|
19
|
+
|
20
|
+
leak_exceptions true
|
21
|
+
main do |stage, command|
|
22
|
+
logger = Logger.new(STDERR,
|
23
|
+
level: (options[:debug]) ? Logger::DEBUG : Logger::INFO)
|
24
|
+
stage = GrafanaSync::Stage.new(stage: stage.to_sym,
|
25
|
+
make_folders: options['make-folders'],
|
26
|
+
debug: options[:debug],
|
27
|
+
logger: logger)
|
28
|
+
|
29
|
+
case command
|
30
|
+
when "pull"
|
31
|
+
stage.pull
|
32
|
+
when "push"
|
33
|
+
stage.push
|
34
|
+
when "diff"
|
35
|
+
stage.diff
|
36
|
+
else
|
37
|
+
GrafanaSync::die("Unknown command '#{command}'!")
|
38
|
+
end
|
39
|
+
|
40
|
+
0
|
41
|
+
end
|
42
|
+
|
43
|
+
go!
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative 'lib/grafana_sync/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "grafana_sync"
|
5
|
+
spec.version = GrafanaSync::VERSION
|
6
|
+
spec.authors = ["Nikolay Epifanov"]
|
7
|
+
spec.email = ["nik.epifanov@gmail.com"]
|
8
|
+
spec.licenses = ["MIT"]
|
9
|
+
|
10
|
+
spec.summary = "Syncs dashboards between Grafana instances."
|
11
|
+
spec.description = <<-EOF
|
12
|
+
Grafana HTTP API tool to fetch, diff and create/update dashboards to
|
13
|
+
ease the burden of migrating changes between Grafana instances for each
|
14
|
+
environment.
|
15
|
+
EOF
|
16
|
+
spec.homepage = "https://github.com/funbox/grafana_sync"
|
17
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
18
|
+
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
20
|
+
spec.metadata["changelog_uri"] = spec.homepage + "/blob/master/CHANGELOG.md"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(sample_repo|test|spec|features)/}) }
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
spec.add_runtime_dependency 'activesupport', '~> 6.0'
|
32
|
+
spec.add_runtime_dependency 'methadone', '~> 2.0'
|
33
|
+
spec.add_runtime_dependency 'http', '~> 4.4'
|
34
|
+
spec.add_runtime_dependency 'httplog', '~> 1.4'
|
35
|
+
spec.add_runtime_dependency 'diffy', '~> 3.3'
|
36
|
+
spec.add_development_dependency 'pry-byebug'
|
37
|
+
spec.add_development_dependency 'rspec'
|
38
|
+
spec.add_development_dependency 'webmock'
|
39
|
+
end
|
data/lib/grafana_sync.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'grafana_sync/stage'
|
2
|
+
require 'grafana_sync/version'
|
3
|
+
|
4
|
+
module GrafanaSync
|
5
|
+
class << self
|
6
|
+
def load_config
|
7
|
+
@load_config ||= load('config.rb')
|
8
|
+
end
|
9
|
+
|
10
|
+
def config
|
11
|
+
@config ||= {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def merge_config(file_config)
|
15
|
+
config.merge!(file_config)
|
16
|
+
end
|
17
|
+
|
18
|
+
def die(msg)
|
19
|
+
puts("""Error: #{msg}
|
20
|
+
Use --debug option to get verbose output.""")
|
21
|
+
exit(false)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'diffy'
|
3
|
+
require 'http'
|
4
|
+
# Must be after 'http' gem.
|
5
|
+
require 'httplog'
|
6
|
+
require 'io/console'
|
7
|
+
require 'logger'
|
8
|
+
|
9
|
+
module GrafanaSync
|
10
|
+
class Stage
|
11
|
+
delegate :config, :die, to: GrafanaSync
|
12
|
+
|
13
|
+
DASHBOARDS_ROOT = "dashboards"
|
14
|
+
FILE_ENCODING = "UTF-8"
|
15
|
+
FILE_EXTENSION = ".json"
|
16
|
+
CREDENTIALS = File.expand_path("~/.grafana_sync_rc")
|
17
|
+
|
18
|
+
def initialize(stage:, make_folders: false, debug: false,
|
19
|
+
logger: Logger.new(STDERR, level: Logger::INFO))
|
20
|
+
@stage_name = stage
|
21
|
+
@make_folders = make_folders
|
22
|
+
@logger = logger
|
23
|
+
HttpLog.configure do |config|
|
24
|
+
config.logger = logger
|
25
|
+
config.log_connect = false
|
26
|
+
config.log_data = debug
|
27
|
+
config.log_response = debug
|
28
|
+
config.log_benchmark = false
|
29
|
+
end
|
30
|
+
|
31
|
+
GrafanaSync.load_config()
|
32
|
+
validate_config!
|
33
|
+
@base_url = stage_config[:url].chomp("/")
|
34
|
+
folder = stage_config[:folder]
|
35
|
+
# If there's no folder specified in Grafana dashboard config then it's "General".
|
36
|
+
@folder_name = (folder == "General") ? nil : folder
|
37
|
+
end
|
38
|
+
|
39
|
+
def pull
|
40
|
+
FileUtils.mkdir_p(DASHBOARDS_ROOT)
|
41
|
+
FileUtils.rm(dashboard_files)
|
42
|
+
remote_dashboards.each do |title, db|
|
43
|
+
@logger.info("Saving dashboard '#{title}'")
|
44
|
+
IO.write(File.join(DASHBOARDS_ROOT, title+FILE_EXTENSION),
|
45
|
+
JSON.pretty_generate(db), encoding: FILE_ENCODING, mode: "w")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def push
|
50
|
+
dashboards_to_delete.each do |title, _db|
|
51
|
+
@logger.info("Deleting dashboard '#{title}'")
|
52
|
+
uid = dashboard_uid(title)
|
53
|
+
http_delete("/api/dashboards/uid/#{uid}")
|
54
|
+
end
|
55
|
+
|
56
|
+
dashboards_to_update.each do |title, db|
|
57
|
+
db["folderId"] = (folder_id or make_folder)
|
58
|
+
db["overwrite"] = true
|
59
|
+
@logger.info("Updating dashboard '#{title}'")
|
60
|
+
http_post("/api/dashboards/db", json: db)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def diff
|
65
|
+
dashboards_to_delete.keys.each do |key|
|
66
|
+
puts("--- #{@stage_name}/#{key}")
|
67
|
+
puts("+++ /dev/null")
|
68
|
+
puts
|
69
|
+
end
|
70
|
+
|
71
|
+
dashboards_to_update.keys.sort.each do |key|
|
72
|
+
diff_str = Diffy::Diff.new(JSON.pretty_generate(remote_dashboards[key]),
|
73
|
+
JSON.pretty_generate(local_dashboards[key]),
|
74
|
+
context: 3, diff: '-w', include_diff_info: true).to_s(:color)
|
75
|
+
unless diff_str.chop.empty?
|
76
|
+
puts("--- #{@stage_name}/#{key}")
|
77
|
+
puts("+++ local/#{key}")
|
78
|
+
puts(diff_str)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def validate_config!
|
86
|
+
unless config.has_key?(@stage_name)
|
87
|
+
die("There's no environment ':#{@stage_name}' defined in config.rb!")
|
88
|
+
end
|
89
|
+
|
90
|
+
config.keys.each do |stage|
|
91
|
+
[:url, :folder].each {|key|
|
92
|
+
die("config.rb has no :#{key} specified for :#{stage}!") if config[stage][key].nil?
|
93
|
+
}
|
94
|
+
|
95
|
+
config[stage][:datasource_replace].try do |ds_hash|
|
96
|
+
if ds_hash.has_key?(nil) or ds_hash.has_value?(nil)
|
97
|
+
die("config.rb:#{stage}: nil value in :datasource_replace is not supported!")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def stage_config
|
104
|
+
config[@stage_name]
|
105
|
+
end
|
106
|
+
|
107
|
+
def dashboards_to_delete
|
108
|
+
remote_dashboards.slice(*(remote_dashboards.keys - local_dashboards.keys))
|
109
|
+
end
|
110
|
+
|
111
|
+
def dashboards_to_update
|
112
|
+
local_dashboards
|
113
|
+
end
|
114
|
+
|
115
|
+
def remote_dashboards
|
116
|
+
@remote_dashboards ||= dashboard_uids.lazy.map do |uid|
|
117
|
+
db = http_get("/api/dashboards/uid/#{uid}")
|
118
|
+
db.delete("meta")
|
119
|
+
["id", "uid", "version"].each {|key|
|
120
|
+
db["dashboard"].delete(key)
|
121
|
+
}
|
122
|
+
[db["dashboard"]["title"], db]
|
123
|
+
end.to_h
|
124
|
+
end
|
125
|
+
|
126
|
+
def dashboard_uid(title)
|
127
|
+
index.find {|item| item["type"]=="dash-db" and item["title"]==title}
|
128
|
+
.try {|item| item["uid"]}.tap {|val|
|
129
|
+
die("Dashboard #{title} not found on #{@stage_name}!") if val.nil?
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
def dashboard_uids
|
134
|
+
index.filter {|item| item["type"]=="dash-db" and item["folderTitle"]==@folder_name}
|
135
|
+
.map {|item| item["uid"]}.tap {|val|
|
136
|
+
@logger.warn("No dashboards found on #{@stage_name}!") if val.empty?
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
def folder_id
|
141
|
+
@folder_id ||= if @folder_name.nil?
|
142
|
+
0 # "General" folder ID is always 0.
|
143
|
+
else
|
144
|
+
index.find {|item|
|
145
|
+
item["type"]=="dash-folder" and item["title"]==@folder_name
|
146
|
+
}.try {|item| item["id"] }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def make_folder
|
151
|
+
die("""There is no folder '#{@folder_name}' for '#{@stage_name}'!
|
152
|
+
To create it add --make-folders option to command-line.""") unless @make_folders
|
153
|
+
|
154
|
+
@logger.info("Making Grafana folder '#{@folder_name}'")
|
155
|
+
response = http_post("/api/folders", json: {title: @folder_name})
|
156
|
+
response["id"].tap {
|
157
|
+
invalidate_index
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
def invalidate_index
|
162
|
+
@index = nil
|
163
|
+
end
|
164
|
+
|
165
|
+
def local_dashboards
|
166
|
+
@local_dashboards ||= dashboard_files.map do |path|
|
167
|
+
@logger.debug("Loading '#{path}'")
|
168
|
+
db = JSON.parse(IO.read(path, encoding: FILE_ENCODING))
|
169
|
+
title = db["dashboard"]["title"]
|
170
|
+
next if stage_config[:exclude].try { |array| array.include?(title) }
|
171
|
+
replace_datasources!(db)
|
172
|
+
[title, db]
|
173
|
+
end.compact.to_h
|
174
|
+
end
|
175
|
+
|
176
|
+
def dashboard_files
|
177
|
+
# dashboards/*.json
|
178
|
+
Dir.glob(File.join(DASHBOARDS_ROOT, '*'+FILE_EXTENSION))
|
179
|
+
end
|
180
|
+
|
181
|
+
def replace_datasources!(obj)
|
182
|
+
replaces = stage_config[:datasource_replace]
|
183
|
+
return if replaces.nil?
|
184
|
+
|
185
|
+
if obj.is_a?(Hash)
|
186
|
+
datasource = obj["datasource"]
|
187
|
+
if datasource
|
188
|
+
obj["datasource"] = replaces.fetch(datasource, datasource)
|
189
|
+
end
|
190
|
+
obj.each_value {|value| replace_datasources!(value)}
|
191
|
+
elsif obj.is_a?(Array)
|
192
|
+
obj.each {|value| replace_datasources!(value)}
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def index
|
197
|
+
@index ||= http_get("/api/search")
|
198
|
+
end
|
199
|
+
|
200
|
+
def http_get(path)
|
201
|
+
url = @base_url + path
|
202
|
+
response = http.get(url)
|
203
|
+
die("Failed to GET #{url}!") if response.code != 200
|
204
|
+
JSON.parse(response.to_s)
|
205
|
+
end
|
206
|
+
|
207
|
+
def http_post(path, json: {})
|
208
|
+
url = @base_url + path
|
209
|
+
response = http.post(url, json: json)
|
210
|
+
die("Failed to POST #{url}!") if response.code != 200
|
211
|
+
JSON.parse(response.to_s)
|
212
|
+
end
|
213
|
+
|
214
|
+
def http_delete(path, json: {})
|
215
|
+
url = @base_url + path
|
216
|
+
response = http.delete(url, json: json)
|
217
|
+
die("Failed to DELETE #{url}!") if response.code != 200
|
218
|
+
JSON.parse(response.to_s)
|
219
|
+
end
|
220
|
+
|
221
|
+
def http
|
222
|
+
@http ||= HTTP.basic_auth(user: credentials[:login],
|
223
|
+
pass: credentials[:password]).follow
|
224
|
+
end
|
225
|
+
|
226
|
+
def credentials
|
227
|
+
@credentials ||= load_credentials or ask_credentials
|
228
|
+
end
|
229
|
+
|
230
|
+
def load_credentials
|
231
|
+
if File.exist?(CREDENTIALS)
|
232
|
+
JSON.parse(IO.read(CREDENTIALS, encoding: FILE_ENCODING)).transform_keys(&:to_sym)
|
233
|
+
else
|
234
|
+
nil
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def ask_credentials
|
239
|
+
{login: ask_input("User: "),
|
240
|
+
password: ask_input("Password: ", hide: true)}.tap do |cred_hash|
|
241
|
+
IO.write(CREDENTIALS,
|
242
|
+
JSON.pretty_generate(cred_hash),
|
243
|
+
encoding: FILE_ENCODING, mode: "w")
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def ask_input(invitation, hide: false)
|
248
|
+
print(invitation)
|
249
|
+
if hide
|
250
|
+
STDIN.noecho(&:gets).tap { puts }
|
251
|
+
else
|
252
|
+
STDIN.gets
|
253
|
+
end.strip
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grafana_sync
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nikolay Epifanov
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-10-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: methadone
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: http
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: httplog
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.4'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: diffy
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.3'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry-byebug
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: webmock
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: |2
|
126
|
+
Grafana HTTP API tool to fetch, diff and create/update dashboards to
|
127
|
+
ease the burden of migrating changes between Grafana instances for each
|
128
|
+
environment.
|
129
|
+
email:
|
130
|
+
- nik.epifanov@gmail.com
|
131
|
+
executables:
|
132
|
+
- grafync
|
133
|
+
extensions: []
|
134
|
+
extra_rdoc_files: []
|
135
|
+
files:
|
136
|
+
- ".github/workflows/ruby.yml"
|
137
|
+
- ".gitignore"
|
138
|
+
- ".rspec"
|
139
|
+
- ".tool-versions"
|
140
|
+
- Gemfile
|
141
|
+
- Gemfile.lock
|
142
|
+
- README.md
|
143
|
+
- exe/grafync
|
144
|
+
- grafana_sync.gemspec
|
145
|
+
- lib/grafana_sync.rb
|
146
|
+
- lib/grafana_sync/stage.rb
|
147
|
+
- lib/grafana_sync/version.rb
|
148
|
+
homepage: https://github.com/funbox/grafana_sync
|
149
|
+
licenses:
|
150
|
+
- MIT
|
151
|
+
metadata:
|
152
|
+
source_code_uri: https://github.com/funbox/grafana_sync
|
153
|
+
changelog_uri: https://github.com/funbox/grafana_sync/blob/master/CHANGELOG.md
|
154
|
+
post_install_message:
|
155
|
+
rdoc_options: []
|
156
|
+
require_paths:
|
157
|
+
- lib
|
158
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 2.7.0
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '0'
|
168
|
+
requirements: []
|
169
|
+
rubygems_version: 3.1.2
|
170
|
+
signing_key:
|
171
|
+
specification_version: 4
|
172
|
+
summary: Syncs dashboards between Grafana instances.
|
173
|
+
test_files: []
|