myer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +9 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +21 -0
  7. data/README.md +5 -0
  8. data/Rakefile +44 -0
  9. data/bin/myer +198 -0
  10. data/features/myer.feature +8 -0
  11. data/features/step_definitions/myer_steps.rb +6 -0
  12. data/features/support/env.rb +15 -0
  13. data/lib/myer.rb +25 -0
  14. data/lib/myer/admin_cli_controller.rb +70 -0
  15. data/lib/myer/api.rb +104 -0
  16. data/lib/myer/cli_controller.rb +204 -0
  17. data/lib/myer/config.rb +86 -0
  18. data/lib/myer/content.rb +92 -0
  19. data/lib/myer/crypto.rb +40 -0
  20. data/lib/myer/exceptions.rb +7 -0
  21. data/lib/myer/plot.rb +11 -0
  22. data/lib/myer/proc_net_parser.rb +11 -0
  23. data/lib/myer/test_cli_controller.rb +52 -0
  24. data/lib/myer/ticket.rb +3 -0
  25. data/lib/myer/ticket_store.rb +61 -0
  26. data/lib/myer/version.rb +3 -0
  27. data/myer.gemspec +26 -0
  28. data/myer.rdoc +5 -0
  29. data/scripts/plot-helper.py +32 -0
  30. data/spec/admin_cli_controller_spec.rb +128 -0
  31. data/spec/api_spec.rb +136 -0
  32. data/spec/cli_controller_spec.rb +368 -0
  33. data/spec/config_spec.rb +116 -0
  34. data/spec/content_spec.rb +103 -0
  35. data/spec/crypto_spec.rb +37 -0
  36. data/spec/data/myer-full.config +9 -0
  37. data/spec/data/myer-multiple.config +14 -0
  38. data/spec/data/myer.config +6 -0
  39. data/spec/data/plot-data.csv +31 -0
  40. data/spec/data/secret-ticket-12345678.json +1 -0
  41. data/spec/data/secret-ticket-987654321.json +1 -0
  42. data/spec/plot_spec.rb +9 -0
  43. data/spec/proc_net_parser_spec.rb +15 -0
  44. data/spec/shared_examples_for_config.rb +47 -0
  45. data/spec/spec_helper.rb +14 -0
  46. data/spec/test_cli_controller_spec.rb +72 -0
  47. data/spec/ticket_store_spec.rb +86 -0
  48. data/test/default_test.rb +14 -0
  49. data/test/test_helper.rb +9 -0
  50. metadata +220 -0
@@ -0,0 +1,204 @@
1
+ class CliController
2
+ include Myer::Config
3
+
4
+ attr_accessor :out, :crypto
5
+
6
+ def initialize
7
+ @out = STDOUT
8
+ initialize_config
9
+ @crypto = Crypto.new
10
+ end
11
+
12
+ def api(server_name = default_server)
13
+ api = MySelf::Api.new
14
+ api.server = server_name
15
+ if server(server_name)
16
+ api.user = server(server_name).user_id
17
+ api.password = server(server_name).user_password
18
+ end
19
+ api
20
+ end
21
+
22
+ def create_bucket(name)
23
+ read_config
24
+
25
+ bucket_id = api.create_bucket
26
+
27
+ self.default_bucket_id = bucket_id
28
+
29
+ ticket = Ticket.new
30
+ ticket.server = default_server
31
+ ticket.bucket_id = bucket_id
32
+ ticket.key = @crypto.generate_passphrase
33
+ ticket.name = name
34
+
35
+ store = TicketStore.new(config_dir)
36
+ store.save_ticket(ticket)
37
+
38
+ write_config
39
+
40
+ out.puts("Created new bucket and stored its secret ticket at #{store.ticket_path(ticket)}.")
41
+ out.puts("You need this ticket to give other clients access to the bucket.")
42
+ out.puts("Keep it safe and secret. Everybody who has the ticket can read the bucket data.")
43
+
44
+ bucket_id
45
+ end
46
+
47
+ def create_token
48
+ read_config
49
+
50
+ token = api.create_token
51
+
52
+ out.puts("Created token: #{token}")
53
+ out.puts("Use this token to register another client, " +
54
+ "e.g. with `myer register #{default_server} #{token}`.")
55
+
56
+ token
57
+ end
58
+
59
+ def register(server_name, token)
60
+ read_config
61
+
62
+ self.default_server = server_name
63
+ self.user_id, self.user_password = api(server_name).register(token)
64
+
65
+ write_config
66
+ end
67
+
68
+ def write_item(bucket_id, content)
69
+ read_config
70
+
71
+ return api.create_item(bucket_id, content)
72
+ end
73
+
74
+ def write_raw(content)
75
+ read_config
76
+ write_item(default_bucket_id, content)
77
+ end
78
+
79
+ def write(content)
80
+ read_config
81
+
82
+ store = TicketStore.new(config_dir)
83
+ ticket = store.load_ticket(default_bucket_id)
84
+
85
+ @crypto.passphrase = ticket.key
86
+
87
+ encrypted_content = @crypto.encrypt(content)
88
+
89
+ write_item(default_bucket_id, encrypted_content)
90
+ end
91
+
92
+ def read_items(bucket_id)
93
+ read_config
94
+
95
+ store = TicketStore.new(config_dir)
96
+ ticket = store.load_ticket(default_bucket_id)
97
+
98
+ @crypto.passphrase = ticket.key
99
+
100
+ items = api.get_items(bucket_id)
101
+
102
+ content_list = []
103
+ items.each do |item|
104
+ content = @crypto.decrypt(item.content)
105
+ out.puts("#{item.id}: #{content}")
106
+ content_list.push(content)
107
+ end
108
+
109
+ content_list
110
+ end
111
+
112
+ def read
113
+ read_config
114
+ if !default_bucket_id || default_bucket_id.empty?
115
+ raise Myer::Error.new("Default bucket id not set")
116
+ end
117
+ inner_items = read_items(default_bucket_id)
118
+
119
+ FileUtils.mkdir_p(data_dir)
120
+ csv_file = local_csv_path(default_bucket_id)
121
+ content = Content.new
122
+
123
+ inner_items.each do |inner_item|
124
+ content.add(inner_item)
125
+ end
126
+
127
+ content.write_as_csv(csv_file)
128
+
129
+ inner_items
130
+ end
131
+
132
+ def create_payload(value, tag = nil)
133
+ payload = {}
134
+ payload["id"] = SecureRandom.hex
135
+ payload["written_at"] = Time.now.utc.strftime("%FT%TZ")
136
+ payload["tag"] = tag if tag
137
+ payload["data"] = value.to_s
138
+ JSON.generate(payload)
139
+ end
140
+
141
+ def write_value(value, tag = nil)
142
+ write(create_payload(value, tag))
143
+ end
144
+
145
+ def write_pair(value1, value2)
146
+ json = [ value1, value2 ]
147
+ write_value(JSON.generate(json))
148
+ end
149
+
150
+ def plot(dont_sync: false)
151
+ read_config
152
+
153
+ read unless dont_sync
154
+
155
+ plot = Plot.new
156
+ plot.show(local_csv_path(default_bucket_id))
157
+ end
158
+
159
+ def export(output_path)
160
+ read_config
161
+
162
+ content = Content.new
163
+ inner_items = read
164
+ inner_items.each do |inner_item|
165
+ content.add(inner_item)
166
+ end
167
+
168
+ content.write_as_json(output_path)
169
+ end
170
+
171
+ def consume_ticket(ticket_source_path)
172
+ read_config
173
+
174
+ ticket_target_path = File.join(config_dir, File.basename(ticket_source_path))
175
+ FileUtils.mv(ticket_source_path, ticket_target_path)
176
+ store = TicketStore.new(config_dir)
177
+ ticket = store.load_ticket_from_file(ticket_target_path)
178
+ self.default_bucket_id = ticket.bucket_id
179
+
180
+ write_config
181
+ end
182
+
183
+ def list_tickets(show_status: false)
184
+ read_config
185
+
186
+ store = TicketStore.new(config_dir)
187
+ out.puts "Available Tickets:"
188
+ store.tickets_per_server.each do |server_name,tickets|
189
+ if show_status
190
+ server_api = api(server_name)
191
+ begin
192
+ server_api.ping
193
+ status = " [pings]"
194
+ rescue StandardError => e
195
+ status = " [ping error: #{e.message.chomp}]"
196
+ end
197
+ end
198
+ out.puts " Server '#{server_name}'#{status}:"
199
+ tickets.each do |ticket|
200
+ out.puts " Bucket '#{ticket.name}' (#{ticket.bucket_id})"
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,86 @@
1
+ module Myer
2
+ module Config
3
+ include XDG::BaseDir::Mixin
4
+
5
+ def subdirectory
6
+ "myer"
7
+ end
8
+
9
+ attr_accessor :config_dir, :data_dir
10
+
11
+ def initialize_config
12
+ @config_dir = config.home.to_s
13
+ @data_dir = data.home.to_s
14
+ end
15
+
16
+ def local_csv_path(bucket_id)
17
+ File.join(@data_dir, bucket_id + ".csv")
18
+ end
19
+
20
+ class ServerConfig
21
+ attr_accessor :admin_id, :admin_password
22
+ attr_accessor :user_id, :user_password
23
+ attr_accessor :default_bucket_id
24
+
25
+ def initialize(yaml)
26
+ @admin_id = yaml["admin_id"]
27
+ @admin_password = yaml["admin_password"]
28
+ @user_id = yaml["user_id"]
29
+ @user_password = yaml["user_password"]
30
+ @default_bucket_id = yaml["default_bucket_id"]
31
+ end
32
+ end
33
+
34
+ def default_server=(value)
35
+ @config ||= {}
36
+ @config["default_server"] = value
37
+ end
38
+
39
+ def default_server
40
+ @config["default_server"]
41
+ end
42
+
43
+ def self.define_attribute(name)
44
+ define_method(name.to_s) do
45
+ return nil if !@config
46
+ @config["servers"][default_server][name.to_s]
47
+ end
48
+
49
+ define_method(name.to_s + "=") do |value|
50
+ @config ||= {}
51
+ @config["servers"] ||= {}
52
+ @config["servers"][default_server] ||= {}
53
+ @config["servers"][default_server][name.to_s] = value
54
+ end
55
+ end
56
+
57
+ define_attribute :admin_id
58
+ define_attribute :admin_password
59
+ define_attribute :user_id
60
+ define_attribute :user_password
61
+ define_attribute :default_bucket_id
62
+
63
+ def write_config
64
+ FileUtils.mkdir_p(@config_dir)
65
+ File.write(File.join(@config_dir, "myer.config"), @config.to_yaml)
66
+ end
67
+
68
+ def read_config
69
+ config_file = File.join(@config_dir, "myer.config")
70
+ return if !File.exist?(config_file)
71
+
72
+ @config = YAML.load_file(config_file)
73
+
74
+ @default_server = @config["default_server"]
75
+ end
76
+
77
+ def servers
78
+ @config["servers"].keys
79
+ end
80
+
81
+ def server(name)
82
+ return nil if !@config["servers"] || !@config["servers"].has_key?(name)
83
+ ServerConfig.new(@config["servers"][name])
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,92 @@
1
+ class Content
2
+ class Item
3
+ attr_accessor :id, :written_at, :tag
4
+
5
+ def initialize(container)
6
+ @container = container
7
+ end
8
+
9
+ def data=(value)
10
+ @data = value
11
+ end
12
+
13
+ def data
14
+ if @container.type == "json"
15
+ JSON.parse(@data)
16
+ else
17
+ @data
18
+ end
19
+ end
20
+ end
21
+
22
+ attr_reader :title, :type
23
+
24
+ def initialize
25
+ @items = []
26
+ end
27
+
28
+ def add(content)
29
+ json = JSON.parse(content)
30
+
31
+ item = Item.new(self)
32
+ item.id = json["id"]
33
+ item.written_at = json["written_at"]
34
+ item.tag = json["tag"]
35
+ if item.tag == "title"
36
+ @title = json["data"]
37
+ elsif item.tag == "type"
38
+ @type = json["data"]
39
+ else
40
+ item.data = json["data"]
41
+ @items.push(item)
42
+ end
43
+ end
44
+
45
+ def first
46
+ at(0)
47
+ end
48
+
49
+ def at(index)
50
+ @items.at(index)
51
+ end
52
+
53
+ def empty?
54
+ @items.empty?
55
+ end
56
+
57
+ def length
58
+ @items.length
59
+ end
60
+
61
+ def write_as_csv(output_path)
62
+ File.open(output_path, "w") do |file|
63
+ @items.each do |item|
64
+ if type == "json"
65
+ file.puts(item.data.join(","))
66
+ else
67
+ file.puts(item.data)
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ def write_as_json(output_path)
74
+ json = {}
75
+ json["title"] = title
76
+
77
+ data_array = []
78
+ @items.each do |item|
79
+ data_item = {}
80
+ data_item["date"] = item.data[0]
81
+ data_item["value"] = item.data[1]
82
+
83
+ data_array.push(data_item)
84
+ end
85
+
86
+ json["data"] = data_array
87
+
88
+ File.open(output_path, "w") do |file|
89
+ file.write(json.to_json)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,40 @@
1
+ class Crypto
2
+
3
+ attr_accessor :passphrase
4
+
5
+ def generate_passphrase
6
+ `gpg --armor --gen-random 1 16`.chomp
7
+ end
8
+
9
+ def call_cmd(cmd, input)
10
+ output = nil
11
+ Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
12
+ stdin.puts(input)
13
+ stdin.close
14
+ output = stdout.read
15
+
16
+ if !wait_thr.value.success?
17
+ raise Myer::CmdFailed.new(stderr.read)
18
+ end
19
+ end
20
+ output
21
+ end
22
+
23
+ def encrypt(plaintext)
24
+ cmd = "gpg --batch --armor --passphrase '#{passphrase}' --symmetric"
25
+ begin
26
+ return call_cmd(cmd, plaintext)
27
+ rescue Myer::CmdFailed => e
28
+ raise "Encryption failed: #{e}"
29
+ end
30
+ end
31
+
32
+ def decrypt(ciphertext)
33
+ cmd = "gpg --batch --passphrase '#{passphrase}' --decrypt"
34
+ begin
35
+ return call_cmd(cmd, ciphertext).chomp
36
+ rescue Myer::CmdFailed => e
37
+ raise Myer::DecryptionFailed.new("Decryption failed: #{e}")
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,7 @@
1
+ module Myer
2
+ class Error < StandardError; end
3
+
4
+ class CmdFailed < Error; end
5
+ class DecryptionFailed < Error; end
6
+ end
7
+
@@ -0,0 +1,11 @@
1
+ class Plot
2
+ def call_helper(csv_file_path)
3
+ plot_helper = File.expand_path( "../../../scripts/plot-helper.py", __FILE__ )
4
+ cmd = "python #{plot_helper} #{csv_file_path}"
5
+ system(cmd)
6
+ end
7
+
8
+ def show(csv_file)
9
+ call_helper(csv_file)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class ProcNetParser
2
+ attr_reader :received_bytes
3
+
4
+ def parse(input)
5
+ input.each_line do |line|
6
+ if line =~ /^en(.*):\s+(\d+)\s+/
7
+ @received_bytes = $2.to_i
8
+ end
9
+ end
10
+ end
11
+ end